Skip to content

fix(PaginatedTable): fix autorefresh when no data #1650

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/AutoRefreshControl/AutoRefreshControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export function AutoRefreshControl({className}: AutoRefreshControlProps) {
setAutoRefreshInterval(Number(v));
}}
width={85}
qa="ydb-autorefresh-select"
>
<Select.Option value="0">{i18n('None')}</Select.Option>
<Select.Option value="15000">{i18n('15 sec')}</Select.Option>
Expand Down
25 changes: 9 additions & 16 deletions src/components/PaginatedTable/PaginatedTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -75,10 +73,14 @@ export const PaginatedTable = <T, F>({
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);
Expand All @@ -97,16 +99,6 @@ export const PaginatedTable = <T, F>({
}, [filters, initialFound, initialTotal, parentRef]);

const renderChunks = () => {
if (!isInitialLoad && foundEntities === 0) {
return (
<tbody>
<EmptyTableRow columns={columns}>
{renderEmptyDataMessage ? renderEmptyDataMessage() : i18n('empty')}
</EmptyTableRow>
</tbody>
);
}

return activeChunks.map((isActive, index) => (
<TableChunk<T, F>
key={index}
Expand All @@ -121,6 +113,7 @@ export const PaginatedTable = <T, F>({
sortParams={sortParams}
getRowClassName={getRowClassName}
renderErrorMessage={renderErrorMessage}
renderEmptyDataMessage={renderEmptyDataMessage}
onDataFetched={handleDataFetched}
isActive={isActive}
/>
Expand Down
23 changes: 21 additions & 2 deletions src/components/PaginatedTable/TableChunk.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,7 +33,8 @@ interface TableChunkProps<T, F> {

fetchData: FetchData<T, F>;
getRowClassName?: GetRowClassName<T>;
renderErrorMessage?: (error: IResponseError) => React.ReactNode;
renderErrorMessage?: RenderErrorMessage;
renderEmptyDataMessage?: RenderEmptyDataMessage;
onDataFetched: (total: number, found: number) => void;
}

Expand All @@ -42,6 +51,7 @@ export const TableChunk = typedMemo(function TableChunk<T, F>({
sortParams,
getRowClassName,
renderErrorMessage,
renderEmptyDataMessage,
onDataFetched,
isActive,
}: TableChunkProps<T, F>) {
Expand Down Expand Up @@ -119,6 +129,15 @@ export const TableChunk = typedMemo(function TableChunk<T, F>({
}
}

// Data is loaded, but there are no entities in the chunk
if (!currentData.data?.length) {
return (
<EmptyTableRow columns={columns}>
{renderEmptyDataMessage ? renderEmptyDataMessage() : i18n('empty')}
</EmptyTableRow>
);
}

return currentData.data.map((rowData, index) => (
<TableRow
key={index}
Expand Down
45 changes: 45 additions & 0 deletions tests/suites/nodes/nodes.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {expect, test} from '@playwright/test';

import {backend} from '../../utils/constants';
import {toggleExperiment} from '../../utils/toggleExperiment';
import {NodesPage} from '../nodes/NodesPage';
import {PaginatedTable} from '../paginatedTable/paginatedTable';
Expand Down Expand Up @@ -143,4 +144,48 @@ test.describe('Test Nodes Paginated Table', async () => {
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);
});
});
10 changes: 8 additions & 2 deletions tests/suites/paginatedTable/paginatedTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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'});
}
Expand Down Expand Up @@ -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<string> {
Expand Down
Loading