Skip to content

Commit 7255bfd

Browse files
committed
feat: redesign CPU section
1 parent d06f228 commit 7255bfd

File tree

13 files changed

+184
-57
lines changed

13 files changed

+184
-57
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ playwright-artifacts
2222
.env.test.local
2323
.env.production.local
2424
.vscode
25+
.cursor
2526

2627
npm-debug.log*
2728
yarn-debug.log*
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.tenant-cpu {
2+
&__tabs-container {
3+
margin-top: var(--g-spacing-3);
4+
}
5+
6+
&__tab-content {
7+
margin-top: var(--g-spacing-3);
8+
}
9+
}
Lines changed: 135 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,29 @@
11
import React from 'react';
22

3+
import {ChevronRight} from '@gravity-ui/icons';
4+
import {
5+
Button,
6+
Flex,
7+
Icon,
8+
SegmentedRadioGroup,
9+
Tab,
10+
TabList,
11+
TabProvider,
12+
} from '@gravity-ui/uikit';
13+
import {useLocation} from 'react-router-dom';
14+
15+
import {InternalLink} from '../../../../../components/InternalLink';
16+
import {parseQuery} from '../../../../../routes';
17+
import {
18+
TENANT_CPU_TABS_IDS,
19+
TENANT_DIAGNOSTICS_TABS_IDS,
20+
} from '../../../../../store/reducers/tenant/constants';
21+
import {setCpuTab} from '../../../../../store/reducers/tenant/tenant';
322
import type {AdditionalNodesProps} from '../../../../../types/additionalProps';
23+
import {cn} from '../../../../../utils/cn';
24+
import {useTypedDispatch, useTypedSelector} from '../../../../../utils/hooks';
25+
import {useDiagnosticsPageLinkGetter} from '../../../Diagnostics/DiagnosticsPages';
26+
import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
427
import {TenantDashboard} from '../TenantDashboard/TenantDashboard';
528

629
import {TopNodesByCpu} from './TopNodesByCpu';
@@ -9,19 +32,127 @@ import {TopQueries} from './TopQueries';
932
import {TopShards} from './TopShards';
1033
import {cpuDashboardConfig} from './cpuDashboardConfig';
1134

35+
import './TenantCpu.scss';
36+
37+
const b = cn('tenant-cpu');
38+
39+
const cpuTabs = [
40+
{id: TENANT_CPU_TABS_IDS.nodes, title: 'Top Nodes'},
41+
{id: TENANT_CPU_TABS_IDS.shards, title: 'Top Shards'},
42+
{id: TENANT_CPU_TABS_IDS.queries, title: 'Top Queries'},
43+
];
44+
45+
const NodesModeIds = {
46+
load: 'load',
47+
pools: 'pools',
48+
} as const;
49+
1250
interface TenantCpuProps {
1351
tenantName: string;
1452
additionalNodesProps?: AdditionalNodesProps;
1553
}
1654

1755
export function TenantCpu({tenantName, additionalNodesProps}: TenantCpuProps) {
56+
const dispatch = useTypedDispatch();
57+
const location = useLocation();
58+
const {cpuTab = TENANT_CPU_TABS_IDS.nodes} = useTypedSelector((state) => state.tenant);
59+
const getDiagnosticsPageLink = useDiagnosticsPageLinkGetter();
60+
61+
const [nodesMode, setNodesMode] = React.useState(NodesModeIds.load);
62+
63+
const queryParams = parseQuery(location);
64+
65+
React.useEffect(() => {
66+
if (!cpuTab) {
67+
dispatch(setCpuTab(TENANT_CPU_TABS_IDS.nodes));
68+
}
69+
}, [cpuTab, dispatch]);
70+
71+
const renderNodesContent = () => {
72+
const nodesModeControl = (
73+
<SegmentedRadioGroup value={nodesMode} onUpdate={setNodesMode}>
74+
<SegmentedRadioGroup.Option value={NodesModeIds.load}>
75+
By Load
76+
</SegmentedRadioGroup.Option>
77+
<SegmentedRadioGroup.Option value={NodesModeIds.pools}>
78+
By Pool Usage
79+
</SegmentedRadioGroup.Option>
80+
</SegmentedRadioGroup>
81+
);
82+
83+
const allNodesButton = (
84+
<Button
85+
view="flat-secondary"
86+
size="s"
87+
href={getDiagnosticsPageLink(TENANT_DIAGNOSTICS_TABS_IDS.nodes)}
88+
>
89+
All Nodes
90+
<Icon data={ChevronRight} size={16} />
91+
</Button>
92+
);
93+
94+
const nodesComponent =
95+
nodesMode === NodesModeIds.load ? (
96+
<TopNodesByLoad
97+
tenantName={tenantName}
98+
additionalNodesProps={additionalNodesProps}
99+
/>
100+
) : (
101+
<TopNodesByCpu
102+
tenantName={tenantName}
103+
additionalNodesProps={additionalNodesProps}
104+
/>
105+
);
106+
107+
return (
108+
<Flex direction="column" gap={3}>
109+
<Flex justifyContent="space-between" alignItems="center">
110+
{nodesModeControl}
111+
{allNodesButton}
112+
</Flex>
113+
{nodesComponent}
114+
</Flex>
115+
);
116+
};
117+
118+
const renderTabContent = () => {
119+
switch (cpuTab) {
120+
case TENANT_CPU_TABS_IDS.nodes:
121+
return renderNodesContent();
122+
case TENANT_CPU_TABS_IDS.shards:
123+
return <TopShards tenantName={tenantName} path={tenantName} />;
124+
case TENANT_CPU_TABS_IDS.queries:
125+
return <TopQueries tenantName={tenantName} />;
126+
default:
127+
return null;
128+
}
129+
};
130+
18131
return (
19132
<React.Fragment>
20133
<TenantDashboard database={tenantName} charts={cpuDashboardConfig} />
21-
<TopNodesByLoad tenantName={tenantName} additionalNodesProps={additionalNodesProps} />
22-
<TopNodesByCpu tenantName={tenantName} additionalNodesProps={additionalNodesProps} />
23-
<TopShards tenantName={tenantName} path={tenantName} />
24-
<TopQueries tenantName={tenantName} />
134+
135+
<div className={b('tabs-container')}>
136+
<TabProvider value={cpuTab}>
137+
<TabList size="m">
138+
{cpuTabs.map(({id, title}) => {
139+
const path = getTenantPath({
140+
...queryParams,
141+
[TenantTabsGroups.cpuTab]: id,
142+
});
143+
return (
144+
<Tab key={id} value={id}>
145+
<InternalLink to={path} as="tab">
146+
{title}
147+
</InternalLink>
148+
</Tab>
149+
);
150+
})}
151+
</TabList>
152+
</TabProvider>
153+
154+
<div className={b('tab-content')}>{renderTabContent()}</div>
155+
</div>
25156
</React.Fragment>
26157
);
27158
}

