Skip to content

Commit d73717a

Browse files
CopilotRaubzeug
andauthored
fix: conditionally show threads tab based on API response data (#2666)
Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: Raubzeug <[email protected]> Co-authored-by: Elena Makarova <[email protected]>
1 parent 66b45ee commit d73717a

File tree

3 files changed

+171
-5
lines changed

3 files changed

+171
-5
lines changed

src/containers/Node/Node.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,19 +68,25 @@ export function Node() {
6868

6969
const isStorageNode = node?.Roles?.find((el) => el === STORAGE_ROLE);
7070

71+
const threadsQuantity = node?.Threads?.length;
72+
7173
const {activeTab, nodeTabs} = React.useMemo(() => {
72-
let actulaNodeTabs = isStorageNode
74+
let actualNodeTabs = isStorageNode
7375
? NODE_TABS
7476
: NODE_TABS.filter((el) => el.id !== 'storage');
7577
if (isDiskPagesAvailable) {
76-
actulaNodeTabs = actulaNodeTabs.filter((el) => el.id !== 'structure');
78+
actualNodeTabs = actualNodeTabs.filter((el) => el.id !== 'structure');
79+
}
80+
// Filter out threads tab if there's no thread data in the API response
81+
if (!threadsQuantity) {
82+
actualNodeTabs = actualNodeTabs.filter((el) => el.id !== 'threads');
7783
}
7884

7985
const actualActiveTab =
80-
actulaNodeTabs.find(({id}) => id === activeTabId) ?? actulaNodeTabs[0];
86+
actualNodeTabs.find(({id}) => id === activeTabId) ?? actualNodeTabs[0];
8187

82-
return {activeTab: actualActiveTab, nodeTabs: actulaNodeTabs};
83-
}, [isStorageNode, isDiskPagesAvailable, activeTabId]);
88+
return {activeTab: actualActiveTab, nodeTabs: actualNodeTabs};
89+
}, [isStorageNode, isDiskPagesAvailable, activeTabId, threadsQuantity]);
8490

8591
const tenantName = node?.Tenants?.[0] || tenantNameFromQuery?.toString();
8692

tests/suites/nodes/NodePage.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type {Locator, Page} from '@playwright/test';
2+
3+
import {PageModel} from '../../models/PageModel';
4+
import {VISIBILITY_TIMEOUT} from '../tenant/TenantPage';
5+
6+
export class NodePage extends PageModel {
7+
readonly tabs: Locator;
8+
readonly threadsTab: Locator;
9+
readonly tabletsTab: Locator;
10+
readonly storageTab: Locator;
11+
12+
constructor(page: Page, nodeId: string) {
13+
super(page, `node/${nodeId}`);
14+
15+
this.tabs = this.selector.locator('.node__tab-list');
16+
this.threadsTab = this.tabs.locator('[value="threads"]');
17+
this.tabletsTab = this.tabs.locator('[value="tablets"]');
18+
this.storageTab = this.tabs.locator('[value="storage"]');
19+
}
20+
21+
async waitForNodePageLoad() {
22+
// Wait for the page to load and tabs to be visible
23+
try {
24+
await this.tabs.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT});
25+
} catch (error) {
26+
console.error('Failed to load node page tabs:', error);
27+
throw error;
28+
}
29+
}
30+
31+
async isThreadsTabVisible() {
32+
const threadsTab = this.tabs.locator('.g-tab:has-text("Threads")');
33+
try {
34+
await threadsTab.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT});
35+
return true;
36+
} catch {
37+
console.error('Threads tab is not visible');
38+
return false;
39+
}
40+
}
41+
42+
async clickThreadsTab() {
43+
const threadsTab = this.tabs.locator('.g-tab:has-text("Threads")');
44+
await threadsTab.click();
45+
}
46+
47+
async getAllTabNames() {
48+
const tabs = await this.tabs.locator('.g-tab').allTextContents();
49+
return tabs;
50+
}
51+
}

tests/suites/nodes/nodes.test.ts

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

