ydb-embedded-ui 4.2.1 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. package/CHANGELOG.md +12 -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/hooks/index.ts +1 -0
  22. package/dist/utils/hooks/useSetting.ts +23 -0
  23. package/dist/utils/tablet.ts +14 -13
  24. package/package.json +1 -1
  25. package/dist/containers/Tenant/Diagnostics/Partitions/PartitionsWrapper.tsx +0 -81
  26. package/dist/containers/Tenant/Diagnostics/Partitions/index.ts +0 -1
  27. package/dist/store/reducers/consumer.ts +0 -174
  28. package/dist/types/store/consumer.ts +0 -63
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
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
+
3
15
  ## [4.2.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.2.0...v4.2.1) (2023-05-18)
4
16
 
5
17
 
@@ -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
+ });
@@ -71,4 +71,16 @@ export const PARTITIONS_READ_LAGS_SUB_COLUMNS_TITLES = {
71
71
  [PARTITIONS_READ_LAGS_SUB_COLUMNS_IDS.CONSUMER_READ_IDLE_TIME]: 'read idle time',
72
72
  } as const;
73
73
 
74
- export const PARTITIONS_DEFAULT_SELECTED_COLUMNS = Object.values(PARTITIONS_COLUMNS_IDS);
74
+ // Topics without consumers have partitions data with no data corresponding to consumers
75
+ // These columns will be empty and should not be displayed
76
+ export const generalPartitionColumnsIds = [
77
+ PARTITIONS_COLUMNS_IDS.PARTITION_ID,
78
+ PARTITIONS_COLUMNS_IDS.STORE_SIZE,
79
+ PARTITIONS_COLUMNS_IDS.WRITE_SPEED,
80
+ PARTITIONS_COLUMNS_IDS.WRITE_LAGS,
81
+ PARTITIONS_COLUMNS_IDS.START_OFFSET,
82
+ PARTITIONS_COLUMNS_IDS.END_OFFSET,
83
+ PARTITIONS_COLUMNS_IDS.PARTITION_HOST,
84
+ ];
85
+
86
+ export const allPartitionsColumnsIds = Object.values(PARTITIONS_COLUMNS_IDS);