ydb-embedded-ui 3.4.4 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/components/Icon/Icon.tsx +6 -0
  3. package/dist/components/InfoViewer/InfoViewer.tsx +2 -2
  4. package/dist/components/InfoViewer/formatters/index.ts +0 -1
  5. package/dist/components/InfoViewer/formatters/table.ts +6 -0
  6. package/dist/components/LabelWithPopover/LabelWithPopover.tsx +3 -7
  7. package/dist/components/LagPopoverContent/LagPopoverContent.scss +13 -0
  8. package/dist/components/LagPopoverContent/LagPopoverContent.tsx +19 -0
  9. package/dist/components/LagPopoverContent/index.ts +1 -0
  10. package/dist/components/Tooltips/NodeEndpointsTooltip/NodeEndpointsTooltip.scss +5 -0
  11. package/dist/components/Tooltips/NodeEndpointsTooltip/NodeEndpointsTooltip.tsx +31 -0
  12. package/dist/components/TruncatedQuery/TruncatedQuery.js +1 -1
  13. package/dist/components/TruncatedQuery/TruncatedQuery.scss +7 -3
  14. package/dist/containers/Node/{NodePages.js → NodePages.ts} +1 -1
  15. package/dist/containers/Nodes/Nodes.tsx +2 -6
  16. package/dist/containers/Nodes/NodesTable.scss +11 -10
  17. package/dist/containers/Nodes/getNodesColumns.tsx +29 -24
  18. package/dist/containers/Storage/PDisk/PDisk.scss +2 -0
  19. package/dist/containers/Storage/PDiskPopup/PDiskPopup.tsx +6 -9
  20. package/dist/containers/Storage/Storage.js +12 -5
  21. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +3 -1
  22. package/dist/containers/Storage/StorageNodes/StorageNodes.scss +20 -7
  23. package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +43 -7
  24. package/dist/containers/Storage/VDisk/VDisk.tsx +3 -2
  25. package/dist/containers/Storage/VDiskPopup/VDiskPopup.tsx +4 -2
  26. package/dist/containers/Tablet/TabletControls/TabletControls.tsx +2 -2
  27. package/dist/containers/Tenant/Diagnostics/Consumers/Headers/Headers.scss +0 -8
  28. package/dist/containers/Tenant/Diagnostics/Consumers/Headers/Headers.tsx +3 -10
  29. package/dist/containers/Tenant/Diagnostics/Consumers/i18n/ru.json +1 -1
  30. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +11 -43
  31. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/TableInfo.tsx +19 -17
  32. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo.ts +192 -37
  33. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx +51 -32
  34. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/en.json +2 -1
  35. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/ru.json +2 -1
  36. package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.scss +0 -8
  37. package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.tsx +7 -21
  38. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss +20 -14
  39. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +49 -12
  40. package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.tsx +37 -18
  41. package/dist/containers/Tenant/QueryEditor/QueryEditor.js +1 -0
  42. package/dist/routes.ts +1 -1
  43. package/dist/services/api.d.ts +4 -0
  44. package/dist/services/api.js +3 -3
  45. package/dist/store/reducers/{executeQuery.js → executeQuery.ts} +51 -21
  46. package/dist/store/reducers/executeTopQueries.ts +5 -1
  47. package/dist/store/reducers/{nodesList.js → nodesList.ts} +19 -7
  48. package/dist/store/reducers/{olapStats.js → olapStats.ts} +8 -18
  49. package/dist/store/reducers/settings.js +1 -1
  50. package/dist/store/reducers/storage.js +8 -18
  51. package/dist/types/api/nodesList.ts +25 -0
  52. package/dist/types/api/query.ts +4 -1
  53. package/dist/types/api/schema.ts +523 -3
  54. package/dist/types/common.ts +1 -0
  55. package/dist/types/store/executeQuery.ts +42 -0
  56. package/dist/types/store/nodesList.ts +24 -0
  57. package/dist/types/store/olapStats.ts +14 -0
  58. package/dist/utils/index.js +9 -1
  59. package/dist/utils/nodes.ts +4 -0
  60. package/dist/utils/query.test.ts +42 -29
  61. package/dist/utils/query.ts +34 -22
  62. package/dist/utils/timeParsers/formatDuration.ts +30 -12
  63. package/dist/utils/timeParsers/i18n/en.json +4 -0
  64. package/dist/utils/timeParsers/i18n/ru.json +4 -0
  65. package/dist/utils/tooltip.js +2 -28
  66. package/package.json +1 -1
  67. package/dist/components/InfoViewer/formatters/topicStats.tsx +0 -29
