ydb-embedded-ui 3.3.4 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }