From c8fbcf29995bd7b01ab15ba683d47f7957301e56 Mon Sep 17 00:00:00 2001 From: mufazalov Date: Mon, 18 Nov 2024 18:49:10 +0300 Subject: [PATCH] fix(PaginatedTable): fix autorefresh when no data --- .../AutoRefreshControl/AutoRefreshControl.tsx | 1 + .../PaginatedTable/PaginatedTable.tsx | 25 ++++------- src/components/PaginatedTable/TableChunk.tsx | 23 +++++++++- tests/suites/nodes/nodes.test.ts | 45 +++++++++++++++++++ tests/suites/paginatedTable/paginatedTable.ts | 10 ++++- 5 files changed, 84 insertions(+), 20 deletions(-) diff --git a/src/components/AutoRefreshControl/AutoRefreshControl.tsx b/src/components/AutoRefreshControl/AutoRefreshControl.tsx index 270d6f395..ce24725ae 100644 --- a/src/components/AutoRefreshControl/AutoRefreshControl.tsx +++ b/src/components/AutoRefreshControl/AutoRefreshControl.tsx @@ -37,6 +37,7 @@ export function AutoRefreshControl({className}: AutoRefreshControlProps) { setAutoRefreshInterval(Number(v)); }} width={85} + qa="ydb-autorefresh-select" > {i18n('None')} {i18n('15 sec')} diff --git a/src/components/PaginatedTable/PaginatedTable.tsx b/src/components/PaginatedTable/PaginatedTable.tsx index ad794127f..6e9c4cf45 100644 --- a/src/components/PaginatedTable/PaginatedTable.tsx +++ b/src/components/PaginatedTable/PaginatedTable.tsx @@ -4,9 +4,7 @@ import {TableWithControlsLayout} from '../TableWithControlsLayout/TableWithContr import {TableChunk} from './TableChunk'; import {TableHead} from './TableHead'; -import {EmptyTableRow} from './TableRow'; import {DEFAULT_TABLE_ROW_HEIGHT} from './constants'; -import i18n from './i18n'; import {b} from './shared'; import type { Column, @@ -75,10 +73,14 @@ export const PaginatedTable = ({ chunkSize, }); - const lastChunkSize = React.useMemo( - () => foundEntities % chunkSize || chunkSize, - [foundEntities, chunkSize], - ); + const lastChunkSize = React.useMemo(() => { + // If foundEntities = 0, there will only first chunk + // Display it with 1 row, to display empty data message + if (!foundEntities) { + return 1; + } + return foundEntities % chunkSize || chunkSize; + }, [foundEntities, chunkSize]); const handleDataFetched = React.useCallback((total: number, found: number) => { setTotalEntities(total); @@ -97,16 +99,6 @@ export const PaginatedTable = ({ }, [filters, initialFound, initialTotal, parentRef]); const renderChunks = () => { - if (!isInitialLoad && foundEntities === 0) { - return ( - - - {renderEmptyDataMessage ? renderEmptyDataMessage() : i18n('empty')} - - - ); - } - return activeChunks.map((isActive, index) => ( key={index} @@ -121,6 +113,7 @@ export const PaginatedTable = ({ sortParams={sortParams} getRowClassName={getRowClassName} renderErrorMessage={renderErrorMessage} + renderEmptyDataMessage={renderEmptyDataMessage} onDataFetched={handleDataFetched} isActive={isActive} /> diff --git a/src/components/PaginatedTable/TableChunk.tsx b/src/components/PaginatedTable/TableChunk.tsx index 347c11025..4702106fb 100644 --- a/src/components/PaginatedTable/TableChunk.tsx +++ b/src/components/PaginatedTable/TableChunk.tsx @@ -7,7 +7,15 @@ import {useAutoRefreshInterval} from '../../utils/hooks'; import {ResponseError} from '../Errors/ResponseError'; import {EmptyTableRow, LoadingTableRow, TableRow} from './TableRow'; -import type {Column, FetchData, GetRowClassName, SortParams} from './types'; +import i18n from './i18n'; +import type { + Column, + FetchData, + GetRowClassName, + RenderEmptyDataMessage, + RenderErrorMessage, + SortParams, +} from './types'; import {typedMemo} from './utils'; const DEBOUNCE_TIMEOUT = 200; @@ -25,7 +33,8 @@ interface TableChunkProps { fetchData: FetchData; getRowClassName?: GetRowClassName; - renderErrorMessage?: (error: IResponseError) => React.ReactNode; + renderErrorMessage?: RenderErrorMessage; + renderEmptyDataMessage?: RenderEmptyDataMessage; onDataFetched: (total: number, found: number) => void; } @@ -42,6 +51,7 @@ export const TableChunk = typedMemo(function TableChunk({ sortParams, getRowClassName, renderErrorMessage, + renderEmptyDataMessage, onDataFetched, isActive, }: TableChunkProps) { @@ -119,6 +129,15 @@ export const TableChunk = typedMemo(function TableChunk({ } } + // Data is loaded, but there are no entities in the chunk + if (!currentData.data?.length) { + return ( + + {renderEmptyDataMessage ? renderEmptyDataMessage() : i18n('empty')} + + ); + } + return currentData.data.map((rowData, index) => ( { expect(uptimeValues.length).toBeGreaterThan(0); expect(hostValues.length).toBe(uptimeValues.length); }); + + test('Table displays empty data message when no entities', async ({page}) => { + const paginatedTable = new PaginatedTable(page); + + await paginatedTable.waitForTableToLoad(); + await paginatedTable.waitForTableData(); + + await paginatedTable.search('Some Invalid search string !%#@[]'); + + await paginatedTable.waitForTableData(); + + const emptyDataMessage = await paginatedTable.getEmptyDataMessageLocator(); + await expect(emptyDataMessage).toContainText('No such nodes'); + }); + + test('Autorefresh updates data when initially empty data', async ({page}) => { + const paginatedTable = new PaginatedTable(page); + + const emptyRequest = page.route(`${backend}/viewer/json/nodes?*`, async (route) => { + await route.fulfill({json: {FoundNodes: 0, TotalNodes: 0, Nodes: []}}); + }); + await paginatedTable.clickRefreshButton(); + + await emptyRequest; + + const emptyDataMessage = await paginatedTable.getEmptyDataMessageLocator(); + await expect(emptyDataMessage).toContainText('No such nodes'); + + await paginatedTable.setRefreshInterval('15 sec'); + + const requestWithData = page.route(`${backend}/viewer/json/nodes?*`, async (route) => { + await route.continue(); + }); + + await page.waitForTimeout(15_000); // Wait for autorefresh + + await requestWithData; + await paginatedTable.waitForTableData(); + + await expect(emptyDataMessage).toBeHidden(); + + const hostValues = await paginatedTable.getColumnValues('Host'); + expect(hostValues.length).toBeGreaterThan(0); + }); }); diff --git a/tests/suites/paginatedTable/paginatedTable.ts b/tests/suites/paginatedTable/paginatedTable.ts index 79ef716a1..5b2abfa6e 100644 --- a/tests/suites/paginatedTable/paginatedTable.ts +++ b/tests/suites/paginatedTable/paginatedTable.ts @@ -7,6 +7,7 @@ export class PaginatedTable { private radioButtons: Locator; private countLabel: Locator; private tableRows: Locator; + private emptyTableRows: Locator; private refreshButton: Locator; private refreshIntervalSelect: Locator; private headCells: Locator; @@ -19,8 +20,9 @@ export class PaginatedTable { this.countLabel = this.tableSelector.locator('.ydb-entities-count .g-label__content'); this.headCells = this.tableSelector.locator('.ydb-paginated-table__head-cell'); this.tableRows = this.tableSelector.locator('.ydb-paginated-table__row'); + this.emptyTableRows = this.tableSelector.locator('.ydb-paginated-table__row_empty'); this.refreshButton = page.locator('.auto-refresh-control button[aria-label="Refresh"]'); - this.refreshIntervalSelect = page.locator('.cluster__auto-refresh-select'); + this.refreshIntervalSelect = page.getByTestId('ydb-autorefresh-select'); } async search(searchTerm: string) { @@ -70,6 +72,10 @@ export class PaginatedTable { return this.tableRows.count(); } + async getEmptyDataMessageLocator() { + return this.emptyTableRows.nth(0); + } + async waitForTableToLoad() { await this.tableSelector.waitFor({state: 'visible'}); } @@ -97,7 +103,7 @@ export class PaginatedTable { async setRefreshInterval(interval: string) { await this.refreshIntervalSelect.click(); - await this.page.locator('.g-select-popup__option', {hasText: interval}).click(); + await this.page.locator('.g-select-list__option', {hasText: interval}).click(); } async getRefreshInterval(): Promise {