ydb-embedded-ui 3.3.4 → 3.4.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 (73) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/components/Errors/ResponseError/ResponseError.tsx +2 -2
  3. package/dist/components/InfoViewer/formatters/topicStats.tsx +8 -29
  4. package/dist/components/LabelWithPopover/LabelWithPopover.tsx +20 -0
  5. package/dist/components/LabelWithPopover/index.ts +1 -0
  6. package/dist/components/LagImages/LagImages.tsx +205 -0
  7. package/dist/components/LagImages/index.ts +1 -0
  8. package/dist/components/SpeedMultiMeter/SpeedMultiMeter.scss +92 -0
  9. package/dist/components/SpeedMultiMeter/SpeedMultiMeter.tsx +120 -0
  10. package/dist/components/SpeedMultiMeter/i18n/en.json +6 -0
  11. package/dist/components/SpeedMultiMeter/i18n/index.ts +13 -0
  12. package/dist/components/SpeedMultiMeter/i18n/ru.json +6 -0
  13. package/dist/components/SpeedMultiMeter/index.ts +1 -0
  14. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +18 -14
  15. package/dist/containers/Storage/VDisk/VDisk.tsx +20 -5
  16. package/dist/containers/Storage/VDiskPopup/VDiskPopup.tsx +34 -5
  17. package/dist/containers/Storage/utils/types.ts +5 -0
  18. package/dist/containers/Tenant/Diagnostics/Consumers/Consumers.scss +32 -3
  19. package/dist/containers/Tenant/Diagnostics/Consumers/Consumers.tsx +62 -69
  20. package/dist/containers/Tenant/Diagnostics/Consumers/Headers/Headers.scss +13 -0
  21. package/dist/containers/Tenant/Diagnostics/Consumers/Headers/Headers.tsx +27 -0
  22. package/dist/containers/Tenant/Diagnostics/Consumers/Headers/index.ts +1 -0
  23. package/dist/containers/Tenant/Diagnostics/Consumers/TopicStats/ConsumersTopicStats.scss +32 -0
  24. package/dist/containers/Tenant/Diagnostics/Consumers/TopicStats/ConsumersTopicStats.tsx +43 -0
  25. package/dist/containers/Tenant/Diagnostics/Consumers/TopicStats/index.ts +1 -0
  26. package/dist/containers/Tenant/Diagnostics/Consumers/columns/Columns.scss +5 -0
  27. package/dist/containers/Tenant/Diagnostics/Consumers/columns/columns.tsx +66 -0
  28. package/dist/containers/Tenant/Diagnostics/Consumers/columns/index.ts +1 -0
  29. package/dist/containers/Tenant/Diagnostics/Consumers/i18n/en.json +4 -3
  30. package/dist/containers/Tenant/Diagnostics/Consumers/i18n/ru.json +4 -3
  31. package/dist/containers/Tenant/Diagnostics/Consumers/utils/constants.ts +23 -0
  32. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +4 -0
  33. package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +8 -2
  34. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.scss +9 -1
  35. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx +6 -8
  36. package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.scss +33 -0
  37. package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.tsx +76 -0
  38. package/dist/containers/Tenant/Diagnostics/Partitions/Headers/index.ts +1 -0
  39. package/dist/containers/Tenant/Diagnostics/Partitions/Partitions.scss +45 -0
  40. package/dist/containers/Tenant/Diagnostics/Partitions/Partitions.tsx +254 -0
  41. package/dist/containers/Tenant/Diagnostics/Partitions/PartitionsWrapper.tsx +79 -0
  42. package/dist/containers/Tenant/Diagnostics/Partitions/columns/Columns.scss +13 -0
  43. package/dist/containers/Tenant/Diagnostics/Partitions/columns/columns.tsx +246 -0
  44. package/dist/containers/Tenant/Diagnostics/Partitions/columns/index.ts +1 -0
  45. package/dist/containers/Tenant/Diagnostics/Partitions/i18n/en.json +13 -0
  46. package/dist/containers/Tenant/Diagnostics/Partitions/i18n/index.ts +11 -0
  47. package/dist/containers/Tenant/Diagnostics/Partitions/i18n/ru.json +13 -0
  48. package/dist/containers/Tenant/Diagnostics/Partitions/index.ts +1 -0
  49. package/dist/containers/Tenant/Diagnostics/Partitions/utils/constants.ts +74 -0
  50. package/dist/containers/Tenant/Diagnostics/Partitions/utils/types.ts +6 -0
  51. package/dist/containers/Tenant/utils/schema.ts +1 -16
  52. package/dist/services/api.d.ts +4 -0
  53. package/dist/services/api.js +22 -6
  54. package/dist/store/reducers/consumer.ts +160 -0
  55. package/dist/store/reducers/index.ts +2 -0
  56. package/dist/store/reducers/settings.js +2 -0
  57. package/dist/store/reducers/topic.ts +82 -2
  58. package/dist/types/store/consumer.ts +55 -0
  59. package/dist/types/store/topic.ts +23 -6
  60. package/dist/utils/bytesParsers/convertBytesObjectToSpeed.ts +24 -0
  61. package/dist/utils/bytesParsers/formatBytesCustom.ts +57 -0
  62. package/dist/utils/bytesParsers/i18n/en.json +7 -0
  63. package/dist/utils/bytesParsers/i18n/index.ts +11 -0
  64. package/dist/utils/bytesParsers/i18n/ru.json +7 -0
  65. package/dist/utils/bytesParsers/index.ts +2 -0
  66. package/dist/utils/constants.ts +3 -0
  67. package/dist/utils/index.js +6 -0
  68. package/dist/utils/storage.ts +2 -2
  69. package/dist/utils/timeParsers/index.ts +2 -1
  70. package/dist/utils/timeParsers/parsers.ts +18 -0
  71. package/dist/utils/timeParsers/{protobuf.ts → protobufParsers.ts} +0 -0
  72. package/dist/utils/utils.js +3 -3
  73. package/package.json +2 -2