33
import {backend} from '../../utils/constants';
4+
import {NodePage} from '../nodes/NodePage';
45
import {NodesPage} from '../nodes/NodesPage';
56
import {ClusterNodesTable} from '../paginatedTable/paginatedTable';
67

@@ -193,3 +194,111 @@ test.describe('Test Nodes Paginated Table', async () => {
193194
expect(hostValues.length).toBeGreaterThan(0);
194195
});
195196
});
197+
198+
test.describe('Test Node Page Threads Tab', async () => {
199+
test('Threads tab is hidden when node has no thread data', async ({page}) => {
200+
// Mock the node API to return no thread data
201+
await page.route(`**/viewer/json/sysinfo?*`, async (route) => {
202+
await route.fulfill({
203+
status: 200,
204+
contentType: 'application/json',
205+
body: JSON.stringify({
206+
SystemStateInfo: [
207+
{
208+
Host: 'localhost',
209+
NodeId: 1,
210+
SystemState: 'Green',
211+
Version: 'test-version',
212+
},
213+
],
214+
// No Threads property
215+
}),
216+
});
217+
});
218+
219+
// Navigate directly to node page
220+
const nodePage = new NodePage(page, '1');
221+
await nodePage.goto();
222+
await nodePage.waitForNodePageLoad();
223+
224+
// Verify other tabs are still visible
225+
const tabNames = await nodePage.getAllTabNames();
226+
expect(tabNames).toContain('Tablets');
227+
expect(tabNames).not.toContain('Threads');
228+
});
229+
230+
test('Threads tab is visible when node has thread data', async ({page}) => {
231+
// Mock the node API to return thread data
232+
await page.route(`**/viewer/json/sysinfo?*`, async (route) => {
233+
await route.fulfill({
234+
status: 200,
235+
contentType: 'application/json',
236+
body: JSON.stringify({
237+
SystemStateInfo: [
238+
{
239+
Host: 'localhost',
240+
NodeId: 1,
241+
SystemState: 'Green',
242+
Version: 'test-version',
243+
Threads: [
244+
{
245+
Name: 'TestPool',
246+
Threads: 4,
247+
},
248+
],
249+
},
250+
],
251+
}),
252+
});
253+
});
254+
255+
// Navigate directly to node page
256+
const nodePage = new NodePage(page, '1');
257+
await nodePage.goto();
258+
await nodePage.waitForNodePageLoad();
259+
260+
// Verify threads tab is visible
261+
const isThreadsTabVisible = await nodePage.isThreadsTabVisible();
262+
expect(isThreadsTabVisible).toBe(true);
263+
264+
// Verify can click on threads tab
265+
await nodePage.clickThreadsTab();
266+
await page.waitForURL(/\/node\/\d+\/threads/);
267+
268+
// Verify other tabs are also visible
269+
const tabNames = await nodePage.getAllTabNames();
270+
expect(tabNames).toContain('Tablets');
271+
expect(tabNames).toContain('Threads');
272+
});
273+
274+
test('Threads tab is hidden when node has empty thread array', async ({page}) => {
275+
// Mock the node API to return empty thread data
276+
await page.route(`**/viewer/json/sysinfo?*`, async (route) => {
277+
await route.fulfill({
278+
status: 200,
279+
contentType: 'application/json',
280+
body: JSON.stringify({
281+
SystemStateInfo: [
282+
{
283+
Host: 'localhost',
284+
NodeId: 1,
285+
SystemState: 'Green',
286+
Version: 'test-version',
287+
},
288+
],
289+
Threads: [], // Empty array
290+
}),
291+
});
292+
});
293+
294+
// Navigate directly to node page
295+
const nodePage = new NodePage(page, '1');
296+
await nodePage.goto();
297+
await nodePage.waitForNodePageLoad();
298+
299+
// Verify other tabs are still visible
300+
const tabNames = await nodePage.getAllTabNames();
301+
expect(tabNames).toContain('Tablets');
302+
expect(tabNames).not.toContain('Threads');
303+
});
304+
});

0 commit comments

Comments
 (0)