Skip to content

Commit ed9a03b

Browse files
fix(PaginatedTable): fix autorefresh when no data (#1650)
1 parent 09b1238 commit ed9a03b

File tree

5 files changed

+84
-20
lines changed

5 files changed

+84
-20
lines changed

src/components/AutoRefreshControl/AutoRefreshControl.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export function AutoRefreshControl({className}: AutoRefreshControlProps) {
3737
setAutoRefreshInterval(Number(v));
3838
}}
3939
width={85}
40+
qa="ydb-autorefresh-select"
4041
>
4142
<Select.Option value="0">{i18n('None')}</Select.Option>
4243
<Select.Option value="15000">{i18n('15 sec')}</Select.Option>

src/components/PaginatedTable/PaginatedTable.tsx

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ import {TableWithControlsLayout} from '../TableWithControlsLayout/TableWithContr
44

55
import {TableChunk} from './TableChunk';
66
import {TableHead} from './TableHead';
7-
import {EmptyTableRow} from './TableRow';
87
import {DEFAULT_TABLE_ROW_HEIGHT} from './constants';
9-
import i18n from './i18n';
108
import {b} from './shared';
119
import type {
1210
Column,
@@ -75,10 +73,14 @@ export const PaginatedTable = <T, F>({
7573
chunkSize,
7674
});
7775

78-
const lastChunkSize = React.useMemo(
79-
() => foundEntities % chunkSize || chunkSize,
80-
[foundEntities, chunkSize],
81-
);
76+
const lastChunkSize = React.useMemo(() => {
77+
// If foundEntities = 0, there will only first chunk
78+
// Display it with 1 row, to display empty data message
79+
if (!foundEntities) {
80+
return 1;
81+
}
82+
return foundEntities % chunkSize || chunkSize;
83+
}, [foundEntities, chunkSize]);
8284

8385
const handleDataFetched = React.useCallback((total: number, found: number) => {
8486
setTotalEntities(total);
@@ -97,16 +99,6 @@ export const PaginatedTable = <T, F>({
9799
}, [filters, initialFound, initialTotal, parentRef]);
98100

99101
const renderChunks = () => {
100-
if (!isInitialLoad && foundEntities === 0) {
101-
return (
102-
<tbody>
103-
<EmptyTableRow columns={columns}>
104-
{renderEmptyDataMessage ? renderEmptyDataMessage() : i18n('empty')}
105-
</EmptyTableRow>
106-
</tbody>
107-
);
108-
}
109-
110102
return activeChunks.map((isActive, index) => (
111103
<TableChunk<T, F>
112104
key={index}
@@ -121,6 +113,7 @@ export const PaginatedTable = <T, F>({
121113
sortParams={sortParams}
122114
getRowClassName={getRowClassName}
123115
renderErrorMessage={renderErrorMessage}
116+
renderEmptyDataMessage={renderEmptyDataMessage}
124117
onDataFetched={handleDataFetched}
125118
isActive={isActive}
126119
/>

src/components/PaginatedTable/TableChunk.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,15 @@ import {useAutoRefreshInterval} from '../../utils/hooks';
77
import {ResponseError} from '../Errors/ResponseError';
88

99
import {EmptyTableRow, LoadingTableRow, TableRow} from './TableRow';
10-
import type {Column, FetchData, GetRowClassName, SortParams} from './types';
10+
import i18n from './i18n';
11+
import type {
12+
Column,
13+
FetchData,
14+
GetRowClassName,
15+
RenderEmptyDataMessage,
16+
RenderErrorMessage,
17+
SortParams,
18+
} from './types';
1119
import {typedMemo} from './utils';
1220

1321
const DEBOUNCE_TIMEOUT = 200;
@@ -25,7 +33,8 @@ interface TableChunkProps<T, F> {
2533

2634
fetchData: FetchData<T, F>;
2735
getRowClassName?: GetRowClassName<T>;
28-
renderErrorMessage?: (error: IResponseError) => React.ReactNode;
36+
renderErrorMessage?: RenderErrorMessage;
37+
renderEmptyDataMessage?: RenderEmptyDataMessage;
2938
onDataFetched: (total: number, found: number) => void;
3039
}
3140

@@ -42,6 +51,7 @@ export const TableChunk = typedMemo(function TableChunk<T, F>({
4251
sortParams,
4352
getRowClassName,
4453
renderErrorMessage,
54+
renderEmptyDataMessage,
4555
onDataFetched,
4656
isActive,
4757
}: TableChunkProps<T, F>) {
@@ -119,6 +129,15 @@ export const TableChunk = typedMemo(function TableChunk<T, F>({
119129
}
120130
}
121131

132+
// Data is loaded, but there are no entities in the chunk
133+
if (!currentData.data?.length) {
134+
return (
135+
<EmptyTableRow columns={columns}>
136+
{renderEmptyDataMessage ? renderEmptyDataMessage() : i18n('empty')}
137+
</EmptyTableRow>
138+
);
139+
}
140+
122141
return currentData.data.map((rowData, index) => (
123142
<TableRow
124143
key={index}

tests/suites/nodes/nodes.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {expect, test} from '@playwright/test';
22

3+
import {backend} from '../../utils/constants';
34
import {toggleExperiment} from '../../utils/toggleExperiment';
45
import {NodesPage} from '../nodes/NodesPage';
56
import {PaginatedTable} from '../paginatedTable/paginatedTable';
@@ -143,4 +144,48 @@ test.describe('Test Nodes Paginated Table', async () => {
143144
expect(uptimeValues.length).toBeGreaterThan(0);
144145
expect(hostValues.length).toBe(uptimeValues.length);
145146
});
147+
148+
test('Table displays empty data message when no entities', async ({page}) => {
149+
const paginatedTable = new PaginatedTable(page);
150+
151+
await paginatedTable.waitForTableToLoad();
152+
await paginatedTable.waitForTableData();
153+
154+
await paginatedTable.search('Some Invalid search string !%#@[]');
155+
156+
await paginatedTable.waitForTableData();
157+
158+
const emptyDataMessage = await paginatedTable.getEmptyDataMessageLocator();
159+
await expect(emptyDataMessage).toContainText('No such nodes');
160+
});
161+
162+
test('Autorefresh updates data when initially empty data', async ({page}) => {
163+
const paginatedTable = new PaginatedTable(page);
164+
165+
const emptyRequest = page.route(`${backend}/viewer/json/nodes?*`, async (route) => {
166+
await route.fulfill({json: {FoundNodes: 0, TotalNodes: 0, Nodes: []}});
167+
});
168+
await paginatedTable.clickRefreshButton();
169+
170+
await emptyRequest;
171+
172+
const emptyDataMessage = await paginatedTable.getEmptyDataMessageLocator();
173+
await expect(emptyDataMessage).toContainText('No such nodes');
174+
175+
await paginatedTable.setRefreshInterval('15 sec');
176+
177+
const requestWithData = page.route(`${backend}/viewer/json/nodes?*`, async (route) => {
178+
await route.continue();
179+
});
180+
181+
await page.waitForTimeout(15_000); // Wait for autorefresh
182+
183+
await requestWithData;
184+
await paginatedTable.waitForTableData();
185+
186+
await expect(emptyDataMessage).toBeHidden();
187+
188+
const hostValues = await paginatedTable.getColumnValues('Host');
189+
expect(hostValues.length).toBeGreaterThan(0);
190+
});
146191
});

tests/suites/paginatedTable/paginatedTable.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export class PaginatedTable {
77
private radioButtons: Locator;
88
private countLabel: Locator;
99
private tableRows: Locator;
10+
private emptyTableRows: Locator;
1011
private refreshButton: Locator;
1112
private refreshIntervalSelect: Locator;
1213
private headCells: Locator;
@@ -19,8 +20,9 @@ export class PaginatedTable {
1920
this.countLabel = this.tableSelector.locator('.ydb-entities-count .g-label__content');
2021
this.headCells = this.tableSelector.locator('.ydb-paginated-table__head-cell');
2122
this.tableRows = this.tableSelector.locator('.ydb-paginated-table__row');
23+
this.emptyTableRows = this.tableSelector.locator('.ydb-paginated-table__row_empty');
2224
this.refreshButton = page.locator('.auto-refresh-control button[aria-label="Refresh"]');
23-
this.refreshIntervalSelect = page.locator('.cluster__auto-refresh-select');
25+
this.refreshIntervalSelect = page.getByTestId('ydb-autorefresh-select');
2426
}
2527

2628
async search(searchTerm: string) {
@@ -70,6 +72,10 @@ export class PaginatedTable {
7072
return this.tableRows.count();
7173
}
7274

75+
async getEmptyDataMessageLocator() {
76+
return this.emptyTableRows.nth(0);
77+
}
78+
7379
async waitForTableToLoad() {
7480
await this.tableSelector.waitFor({state: 'visible'});
7581
}
@@ -97,7 +103,7 @@ export class PaginatedTable {
97103

98104
async setRefreshInterval(interval: string) {
99105
await this.refreshIntervalSelect.click();
100-
await this.page.locator('.g-select-popup__option', {hasText: interval}).click();
106+
await this.page.locator('.g-select-list__option', {hasText: interval}).click();
101107
}
102108

103109
async getRefreshInterval(): Promise<string> {

0 commit comments

Comments
 (0)