Skip to content

Commit 0843a49

Browse files
feat(Partitions): display partitions for topic without consumers
1 parent 3b30565 commit 0843a49

File tree

24 files changed

+652
-408
lines changed

24 files changed

+652
-408
lines changed

src/containers/Tenant/Diagnostics/Diagnostics.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {Nodes} from '../../Nodes';
2727
//@ts-ignore
2828
import {Tablets} from '../../Tablets';
2929
import {Consumers} from './Consumers';
30-
import {PartitionsWrapper} from './Partitions';
30+
import {Partitions} from './Partitions/Partitions';
3131

3232
import routes, {createHref} from '../../../routes';
3333
import type {EPathType} from '../../../types/api/schema';
@@ -159,7 +159,7 @@ function Diagnostics(props: DiagnosticsProps) {
159159
return <Consumers path={currentSchemaPath} type={type} />;
160160
}
161161
case GeneralPagesIds.partitions: {
162-
return <PartitionsWrapper path={currentSchemaPath} type={type} />;
162+
return <Partitions path={currentSchemaPath} />;
163163
}
164164
default: {
165165
return <div>No data...</div>;

src/containers/Tenant/Diagnostics/Partitions/Partitions.tsx

Lines changed: 112 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,32 @@
11
import block from 'bem-cn-lite';
22
import {useCallback, useEffect, useMemo, useState} from 'react';
33
import {useDispatch} from 'react-redux';
4-
import {escapeRegExp} from 'lodash/fp';
54

65
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';
96

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';
119

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 {setSelectedConsumer} from '../../../../store/reducers/consumer';
12+
import {
13+
cleanTopicData,
14+
getTopic,
15+
selectConsumersNames,
16+
setDataWasNotLoaded as setTopicDataWasNotLoaded,
17+
} from '../../../../store/reducers/topic';
1618
import {
17-
getConsumer,
18-
selectPreparedPartitionsData,
19-
setDataWasNotLoaded,
20-
setSelectedConsumer,
21-
} from '../../../../store/reducers/consumer';
19+
getPartitions,
20+
setDataWasNotLoaded as setPartitionsDataWasNotLoaded,
21+
} from '../../../../store/reducers/partitions/partitions';
2222

2323
import {TableSkeleton} from '../../../../components/TableSkeleton/TableSkeleton';
24-
import {Search} from '../../../../components/Search';
2524
import {ResponseError} from '../../../../components/Errors/ResponseError';
2625

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';
3730

3831
import i18n from './i18n';
3932

@@ -43,215 +36,140 @@ export const b = block('ydb-diagnostics-partitions');
4336

4437
interface PartitionsProps {
4538
path?: string;
46-
type?: EPathType;
47-
nodes?: Record<number, string>;
48-
consumers?: string[];
4939
}
5040

51-
export const Partitions = ({path, type, nodes, consumers}: PartitionsProps) => {
52-
const isCdcStream = isCdcStreamEntityType(type);
53-
41+
export const Partitions = ({path}: PartitionsProps) => {
5442
const dispatch = useDispatch();
5543

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
5946
const [componentCurrentPath, setComponentCurrentPath] = useState(path);
6047

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+
[],
6450
);
6551

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 {selectedConsumer} = useTypedSelector((state) => state.consumer);
56+
const {
57+
loading: partitionsLoading,
58+
wasLoaded: partitionsWasLoaded,
59+
error: partitionsError,
60+
partitions: rawPartitions,
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+
[],
7076
);
7177

78+
const [columns, columnsIdsForSelector] = useGetPartitionsColumns(selectedConsumer);
79+
7280
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+
7487
setComponentCurrentPath(path);
7588
}, [dispatch, path]);
7689

77-
const fetchConsumerData = useCallback(
90+
const partitionsWithHosts = useMemo(() => {
91+
return addHostToPartitions(rawPartitions, nodesMap);
92+
}, [rawPartitions, nodesMap]);
93+
94+
const fetchData = useCallback(
7895
(isBackground: boolean) => {
7996
if (!isBackground) {
80-
dispatch(setDataWasNotLoaded());
97+
dispatch(setPartitionsDataWasNotLoaded());
8198
}
82-
83-
if (selectedConsumer && consumers && consumers.includes(selectedConsumer)) {
84-
dispatch(getConsumer(componentCurrentPath, selectedConsumer));
99+
if (topicWasLoaded && componentCurrentPath) {
100+
dispatch(getPartitions(componentCurrentPath, selectedConsumer));
85101
}
86102
},
87-
[dispatch, selectedConsumer, componentCurrentPath, consumers],
103+
[dispatch, selectedConsumer, componentCurrentPath, topicWasLoaded],
88104
);
89105

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);
102107

108+
// Wrong consumer could be passed in search query
109+
// Reset consumer if it doesn't exist for current topic
103110
useEffect(() => {
104-
const shouldUpdateSelectedConsumer =
105-
!selectedConsumer || (consumers && !consumers.includes(selectedConsumer));
111+
const isTopicWithoutConsumers = topicWasLoaded && !consumers;
112+
const wrongSelectedConsumer =
113+
selectedConsumer && consumers && !consumers.includes(selectedConsumer);
106114

107-
if (consumersToSelect && consumersToSelect.length && shouldUpdateSelectedConsumer) {
108-
dispatch(setSelectedConsumer(consumersToSelect[0].value));
115+
if (isTopicWithoutConsumers || wrongSelectedConsumer) {
116+
dispatch(setSelectedConsumer());
109117
}
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]);
130119

131120
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]);
169123

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);
186126
};
187127

188-
const handleConsumerSelectChange = (value: string[]) => {
189-
dispatch(setSelectedConsumer(value[0]));
128+
const handleSelectedConsumerChange = (value?: string) => {
129+
dispatch(setSelectedConsumer(value));
190130
};
191131

192-
const handlePartitionIdSearchChange = (value: string) => {
193-
setPartitionIdSearchValue(value);
194-
};
132+
const loading =
133+
(topicLoading && !topicWasLoaded) ||
134+
(nodesLoading && !nodesWasLoaded) ||
135+
(partitionsLoading && !partitionsWasLoaded);
195136

196-
const handleGeneralSearchChange = (value: string) => {
197-
setGeneralSearchValue(value);
198-
};
137+
const error = nodesError || topicError || partitionsError;
199138

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+
}
203146

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+
};
207157

208158
return (
209159
<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+
/>
241171
<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>
255173
</div>
256174
</div>
257175
);

0 commit comments

Comments
 (0)