Skip to content

Commit 61acb4f

Browse files
committed
feat: improve tenant diagnostics - CPU
1 parent 87aca43 commit 61acb4f

File tree

18 files changed

+357
-455
lines changed

18 files changed

+357
-455
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {ArrowRight} from '@gravity-ui/icons';
2+
import {Flex, Icon, Text} from '@gravity-ui/uikit';
3+
4+
import {InternalLink} from '../InternalLink';
5+
6+
import i18n from './i18n';
7+
8+
interface SeeAllButtonProps {
9+
to: string;
10+
className?: string;
11+
onClick?: () => void;
12+
}
13+
14+
export function SeeAllButton({to, className, onClick}: SeeAllButtonProps) {
15+
return (
16+
<InternalLink className={className} to={to} onClick={onClick}>
17+
<Flex alignItems="center" gap={1}>
18+
<Text>{i18n('action_see-all')}</Text>
19+
<Icon data={ArrowRight} size={16} />
20+
</Flex>
21+
</InternalLink>
22+
);
23+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"action_see-all": "See all"
3+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import {registerKeysets} from '../../../utils/i18n';
2+
3+
import en from './en.json';
4+
5+
const COMPONENT = 'ydb-see-all-button';
6+
7+
export default registerKeysets(COMPONENT, {en});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.ydb-stats-wrapper {
2+
overflow: auto;
3+
4+
padding: var(--g-spacing-4);
5+
6+
border: 1px solid var(--g-color-line-generic);
7+
border-radius: var(--g-border-radius-xs);
8+
9+
&__header {
10+
position: sticky;
11+
top: 0;
12+
left: 0;
13+
}
14+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import {Flex, Text} from '@gravity-ui/uikit';
2+
3+
import {SeeAllButton} from '../../../../../components/SeeAllButton/SeeAllButton';
4+
import {cn} from '../../../../../utils/cn';
5+
6+
const b = cn('ydb-stats-wrapper');
7+
8+
import './StatsWrapper.scss';
9+
10+
interface StatsWrapperProps {
11+
children: React.ReactNode;
12+
className?: string;
13+
title: string;
14+
description?: string;
15+
allEntitiesLink?: string;
16+
onAllEntitiesClick?: () => void;
17+
}
18+
19+
export function StatsWrapper({
20+
children,
21+
className,
22+
title,
23+
description,
24+
allEntitiesLink,
25+
onAllEntitiesClick,
26+
}: StatsWrapperProps) {
27+
return (
28+
<Flex className={b(null, className)} gap={2} direction="column">
29+
<Flex justifyContent="space-between" wrap="nowrap" className={b('header')}>
30+
<Flex direction="column">
31+
<Text variant="subheader-2">{title}</Text>
32+
{description && <Text color="secondary">{description}</Text>}
33+
</Flex>
34+
{allEntitiesLink && (
35+
<SeeAllButton to={allEntitiesLink} onClick={onAllEntitiesClick} />
36+
)}
37+
</Flex>
38+
{children}
39+
</Flex>
40+
);
41+
}

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

Lines changed: 0 additions & 21 deletions
This file was deleted.
Lines changed: 41 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
1-
import React from 'react';
1+
import {Flex} from '@gravity-ui/uikit';
22

3-
import {ArrowRight} from '@gravity-ui/icons';
4-
import {Flex, Icon, SegmentedRadioGroup, Tab, TabList, TabProvider} from '@gravity-ui/uikit';
5-
6-
import {InternalLink} from '../../../../../components/InternalLink';
7-
import {
8-
TENANT_CPU_NODES_MODE_IDS,
9-
TENANT_CPU_TABS_IDS,
10-
TENANT_DIAGNOSTICS_TABS_IDS,
11-
} from '../../../../../store/reducers/tenant/constants';
3+
import {setTopQueriesFilters} from '../../../../../store/reducers/executeTopQueries/executeTopQueries';
4+
import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants';
125
import type {AdditionalNodesProps} from '../../../../../types/additionalProps';
13-
import {cn} from '../../../../../utils/cn';
6+
import {useTypedDispatch} from '../../../../../utils/hooks';
147
import {useDiagnosticsPageLinkGetter} from '../../../Diagnostics/DiagnosticsPages';
8+
import {StatsWrapper} from '../StatsWrapper/StatsWrapper';
159
import {TenantDashboard} from '../TenantDashboard/TenantDashboard';
1610
import i18n from '../i18n';
1711

@@ -20,106 +14,60 @@ import {TopNodesByLoad} from './TopNodesByLoad';
2014
import {TopQueries} from './TopQueries';
2115
import {TopShards} from './TopShards';
2216
import {cpuDashboardConfig} from './cpuDashboardConfig';
23-
import {useTenantCpuQueryParams} from './useTenantCpuQueryParams';
24-
25-
import './TenantCpu.scss';
26-
27-
const b = cn('tenant-cpu');
28-
29-
const cpuTabs = [
30-
{id: TENANT_CPU_TABS_IDS.nodes, title: i18n('title_top-nodes')},
31-
{id: TENANT_CPU_TABS_IDS.shards, title: i18n('title_top-shards')},
32-
{id: TENANT_CPU_TABS_IDS.queries, title: i18n('title_top-queries')},
33-
];
3417

3518
interface TenantCpuProps {
3619
tenantName: string;
3720
additionalNodesProps?: AdditionalNodesProps;
3821
}
3922

4023
export function TenantCpu({tenantName, additionalNodesProps}: TenantCpuProps) {
41-
const {cpuTab, nodesMode, handleCpuTabChange, handleNodesModeChange} =
42-
useTenantCpuQueryParams();
24+
const dispatch = useTypedDispatch();
4325
const getDiagnosticsPageLink = useDiagnosticsPageLinkGetter();
4426

45-
const renderNodesContent = () => {
46-
const nodesModeControl = (
47-
<SegmentedRadioGroup value={nodesMode} onUpdate={handleNodesModeChange}>
48-
<SegmentedRadioGroup.Option value={TENANT_CPU_NODES_MODE_IDS.load}>
49-
{i18n('action_by-load')}
50-
</SegmentedRadioGroup.Option>
51-
<SegmentedRadioGroup.Option value={TENANT_CPU_NODES_MODE_IDS.pools}>
52-
{i18n('action_by-pool-usage')}
53-
</SegmentedRadioGroup.Option>
54-
</SegmentedRadioGroup>
55-
);
27+
const allNodesLink = getDiagnosticsPageLink(TENANT_DIAGNOSTICS_TABS_IDS.nodes);
28+
const topShardsLink = getDiagnosticsPageLink(TENANT_DIAGNOSTICS_TABS_IDS.topShards);
29+
const topQueriesLink = getDiagnosticsPageLink(TENANT_DIAGNOSTICS_TABS_IDS.topQueries);
5630

57-
const allNodesButton = (
58-
<InternalLink
59-
className={b('all-nodes-link')}
60-
to={getDiagnosticsPageLink(TENANT_DIAGNOSTICS_TABS_IDS.nodes)}
31+
return (
32+
<Flex direction="column" gap={4}>
33+
<TenantDashboard database={tenantName} charts={cpuDashboardConfig} />
34+
<StatsWrapper
35+
allEntitiesLink={allNodesLink}
36+
title={i18n('title_top-nodes-load')}
37+
description={i18n('context_top-nodes-load')}
6138
>
62-
{i18n('action_all-nodes')}
63-
<Icon data={ArrowRight} size={16} />
64-
</InternalLink>
65-
);
66-
67-
const nodesComponent =
68-
nodesMode === TENANT_CPU_NODES_MODE_IDS.load ? (
6939
<TopNodesByLoad
7040
tenantName={tenantName}
7141
additionalNodesProps={additionalNodesProps}
7242
/>
73-
) : (
43+
</StatsWrapper>
44+
<StatsWrapper
45+
title={i18n('title_top-nodes-pool')}
46+
allEntitiesLink={allNodesLink}
47+
description={i18n('context_top-nodes-pool')}
48+
>
7449
<TopNodesByCpu
7550
tenantName={tenantName}
7651
additionalNodesProps={additionalNodesProps}
7752
/>
78-
);
79-
80-
return (
81-
<Flex direction="column" gap={2}>
82-
<Flex justifyContent="space-between" alignItems="center">
83-
{nodesModeControl}
84-
{allNodesButton}
85-
</Flex>
86-
{nodesComponent}
87-
</Flex>
88-
);
89-
};
90-
91-
const renderTabContent = () => {
92-
switch (cpuTab) {
93-
case TENANT_CPU_TABS_IDS.nodes:
94-
return renderNodesContent();
95-
case TENANT_CPU_TABS_IDS.shards:
96-
return <TopShards tenantName={tenantName} path={tenantName} />;
97-
case TENANT_CPU_TABS_IDS.queries:
98-
return <TopQueries tenantName={tenantName} />;
99-
default:
100-
return null;
101-
}
102-
};
103-
104-
return (
105-
<React.Fragment>
106-
<TenantDashboard database={tenantName} charts={cpuDashboardConfig} />
107-
108-
<div className={b('tabs-container')}>
109-
<TabProvider value={cpuTab}>
110-
<TabList size="m">
111-
{cpuTabs.map(({id, title}) => {
112-
return (
113-
<Tab key={id} value={id} onClick={() => handleCpuTabChange(id)}>
114-
{title}
115-
</Tab>
116-
);
117-
})}
118-
</TabList>
119-
</TabProvider>
120-
121-
<div className={b('tab-content')}>{renderTabContent()}</div>
122-
</div>
123-
</React.Fragment>
53+
</StatsWrapper>
54+
<StatsWrapper
55+
title={i18n('title_top-shards')}
56+
allEntitiesLink={topShardsLink}
57+
description={i18n('context_top-shards')}
58+
>
59+
<TopShards tenantName={tenantName} path={tenantName} />
60+
</StatsWrapper>
61+
<StatsWrapper
62+
title={i18n('title_top-queries')}
63+
allEntitiesLink={topQueriesLink}
64+
description={i18n('context_top-queries')}
65+
onAllEntitiesClick={() =>
66+
dispatch(setTopQueriesFilters({from: undefined, to: undefined}))
67+
}
68+
>
69+
<TopQueries tenantName={tenantName} />
70+
</StatsWrapper>
71+
</Flex>
12472
);
12573
}

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

Lines changed: 9 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,24 @@
1-
import React from 'react';
2-
3-
import {useHistory, useLocation} from 'react-router-dom';
4-
5-
import {ResizeableDataTable} from '../../../../../components/ResizeableDataTable/ResizeableDataTable';
6-
import {parseQuery} from '../../../../../routes';
7-
import {
8-
setTopQueriesFilters,
9-
topQueriesApi,
10-
} from '../../../../../store/reducers/executeTopQueries/executeTopQueries';
11-
import {changeUserInput, setIsDirty} from '../../../../../store/reducers/query/query';
12-
import {
13-
TENANT_DIAGNOSTICS_TABS_IDS,
14-
TENANT_PAGE,
15-
TENANT_PAGES_IDS,
16-
TENANT_QUERY_TABS_ID,
17-
} from '../../../../../store/reducers/tenant/constants';
1+
import {topQueriesApi} from '../../../../../store/reducers/executeTopQueries/executeTopQueries';
182
import {
193
TENANT_OVERVIEW_TABLES_LIMIT,
204
TENANT_OVERVIEW_TABLES_SETTINGS,
215
} from '../../../../../utils/constants';
22-
import {useAutoRefreshInterval, useTypedDispatch} from '../../../../../utils/hooks';
23-
import {useChangeInputWithConfirmation} from '../../../../../utils/hooks/withConfirmation/useChangeInputWithConfirmation';
6+
import {useAutoRefreshInterval} from '../../../../../utils/hooks';
247
import {parseQueryErrorToString} from '../../../../../utils/query';
25-
import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
8+
import {QueriesTableWithDrawer} from '../../TopQueries/QueriesTableWithDrawer';
269
import {getTenantOverviewTopQueriesColumns} from '../../TopQueries/columns/columns';
2710
import {TOP_QUERIES_COLUMNS_WIDTH_LS_KEY} from '../../TopQueries/columns/constants';
2811
import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
29-
import {getSectionTitle} from '../getSectionTitle';
30-
import i18n from '../i18n';
31-
import {b} from '../utils';
3212

3313
interface TopQueriesProps {
3414
tenantName: string;
3515
}
3616

37-
export function TopQueries({tenantName}: TopQueriesProps) {
38-
const dispatch = useTypedDispatch();
39-
const location = useLocation();
40-
const history = useHistory();
41-
42-
const query = parseQuery(location);
17+
const columns = getTenantOverviewTopQueriesColumns();
4318

19+
export function TopQueries({tenantName}: TopQueriesProps) {
4420
const [autoRefreshInterval] = useAutoRefreshInterval();
4521

46-
const columns = React.useMemo(() => {
47-
return getTenantOverviewTopQueriesColumns();
48-
}, []);
49-
5022
const {currentData, isFetching, error} = topQueriesApi.useGetTopQueriesQuery(
5123
{database: tenantName, timeFrame: 'hour', limit: TENANT_OVERVIEW_TABLES_LIMIT},
5224
{pollingInterval: autoRefreshInterval},
@@ -55,54 +27,19 @@ export function TopQueries({tenantName}: TopQueriesProps) {
5527
const loading = isFetching && currentData === undefined;
5628
const data = currentData?.resultSets?.[0]?.result || [];
5729

58-
const applyRowClick = React.useCallback(
59-
(row: any) => {
60-
const {QueryText: input} = row;
61-
62-
dispatch(changeUserInput({input}));
63-
dispatch(setIsDirty(false));
64-
65-
const queryParams = parseQuery(location);
66-
67-
const queryPath = getTenantPath({
68-
...queryParams,
69-
[TENANT_PAGE]: TENANT_PAGES_IDS.query,
70-
[TenantTabsGroups.queryTab]: TENANT_QUERY_TABS_ID.newQuery,
71-
});
72-
73-
history.push(queryPath);
74-
},
75-
[dispatch, history, location],
76-
);
77-
78-
const handleRowClick = useChangeInputWithConfirmation(applyRowClick);
79-
80-
const title = getSectionTitle({
81-
entity: i18n('queries'),
82-
postfix: i18n('by-cpu-time', {executionPeriod: i18n('executed-last-hour')}),
83-
onClick: () => {
84-
dispatch(setTopQueriesFilters({from: undefined, to: undefined}));
85-
},
86-
link: getTenantPath({
87-
...query,
88-
[TenantTabsGroups.diagnosticsTab]: TENANT_DIAGNOSTICS_TABS_IDS.topQueries,
89-
}),
90-
});
91-
9230
return (
9331
<TenantOverviewTableLayout
94-
title={title}
9532
loading={loading}
9633
error={parseQueryErrorToString(error)}
9734
withData={Boolean(currentData)}
9835
>
99-
<ResizeableDataTable
36+
<QueriesTableWithDrawer
10037
columnsWidthLSKey={TOP_QUERIES_COLUMNS_WIDTH_LS_KEY}
10138
data={data}
10239
columns={columns}
103-
onRowClick={handleRowClick}
104-
rowClassName={() => b('top-queries-row')}
105-
settings={TENANT_OVERVIEW_TABLES_SETTINGS}
40+
tableSettings={TENANT_OVERVIEW_TABLES_SETTINGS}
41+
drawerId="tenant-overview-query-details"
42+
storageKey="tenant-overview-queries-drawer-width"
10643
/>
10744
</TenantOverviewTableLayout>
10845
);

0 commit comments

Comments
 (0)