@@ -12,6 +12,7 @@ export enum GeneralPagesIds {
12
12
  'hotKeys' = 'hotKeys',
13
13
  'graph' = 'graph',
14
14
  'consumers' = 'consumers',
15
+ 'partitions' = 'partitions',
15
16
  }
16
17
 
17
18
  type Page = {
@@ -72,6 +73,11 @@ const consumers = {
72
73
  title: 'Consumers',
73
74
  };
74
75
 
76
+ const partitions = {
77
+ id: GeneralPagesIds.partitions,
78
+ title: 'Partitions',
79
+ };
80
+
75
81
  export const DATABASE_PAGES = [
76
82
  overview,
77
83
  topQueries,
@@ -87,8 +93,8 @@ export const TABLE_PAGES = [overview, topShards, graph, tablets, hotKeys, descri
87
93
 
88
94
  export const DIR_PAGES = [overview, topShards, describe];
89
95
 
90
- export const CDC_STREAM_PAGES = [overview, consumers, describe];
91
- export const TOPIC_PAGES = [overview, consumers, describe];
96
+ export const CDC_STREAM_PAGES = [overview, consumers, partitions, describe];
97
+ export const TOPIC_PAGES = [overview, consumers, partitions, describe];
92
98
 
93
99
  // verbose mapping to guarantee correct tabs for new path types
94
100
  // TS will error when a new type is added but not mapped here
@@ -9,8 +9,16 @@
9
9
  margin-top: 50px;
10
10
  }
11
11
 
12
- .ydb-bars {
12
+ .info-viewer__row {
13
+ align-items: flex-start;
14
+ }
15
+
16
+ .speed-multimeter {
13
17
  margin-top: -5px;
18
+
19
+ &__content {
20
+ justify-content: flex-start;
21
+ }
14
22
  }
15
23
 
16
24
  &__info {
@@ -6,12 +6,10 @@ import type {DescribeTopicResult} from '../../../../../types/api/topic';
6
6
  import {Loader} from '../../../../../components/Loader';
7
7
  import {InfoViewerItem, formatObject, InfoViewer} from '../../../../../components/InfoViewer';
8
8
 
9
- import {
10
- prepareBytesWritten,
11
- formatTopicStats,
12
- } from '../../../../../components/InfoViewer/formatters';
9
+ import {formatTopicStats} from '../../../../../components/InfoViewer/formatters';
13
10
 
14
11
  import {useTypedSelector} from '../../../../../utils/hooks';
12
+ import {convertBytesObjectToSpeed} from '../../../../../utils/bytesParsers';
15
13
  import {formatBps} from '../../../../../utils';
16
14
 
17
15
  import i18n from './i18n';
@@ -29,20 +27,20 @@ const prepareTopicInfo = (data: DescribeTopicResult): Array<InfoViewerItem> => {
29
27
  };
30
28
 
31
29
  const prepareBytesWrittenInfo = (data: DescribeTopicResult): Array<InfoViewerItem> => {
32
- const preparedBytes = prepareBytesWritten(data?.topic_stats?.bytes_written);
30
+ const preparedBytes = convertBytesObjectToSpeed(data?.topic_stats?.bytes_written);
33
31
 
34
32
  return [
35
33
  {
36
34
  label: 'per minute',
37
- value: formatBps(preparedBytes.per_minute),
35
+ value: formatBps(preparedBytes.perMinute),
38
36
  },
39
37
  {
40
38
  label: 'per hour',
41
- value: formatBps(preparedBytes.per_hour),
39
+ value: formatBps(preparedBytes.perHour),
42
40
  },
43
41
  {
44
42
  label: 'per day',
45
- value: formatBps(preparedBytes.per_day),
43
+ value: formatBps(preparedBytes.perDay),
46
44
  },
47
45
  ];
48
46
  };
@@ -0,0 +1,33 @@
1
+ .ydb-diagnostics-partitions-columns-header {
2
+ &__multiline {
3
+ white-space: normal;
4
+ }
5
+
6
+ &__read-session {
7
+ width: 80px;
8
+
9
+ white-space: normal;
10
+ }
11
+
12
+ &__lags {
13
+ white-space: nowrap;
14
+ }
15
+
16
+ &__messages {
17
+ width: 90px;
18
+
19
+ white-space: normal;
20
+ }
21
+
22
+ &__lags-popover-content {
23
+ max-width: 300px;
24
+
25
+ div:nth-child(1) {
26
+ margin-bottom: 10px;
27
+ }
28
+ }
29
+
30
+ &__messages-popover-content {
31
+ max-width: 200px;
32
+ }
33
+ }
@@ -0,0 +1,76 @@
1
+ import block from 'bem-cn-lite';
2
+
3
+ import {LabelWithPopover} from '../../../../../components/LabelWithPopover';
4
+ import {WriteLagImage, ReadLagImage} from '../../../../../components/LagImages';
5
+
6
+ import {PARTITIONS_COLUMNS_IDS, PARTITIONS_COLUMNS_TITILES} from '../utils/constants';
7
+
8
+ import i18n from '../i18n';
9
+
10
+ import './Headers.scss';
11
+
12
+ const b = block('ydb-diagnostics-partitions-columns-header');
13
+
14
+ interface MultilineHeaderProps {
15
+ title: string;
16
+ }
17
+
18
+ export const MultilineHeader = ({title}: MultilineHeaderProps) => (
19
+ <div className={b('multiline')}>{title}</div>
20
+ );
21
+
22
+ export const ReadSessionHeader = () => (
23
+ <div className={b('read-session')}>
24
+ {PARTITIONS_COLUMNS_TITILES[PARTITIONS_COLUMNS_IDS.READ_SESSION_ID]}
25
+ </div>
26
+ );
27
+
28
+ export const WriteLagsHeader = () => (
29
+ <LabelWithPopover
30
+ className={b('lags')}
31
+ headerText={PARTITIONS_COLUMNS_TITILES[PARTITIONS_COLUMNS_IDS.READ_LAGS]}
32
+ popoverContent={
33
+ <div className={b('lags-popover-content')}>
34
+ <div>{i18n('lagsPopover.writeLags')}</div>
35
+ <div>
36
+ <WriteLagImage />
37
+ </div>
38
+ </div>
39
+ }
40
+ />
41
+ );
42
+
43
+ export const ReadLagsHeader = () => (
44
+ <LabelWithPopover
45
+ className={b('lags')}
46
+ headerText={PARTITIONS_COLUMNS_TITILES[PARTITIONS_COLUMNS_IDS.WRITE_LAGS]}
47
+ popoverContent={
48
+ <div className={b('lags-popover-content')}>
49
+ <div>{i18n('lagsPopover.readLags')}</div>
50
+ <div>
51
+ <ReadLagImage />
52
+ </div>
53
+ </div>
54
+ }
55
+ />
56
+ );
57
+
58
+ export const UnreadMessagesHeader = () => (
59
+ <LabelWithPopover
60
+ className={b('messages')}
61
+ headerText={PARTITIONS_COLUMNS_TITILES[PARTITIONS_COLUMNS_IDS.UNREAD_MESSAGES]}
62
+ popoverContent={
63
+ <div className={b('messages-popover-content')}>{i18n('headers.unread')}</div>
64
+ }
65
+ />
66
+ );
67
+
68
+ export const UncommitedMessagesHeader = () => (
69
+ <LabelWithPopover
70
+ className={b('messages')}
71
+ headerText={PARTITIONS_COLUMNS_TITILES[PARTITIONS_COLUMNS_IDS.UNCOMMITED_MESSAGES]}
72
+ popoverContent={
73
+ <div className={b('messages-popover-content')}>{i18n('headers.uncommited')}</div>
74
+ }
75
+ />
76
+ );
@@ -0,0 +1 @@
1
+ export * from './Headers';
@@ -0,0 +1,45 @@
1
+ @import '../../../../styles/mixins.scss';
2
+
3
+ .ydb-diagnostics-partitions {
4
+ overflow: auto;
5
+ flex-grow: 1;
6
+
7
+ height: 100%;
8
+
9
+ @include flex-container();
10
+
11
+ &__controls {
12
+ @include controls();
13
+ }
14
+
15
+ &__consumer-select {
16
+ width: 220px;
17
+ }
18
+
19
+ &__search {
20
+ @include search();
21
+ &_partition {
22
+ width: 100px;
23
+ }
24
+ &_general {
25
+ width: 280px;
26
+ }
27
+ }
28
+
29
+ &__table-settings .yc-icon {
30
+ width: 20px;
31
+ }
32
+
33
+ &__table-wrapper {
34
+ overflow: auto;
35
+ @include flex-container();
36
+ }
37
+
38
+ &__table-content {
39
+ overflow: auto;
40
+
41
+ height: 100%;
42
+
43
+ @include freeze-nth-column(1);
44
+ }
45
+ }
@@ -0,0 +1,254 @@
1
+ import block from 'bem-cn-lite';
2
+ import {useCallback, useEffect, useMemo, useState} from 'react';
3
+ import {useDispatch} from 'react-redux';
4
+ import {escapeRegExp} from 'lodash/fp';
5
+
6
+ import DataTable from '@gravity-ui/react-data-table';
7
+ import {Select, TableColumnSetup} from '@gravity-ui/uikit';
8
+ import {TableColumnSetupItem} from '@gravity-ui/uikit/build/esm/components/Table/hoc/withTableSettings/withTableSettings';
9
+
10
+ import type {EPathType} from '../../../../types/api/schema';
11
+
12
+ import {useAutofetcher, useTypedSelector} from '../../../../utils/hooks';
13
+ import {DEFAULT_TABLE_SETTINGS, PARTITIONS_SELECTED_COLUMNS_KEY} from '../../../../utils/constants';
14
+
15
+ import {getSettingValue, setSettingValue} from '../../../../store/reducers/settings';
16
+ import {
17
+ getConsumer,
18
+ selectPreparedPartitionsData,
19
+ setDataWasNotLoaded,
20
+ } from '../../../../store/reducers/consumer';
21
+
22
+ import {TableSkeleton} from '../../../../components/TableSkeleton/TableSkeleton';
23
+ import {Search} from '../../../../components/Search';
24
+ import {ResponseError} from '../../../../components/Errors/ResponseError';
25
+
26
+ import {isCdcStreamEntityType} from '../../utils/schema';
27
+
28
+ import type {IPreparedPartitionDataWithHosts} from './utils/types';
29
+ import {
30
+ PARTITIONS_COLUMNS_IDS,
31
+ PARTITIONS_COLUMNS_TITILES,
32
+ PARTITIONS_DEFAULT_SELECTED_COLUMNS,
33
+ } from './utils/constants';
34
+
35
+ import {columns as partitionsColumns} from './columns';
36
+
37
+ import i18n from './i18n';
38
+
39
+ import './Partitions.scss';
40
+
41
+ export const b = block('ydb-diagnostics-partitions');
42
+
43
+ interface PartitionsProps {
44
+ path?: string;
45
+ type?: EPathType;
46
+ nodes?: Record<number, string>;
47
+ consumers?: string[];
48
+ }
49
+
50
+ export const Partitions = ({path, type, nodes, consumers}: PartitionsProps) => {
51
+ const isCdcStream = isCdcStreamEntityType(type);
52
+
53
+ const dispatch = useDispatch();
54
+
55
+ const [selectedConsumer, setSelectedConsumer] = useState<string[]>();
56
+ const [generalSearchValue, setGeneralSearchValue] = useState('');
57
+ const [partitionIdSearchValue, setPartitionIdSearchValue] = useState('');
58
+
59
+ const [componentCurrentPath, setComponentCurrentPath] = useState(path);
60
+
61
+ const {autorefresh} = useTypedSelector((state) => state.schema);
62
+ const {loading, wasLoaded, error} = useTypedSelector((state) => state.consumer);
63
+
64
+ const partitions = useTypedSelector((state) => selectPreparedPartitionsData(state));
65
+
66
+ const savedSelectedColumns: string = useTypedSelector((state) =>
67
+ getSettingValue(state, PARTITIONS_SELECTED_COLUMNS_KEY),
68
+ );
69
+
70
+ useEffect(() => {
71
+ // Manual path control to ensure it updates with other values so no request with wrong params will be sent
72
+ setComponentCurrentPath(path);
73
+ }, [path]);
74
+
75
+ const fetchConsumerData = useCallback(
76
+ (isBackground: boolean) => {
77
+ if (!isBackground) {
78
+ dispatch(setDataWasNotLoaded());
79
+ }
80
+
81
+ if (selectedConsumer && selectedConsumer.length) {
82
+ dispatch(getConsumer(componentCurrentPath, selectedConsumer[0]));
83
+ }
84
+ },
85
+ [dispatch, selectedConsumer, componentCurrentPath],
86
+ );
87
+
88
+ useAutofetcher(fetchConsumerData, [fetchConsumerData], autorefresh);
89
+
90
+ const consumersToSelect = useMemo(
91
+ () =>
92
+ consumers
93
+ ? consumers.map((consumer) => ({
94
+ value: consumer,
95
+ content: consumer,
96
+ }))
97
+ : undefined,
98
+ [consumers],
99
+ );
100
+
101
+ useEffect(() => {
102
+ if (consumersToSelect && consumersToSelect.length) {
103
+ setSelectedConsumer([consumersToSelect[0].value]);
104
+ } else {
105
+ setSelectedConsumer(undefined);
106
+ }
107
+ }, [consumersToSelect]);
108
+
109
+ const selectedColumns: string[] = useMemo(
110
+ () =>
111
+ savedSelectedColumns
112
+ ? JSON.parse(savedSelectedColumns)
113
+ : PARTITIONS_DEFAULT_SELECTED_COLUMNS,
114
+ [savedSelectedColumns],
115
+ );
116
+
117
+ const columnsToSelect = useMemo(() => {
118
+ return Object.values(PARTITIONS_COLUMNS_IDS).map((id) => {
119
+ return {
120
+ title: PARTITIONS_COLUMNS_TITILES[id],
121
+ selected: Boolean(selectedColumns?.includes(id)),
122
+ id: id,
123
+ required: id === PARTITIONS_COLUMNS_IDS.PARTITION_ID,
124
+ };
125
+ });
126
+ }, [selectedColumns]);
127
+
128
+ const columnsToShow = useMemo(() => {
129
+ return partitionsColumns.filter((column) => selectedColumns?.includes(column.name));
130
+ }, [selectedColumns]);
131
+
132
+ const partitionsWithHosts: IPreparedPartitionDataWithHosts[] | undefined = useMemo(() => {
133
+ return partitions?.map((partition) => {
134
+ const partitionHost =
135
+ partition.partitionNodeId && nodes ? nodes[partition.partitionNodeId] : undefined;
136
+
137
+ const connectionHost =
138
+ partition.connectionNodeId && nodes ? nodes[partition.connectionNodeId] : undefined;
139
+
140
+ return {
141
+ ...partition,
142
+ partitionHost,
143
+ connectionHost,
144
+ };
145
+ });
146
+ }, [partitions, nodes]);
147
+
148
+ const dataToRender = useMemo(() => {
149
+ if (!partitionsWithHosts) {
150
+ return [];
151
+ }
152
+
153
+ const partitionIdRe = new RegExp(escapeRegExp(partitionIdSearchValue), 'i');
154
+ const generalRe = new RegExp(escapeRegExp(generalSearchValue), 'i');
155
+
156
+ return partitionsWithHosts.filter((partition) => {
157
+ const {
158
+ partitionId,
159
+ readerName = '',
160
+ readSessionId = '',
161
+ partitionNodeId,
162
+ connectionNodeId,
163
+ partitionHost = '',
164
+ connectionHost = '',
165
+ } = partition;
166
+
167
+ const isPartitionIdMatch = partitionIdRe.test(partitionId);
168
+ const isOtherValuesMatch =
169
+ generalRe.test(readerName) ||
170
+ generalRe.test(readSessionId) ||
171
+ generalRe.test(String(partitionNodeId)) ||
172
+ generalRe.test(String(connectionNodeId)) ||
173
+ generalRe.test(partitionHost) ||
174
+ generalRe.test(connectionHost);
175
+
176
+ return isPartitionIdMatch && isOtherValuesMatch;
177
+ });
178
+ }, [partitionIdSearchValue, generalSearchValue, partitionsWithHosts]);
179
+
180
+ const hadleTableColumnsSetupChange = (value: TableColumnSetupItem[]) => {
181
+ const columns = value.filter((el) => el.selected).map((el) => el.id);
182
+ dispatch(setSettingValue(PARTITIONS_SELECTED_COLUMNS_KEY, JSON.stringify(columns)));
183
+ };
184
+
185
+ const handleConsumerSelectChange = (value: string[]) => {
186
+ setSelectedConsumer(value);
187
+ };
188
+
189
+ const handlePartitionIdSearchChange = (value: string) => {
190
+ setPartitionIdSearchValue(value);
191
+ };
192
+
193
+ const handleGeneralSearchChange = (value: string) => {
194
+ setGeneralSearchValue(value);
195
+ };
196
+
197
+ if (error) {
198
+ return <ResponseError error={error} />;
199
+ }
200
+
201
+ if (!consumersToSelect || !consumersToSelect.length) {
202
+ return <div>{i18n(`noConsumersMessage.${isCdcStream ? 'stream' : 'topic'}`)}</div>;
203
+ }
204
+
205
+ return (
206
+ <div className={b()}>
207
+ <div className={b('controls')}>
208
+ <Select
209
+ className={b('consumer-select')}
210
+ placeholder={i18n('controls.consumerSelector.placeholder')}
211
+ label={i18n('controls.consumerSelector')}
212
+ options={consumersToSelect}
213
+ value={selectedConsumer}
214
+ onUpdate={handleConsumerSelectChange}
215
+ />
216
+ <Search
217
+ onChange={handlePartitionIdSearchChange}
218
+ placeholder={i18n('controls.partitionSearch')}
219
+ className={b('search', {partition: true})}
220
+ value={partitionIdSearchValue}
221
+ />
222
+ <Search
223
+ onChange={handleGeneralSearchChange}
224
+ placeholder={i18n('controls.generalSearch')}
225
+ className={b('search', {general: true})}
226
+ value={generalSearchValue}
227
+ />
228
+ <TableColumnSetup
229
+ key="TableColumnSetup"
230
+ popupWidth="242px"
231
+ items={columnsToSelect}
232
+ showStatus
233
+ onUpdate={hadleTableColumnsSetupChange}
234
+ className={b('table-settings')}
235
+ />
236
+ </div>
237
+ <div className={b('table-wrapper')}>
238
+ <div className={b('table-content')}>
239
+ {loading && !wasLoaded ? (
240
+ <TableSkeleton className={b('loader')} />
241
+ ) : (
242
+ <DataTable
243
+ theme="yandex-cloud"
244
+ data={dataToRender}
245
+ columns={columnsToShow}
246
+ settings={DEFAULT_TABLE_SETTINGS}
247
+ emptyDataMessage={i18n('table.emptyDataMessage')}
248
+ />
249
+ )}
250
+ </div>
251
+ </div>
252
+ </div>
253
+ );
254
+ };
@@ -0,0 +1,79 @@
1
+ import {useEffect, useMemo} from 'react';
2
+ import {useDispatch} from 'react-redux';
3
+
4
+ import type {EPathType} from '../../../../types/api/schema';
5
+
6
+ import {useTypedSelector} from '../../../../utils/hooks';
7
+
8
+ import {
9
+ getTopic,
10
+ setDataWasNotLoaded as setTopicDataWasNotLoaded,
11
+ } from '../../../../store/reducers/topic';
12
+ import {
13
+ getNodes,
14
+ setDataWasNotLoaded as setNodesDataWasNotLoaded,
15
+ } from '../../../../store/reducers/nodes';
16
+
17
+ import {Loader} from '../../../../components/Loader';
18
+ import {ResponseError} from '../../../../components/Errors/ResponseError';
19
+
20
+ import {Partitions} from './Partitions';
21
+
22
+ interface PartitionsWrapperProps {
23
+ path?: string;
24
+ type?: EPathType;
25
+ }
26
+
27
+ export const PartitionsWrapper = ({path, type}: PartitionsWrapperProps) => {
28
+ const dispatch = useDispatch();
29
+
30
+ const {
31
+ loading: topicLoading,
32
+ wasLoaded: topicWasLoaded,
33
+ error: topicError,
34
+ data: topicData,
35
+ } = useTypedSelector((state) => state.topic);
36
+
37
+ const {
38
+ loading: nodesLoading,
39
+ wasLoaded: nodesWasLoaded,
40
+ error: nodesError,
41
+ data: nodesData,
42
+ } = useTypedSelector((state) => state.nodes);
43
+
44
+ const consumers = useMemo(
45
+ () =>
46
+ topicData?.consumers
47
+ ?.map((consumer) => consumer?.name)
48
+ .filter((consumer): consumer is string => consumer !== undefined),
49
+ [topicData],
50
+ );
51
+
52
+ const nodes = useMemo(() => {
53
+ const preparedNodesObject: Record<number, string> = {};
54
+ nodesData?.forEach((node) => {
55
+ if (node.NodeId && node.Host) {
56
+ preparedNodesObject[node.NodeId] = node.Host;
57
+ }
58
+ });
59
+ return preparedNodesObject;
60
+ }, [nodesData]);
61
+
62
+ useEffect(() => {
63
+ dispatch(setTopicDataWasNotLoaded());
64
+ dispatch(setNodesDataWasNotLoaded());
65
+
66
+ dispatch(getTopic(path));
67
+ dispatch(getNodes({}));
68
+ }, [dispatch, path]);
69
+
70
+ if ((topicLoading && !topicWasLoaded) || (nodesLoading && !nodesWasLoaded)) {
71
+ return <Loader />;
72
+ }
73
+
74
+ if (topicError || nodesError) {
75
+ return <ResponseError error={topicError || nodesError} />;
76
+ }
77
+
78
+ return <Partitions path={path} type={type} consumers={consumers} nodes={nodes} />;
79
+ };
@@ -0,0 +1,13 @@
1
+ .ydb-diagnostics-partitions-columns {
2
+ &__lags-header {
3
+ text-align: center;
4
+ }
5
+
6
+ &__string-with-copy {
7
+ overflow: hidden;
8
+
9
+ max-width: 150px;
10
+
11
+ text-overflow: ellipsis;
12
+ }
13
+ }