ydb-embedded-ui 4.2.0 → 4.3.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 (29) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +2 -2
  3. package/dist/containers/Tenant/Diagnostics/Partitions/Partitions.tsx +111 -193
  4. package/dist/containers/Tenant/Diagnostics/Partitions/PartitionsControls/PartitionsControls.tsx +182 -0
  5. package/dist/containers/Tenant/Diagnostics/Partitions/columns/Columns.scss +1 -1
  6. package/dist/containers/Tenant/Diagnostics/Partitions/columns/columns.tsx +15 -6
  7. package/dist/containers/Tenant/Diagnostics/Partitions/utils/constants.ts +13 -1
  8. package/dist/containers/Tenant/Diagnostics/Partitions/utils/index.ts +26 -0
  9. package/dist/containers/Tenant/Diagnostics/Partitions/utils/types.ts +2 -2
  10. package/dist/containers/Tenant/Diagnostics/Partitions/utils/useGetPartitionsColumns.ts +27 -0
  11. package/dist/services/api.ts +1 -1
  12. package/dist/store/reducers/index.ts +2 -2
  13. package/dist/store/reducers/partitions/partitions.ts +104 -0
  14. package/dist/store/reducers/partitions/types.ts +47 -0
  15. package/dist/store/reducers/partitions/utils.ts +99 -0
  16. package/dist/store/reducers/settings.js +2 -2
  17. package/dist/store/reducers/topic.ts +7 -0
  18. package/dist/store/state-url-mapping.js +1 -1
  19. package/dist/types/api/topic.ts +1 -1
  20. package/dist/utils/constants.ts +1 -1
  21. package/dist/utils/createToast.tsx +1 -1
  22. package/dist/utils/hooks/index.ts +1 -0
  23. package/dist/utils/hooks/useSetting.ts +23 -0
  24. package/dist/utils/tablet.ts +14 -13
  25. package/package.json +1 -1
  26. package/dist/containers/Tenant/Diagnostics/Partitions/PartitionsWrapper.tsx +0 -81
  27. package/dist/containers/Tenant/Diagnostics/Partitions/index.ts +0 -1
  28. package/dist/store/reducers/consumer.ts +0 -174
  29. package/dist/types/store/consumer.ts +0 -63
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.3.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.2.1...v4.3.0) (2023-05-18)
4
+
5
+
6
+ ### Features
7
+
8
+ * **Partitions:** display partitions for topic without consumers ([0843a49](https://github.com/ydb-platform/ydb-embedded-ui/commit/0843a49c46cb6765b376832a847c3ac0ce8b6b85))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **Tablet:** update state to color mapping ([7ccc8c7](https://github.com/ydb-platform/ydb-embedded-ui/commit/7ccc8c79225cd311a7a3674150335b58a94f293e))
14
+
15
+ ## [4.2.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.2.0...v4.2.1) (2023-05-18)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * export toaster ([b5d12c0](https://github.com/ydb-platform/ydb-embedded-ui/commit/b5d12c0aa39ea3877a9b74071e3124f89a309ca3))
21
+
3
22
  ## [4.2.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.1.0...v4.2.0) (2023-05-16)
4
23
 
5
24
 
@@ -27,7 +27,7 @@ import {Nodes} from '../../Nodes';
27
27
  //@ts-ignore
28
28
  import {Tablets} from '../../Tablets';
29
29
  import {Consumers} from './Consumers';
30
- import {PartitionsWrapper} from './Partitions';
30
+ import {Partitions} from './Partitions/Partitions';
31
31
 
32
32
  import routes, {createHref} from '../../../routes';
33
33
  import type {EPathType} from '../../../types/api/schema';
@@ -159,7 +159,7 @@ function Diagnostics(props: DiagnosticsProps) {
159
159
  return <Consumers path={currentSchemaPath} type={type} />;
160
160
  }
161
161
  case GeneralPagesIds.partitions: {
162
- return <PartitionsWrapper path={currentSchemaPath} type={type} />;
162
+ return <Partitions path={currentSchemaPath} />;
163
163
  }
164
164
  default: {
165
165
  return <div>No data...</div>;
@@ -1,39 +1,32 @@
1
1
  import block from 'bem-cn-lite';
2
2
  import {useCallback, useEffect, useMemo, useState} from 'react';
3
3
  import {useDispatch} from 'react-redux';
4
- import {escapeRegExp} from 'lodash/fp';
5
4
 
6
5
  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
6
 
10
- import type {EPathType} from '../../../../types/api/schema';
7
+ import {useAutofetcher, useTypedSelector, useSetting} from '../../../../utils/hooks';
8
+ import {DEFAULT_TABLE_SETTINGS, PARTITIONS_HIDDEN_COLUMNS_KEY} from '../../../../utils/constants';
11
9
 
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';
10
+ import {getNodesList, selectNodesMap} from '../../../../store/reducers/nodesList';
11
+ import {
12
+ cleanTopicData,
13
+ getTopic,
14
+ selectConsumersNames,
15
+ setDataWasNotLoaded as setTopicDataWasNotLoaded,
16
+ } from '../../../../store/reducers/topic';
16
17
  import {
17
- getConsumer,
18
- selectPreparedPartitionsData,
19
- setDataWasNotLoaded,
18
+ getPartitions,
19
+ setDataWasNotLoaded as setPartitionsDataWasNotLoaded,
20
20
  setSelectedConsumer,
21
- } from '../../../../store/reducers/consumer';
21
+ } from '../../../../store/reducers/partitions/partitions';
22
22
 
23
23
  import {TableSkeleton} from '../../../../components/TableSkeleton/TableSkeleton';
24
- import {Search} from '../../../../components/Search';
25
24
  import {ResponseError} from '../../../../components/Errors/ResponseError';
26
25
 
27
- import {isCdcStreamEntityType} from '../../utils/schema';
28
-
29
- import type {IPreparedPartitionDataWithHosts} from './utils/types';
30
- import {
31
- PARTITIONS_COLUMNS_IDS,
32
- PARTITIONS_COLUMNS_TITILES,
33
- PARTITIONS_DEFAULT_SELECTED_COLUMNS,
34
- } from './utils/constants';
35
-
36
- import {columns as partitionsColumns} from './columns';
26
+ import type {PreparedPartitionDataWithHosts} from './utils/types';
27
+ import {addHostToPartitions} from './utils';
28
+ import {PartitionsControls} from './PartitionsControls/PartitionsControls';
29
+ import {useGetPartitionsColumns} from './utils/useGetPartitionsColumns';
37
30
 
38
31
  import i18n from './i18n';
39
32
 
@@ -43,215 +36,140 @@ export const b = block('ydb-diagnostics-partitions');
43
36
 
44
37
  interface PartitionsProps {
45
38
  path?: string;
46
- type?: EPathType;
47
- nodes?: Record<number, string>;
48
- consumers?: string[];
49
39
  }
50
40
 
51
- export const Partitions = ({path, type, nodes, consumers}: PartitionsProps) => {
52
- const isCdcStream = isCdcStreamEntityType(type);
53
-
41
+ export const Partitions = ({path}: PartitionsProps) => {
54
42
  const dispatch = useDispatch();
55
43
 
56
- const [generalSearchValue, setGeneralSearchValue] = useState('');
57
- const [partitionIdSearchValue, setPartitionIdSearchValue] = useState('');
58
-
44
+ // Manual path control to ensure that topic state will be reset before data fetch
45
+ // so no request with wrong params will be sent
59
46
  const [componentCurrentPath, setComponentCurrentPath] = useState(path);
60
47
 
61
- const {autorefresh} = useTypedSelector((state) => state.schema);
62
- const {loading, wasLoaded, error, selectedConsumer} = useTypedSelector(
63
- (state) => state.consumer,
48
+ const [partitionsToRender, setPartitionsToRender] = useState<PreparedPartitionDataWithHosts[]>(
49
+ [],
64
50
  );
65
51
 
66
- const partitions = useTypedSelector((state) => selectPreparedPartitionsData(state));
67
-
68
- const savedSelectedColumns: string = useTypedSelector((state) =>
69
- getSettingValue(state, PARTITIONS_SELECTED_COLUMNS_KEY),
52
+ const consumers = useTypedSelector(selectConsumersNames);
53
+ const nodesMap = useTypedSelector(selectNodesMap);
54
+ const {autorefresh} = useTypedSelector((state) => state.schema);
55
+ const {
56
+ loading: partitionsLoading,
57
+ wasLoaded: partitionsWasLoaded,
58
+ error: partitionsError,
59
+ partitions: rawPartitions,
60
+ selectedConsumer,
61
+ } = useTypedSelector((state) => state.partitions);
62
+ const {
63
+ loading: topicLoading,
64
+ wasLoaded: topicWasLoaded,
65
+ error: topicError,
66
+ } = useTypedSelector((state) => state.topic);
67
+ const {
68
+ loading: nodesLoading,
69
+ wasLoaded: nodesWasLoaded,
70
+ error: nodesError,
71
+ } = useTypedSelector((state) => state.nodesList);
72
+
73
+ const [hiddenColumns, setHiddenColumns] = useSetting<string[]>(
74
+ PARTITIONS_HIDDEN_COLUMNS_KEY,
75
+ [],
70
76
  );
71
77
 
78
+ const [columns, columnsIdsForSelector] = useGetPartitionsColumns(selectedConsumer);
79
+
72
80
  useEffect(() => {
73
- // Manual path control to ensure it updates with other values so no request with wrong params will be sent
81
+ dispatch(cleanTopicData());
82
+ dispatch(setTopicDataWasNotLoaded());
83
+
84
+ dispatch(getNodesList());
85
+ dispatch(getTopic(path));
86
+
74
87
  setComponentCurrentPath(path);
75
88
  }, [dispatch, path]);
76
89
 
77
- const fetchConsumerData = useCallback(
90
+ const partitionsWithHosts = useMemo(() => {
91
+ return addHostToPartitions(rawPartitions, nodesMap);
92
+ }, [rawPartitions, nodesMap]);
93
+
94
+ const fetchData = useCallback(
78
95
  (isBackground: boolean) => {
79
96
  if (!isBackground) {
80
- dispatch(setDataWasNotLoaded());
97
+ dispatch(setPartitionsDataWasNotLoaded());
81
98
  }
82
-
83
- if (selectedConsumer && consumers && consumers.includes(selectedConsumer)) {
84
- dispatch(getConsumer(componentCurrentPath, selectedConsumer));
99
+ if (topicWasLoaded && componentCurrentPath) {
100
+ dispatch(getPartitions(componentCurrentPath, selectedConsumer));
85
101
  }
86
102
  },
87
- [dispatch, selectedConsumer, componentCurrentPath, consumers],
103
+ [dispatch, selectedConsumer, componentCurrentPath, topicWasLoaded],
88
104
  );
89
105
 
90
- useAutofetcher(fetchConsumerData, [fetchConsumerData], autorefresh);
91
-
92
- const consumersToSelect = useMemo(
93
- () =>
94
- consumers
95
- ? consumers.map((consumer) => ({
96
- value: consumer,
97
- content: consumer,
98
- }))
99
- : undefined,
100
- [consumers],
101
- );
106
+ useAutofetcher(fetchData, [fetchData], autorefresh);
102
107
 
108
+ // Wrong consumer could be passed in search query
109
+ // Reset consumer if it doesn't exist for current topic
103
110
  useEffect(() => {
104
- const shouldUpdateSelectedConsumer =
105
- !selectedConsumer || (consumers && !consumers.includes(selectedConsumer));
111
+ const isTopicWithoutConsumers = topicWasLoaded && !consumers;
112
+ const wrongSelectedConsumer =
113
+ selectedConsumer && consumers && !consumers.includes(selectedConsumer);
106
114
 
107
- if (consumersToSelect && consumersToSelect.length && shouldUpdateSelectedConsumer) {
108
- dispatch(setSelectedConsumer(consumersToSelect[0].value));
115
+ if (isTopicWithoutConsumers || wrongSelectedConsumer) {
116
+ dispatch(setSelectedConsumer());
109
117
  }
110
- }, [dispatch, consumersToSelect, selectedConsumer, consumers]);
111
-
112
- const selectedColumns: string[] = useMemo(
113
- () =>
114
- savedSelectedColumns
115
- ? JSON.parse(savedSelectedColumns)
116
- : PARTITIONS_DEFAULT_SELECTED_COLUMNS,
117
- [savedSelectedColumns],
118
- );
119
-
120
- const columnsToSelect = useMemo(() => {
121
- return Object.values(PARTITIONS_COLUMNS_IDS).map((id) => {
122
- return {
123
- title: PARTITIONS_COLUMNS_TITILES[id],
124
- selected: Boolean(selectedColumns?.includes(id)),
125
- id: id,
126
- required: id === PARTITIONS_COLUMNS_IDS.PARTITION_ID,
127
- };
128
- });
129
- }, [selectedColumns]);
118
+ }, [dispatch, topicWasLoaded, selectedConsumer, consumers]);
130
119
 
131
120
  const columnsToShow = useMemo(() => {
132
- return partitionsColumns.filter((column) => selectedColumns?.includes(column.name));
133
- }, [selectedColumns]);
134
-
135
- const partitionsWithHosts: IPreparedPartitionDataWithHosts[] | undefined = useMemo(() => {
136
- return partitions?.map((partition) => {
137
- const partitionHost =
138
- partition.partitionNodeId && nodes ? nodes[partition.partitionNodeId] : undefined;
139
-
140
- const connectionHost =
141
- partition.connectionNodeId && nodes ? nodes[partition.connectionNodeId] : undefined;
142
-
143
- return {
144
- ...partition,
145
- partitionHost,
146
- connectionHost,
147
- };
148
- });
149
- }, [partitions, nodes]);
150
-
151
- const dataToRender = useMemo(() => {
152
- if (!partitionsWithHosts) {
153
- return [];
154
- }
155
-
156
- const partitionIdRe = new RegExp(escapeRegExp(partitionIdSearchValue), 'i');
157
- const generalRe = new RegExp(escapeRegExp(generalSearchValue), 'i');
158
-
159
- return partitionsWithHosts.filter((partition) => {
160
- const {
161
- partitionId,
162
- readerName = '',
163
- readSessionId = '',
164
- partitionNodeId,
165
- connectionNodeId,
166
- partitionHost = '',
167
- connectionHost = '',
168
- } = partition;
121
+ return columns.filter((column) => !hiddenColumns.includes(column.name));
122
+ }, [columns, hiddenColumns]);
169
123
 
170
- const isPartitionIdMatch = partitionIdRe.test(partitionId);
171
- const isOtherValuesMatch =
172
- generalRe.test(readerName) ||
173
- generalRe.test(readSessionId) ||
174
- generalRe.test(String(partitionNodeId)) ||
175
- generalRe.test(String(connectionNodeId)) ||
176
- generalRe.test(partitionHost) ||
177
- generalRe.test(connectionHost);
178
-
179
- return isPartitionIdMatch && isOtherValuesMatch;
180
- });
181
- }, [partitionIdSearchValue, generalSearchValue, partitionsWithHosts]);
182
-
183
- const hadleTableColumnsSetupChange = (value: TableColumnSetupItem[]) => {
184
- const columns = value.filter((el) => el.selected).map((el) => el.id);
185
- dispatch(setSettingValue(PARTITIONS_SELECTED_COLUMNS_KEY, JSON.stringify(columns)));
124
+ const hadleTableColumnsSetupChange = (newHiddenColumns: string[]) => {
125
+ setHiddenColumns(newHiddenColumns);
186
126
  };
187
127
 
188
- const handleConsumerSelectChange = (value: string[]) => {
189
- dispatch(setSelectedConsumer(value[0]));
128
+ const handleSelectedConsumerChange = (value?: string) => {
129
+ dispatch(setSelectedConsumer(value));
190
130
  };
191
131
 
192
- const handlePartitionIdSearchChange = (value: string) => {
193
- setPartitionIdSearchValue(value);
194
- };
132
+ const loading =
133
+ (topicLoading && !topicWasLoaded) ||
134
+ (nodesLoading && !nodesWasLoaded) ||
135
+ (partitionsLoading && !partitionsWasLoaded);
195
136
 
196
- const handleGeneralSearchChange = (value: string) => {
197
- setGeneralSearchValue(value);
198
- };
137
+ const error = nodesError || topicError || partitionsError;
199
138
 
200
- if (error) {
201
- return <ResponseError error={error} />;
202
- }
139
+ const getContent = () => {
140
+ if (loading) {
141
+ return <TableSkeleton className={b('loader')} />;
142
+ }
143
+ if (error) {
144
+ return <ResponseError error={error} />;
145
+ }
203
146
 
204
- if (!consumersToSelect || !consumersToSelect.length) {
205
- return <div>{i18n(`noConsumersMessage.${isCdcStream ? 'stream' : 'topic'}`)}</div>;
206
- }
147
+ return (
148
+ <DataTable
149
+ theme="yandex-cloud"
150
+ data={partitionsToRender}
151
+ columns={columnsToShow}
152
+ settings={DEFAULT_TABLE_SETTINGS}
153
+ emptyDataMessage={i18n('table.emptyDataMessage')}
154
+ />
155
+ );
156
+ };
207
157
 
208
158
  return (
209
159
  <div className={b()}>
210
- <div className={b('controls')}>
211
- <Select
212
- className={b('consumer-select')}
213
- placeholder={i18n('controls.consumerSelector.placeholder')}
214
- label={i18n('controls.consumerSelector')}
215
- options={consumersToSelect}
216
- value={[selectedConsumer || '']}
217
- onUpdate={handleConsumerSelectChange}
218
- filterable={consumers && consumers.length > 5}
219
- />
220
- <Search
221
- onChange={handlePartitionIdSearchChange}
222
- placeholder={i18n('controls.partitionSearch')}
223
- className={b('search', {partition: true})}
224
- value={partitionIdSearchValue}
225
- />
226
- <Search
227
- onChange={handleGeneralSearchChange}
228
- placeholder={i18n('controls.generalSearch')}
229
- className={b('search', {general: true})}
230
- value={generalSearchValue}
231
- />
232
- <TableColumnSetup
233
- key="TableColumnSetup"
234
- popupWidth="242px"
235
- items={columnsToSelect}
236
- showStatus
237
- onUpdate={hadleTableColumnsSetupChange}
238
- className={b('table-settings')}
239
- />
240
- </div>
160
+ <PartitionsControls
161
+ consumers={consumers}
162
+ selectedConsumer={selectedConsumer}
163
+ onSelectedConsumerChange={handleSelectedConsumerChange}
164
+ selectDisabled={Boolean(error) || loading}
165
+ partitions={partitionsWithHosts}
166
+ onSearchChange={setPartitionsToRender}
167
+ hiddenColumns={hiddenColumns}
168
+ onHiddenColumnsChange={hadleTableColumnsSetupChange}
169
+ initialColumnsIds={columnsIdsForSelector}
170
+ />
241
171
  <div className={b('table-wrapper')}>
242
- <div className={b('table-content')}>
243
- {loading && !wasLoaded ? (
244
- <TableSkeleton className={b('loader')} />
245
- ) : (
246
- <DataTable
247
- theme="yandex-cloud"
248
- data={dataToRender}
249
- columns={columnsToShow}
250
- settings={DEFAULT_TABLE_SETTINGS}
251
- emptyDataMessage={i18n('table.emptyDataMessage')}
252
- />
253
- )}
254
- </div>
172
+ <div className={b('table-content')}>{getContent()}</div>
255
173
  </div>
256
174
  </div>
257
175
  );
@@ -0,0 +1,182 @@
1
+ import {useEffect, useMemo, useState} from 'react';
2
+ import {escapeRegExp} from 'lodash/fp';
3
+
4
+ import {TableColumnSetupItem} from '@gravity-ui/uikit/build/esm/components/Table/hoc/withTableSettings/withTableSettings';
5
+ import {Select, TableColumnSetup} from '@gravity-ui/uikit';
6
+
7
+ import type {ValueOf} from '../../../../../types/common';
8
+
9
+ import {Search} from '../../../../../components/Search/Search';
10
+
11
+ import type {PreparedPartitionDataWithHosts} from '../utils/types';
12
+ import {PARTITIONS_COLUMNS_IDS, PARTITIONS_COLUMNS_TITILES} from '../utils/constants';
13
+ import i18n from '../i18n';
14
+ import {b} from '../Partitions';
15
+
16
+ interface PartitionsControlsProps {
17
+ consumers: string[] | undefined;
18
+ selectedConsumer: string | undefined;
19
+ onSelectedConsumerChange: (consumer: string | undefined) => void;
20
+ selectDisabled: boolean;
21
+ partitions: PreparedPartitionDataWithHosts[] | undefined;
22
+ onSearchChange: (filteredPartitions: PreparedPartitionDataWithHosts[]) => void;
23
+ hiddenColumns: string[];
24
+ onHiddenColumnsChange: (newHiddenColumns: string[]) => void;
25
+ initialColumnsIds: string[];
26
+ }
27
+
28
+ export const PartitionsControls = ({
29
+ consumers,
30
+ selectedConsumer,
31
+ onSelectedConsumerChange,
32
+ selectDisabled,
33
+ partitions,
34
+ onSearchChange,
35
+ hiddenColumns,
36
+ onHiddenColumnsChange,
37
+ initialColumnsIds,
38
+ }: PartitionsControlsProps) => {
39
+ const [generalSearchValue, setGeneralSearchValue] = useState('');
40
+ const [partitionIdSearchValue, setPartitionIdSearchValue] = useState('');
41
+
42
+ // Manual select control to enforce single-select behaviour for multiple select type
43
+ const [consumerSelectOpen, setConsumerSelectOpen] = useState(false);
44
+
45
+ useEffect(() => {
46
+ if (!partitions) {
47
+ return;
48
+ }
49
+
50
+ const partitionIdRe = new RegExp(escapeRegExp(partitionIdSearchValue), 'i');
51
+ const generalRe = new RegExp(escapeRegExp(generalSearchValue), 'i');
52
+
53
+ const filteredPartitions = partitions.filter((partition) => {
54
+ const {
55
+ partitionId,
56
+ readerName,
57
+ readSessionId,
58
+ partitionNodeId,
59
+ connectionNodeId,
60
+ partitionHost,
61
+ connectionHost,
62
+ } = partition;
63
+
64
+ const isPartitionIdMatch = partitionIdRe.test(partitionId);
65
+
66
+ const otherValues = [
67
+ readerName,
68
+ readSessionId,
69
+ partitionNodeId,
70
+ connectionNodeId,
71
+ partitionHost,
72
+ connectionHost,
73
+ ]
74
+ .filter(Boolean)
75
+ .map(String);
76
+
77
+ const isOtherValuesMatch =
78
+ otherValues.length === 0 || otherValues.some((value) => generalRe.test(value));
79
+
80
+ return isPartitionIdMatch && isOtherValuesMatch;
81
+ });
82
+
83
+ onSearchChange(filteredPartitions);
84
+ }, [partitionIdSearchValue, generalSearchValue, partitions, onSearchChange]);
85
+
86
+ const consumersToSelect = useMemo(
87
+ () =>
88
+ consumers
89
+ ? consumers.map((consumer) => ({
90
+ value: consumer,
91
+ content: consumer,
92
+ }))
93
+ : undefined,
94
+ [consumers],
95
+ );
96
+
97
+ const columnsToSelect = useMemo(() => {
98
+ return initialColumnsIds.map((id) => {
99
+ return {
100
+ title: PARTITIONS_COLUMNS_TITILES[id as ValueOf<typeof PARTITIONS_COLUMNS_IDS>],
101
+ selected: Boolean(!hiddenColumns.includes(id)),
102
+ id: id,
103
+ required: id === PARTITIONS_COLUMNS_IDS.PARTITION_ID,
104
+ };
105
+ });
106
+ }, [initialColumnsIds, hiddenColumns]);
107
+
108
+ const handleConsumerSelectChange = (value: string[]) => {
109
+ // As we have selector with multiple options, the first value corresponds to previous value
110
+ // The second value is currently chosen consumer
111
+ onSelectedConsumerChange(value[1]);
112
+ setConsumerSelectOpen(false);
113
+ };
114
+
115
+ const handlePartitionIdSearchChange = (value: string) => {
116
+ setPartitionIdSearchValue(value);
117
+ };
118
+
119
+ const handleGeneralSearchChange = (value: string) => {
120
+ setGeneralSearchValue(value);
121
+ };
122
+
123
+ const hadleTableColumnsSetupChange = (value: TableColumnSetupItem[]) => {
124
+ const result = [...hiddenColumns];
125
+
126
+ // Process current set of columns
127
+ // This way we do not remove from hidden these columns, that are not displayed currently
128
+ // The reasons: set of columns differs for partitions with and without consumers
129
+ value.forEach((el) => {
130
+ if (!el.selected && !hiddenColumns.includes(el.id)) {
131
+ result.push(el.id);
132
+ } else if (el.selected && hiddenColumns.includes(el.id)) {
133
+ result.splice(hiddenColumns.indexOf(el.id));
134
+ }
135
+ });
136
+
137
+ onHiddenColumnsChange(result);
138
+ };
139
+
140
+ return (
141
+ <div className={b('controls')}>
142
+ <Select
143
+ className={b('consumer-select')}
144
+ placeholder={i18n('controls.consumerSelector.placeholder')}
145
+ label={i18n('controls.consumerSelector')}
146
+ options={consumersToSelect}
147
+ value={[selectedConsumer || '']}
148
+ onUpdate={handleConsumerSelectChange}
149
+ filterable={consumers && consumers.length > 5}
150
+ disabled={selectDisabled || !consumers || !consumers.length}
151
+ open={consumerSelectOpen}
152
+ onOpenChange={setConsumerSelectOpen}
153
+ // Although only one value could be selected
154
+ // Multiple type Select is used
155
+ // The reason - we need Select to be able to work with no value
156
+ // And it is easier to make multiple Select close on value change
157
+ // Than to enable single select to work with empty values
158
+ multiple
159
+ />
160
+ <Search
161
+ onChange={handlePartitionIdSearchChange}
162
+ placeholder={i18n('controls.partitionSearch')}
163
+ className={b('search', {partition: true})}
164
+ value={partitionIdSearchValue}
165
+ />
166
+ <Search
167
+ onChange={handleGeneralSearchChange}
168
+ placeholder={i18n('controls.generalSearch')}
169
+ className={b('search', {general: true})}
170
+ value={generalSearchValue}
171
+ />
172
+ <TableColumnSetup
173
+ key="TableColumnSetup"
174
+ popupWidth="242px"
175
+ items={columnsToSelect}
176
+ showStatus
177
+ onUpdate={hadleTableColumnsSetupChange}
178
+ className={b('table-settings')}
179
+ />
180
+ </div>
181
+ );
182
+ };
@@ -6,7 +6,7 @@
6
6
  &__string-with-copy {
7
7
  overflow: hidden;
8
8
 
9
- max-width: 150px;
9
+ width: 150px;
10
10
 
11
11
  text-overflow: ellipsis;
12
12
  }
@@ -14,8 +14,9 @@ import {
14
14
  PARTITIONS_READ_LAGS_SUB_COLUMNS_TITLES,
15
15
  PARTITIONS_WRITE_LAGS_SUB_COLUMNS_IDS,
16
16
  PARTITIONS_WRITE_LAGS_SUB_COLUMNS_TITLES,
17
+ generalPartitionColumnsIds,
17
18
  } from '../utils/constants';
18
- import type {IPreparedPartitionDataWithHosts} from '../utils/types';
19
+ import type {PreparedPartitionDataWithHosts} from '../utils/types';
19
20
 
20
21
  import {
21
22
  MultilineHeader,
@@ -30,7 +31,7 @@ import './Columns.scss';
30
31
 
31
32
  const b = block('ydb-diagnostics-partitions-columns');
32
33
 
33
- export const columns: Column<IPreparedPartitionDataWithHosts>[] = [
34
+ export const allColumns: Column<PreparedPartitionDataWithHosts>[] = [
34
35
  {
35
36
  name: PARTITIONS_COLUMNS_IDS.PARTITION_ID,
36
37
  header: (
@@ -63,7 +64,7 @@ export const columns: Column<IPreparedPartitionDataWithHosts>[] = [
63
64
  name: PARTITIONS_COLUMNS_IDS.READ_SPEED,
64
65
  header: PARTITIONS_COLUMNS_TITILES[PARTITIONS_COLUMNS_IDS.READ_SPEED],
65
66
  align: DataTable.LEFT,
66
- sortAccessor: (row) => row.readSpeed.perMinute,
67
+ sortAccessor: (row) => row.readSpeed?.perMinute,
67
68
  render: ({row}) => <SpeedMultiMeter data={row.readSpeed} />,
68
69
  },
69
70
  {
@@ -210,10 +211,10 @@ export const columns: Column<IPreparedPartitionDataWithHosts>[] = [
210
211
  ),
211
212
  align: DataTable.LEFT,
212
213
  render: ({row}) =>
213
- row.partitionHost ? (
214
+ row.partitionNodeId && row.partitionHost ? (
214
215
  <EntityStatus
215
216
  name={row.partitionHost}
216
- path={getDefaultNodePath(row.partitionHost)}
217
+ path={getDefaultNodePath(row.partitionNodeId)}
217
218
  showStatus={false}
218
219
  hasClipboardButton
219
220
  className={b('string-with-copy')}
@@ -231,7 +232,7 @@ export const columns: Column<IPreparedPartitionDataWithHosts>[] = [
231
232
  ),
232
233
  align: DataTable.LEFT,
233
234
  render: ({row}) =>
234
- row.connectionHost ? (
235
+ row.connectionNodeId && row.connectionHost ? (
235
236
  <EntityStatus
236
237
  name={row.connectionHost}
237
238
  path={getDefaultNodePath(row.connectionNodeId)}
@@ -244,3 +245,11 @@ export const columns: Column<IPreparedPartitionDataWithHosts>[] = [
244
245
  ),
245
246
  },
246
247
  ];
248
+
249
+ // Topics without consumers have partitions data with no data corresponding to consumers
250
+ // These columns will be empty and should not be displayed
251
+ export const generalColumns = allColumns.filter((column) => {
252
+ return generalPartitionColumnsIds.includes(
253
+ column.name as typeof generalPartitionColumnsIds[number],
254
+ );
255
+ });