ydb-embedded-ui 4.22.0 → 4.24.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/components/ProgressViewer/ProgressViewer.tsx +1 -1
  3. package/dist/containers/Cluster/Cluster.tsx +2 -0
  4. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.scss +14 -5
  5. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.tsx +104 -24
  6. package/dist/containers/Cluster/ClusterInfoSkeleton/ClusterInfoSkeleton.tsx +1 -1
  7. package/dist/containers/Cluster/i18n/en.json +16 -0
  8. package/dist/containers/Cluster/i18n/index.ts +11 -0
  9. package/dist/containers/Cluster/i18n/ru.json +16 -0
  10. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByCpu.tsx +18 -3
  11. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByLoad.tsx +18 -3
  12. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopQueries.tsx +17 -1
  13. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopShards.tsx +20 -1
  14. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TopNodesByMemory.tsx +18 -3
  15. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverviewTableLayout.tsx +2 -1
  16. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopGroups.tsx +19 -2
  17. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopTables.tsx +8 -1
  18. package/dist/containers/Tenant/Diagnostics/TenantOverview/getSectionTitle.tsx +28 -0
  19. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json +17 -1
  20. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/ru.json +17 -1
  21. package/dist/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx +19 -11
  22. package/dist/services/api.ts +1 -2
  23. package/dist/store/reducers/cluster/__test__/parseGroupsStatsQueryResponse.test.ts +121 -0
  24. package/dist/store/reducers/cluster/cluster.ts +46 -2
  25. package/dist/store/reducers/cluster/types.ts +29 -4
  26. package/dist/store/reducers/cluster/utils.ts +88 -0
  27. package/dist/store/reducers/settings/settings.ts +14 -6
  28. package/dist/types/api/cluster.ts +3 -0
  29. package/dist/utils/hooks/index.ts +1 -0
  30. package/dist/utils/hooks/useSearchQuery.ts +9 -0
  31. package/dist/utils/settings.ts +4 -1
  32. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.24.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.23.0...v4.24.0) (2023-12-07)