src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByCpu.tsx

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,15 @@ import {
1313
import type {GetNodesColumnsParams} from '../../../../../components/nodesColumns/types';
1414
import {nodesApi} from '../../../../../store/reducers/nodes/nodes';
1515
import type {NodesPreparedEntity} from '../../../../../store/reducers/nodes/types';
16-
import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants';
1716
import type {AdditionalNodesProps} from '../../../../../types/additionalProps';
1817
import type {NodesRequiredField} from '../../../../../types/api/nodes';
1918
import {
2019
TENANT_OVERVIEW_TABLES_LIMIT,
2120
TENANT_OVERVIEW_TABLES_SETTINGS,
2221
} from '../../../../../utils/constants';
23-
import {useAutoRefreshInterval, useSearchQuery} from '../../../../../utils/hooks';
22+
import {useAutoRefreshInterval} from '../../../../../utils/hooks';
2423
import {getRequiredDataFields} from '../../../../../utils/tableUtils/getRequiredDataFields';
25-
import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
2624
import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
27-
import {getSectionTitle} from '../getSectionTitle';
2825
import i18n from '../i18n';
2926

3027
function getTopNodesByCpuColumns(
@@ -50,8 +47,6 @@ interface TopNodesByCpuProps {
5047
}
5148

5249
export function TopNodesByCpu({tenantName, additionalNodesProps}: TopNodesByCpuProps) {
53-
const query = useSearchQuery();
54-
5550
const [autoRefreshInterval] = useAutoRefreshInterval();
5651
const [columns, fieldsRequired] = getTopNodesByCpuColumns({
5752
getNodeRef: additionalNodesProps?.getNodeRef,
@@ -74,22 +69,8 @@ export function TopNodesByCpu({tenantName, additionalNodesProps}: TopNodesByCpuP
7469

7570
const topNodes = currentData?.Nodes || [];
7671

77-
const title = getSectionTitle({
78-
entity: i18n('nodes'),
79-
postfix: i18n('by-pools-usage'),
80-
link: getTenantPath({
81-
...query,
82-
[TenantTabsGroups.diagnosticsTab]: TENANT_DIAGNOSTICS_TABS_IDS.nodes,
83-
}),
84-
});
85-
8672
return (
87-
<TenantOverviewTableLayout
88-
title={title}
89-
loading={loading}
90-
error={error}
91-
withData={Boolean(currentData)}
92-
>
73+
<TenantOverviewTableLayout loading={loading} error={error} withData={Boolean(currentData)}>
9374
<ResizeableDataTable
9475
columnsWidthLSKey={NODES_COLUMNS_WIDTH_LS_KEY}
9576
data={topNodes}

src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByLoad.tsx

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,15 @@ import {
1414
import type {GetNodesColumnsParams} from '../../../../../components/nodesColumns/types';
1515
import {nodesApi} from '../../../../../store/reducers/nodes/nodes';
1616
import type {NodesPreparedEntity} from '../../../../../store/reducers/nodes/types';
17-
import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants';
1817
import type {AdditionalNodesProps} from '../../../../../types/additionalProps';
1918
import type {NodesRequiredField} from '../../../../../types/api/nodes';
2019
import {
2120
TENANT_OVERVIEW_TABLES_LIMIT,
2221
TENANT_OVERVIEW_TABLES_SETTINGS,
2322
} from '../../../../../utils/constants';
24-
import {useAutoRefreshInterval, useSearchQuery} from '../../../../../utils/hooks';
23+
import {useAutoRefreshInterval} from '../../../../../utils/hooks';
2524
import {getRequiredDataFields} from '../../../../../utils/tableUtils/getRequiredDataFields';
26-
import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
2725
import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
28-
import {getSectionTitle} from '../getSectionTitle';
2926
import i18n from '../i18n';
3027

3128
function getTopNodesByLoadColumns(
@@ -55,8 +52,6 @@ interface TopNodesByLoadProps {
5552
}
5653

5754
export function TopNodesByLoad({tenantName, additionalNodesProps}: TopNodesByLoadProps) {
58-
const query = useSearchQuery();
59-
6055
const [autoRefreshInterval] = useAutoRefreshInterval();
6156
const [columns, fieldsRequired] = getTopNodesByLoadColumns({
6257
getNodeRef: additionalNodesProps?.getNodeRef,
@@ -79,22 +74,8 @@ export function TopNodesByLoad({tenantName, additionalNodesProps}: TopNodesByLoa
7974

8075
const topNodes = currentData?.Nodes || [];
8176

82-
const title = getSectionTitle({
83-
entity: i18n('nodes'),
84-
postfix: i18n('by-load'),
85-
link: getTenantPath({
86-
...query,
87-
[TenantTabsGroups.diagnosticsTab]: TENANT_DIAGNOSTICS_TABS_IDS.nodes,
88-
}),
89-
});
90-
9177
return (
92-
<TenantOverviewTableLayout
93-
title={title}
94-
loading={loading}
95-
error={error}
96-
withData={Boolean(currentData)}
97-
>
78+
<TenantOverviewTableLayout loading={loading} error={error} withData={Boolean(currentData)}>
9879
<ResizeableDataTable
9980
columnsWidthLSKey={NODES_COLUMNS_WIDTH_LS_KEY}
10081
data={topNodes}

src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
height: 100%;
55
padding-bottom: 20px;
66

7-
&__tenant-name-wrapper {
8-
display: flex;
9-
overflow: hidden;
10-
align-items: center;
7+
// Make header font size smaller using project typography
8+
.data-table__th {
9+
@include mixins.subheader-1-typography();
1110
}
1211

1312
&__top {

src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,15 +99,15 @@ export function TenantOverview({
9999

100100
const renderName = () => {
101101
return (
102-
<div className={b('tenant-name-wrapper')}>
102+
<Flex alignItems="center" style={{overflow: 'hidden'}}>
103103
<EntityStatus
104104
status={Overall}
105105
name={Name || TENANT_DEFAULT_TITLE}
106106
withLeftTrim
107107
hasClipboardButton={Boolean(tenant)}
108108
clipboardButtonAlwaysVisible
109109
/>
110-
</div>
110+
</Flex>
111111
);
112112
};
113113

src/containers/Tenant/Diagnostics/TenantOverview/TenantOverviewTableLayout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {TENANT_OVERVIEW_TABLES_LIMIT} from '../../../../utils/constants';
99
import {b} from './utils';
1010

1111
interface TenantOverviewTableLayoutProps {
12-
title: React.ReactNode;
12+
title?: React.ReactNode;
1313
loading?: boolean;
1414
error?: unknown;
1515
tableClassNameModifiers?: NoStrictEntityMods;
@@ -38,7 +38,7 @@ export function TenantOverviewTableLayout({
3838
};
3939
return (
4040
<React.Fragment>
41-
<div className={b('title')}>{title}</div>
41+
{title && <div className={b('title')}>{title}</div>}
4242
{error ? <ResponseError error={error} /> : null}
4343
<div className={b('table', tableClassNameModifiers)}>{renderContent()}</div>
4444
</React.Fragment>

src/containers/Tenant/TenantPages.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export const TenantTabsGroups = {
2626
queryTab: 'queryTab',
2727
diagnosticsTab: 'diagnosticsTab',
2828
metricsTab: 'metricsTab',
29+
cpuTab: 'cpuTab',
2930
} as const;
3031

3132
export const TENANT_INFO_TABS = [

src/store/reducers/tenant/constants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,9 @@ export const TENANT_METRICS_TABS_IDS = {
4343
storage: 'storage',
4444
memory: 'memory',
4545
} as const;
46+
47+
export const TENANT_CPU_TABS_IDS = {
48+
nodes: 'nodes',
49+
shards: 'shards',
50+
queries: 'queries',
51+
} as const;

0 commit comments

Comments
 (0)