@@ -3,6 +3,8 @@ import cn from 'bem-cn-lite';
3
3
 
4
4
  import {Label, Popup, PopupProps} from '@gravity-ui/uikit';
5
5
 
6
+ import type {NodesMap} from '../../../types/store/nodesList';
7
+
6
8
  import {InfoViewer, InfoViewerItem} from '../../../components/InfoViewer';
7
9
 
8
10
  import {EFlag} from '../../../types/api/enums';
@@ -13,7 +15,7 @@ import {isFullVDiskData} from '../../../utils/storage';
13
15
 
14
16
  import type {IUnavailableDonor} from '../utils/types';
15
17
 
16
- import {NodesHosts, preparePDiskData} from '../PDiskPopup';
18
+ import {preparePDiskData} from '../PDiskPopup';
17
19
 
18
20
  import './VDiskPopup.scss';
19
21
 
@@ -128,7 +130,7 @@ const prepareVDiskData = (data: TVDiskStateInfo, poolName?: string) => {
128
130
  interface VDiskPopupProps extends PopupProps {
129
131
  data: TVDiskStateInfo | IUnavailableDonor;
130
132
  poolName?: string;
131
- nodes?: NodesHosts;
133
+ nodes?: NodesMap;
132
134
  }
133
135
 
134
136
  export const VDiskPopup = ({data, poolName, nodes, ...props}: VDiskPopupProps) => {
@@ -9,8 +9,8 @@ import {b} from '../Tablet';
9
9
 
10
10
  enum EVisibleDialogType {
11
11
  'kill' = 'kill',
12
- 'stop' = 'kill',
13
- 'resume' = 'kill',
12
+ 'stop' = 'stop',
13
+ 'resume' = 'resume',
14
14
  }
15
15
 
16
16
  type VisibleDialogType = EVisibleDialogType | null;
@@ -2,12 +2,4 @@
2
2
  &__lags {
3
3
  white-space: nowrap;
4
4
  }
5
-
6
- &__lags-popover-content {
7
- max-width: 300px;
8
-
9
- div:nth-child(1) {
10
- margin-bottom: 10px;
11
- }
12
- }
13
5
  }
@@ -1,7 +1,7 @@
1
1
  import block from 'bem-cn-lite';
2
2
 
3
- import {ReadLagImage} from '../../../../../components/LagImages';
4
3
  import {LabelWithPopover} from '../../../../../components/LabelWithPopover';
4
+ import {LagPopoverContent} from '../../../../../components/LagPopoverContent';
5
5
 
6
6
  import {CONSUMERS_COLUMNS_IDS, CONSUMERS_COLUMNS_TITILES} from '../utils/constants';
7
7
 
@@ -14,14 +14,7 @@ const b = block('ydb-diagnostics-consumers-columns-header');
14
14
  export const ReadLagsHeader = () => (
15
15
  <LabelWithPopover
16
16
  className={b('lags')}
17
- headerText={CONSUMERS_COLUMNS_TITILES[CONSUMERS_COLUMNS_IDS.READ_LAGS]}
18
- popoverContent={
19
- <div className={b('lags-popover-content')}>
20
- <div>{i18n('lagsPopover.readLags')}</div>
21
- <div>
22
- <ReadLagImage />
23
- </div>
24
- </div>
25
- }
17
+ text={CONSUMERS_COLUMNS_TITILES[CONSUMERS_COLUMNS_IDS.READ_LAGS]}
18
+ popoverContent={<LagPopoverContent text={i18n('lagsPopover.readLags')} type="read" />}
26
19
  />
27
20
  );
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "noConsumersMessage.topic": "У этого топика нет читателей",
3
3
  "noConsumersMessage.stream": "У этого стрима нет читателей",
4
- "lagsPopover.readLags": "Статистика лагов чтения, максимальной значение среди всех партиций читателя (формат времени дд чч:мм:сс)",
4
+ "lagsPopover.readLags": "Статистика лагов чтения, максимальное значение среди всех партиций читателя (формат времени дд чч:мм:сс)",
5
5
  "table.emptyDataMessage": "По заданному поиску нет читателей",
6
6
  "controls.search": "Consumer"
7
7
  }