4
+
5
+
6
+ ### Features
7
+
8
+ * always use localStorage if no settingsApi ([#603](https://github.com/ydb-platform/ydb-embedded-ui/issues/603)) ([ff692df](https://github.com/ydb-platform/ydb-embedded-ui/commit/ff692dffa99d278f6b261bbf1aac0ee24c661a6d))
9
+
10
+ ## [4.23.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.22.0...v4.23.0) (2023-12-06)
11
+
12
+
13
+ ### Features
14
+
15
+ * **ClusterInfo:** display groups stats ([#598](https://github.com/ydb-platform/ydb-embedded-ui/issues/598)) ([c31d048](https://github.com/ydb-platform/ydb-embedded-ui/commit/c31d0480a1b91cf01a660fd1d9726c6708f7c252))
16
+ * **TenantOverview:** add links to sections titles ([#599](https://github.com/ydb-platform/ydb-embedded-ui/issues/599)) ([30401fa](https://github.com/ydb-platform/ydb-embedded-ui/commit/30401fa354d90943bc4af4ddbf65466ce10381f9))
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * **Schema:** display keys in right order ([#596](https://github.com/ydb-platform/ydb-embedded-ui/issues/596)) ([c99b7e2](https://github.com/ydb-platform/ydb-embedded-ui/commit/c99b7e2e97acffc1cab450dfbf758c38b8b6e4d5))
22
+
3
23
  ## [4.22.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.21.1...v4.22.0) (2023-11-27)
4
24
 
5
25
 
@@ -44,7 +44,7 @@ Props description:
44
44
  interface ProgressViewerProps {
45
45
  value?: number | string;
46
46
  capacity?: number | string;
47
- formatValues?: (value?: number, capacity?: number) => (string | undefined)[];
47
+ formatValues?: (value?: number, capacity?: number) => (string | number | undefined)[];
48
48
  percents?: boolean;
49
49
  className?: string;
50
50
  size?: ProgressViewerSize;
@@ -68,6 +68,7 @@ function Cluster({
68
68
  loading: clusterLoading,
69
69
  wasLoaded: clusterWasLoaded,
70
70
  error: clusterError,
71
+ groupsStats,
71
72
  } = useTypedSelector((state) => state.cluster);
72
73
  const {
73
74
  nodes,
@@ -135,6 +136,7 @@ function Cluster({
135
136
  <div className={b()} ref={container}>
136
137
  <ClusterInfo
137
138
  cluster={cluster}
139
+ groupsStats={groupsStats}
138
140
  versionsValues={versionsValues}
139
141
  loading={infoLoading}
140
142
  error={clusterError}
@@ -49,10 +49,6 @@
49
49
  }
50
50
  }
51
51
 
52
- &__metric-field {
53
- margin-top: -8px;
54
- }
55
-
56
52
  &__system-tablets {
57
53
  display: flex;
58
54
  flex-wrap: wrap;
@@ -83,7 +79,6 @@
83
79
  margin-left: 15px;
84
80
  padding: 0 !important;
85
81
  }
86
-
87
82
  &__links {
88
83
  display: flex;
89
84
  flex-flow: row wrap;
@@ -91,6 +86,20 @@
91
86
  gap: 12px;
92
87
  }
93
88
 
89
+ &__storage-groups-stats {
90
+ display: flex;
91
+ flex-direction: column;
92
+ gap: 11px;
93
+ }
94
+
95
+ &__groups-stats-bar {
96
+ cursor: pointer;
97
+ }
98
+
99
+ &__groups-stats-popup-content {
100
+ padding: 12px;
101
+ }
102
+
94
103
  &__clipboard-button {
95
104
  display: flex;
96
105
  align-items: center;
@@ -10,6 +10,7 @@ import {Tablet} from '../../../components/Tablet';
10
10
  import {ResponseError} from '../../../components/Errors/ResponseError';
11
11
  import {ExternalLinkWithIcon} from '../../../components/ExternalLinkWithIcon/ExternalLinkWithIcon';
12
12
  import {IconWrapper as Icon} from '../../../components/Icon/Icon';
13
+ import {ContentWithPopup} from '../../../components/ContentWithPopup/ContentWithPopup';
13
14
 
14
15
  import type {IResponseError} from '../../../types/api/error';
15
16
  import type {AdditionalClusterProps, ClusterLink} from '../../../types/additionalProps';
@@ -18,14 +19,21 @@ import type {TClusterInfo} from '../../../types/api/cluster';
18
19
  import {backend, customBackend} from '../../../store';
19
20
  import {formatStorageValues} from '../../../utils/dataFormatters/dataFormatters';
20
21
  import {useSetting, useTypedSelector} from '../../../utils/hooks';
22
+ import {formatBytes, getSizeWithSignificantDigits} from '../../../utils/bytesParsers';
21
23
  import {
22
24
  CLUSTER_DEFAULT_TITLE,
23
25
  CLUSTER_INFO_HIDDEN_KEY,
24
26
  DEVELOPER_UI_TITLE,
25
27
  } from '../../../utils/constants';
28
+ import type {
29
+ ClusterGroupsStats,
30
+ DiskErasureGroupsStats,
31
+ DiskGroupsStats,
32
+ } from '../../../store/reducers/cluster/types';
26
33
 
27
34
  import {VersionsBar} from '../VersionsBar/VersionsBar';
28
35
  import {ClusterInfoSkeleton} from '../ClusterInfoSkeleton/ClusterInfoSkeleton';
36
+ import i18n from '../i18n';
29
37
 
30
38
  import {compareTablets} from './utils';
31
39
 
@@ -33,9 +41,85 @@ import './ClusterInfo.scss';
33
41
 
34
42
  const b = block('cluster-info');
35
43
 
44
+ interface GroupsStatsPopupContentProps {
45
+ stats: DiskErasureGroupsStats;
46
+ }
47
+
48
+ const GroupsStatsPopupContent = ({stats}: GroupsStatsPopupContentProps) => {
49
+ const {diskType, erasure, allocatedSize, availableSize} = stats;
50
+
51
+ const sizeToConvert = getSizeWithSignificantDigits(Math.max(allocatedSize, availableSize), 2);
52
+
53
+ const convertedAllocatedSize = formatBytes({value: allocatedSize, size: sizeToConvert});
54
+ const convertedAvailableSize = formatBytes({value: availableSize, size: sizeToConvert});
55
+
56
+ const usage = Math.round((allocatedSize / (allocatedSize + availableSize)) * 100);
57
+
58
+ const info = [
59
+ {
60
+ label: i18n('disk-type'),
61
+ value: diskType,
62
+ },
63
+ {
64
+ label: i18n('erasure'),
65
+ value: erasure,
66
+ },
67
+ {
68
+ label: i18n('allocated'),
69
+ value: convertedAllocatedSize,
70
+ },
71
+ {
72
+ label: i18n('available'),
73
+ value: convertedAvailableSize,
74
+ },
75
+ {
76
+ label: i18n('usage'),
77
+ value: usage + '%',
78
+ },
79
+ ];
80
+
81
+ return (
82
+ <InfoViewer dots={true} info={info} className={b('groups-stats-popup-content')} size="s" />
83
+ );
84
+ };
85
+
86
+ interface DiskGroupsStatsProps {
87
+ stats: DiskGroupsStats;
88
+ }
89
+
90
+ const DiskGroupsStatsBars = ({stats}: DiskGroupsStatsProps) => {
91
+ return (
92
+ <div className={b('storage-groups-stats')}>
93
+ {Object.values(stats).map((erasureStats) => (
94
+ <ContentWithPopup
95
+ placement={['right']}
96
+ key={erasureStats.erasure}
97
+ content={<GroupsStatsPopupContent stats={erasureStats} />}
98
+ >
99
+ <ProgressViewer
100
+ className={b('groups-stats-bar')}
101
+ value={erasureStats.createdGroups}
102
+ capacity={erasureStats.totalGroups}
103
+ />
104
+ </ContentWithPopup>
105
+ ))}
106
+ </div>
107
+ );
108
+ };
109
+
110
+ const getGroupsStatsFields = (groupsStats: ClusterGroupsStats) => {
111
+ return Object.keys(groupsStats).map((diskType) => {
112
+ return {
113
+ label: i18n('storage-groups', {diskType}),
114
+ value: <DiskGroupsStatsBars stats={groupsStats[diskType]} />,
115
+ };
116
+ });
117
+ };
118
+
36
119
  const getInfo = (
37
120
  cluster: TClusterInfo,
38
121
  versionsValues: VersionValue[],
122
+ groupsStats: ClusterGroupsStats,
39
123
  additionalInfo: InfoViewerItem[],
40
124
  links: ClusterLink[],
41
125
  ) => {
@@ -43,14 +127,14 @@ const getInfo = (
43
127
 
44
128
  if (cluster.DataCenters) {
45
129
  info.push({
46
- label: 'DC',
130
+ label: i18n('dc'),
47
131
  value: <Tags tags={cluster.DataCenters} />,
48
132
  });
49
133
  }
50
134
 
51
135
  if (cluster.SystemTablets) {
52
136
  info.push({
53
- label: 'Tablets',
137
+ label: i18n('tablets'),
54
138
  value: (
55
139
  <div className={b('system-tablets')}>
56
140
  {cluster.SystemTablets.sort(compareTablets).map((tablet, tabletIndex) => (
@@ -63,46 +147,40 @@ const getInfo = (
63
147
 
64
148
  if (cluster.Tenants) {
65
149
  info.push({
66
- label: 'Databases',
150
+ label: i18n('databases'),
67
151
  value: cluster.Tenants,
68
152
  });
69
153
  }
70
154
 
71
155
  info.push(
72
156
  {
73
- label: 'Nodes',
74
- value: (
75
- <ProgressViewer
76
- className={b('metric-field')}
77
- value={cluster?.NodesAlive}
78
- capacity={cluster?.NodesTotal}
79
- />
80
- ),
157
+ label: i18n('nodes'),
158
+ value: <ProgressViewer value={cluster?.NodesAlive} capacity={cluster?.NodesTotal} />,
81
159
  },
82
160
  {
83
- label: 'Load',
84
- value: (
85
- <ProgressViewer
86
- className={b('metric-field')}
87
- value={cluster?.LoadAverage}
88
- capacity={cluster?.NumberOfCpus}
89
- />
90
- ),
161
+ label: i18n('load'),
162
+ value: <ProgressViewer value={cluster?.LoadAverage} capacity={cluster?.NumberOfCpus} />,
91
163
  },
92
164
  {
93
- label: 'Storage',
165
+ label: i18n('storage-size'),
94
166
  value: (
95
167
  <ProgressViewer
96
- className={b('metric-field')}
97
168
  value={cluster?.StorageUsed}
98
169
  capacity={cluster?.StorageTotal}
99
170
  formatValues={formatStorageValues}
100
171
  />
101
172
  ),
102
173
  },
174
+ );
175
+
176
+ if (Object.keys(groupsStats).length) {
177
+ info.push(...getGroupsStatsFields(groupsStats));
178
+ }
179
+
180
+ info.push(
103
181
  ...additionalInfo,
104
182
  {
105
- label: 'Links',
183
+ label: i18n('links'),
106
184
  value: (
107
185
  <div className={b('links')}>
108
186
  {links.map(({title, url}) => (
@@ -112,7 +190,7 @@ const getInfo = (
112
190
  ),
113
191
  },
114
192
  {
115
- label: 'Versions',
193
+ label: i18n('versions'),
116
194
  value: <VersionsBar versionsValues={versionsValues} />,
117
195
  },
118
196
  );
@@ -123,6 +201,7 @@ const getInfo = (
123
201
  interface ClusterInfoProps {
124
202
  cluster?: TClusterInfo;
125
203
  versionsValues?: VersionValue[];
204
+ groupsStats?: ClusterGroupsStats;
126
205
  loading?: boolean;
127
206
  error?: IResponseError;
128
207
  additionalClusterProps?: AdditionalClusterProps;
@@ -131,6 +210,7 @@ interface ClusterInfoProps {
131
210
  export const ClusterInfo = ({
132
211
  cluster = {},
133
212
  versionsValues = [],
213
+ groupsStats = {},
134
214
  loading,
135
215
  error,
136
216
  additionalClusterProps = {},
@@ -151,7 +231,7 @@ export const ClusterInfo = ({
151
231
 
152
232
  const {info = [], links = []} = additionalClusterProps;
153
233
 
154
- const clusterInfo = getInfo(cluster, versionsValues, info, [
234
+ const clusterInfo = getInfo(cluster, versionsValues, groupsStats, info, [
155
235
  {title: DEVELOPER_UI_TITLE, url: internalLink},
156
236
  ...links,
157
237
  ]);
@@ -18,7 +18,7 @@ interface ClusterInfoSkeletonProps {
18
18
  rows?: number;
19
19
  }
20
20
 
21
- export const ClusterInfoSkeleton = ({rows = 7, className}: ClusterInfoSkeletonProps) => (
21
+ export const ClusterInfoSkeleton = ({rows = 8, className}: ClusterInfoSkeletonProps) => (
22
22
  <div className={b(null, className)}>
23
23
  {[...new Array(rows)].map((_, index) => (
24
24
  <div className={b('row')} key={`skeleton-row-${index}`}>
@@ -0,0 +1,16 @@
1
+ {
2
+ "disk-type": "Disk Type",
3
+ "erasure": "Erasure",
4
+ "allocated": "Allocated",
5
+ "available": "Available",
6
+ "usage": "Usage",
7
+ "dc": "DC",
8
+ "tablets": "Tablets",
9
+ "databases": "Databases",
10
+ "nodes": "Nodes",
11
+ "load": "Load",
12
+ "storage-size": "Storage size",
13
+ "storage-groups": "Storage groups, {{diskType}}",
14
+ "links": "Links",
15
+ "versions": "Versions"
16
+ }
@@ -0,0 +1,11 @@
1
+ import {i18n, Lang} from '../../../utils/i18n';
2
+
3
+ import en from './en.json';
4
+ import ru from './ru.json';
5
+
6
+ const COMPONENT = 'ydb-cluster';
7
+
8
+ i18n.registerKeyset(Lang.En, COMPONENT, en);
9
+ i18n.registerKeyset(Lang.Ru, COMPONENT, ru);
10
+
11
+ export default i18n.keyset(COMPONENT);
@@ -0,0 +1,16 @@
1
+ {
2
+ "disk-type": "Тип диска",
3
+ "erasure": "Режим",
4
+ "allocated": "Использовано",
5
+ "available": "Доступно",
6
+ "usage": "Потребление",
7
+ "dc": "ДЦ",
8
+ "tablets": "Таблетки",
9
+ "databases": "Базы данных",
10
+ "nodes": "Узлы",
11
+ "load": "Нагрузка",
12
+ "storage-size": "Размер хранилища",
13
+ "storage-groups": "Группы хранения, {{diskType}}",
14
+ "links": "Ссылки",
15
+ "versions": "Версии"
16
+ }
@@ -1,16 +1,20 @@
1
1
  import {useDispatch} from 'react-redux';
2
2
  import {useCallback} from 'react';
3
3
 
4
- import {useAutofetcher, useTypedSelector} from '../../../../../utils/hooks';
4
+ import {useAutofetcher, useSearchQuery, useTypedSelector} from '../../../../../utils/hooks';
5
5
  import {
6
6
  getTopNodesByCpu,
7
7
  selectTopNodesByCpu,
8
8
  setDataWasNotLoaded,
9
9
  } from '../../../../../store/reducers/tenantOverview/topNodesByCpu/topNodesByCpu';
10
+ import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants';
10
11
  import type {AdditionalNodesProps} from '../../../../../types/additionalProps';
11
12
  import {getTopNodesByCpuColumns} from '../../../../Nodes/getNodesColumns';
12
- import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
13
13
 
14
+ import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
15
+
16
+ import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
17
+ import {getSectionTitle} from '../getSectionTitle';
14
18
  import i18n from '../i18n';
15
19
 
16
20
  interface TopNodesByCpuProps {
@@ -21,6 +25,8 @@ interface TopNodesByCpuProps {
21
25
  export function TopNodesByCpu({path, additionalNodesProps}: TopNodesByCpuProps) {
22
26
  const dispatch = useDispatch();
23
27
 
28
+ const query = useSearchQuery();
29
+
24
30
  const {wasLoaded, loading, error} = useTypedSelector((state) => state.topNodesByCpu);
25
31
  const {autorefresh} = useTypedSelector((state) => state.schema);
26
32
  const topNodes = useTypedSelector(selectTopNodesByCpu);
@@ -39,11 +45,20 @@ export function TopNodesByCpu({path, additionalNodesProps}: TopNodesByCpuProps)
39
45
 
40
46
  useAutofetcher(fetchNodes, [fetchNodes], autorefresh);
41
47
 
48
+ const title = getSectionTitle({
49
+ entity: i18n('nodes'),
50
+ postfix: i18n('by-pools-usage'),
51
+ link: getTenantPath({
52
+ ...query,
53
+ [TenantTabsGroups.diagnosticsTab]: TENANT_DIAGNOSTICS_TABS_IDS.nodes,
54
+ }),
55
+ });
56
+
42
57
  return (
43
58
  <TenantOverviewTableLayout
44
59
  data={topNodes || []}
45
60
  columns={columns}
46
- title="Top nodes by pools usage"
61
+ title={title}
47
62
  loading={loading}
48
63
  wasLoaded={wasLoaded}
49
64
  error={error}
@@ -1,16 +1,20 @@
1
1
  import {useDispatch} from 'react-redux';
2
2
  import {useCallback} from 'react';
3
3
 
4
- import {useAutofetcher, useTypedSelector} from '../../../../../utils/hooks';
4
+ import {useAutofetcher, useSearchQuery, useTypedSelector} from '../../../../../utils/hooks';
5
5
  import {
6
6
  getTopNodesByLoad,
7
7
  selectTopNodesByLoad,
8
8
  setDataWasNotLoaded,
9
9
  } from '../../../../../store/reducers/tenantOverview/topNodesByLoad/topNodesByLoad';
10
+ import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants';
10
11
  import type {AdditionalNodesProps} from '../../../../../types/additionalProps';
11
12
  import {getTopNodesByLoadColumns} from '../../../../Nodes/getNodesColumns';
12
- import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
13
13
 
14
+ import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
15
+
16
+ import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
17
+ import {getSectionTitle} from '../getSectionTitle';
14
18
  import i18n from '../i18n';
15
19
 
16
20
  interface TopNodesByLoadProps {
@@ -21,6 +25,8 @@ interface TopNodesByLoadProps {
21
25
  export function TopNodesByLoad({path, additionalNodesProps}: TopNodesByLoadProps) {
22
26
  const dispatch = useDispatch();
23
27
 
28
+ const query = useSearchQuery();
29
+
24
30
  const {wasLoaded, loading, error} = useTypedSelector((state) => state.topNodesByLoad);
25
31
  const {autorefresh} = useTypedSelector((state) => state.schema);
26
32
  const topNodes = useTypedSelector(selectTopNodesByLoad);
@@ -39,11 +45,20 @@ export function TopNodesByLoad({path, additionalNodesProps}: TopNodesByLoadProps
39
45
 
40
46
  useAutofetcher(fetchNodes, [fetchNodes], autorefresh);
41
47
 
48
+ const title = getSectionTitle({
49
+ entity: i18n('nodes'),
50
+ postfix: i18n('by-load'),
51
+ link: getTenantPath({
52
+ ...query,
53
+ [TenantTabsGroups.diagnosticsTab]: TENANT_DIAGNOSTICS_TABS_IDS.nodes,
54
+ }),
55
+ });
56
+
42
57
  return (
43
58
  <TenantOverviewTableLayout
44
59
  data={topNodes || []}
45
60
  columns={columns}
46
- title="Top nodes by load"
61
+ title={title}
47
62
  loading={loading}
48
63
  wasLoaded={wasLoaded}
49
64
  error={error}
@@ -3,6 +3,7 @@ import {useHistory, useLocation} from 'react-router';
3
3
  import {useCallback} from 'react';
4
4
 
5
5
  import {
6
+ TENANT_DIAGNOSTICS_TABS_IDS,
6
7
  TENANT_PAGE,
7
8
  TENANT_PAGES_IDS,
8
9
  TENANT_QUERY_TABS_ID,
@@ -14,9 +15,13 @@ import {
14
15
  import {changeUserInput} from '../../../../../store/reducers/executeQuery';
15
16
  import {useAutofetcher, useTypedSelector} from '../../../../../utils/hooks';
16
17
  import {parseQuery} from '../../../../../routes';
18
+
17
19
  import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
18
20
  import {getTenantOverviewTopQueriesColumns} from '../../TopQueries/getTopQueriesColumns';
21
+
19
22
  import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
23
+ import {getSectionTitle} from '../getSectionTitle';
24
+ import i18n from '../i18n';
20
25
 
21
26
  interface TopQueriesProps {
22
27
  path: string;
@@ -27,6 +32,8 @@ export function TopQueries({path}: TopQueriesProps) {
27
32
  const location = useLocation();
28
33
  const history = useHistory();
29
34
 
35
+ const query = parseQuery(location);
36
+
30
37
  const {autorefresh} = useTypedSelector((state) => state.schema);
31
38
 
32
39
  const {
@@ -68,12 +75,21 @@ export function TopQueries({path}: TopQueriesProps) {
68
75
  [dispatch, history, location],
69
76
  );
70
77
 
78
+ const title = getSectionTitle({
79
+ entity: i18n('queries'),
80
+ postfix: i18n('by-cpu-time'),
81
+ link: getTenantPath({
82
+ ...query,
83
+ [TenantTabsGroups.diagnosticsTab]: TENANT_DIAGNOSTICS_TABS_IDS.topQueries,
84
+ }),
85
+ });
86
+
71
87
  return (
72
88
  <TenantOverviewTableLayout
73
89
  data={data || []}
74
90
  columns={columns}
75
91
  onRowClick={handleRowClick}
76
- title="Top queries by cpu time"
92
+ title={title}
77
93
  loading={loading}
78
94
  wasLoaded={wasLoaded}
79
95
  error={error}
@@ -2,12 +2,20 @@ import {useDispatch} from 'react-redux';
2
2
  import {useLocation} from 'react-router';
3
3
 
4
4
  import {useAutofetcher, useTypedSelector} from '../../../../../utils/hooks';
5
+ import {parseQuery} from '../../../../../routes';
6
+
5
7
  import {
6
8
  sendTenantOverviewTopShardsQuery,
7
9
  setDataWasNotLoaded,
8
10
  } from '../../../../../store/reducers/tenantOverview/topShards/tenantOverviewTopShards';
11
+ import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants';
9
12
  import {getTopShardsColumns} from '../../TopShards/getTopShardsColumns';
13
+
14
+ import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
15
+
10
16
  import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
17
+ import {getSectionTitle} from '../getSectionTitle';
18
+ import i18n from '../i18n';
11
19
 
12
20
  interface TopShardsProps {
13
21
  path: string;
@@ -17,6 +25,8 @@ export const TopShards = ({path}: TopShardsProps) => {
17
25
  const dispatch = useDispatch();
18
26
  const location = useLocation();
19
27
 
28
+ const query = parseQuery(location);
29
+
20
30
  const {autorefresh, currentSchemaPath} = useTypedSelector((state) => state.schema);
21
31
 
22
32
  const {
@@ -39,11 +49,20 @@ export const TopShards = ({path}: TopShardsProps) => {
39
49
 
40
50
  const columns = getTopShardsColumns(path, location);
41
51
 
52
+ const title = getSectionTitle({
53
+ entity: i18n('shards'),
54
+ postfix: i18n('by-cpu-usage'),
55
+ link: getTenantPath({
56
+ ...query,
57
+ [TenantTabsGroups.diagnosticsTab]: TENANT_DIAGNOSTICS_TABS_IDS.topShards,
58
+ }),
59
+ });
60
+
42
61
  return (
43
62
  <TenantOverviewTableLayout
44
63
  data={data || []}
45
64
  columns={columns}
46
- title="Top shards by cpu usage"
65
+ title={title}
47
66
  loading={loading}
48
67
  wasLoaded={wasLoaded}
49
68
  error={error}
@@ -1,16 +1,20 @@
1
1
  import {useDispatch} from 'react-redux';
2
2
  import {useCallback} from 'react';
3
3
 
4
- import {useAutofetcher, useTypedSelector} from '../../../../../utils/hooks';
4
+ import {useAutofetcher, useTypedSelector, useSearchQuery} from '../../../../../utils/hooks';
5
5
  import {
6
6
  getTopNodesByMemory,
7
7
  selectTopNodesByMemory,
8
8
  setDataWasNotLoaded,
9
9
  } from '../../../../../store/reducers/tenantOverview/topNodesByMemory/topNodesByMemory';
10
+ import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants';
10
11
  import type {AdditionalNodesProps} from '../../../../../types/additionalProps';
11
12
  import {getTopNodesByMemoryColumns} from '../../../../Nodes/getNodesColumns';
12
- import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
13
13
 
14
+ import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
15
+
16
+ import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
17
+ import {getSectionTitle} from '../getSectionTitle';
14
18
  import i18n from '../i18n';
15
19
 
16
20
  interface TopNodesByMemoryProps {
@@ -21,6 +25,8 @@ interface TopNodesByMemoryProps {
21
25
  export function TopNodesByMemory({path, additionalNodesProps}: TopNodesByMemoryProps) {
22
26
  const dispatch = useDispatch();
23
27
 
28
+ const query = useSearchQuery();
29
+
24
30
  const {wasLoaded, loading, error} = useTypedSelector((state) => state.topNodesByMemory);
25
31
  const {autorefresh} = useTypedSelector((state) => state.schema);
26
32
  const topNodes = useTypedSelector(selectTopNodesByMemory);
@@ -41,11 +47,20 @@ export function TopNodesByMemory({path, additionalNodesProps}: TopNodesByMemoryP
41
47
 
42
48
  useAutofetcher(fetchNodes, [fetchNodes], autorefresh);
43
49
 
50
+ const title = getSectionTitle({
51
+ entity: i18n('nodes'),
52
+ postfix: i18n('by-memory'),
53
+ link: getTenantPath({
54
+ ...query,
55
+ [TenantTabsGroups.diagnosticsTab]: TENANT_DIAGNOSTICS_TABS_IDS.nodes,
56
+ }),
57
+ });
58
+
44
59
  return (
45
60
  <TenantOverviewTableLayout
46
61
  data={topNodes || []}
47
62
  columns={columns}
48
- title="Top nodes by memory"
63
+ title={title}
49
64
  loading={loading}
50
65
  wasLoaded={wasLoaded}
51
66
  error={error}
@@ -1,3 +1,4 @@
1
+ import type {ReactNode} from 'react';
1
2
  import cn from 'bem-cn-lite';
2
3
 
3
4
  import DataTable from '@gravity-ui/react-data-table';
@@ -14,7 +15,7 @@ import {ResponseError} from '../../../../components/Errors/ResponseError';
14
15
  const b = cn('tenant-overview');
15
16
 
16
17
  interface TenantOverviewTableLayoutProps<T> extends Omit<DataTableProps<T>, 'theme'> {
17
- title: string;
18
+ title: ReactNode;
18
19
  loading?: boolean;
19
20
  wasLoaded?: boolean;
20
21
  error?: IResponseError;