ydb-embedded-ui 3.4.4 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  }