@@ -1,4 +1,4 @@
1
- import {ReactNode, useCallback, useMemo} from 'react';
1
+ import {ReactNode, useCallback} from 'react';
2
2
  import {shallowEqual, useDispatch, useSelector} from 'react-redux';
3
3
 
4
4
  import {Loader} from '../../../../components/Loader';
@@ -11,7 +11,7 @@ import {TopicInfo} from './TopicInfo';
11
11
  import {ChangefeedInfo} from './ChangefeedInfo';
12
12
  import {TableInfo} from './TableInfo';
13
13
 
14
- import {EPathType, TEvDescribeSchemeResult} from '../../../../types/api/schema';
14
+ import {EPathType} from '../../../../types/api/schema';
15
15
  import {
16
16
  isEntityWithMergedImplementation,
17
17
  isColumnEntityType,
@@ -33,35 +33,6 @@ import {
33
33
  } from '../../../../store/reducers/olapStats';
34
34
  import {useAutofetcher, useTypedSelector} from '../../../../utils/hooks';
35
35
 
36
- function prepareOlapTableGeneral(item?: TEvDescribeSchemeResult, olapStats?: any[]) {
37
- const tableData = item?.PathDescription?.ColumnTableDescription;
38
-
39
- const Bytes = olapStats?.reduce((acc, el) => {
40
- acc += parseInt(el.Bytes) || 0;
41
- return acc;
42
- }, 0);
43
- const Rows = olapStats?.reduce((acc, el) => {
44
- acc += parseInt(el.Rows) || 0;
45
- return acc;
46
- }, 0);
47
- const tabletIds = olapStats?.reduce((acc, el) => {
48
- acc.add(el.TabletId);
49
- return acc;
50
- }, new Set());
51
-
52
- return {
53
- PathDescription: {
54
- Self: item?.PathDescription?.Self,
55
- TableStats: {
56
- ColumnShardCount: tableData?.ColumnShardCount,
57
- Bytes: Bytes?.toLocaleString('ru-RU', {useGrouping: true}) ?? 0,
58
- Rows: Rows?.toLocaleString('ru-RU', {useGrouping: true}) ?? 0,
59
- Parts: tabletIds?.size ?? 0,
60
- },
61
- },
62
- };
63
- }
64
-
65
36
  interface OverviewProps {
66
37
  type?: EPathType;
67
38
  className?: string;
@@ -81,7 +52,7 @@ function Overview({type, tenantName, className}: OverviewProps) {
81
52
  } = useSelector((state: any) => state.schema);
82
53
 
83
54
  const {data: {result: olapStats} = {result: undefined}, loading: olapStatsLoading} =
84
- useSelector((state: any) => state.olapStats);
55
+ useTypedSelector((state) => state.olapStats);
85
56
 
86
57
  const loading = schemaLoading || olapStatsLoading;
87
58
 
@@ -134,13 +105,6 @@ function Overview({type, tenantName, className}: OverviewProps) {
134
105
 
135
106
  useAutofetcher(fetchData, [fetchData], autorefresh);
136
107
 
137
- const schemaData = useMemo(() => {
138
- return isTableType(type) && isColumnEntityType(type)
139
- ? // process data for ColumnTable
140
- prepareOlapTableGeneral(currentItem, olapStats)
141
- : currentItem;
142
- }, [type, olapStats, currentItem]);
143
-
144
108
  const renderContent = () => {
145
109
  // verbose mapping to guarantee a correct render for new path types
146
110
  // TS will error when a new type is added but not mapped here
@@ -149,17 +113,21 @@ function Overview({type, tenantName, className}: OverviewProps) {
149
113
  [EPathType.EPathTypeDir]: undefined,
150
114
  [EPathType.EPathTypeTable]: undefined,
151
115
  [EPathType.EPathTypeSubDomain]: undefined,
152
- [EPathType.EPathTypeTableIndex]: () => <TableIndexInfo data={schemaData} />,
116
+ [EPathType.EPathTypeTableIndex]: () => <TableIndexInfo data={currentItem} />,
153
117
  [EPathType.EPathTypeExtSubDomain]: undefined,
154
118
  [EPathType.EPathTypeColumnStore]: undefined,
155
119
  [EPathType.EPathTypeColumnTable]: undefined,
156
120
  [EPathType.EPathTypeCdcStream]: () => (
157
- <ChangefeedInfo data={schemaData} childrenPaths={mergedChildrenPaths} />
121
+ <ChangefeedInfo data={currentItem} childrenPaths={mergedChildrenPaths} />
158
122
  ),
159
- [EPathType.EPathTypePersQueueGroup]: () => <TopicInfo data={schemaData} />,
123
+ [EPathType.EPathTypePersQueueGroup]: () => <TopicInfo data={currentItem} />,
160
124
  };
161
125
 
162
- return (type && pathTypeToComponent[type]?.()) || <TableInfo data={schemaData} />;
126
+ return (
127
+ (type && pathTypeToComponent[type]?.()) || (
128
+ <TableInfo data={currentItem} type={type} olapStats={olapStats} />
129
+ )
130
+ );
163
131
  };
164
132
 
165
133
  if ((loading && !wasLoaded) || (isEntityWithMergedImpl && !mergedChildrenPaths)) {
@@ -1,8 +1,8 @@
1
1
  import {useMemo} from 'react';
2
2
  import cn from 'bem-cn-lite';
3
3
 
4
- import type {TEvDescribeSchemeResult} from '../../../../../types/api/schema';
5
-
4
+ import type {EPathType, TEvDescribeSchemeResult} from '../../../../../types/api/schema';
5
+ import type {KeyValueRow} from '../../../../../types/api/query';
6
6
  import {InfoViewer} from '../../../../../components/InfoViewer';
7
7
 
8
8
  import {getEntityName} from '../../../utils';
@@ -17,27 +17,40 @@ const b = cn('ydb-diagnostics-table-info');
17
17
 
18
18
  interface TableInfoProps {
19
19
  data?: TEvDescribeSchemeResult;
20
+ type?: EPathType;
21
+ olapStats?: KeyValueRow[];
20
22
  }
21
23
 
22
- export const TableInfo = ({data}: TableInfoProps) => {
24
+ export const TableInfo = ({data, type, olapStats}: TableInfoProps) => {
23
25
  const entityName = getEntityName(data?.PathDescription);
24
26
 
25
27
  const {
26
- generalTableInfo = [],
28
+ generalInfo = [],
27
29
  tableStatsInfo = [],
28
30
  tabletMetricsInfo = [],
29
31
  partitionConfigInfo = [],
30
- } = useMemo(() => prepareTableInfo(data), [data]);
32
+ } = useMemo(() => prepareTableInfo(data, type, olapStats), [data, type, olapStats]);
31
33
 
32
34
  return (
33
35
  <div className={b()}>
34
36
  <InfoViewer
35
- info={generalTableInfo}
37
+ info={generalInfo}
36
38
  title={entityName}
37
39
  className={b('info-block')}
38
40
  renderEmptyState={() => <div className={b('title')}>{entityName}</div>}
39
41
  />
40
42
  <div className={b('row')}>
43
+ <div className={b('col')}>
44
+ {tableStatsInfo.map((info, index) => (
45
+ <InfoViewer
46
+ key={index}
47
+ info={info}
48
+ title={index === 0 ? i18n('tableStats') : undefined}
49
+ className={b('info-block')}
50
+ renderEmptyState={() => null}
51
+ />
52
+ ))}
53
+ </div>
41
54
  {tabletMetricsInfo.length > 0 || partitionConfigInfo.length > 0 ? (
42
55
  <div className={b('col')}>
43
56
  <InfoViewer
@@ -54,17 +67,6 @@ export const TableInfo = ({data}: TableInfoProps) => {
54
67
  />
55
68
  </div>
56
69
  ) : null}
57
- <div className={b('col')}>
58
- {tableStatsInfo.map((info, index) => (
59
- <InfoViewer
60
- key={index}
61
- info={info}
62
- title={index === 0 ? i18n('tableStats') : undefined}
63
- className={b('info-block')}
64
- renderEmptyState={() => null}
65
- />
66
- ))}
67
- </div>
68
70
  </div>
69
71
  </div>
70
72
  );
@@ -1,6 +1,16 @@
1
- import type {TEvDescribeSchemeResult} from '../../../../../types/api/schema';
2
-
3
- import {formatObject} from '../../../../../components/InfoViewer';
1
+ import type {
2
+ TColumnDataLifeCycle,
3
+ TColumnTableDescription,
4
+ TEvDescribeSchemeResult,
5
+ TPartitionConfig,
6
+ TTTLSettings,
7
+ } from '../../../../../types/api/schema';
8
+ import type {KeyValueRow} from '../../../../../types/api/query';
9
+ import {EPathType} from '../../../../../types/api/schema';
10
+ import {isNumeric} from '../../../../../utils/utils';
11
+ import {formatBytes, formatNumber} from '../../../../../utils';
12
+ import {formatDurationToShortTimeFormat} from '../../../../../utils/timeParsers';
13
+ import {formatObject, InfoViewerItem} from '../../../../../components/InfoViewer';
4
14
  import {
5
15
  formatFollowerGroupItem,
6
16
  formatPartitionConfigItem,
@@ -8,7 +18,134 @@ import {
8
18
  formatTabletMetricsItem,
9
19
  } from '../../../../../components/InfoViewer/formatters';
10
20
 
11
- export const prepareTableInfo = (data?: TEvDescribeSchemeResult) => {
21
+ const isInStoreColumnTable = (table: TColumnTableDescription) => {
22
+ // SchemaPresetId could be 0
23
+ return table.SchemaPresetName && table.SchemaPresetId !== undefined;
24
+ };
25
+
26
+ const prepareOlapStats = (olapStats?: KeyValueRow[]) => {
27
+ const Bytes = olapStats?.reduce((acc, el) => {
28
+ const value = isNumeric(el.Bytes) ? Number(el.Bytes) : 0;
29
+ return acc + value;
30
+ }, 0);
31
+ const Rows = olapStats?.reduce((acc, el) => {
32
+ const value = isNumeric(el.Rows) ? Number(el.Rows) : 0;
33
+ return acc + value;
34
+ }, 0);
35
+ const tabletIds = olapStats?.reduce((acc, el) => {
36
+ acc.add(el.TabletId);
37
+ return acc;
38
+ }, new Set());
39
+
40
+ return [
41
+ {label: 'PartCount', value: tabletIds?.size ?? 0},
42
+ {label: 'RowCount', value: formatNumber(Rows) ?? 0},
43
+ {label: 'DataSize', value: formatBytes(Bytes) ?? 0},
44
+ ];
45
+ };
46
+
47
+ const prepareTTL = (ttl: TTTLSettings | TColumnDataLifeCycle) => {
48
+ // ExpireAfterSeconds could be 0
49
+ if (ttl.Enabled && ttl.Enabled.ColumnName && ttl.Enabled.ExpireAfterSeconds !== undefined) {
50
+ const value = `column: '${
51
+ ttl.Enabled.ColumnName
52
+ }', expire after: ${formatDurationToShortTimeFormat(
53
+ ttl.Enabled.ExpireAfterSeconds * 1000,
54
+ 1,
55
+ )}`;
56
+
57
+ return {label: 'TTL for rows', value};
58
+ }
59
+ return undefined;
60
+ };
61
+
62
+ function prepareColumnTableGeneralInfo(columnTable: TColumnTableDescription) {
63
+ const columnTableGeneralInfo: InfoViewerItem[] = [];
64
+
65
+ columnTableGeneralInfo.push({
66
+ label: 'Standalone',
67
+ value: String(!isInStoreColumnTable(columnTable)),
68
+ });
69
+
70
+ if (columnTable.Sharding && columnTable.Sharding.HashSharding) {
71
+ columnTableGeneralInfo.push({label: 'Sharding', value: 'hash'});
72
+ }
73
+
74
+ if (columnTable.TtlSettings) {
75
+ const ttlInfo = prepareTTL(columnTable?.TtlSettings);
76
+ if (ttlInfo) {
77
+ columnTableGeneralInfo.push(ttlInfo);
78
+ }
79
+ }
80
+
81
+ return columnTableGeneralInfo;
82
+ }
83
+
84
+ const prepareTableGeneralInfo = (PartitionConfig: TPartitionConfig, TTLSettings?: TTTLSettings) => {
85
+ const {PartitioningPolicy = {}, FollowerGroups, EnableFilterByKey} = PartitionConfig;
86
+
87
+ const generalTableInfo: InfoViewerItem[] = [];
88
+
89
+ const partitioningBySize = PartitioningPolicy.SizeToSplit
90
+ ? `Enabled, split size: ${formatBytes(PartitioningPolicy.SizeToSplit)}`
91
+ : 'Disabled';
92
+
93
+ const partitioningByLoad = PartitioningPolicy.SplitByLoadSettings?.Enabled
94
+ ? 'Enabled'
95
+ : 'Disabled';
96
+
97
+ generalTableInfo.push(
98
+ {label: 'Partitioning by size', value: partitioningBySize},
99
+ {label: 'Partitioning by load', value: partitioningByLoad},
100
+ {
101
+ label: 'Min number of partitions',
102
+ value: PartitioningPolicy.MinPartitionsCount || 0,
103
+ },
104
+ );
105
+
106
+ if (PartitioningPolicy.MaxPartitionsCount) {
107
+ generalTableInfo.push({
108
+ label: 'Max number of partitions',
109
+ value: PartitioningPolicy.MaxPartitionsCount || 0,
110
+ });
111
+ }
112
+
113
+ if (FollowerGroups && FollowerGroups.length) {
114
+ const {RequireAllDataCenters, FollowerCountPerDataCenter, FollowerCount} =
115
+ FollowerGroups[0];
116
+
117
+ let readReplicasConfig: string;
118
+
119
+ if (RequireAllDataCenters && FollowerCountPerDataCenter) {
120
+ readReplicasConfig = `PER_AZ: ${FollowerCount}`;
121
+ } else {
122
+ readReplicasConfig = `ANY_AZ: ${FollowerCount}`;
123
+ }
124
+
125
+ generalTableInfo.push({label: 'Read replicas (followers)', value: readReplicasConfig});
126
+ }
127
+
128
+ if (TTLSettings) {
129
+ const ttlInfo = prepareTTL(TTLSettings);
130
+ if (ttlInfo) {
131
+ generalTableInfo.push(ttlInfo);
132
+ }
133
+ }
134
+
135
+ generalTableInfo.push({
136
+ label: 'Bloom filter',
137
+ value: EnableFilterByKey ? 'Enabled' : 'Disabled',
138
+ });
139
+
140
+ return generalTableInfo;
141
+ };
142
+
143
+ /** Prepares data for Table, ColumnTable and ColumnStore */
144
+ export const prepareTableInfo = (
145
+ data?: TEvDescribeSchemeResult,
146
+ type?: EPathType,
147
+ olapStats?: KeyValueRow[],
148
+ ) => {
12
149
  if (!data) {
13
150
  return {};
14
151
  }
@@ -18,7 +155,8 @@ export const prepareTableInfo = (data?: TEvDescribeSchemeResult) => {
18
155
  const {
19
156
  TableStats = {},
20
157
  TabletMetrics = {},
21
- Table: {PartitionConfig = {}} = {},
158
+ Table: {PartitionConfig = {}, TTLSettings} = {},
159
+ ColumnTableDescription = {},
22
160
  } = PathDescription;
23
161
 
24
162
  const {
@@ -42,45 +180,62 @@ export const prepareTableInfo = (data?: TEvDescribeSchemeResult) => {
42
180
  RowReads,
43
181
  RangeReads,
44
182
  RangeReadRows,
45
-
46
- ...restTableStats
47
183
  } = TableStats;
48
184
 
49
185
  const {FollowerGroups, FollowerCount, CrossDataCenterFollowerCount} = PartitionConfig;
50
186
 
51
- const generalTableInfo = formatObject(formatTableStatsItem, {
52
- PartCount,
53
- RowCount,
54
- DataSize,
55
- IndexSize,
56
- ...restTableStats,
57
- });
187
+ let generalInfo: InfoViewerItem[] = [];
188
+
189
+ switch (type) {
190
+ case EPathType.EPathTypeTable: {
191
+ generalInfo = prepareTableGeneralInfo(PartitionConfig, TTLSettings);
192
+ break;
193
+ }
194
+ case EPathType.EPathTypeColumnTable: {
195
+ generalInfo = prepareColumnTableGeneralInfo(ColumnTableDescription);
196
+ break;
197
+ }
198
+ }
58
199
 
59
- const tableStatsInfo = [
60
- formatObject(formatTableStatsItem, {
61
- LastAccessTime,
62
- LastUpdateTime,
63
- }),
64
- formatObject(formatTableStatsItem, {
65
- ImmediateTxCompleted,
66
- PlannedTxCompleted,
67
- TxRejectedByOverload,
68
- TxRejectedBySpace,
69
- TxCompleteLagMsec,
70
- InFlightTxCount,
71
- }),
72
- formatObject(formatTableStatsItem, {
73
- RowUpdates,
74
- RowDeletes,
75
- RowReads,
76
- RangeReads,
77
- RangeReadRows,
78
- }),
79
- ];
200
+ let tableStatsInfo: InfoViewerItem[][];
201
+
202
+ // There is no TableStats and TabletMetrics for ColumnTables inside ColumnStore
203
+ // Therefore we parse olapStats
204
+ if (type === EPathType.EPathTypeColumnTable && isInStoreColumnTable(ColumnTableDescription)) {
205
+ tableStatsInfo = [prepareOlapStats(olapStats)];
206
+ } else {
207
+ tableStatsInfo = [
208
+ formatObject(formatTableStatsItem, {
209
+ PartCount,
210
+ RowCount,
211
+ DataSize,
212
+ IndexSize,
213
+ }),
214
+ formatObject(formatTableStatsItem, {
215
+ LastAccessTime,
216
+ LastUpdateTime,
217
+ }),
218
+ formatObject(formatTableStatsItem, {
219
+ ImmediateTxCompleted,
220
+ PlannedTxCompleted,
221
+ TxRejectedByOverload,
222
+ TxRejectedBySpace,
223
+ TxCompleteLagMsec,
224
+ InFlightTxCount,
225
+ }),
226
+ formatObject(formatTableStatsItem, {
227
+ RowUpdates,
228
+ RowDeletes,
229
+ RowReads,
230
+ RangeReads,
231
+ RangeReadRows,
232
+ }),
233
+ ];
234
+ }
80
235
 
81
236
  const tabletMetricsInfo = formatObject(formatTabletMetricsItem, TabletMetrics);
82
237
 
83
- let partitionConfigInfo = [];
238
+ let partitionConfigInfo: InfoViewerItem[] = [];
84
239
 
85
240
  if (Array.isArray(FollowerGroups) && FollowerGroups.length > 0) {
86
241
  partitionConfigInfo = formatObject(formatFollowerGroupItem, FollowerGroups[0]);
@@ -92,5 +247,5 @@ export const prepareTableInfo = (data?: TEvDescribeSchemeResult) => {
92
247
  );
93
248
  }
94
249
 
95
- return {generalTableInfo, tableStatsInfo, tabletMetricsInfo, partitionConfigInfo};
250
+ return {generalInfo, tableStatsInfo, tabletMetricsInfo, partitionConfigInfo};
96
251
  };
@@ -1,16 +1,19 @@
1
1
  import cn from 'bem-cn-lite';
2
- import {isEmpty} from 'lodash/fp';
3
2
 
4
- import type {DescribeTopicResult} from '../../../../../types/api/topic';
3
+ import type {IPreparedTopicStats} from '../../../../../types/store/topic';
5
4
 
6
5
  import {Loader} from '../../../../../components/Loader';
7
- import {InfoViewerItem, formatObject, InfoViewer} from '../../../../../components/InfoViewer';
8
-
9
- import {formatTopicStats} from '../../../../../components/InfoViewer/formatters';
6
+ import {InfoViewerItem, InfoViewer} from '../../../../../components/InfoViewer';
7
+ import {SpeedMultiMeter} from '../../../../../components/SpeedMultiMeter';
8
+ import {LabelWithPopover} from '../../../../../components/LabelWithPopover';
9
+ import {LagPopoverContent} from '../../../../../components/LagPopoverContent';
10
+ import {ResponseError} from '../../../../../components/Errors/ResponseError';
10
11
 
11
12
  import {useTypedSelector} from '../../../../../utils/hooks';
12
- import {convertBytesObjectToSpeed} from '../../../../../utils/bytesParsers';
13
- import {formatBps} from '../../../../../utils';
13
+ import {formatDurationToShortTimeFormat} from '../../../../../utils/timeParsers';
14
+ import {formatBps, formatBytes} from '../../../../../utils';
15
+
16
+ import {selectPreparedTopicStats} from '../../../../../store/reducers/topic';
14
17
 
15
18
  import i18n from './i18n';
16
19
 
@@ -18,35 +21,61 @@ import './TopicStats.scss';
18
21
 
19
22
  const b = cn('ydb-overview-topic-stats');
20
23
 
21
- const prepareTopicInfo = (data: DescribeTopicResult): Array<InfoViewerItem> => {
24
+ const prepareTopicInfo = (data: IPreparedTopicStats): Array<InfoViewerItem> => {
22
25
  return [
23
- ...formatObject(formatTopicStats, {
24
- ...data.topic_stats,
25
- }),
26
+ {label: 'Store size', value: formatBytes(data.storeSize)},
27
+ {
28
+ label: (
29
+ <LabelWithPopover
30
+ text={'Write idle time'}
31
+ popoverContent={
32
+ <LagPopoverContent text={i18n('writeIdleTimePopover')} type="write" />
33
+ }
34
+ />
35
+ ),
36
+ value: formatDurationToShortTimeFormat(data.partitionsIdleTime),
37
+ },
38
+ {
39
+ label: (
40
+ <LabelWithPopover
41
+ text={'Write lag'}
42
+ popoverContent={
43
+ <LagPopoverContent text={i18n('writeLagPopover')} type="write" />
44
+ }
45
+ />
46
+ ),
47
+ value: formatDurationToShortTimeFormat(data.partitionsWriteLag),
48
+ },
49
+ {
50
+ label: 'Average write speed',
51
+ value: <SpeedMultiMeter data={data.writeSpeed} withValue={false} />,
52
+ },
26
53
  ];
27
54
  };
28
55
 
29
- const prepareBytesWrittenInfo = (data: DescribeTopicResult): Array<InfoViewerItem> => {
30
- const preparedBytes = convertBytesObjectToSpeed(data?.topic_stats?.bytes_written);
56
+ const prepareBytesWrittenInfo = (data: IPreparedTopicStats): Array<InfoViewerItem> => {
57
+ const writeSpeed = data.writeSpeed;
31
58
 
32
59
  return [
33
60
  {
34
61
  label: 'per minute',
35
- value: formatBps(preparedBytes.perMinute),
62
+ value: formatBps(writeSpeed.perMinute),
36
63
  },
37
64
  {
38
65
  label: 'per hour',
39
- value: formatBps(preparedBytes.perHour),
66
+ value: formatBps(writeSpeed.perHour),
40
67
  },
41
68
  {
42
69
  label: 'per day',
43
- value: formatBps(preparedBytes.perDay),
70
+ value: formatBps(writeSpeed.perDay),
44
71
  },
45
72
  ];
46
73
  };
47
74
 
48
75
  export const TopicStats = () => {
49
- const {data, error, loading, wasLoaded} = useTypedSelector((state) => state.topic);
76
+ const {error, loading, wasLoaded} = useTypedSelector((state) => state.topic);
77
+
78
+ const data = useTypedSelector(selectPreparedTopicStats);
50
79
 
51
80
  if (loading && !wasLoaded) {
52
81
  return (
@@ -56,24 +85,14 @@ export const TopicStats = () => {
56
85
  );
57
86
  }
58
87
 
59
- // There are several backed versions with different behaviour
60
- // Possible returns on older versions:
61
- // 1. Error when trying to access endpoint
62
- // 2. No data
63
- // 3. HTML page of Internal Viewer with an error
64
- // 4. Data with no topic stats
65
- // 5. Topic Stats as an empty object
66
- if (
67
- error ||
68
- !data ||
69
- typeof data !== 'object' ||
70
- !data.topic_stats ||
71
- isEmpty(data.topic_stats)
72
- ) {
88
+ // If there is at least some empty data object
89
+ // we initialize its fields with zero values
90
+ // so no data at all is considered to be error as well
91
+ if (error || !data) {
73
92
  return (
74
93
  <div className={b()}>
75
94
  <div className={b('title')}>Stats</div>
76
- <div className="error">{i18n('notSupportedVersion')}</div>
95
+ <ResponseError error={error} />
77
96
  </div>
78
97
  );
79
98
  }
@@ -1,3 +1,4 @@
1
1
  {
2
- "notSupportedVersion": "Topic stats are not supported in the current cluster version. Update cluster version to see topic stats"
2
+ "writeLagPopover": "Write lag, maximum among all topic partitions",
3
+ "writeIdleTimePopover": "Write idle time, maximum among all topic partitions"
3
4
  }
@@ -1,3 +1,4 @@
1
1
  {
2
- "notSupportedVersion": "Статистика топиков не поддерживается в текущей версии. Обновите кластер до более новой версии, чтобы увидеть статистику топиков"
2
+ "writeLagPopover": "Лаг записи, максимальное значение среди всех партиций топика",
3
+ "writeIdleTimePopover": "Время без записи, максимальное значение среди всех партиций топика"
3
4
  }