Skip to content

Commit 5a789d5

Browse files
authored
feat: add Nodes and Storage filters to url (#849)
1 parent 45084a6 commit 5a789d5

17 files changed

+231
-330
lines changed

package-lock.json

+10-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@
4848
"url": "^0.11.3",
4949
"use-query-params": "^2.2.1",
5050
"web-vitals": "^1.1.2",
51-
"ydb-ui-components": "^4.1.0"
51+
"ydb-ui-components": "^4.1.0",
52+
"zod": "^3.23.8"
5253
},
5354
"scripts": {
5455
"analyze": "source-map-explorer 'build/static/js/*.js'",

src/containers/Nodes/Nodes.tsx

+19-28
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react';
22

33
import {ASCENDING} from '@gravity-ui/react-data-table/build/esm/lib/constants';
44
import {skipToken} from '@reduxjs/toolkit/query';
5+
import {StringParam, useQueryParams} from 'use-query-params';
56

67
import {EntitiesCount} from '../../components/EntitiesCount';
78
import {AccessDenied} from '../../components/Errors/403';
@@ -12,13 +13,7 @@ import {ResizeableDataTable} from '../../components/ResizeableDataTable/Resizeab
1213
import {Search} from '../../components/Search';
1314
import {TableWithControlsLayout} from '../../components/TableWithControlsLayout/TableWithControlsLayout';
1415
import {UptimeFilter} from '../../components/UptimeFIlter';
15-
import {
16-
nodesApi,
17-
setInitialState,
18-
setSearchValue,
19-
setSort,
20-
setUptimeFilter,
21-
} from '../../store/reducers/nodes/nodes';
16+
import {nodesApi} from '../../store/reducers/nodes/nodes';
2217
import {filterNodes} from '../../store/reducers/nodes/selectors';
2318
import type {NodesSortParams} from '../../store/reducers/nodes/types';
2419
import {ProblemFilterValues, changeFilter} from '../../store/reducers/settings/settings';
@@ -35,6 +30,7 @@ import {
3530
NodesUptimeFilterValues,
3631
isSortableNodesProperty,
3732
isUnavailableNode,
33+
nodesUptimeFilterValuesSchema,
3834
} from '../../utils/nodes';
3935

4036
import {NODES_COLUMNS_WIDTH_LS_KEY, getNodesColumns} from './getNodesColumns';
@@ -50,16 +46,17 @@ interface NodesProps {
5046
}
5147

5248
export const Nodes = ({path, additionalNodesProps = {}}: NodesProps) => {
49+
const [queryParams, setQueryParams] = useQueryParams({
50+
uptimeFilter: StringParam,
51+
search: StringParam,
52+
});
53+
const uptimeFilter = nodesUptimeFilterValuesSchema.parse(queryParams.uptimeFilter);
54+
const searchValue = queryParams.search ?? '';
55+
5356
const dispatch = useTypedDispatch();
5457

5558
const isClusterNodes = !path;
5659

57-
const {
58-
uptimeFilter,
59-
searchValue,
60-
sortOrder = ASCENDING,
61-
sortValue = 'NodeId',
62-
} = useTypedSelector((state) => state.nodes);
6360
const problemFilter = useTypedSelector((state) => state.settings.problemFilter);
6461
const {autorefresh} = useTypedSelector((state) => state.schema);
6562

@@ -77,32 +74,26 @@ export const Nodes = ({path, additionalNodesProps = {}}: NodesProps) => {
7774

7875
const {currentData: data, isLoading, error} = useGetComputeNodes ? computeQuery : nodesQuery;
7976

80-
const [sort, handleSort] = useTableSort({sortValue, sortOrder}, (sortParams) =>
81-
dispatch(setSort(sortParams as NodesSortParams)),
82-
);
77+
const [sortValue, setSortValue] = React.useState<NodesSortParams>({
78+
sortValue: 'NodeId',
79+
sortOrder: ASCENDING,
80+
});
81+
const [sort, handleSort] = useTableSort(sortValue, (sortParams) => {
82+
setSortValue(sortParams as NodesSortParams);
83+
});
8384

8485
const handleSearchQueryChange = (value: string) => {
85-
dispatch(setSearchValue(value));
86+
setQueryParams({search: value || undefined}, 'replaceIn');
8687
};
8788

8889
const handleProblemFilterChange = (value: ProblemFilterValue) => {
8990
dispatch(changeFilter(value));
9091
};
9192

9293
const handleUptimeFilterChange = (value: NodesUptimeFilterValues) => {
93-
dispatch(setUptimeFilter(value));
94+
setQueryParams({uptimeFilter: value}, 'replaceIn');
9495
};
9596

96-
// Since Nodes component is used in several places,
97-
// we need to reset filters, searchValue
98-
// in nodes reducer when path changes
99-
React.useEffect(() => {
100-
return () => {
101-
// Clean data on component unmount
102-
dispatch(setInitialState());
103-
};
104-
}, [dispatch, path]);
105-
10697
const nodes = React.useMemo(() => {
10798
return filterNodes(data?.Nodes, {searchValue, uptimeFilter, problemFilter});
10899
}, [data, searchValue, uptimeFilter, problemFilter]);

src/containers/Nodes/VirtualNodes.tsx

+30-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import React from 'react';
22

3+
import {StringParam, useQueryParams} from 'use-query-params';
4+
35
import {EntitiesCount} from '../../components/EntitiesCount';
46
import {AccessDenied} from '../../components/Errors/403';
57
import {ResponseError} from '../../components/Errors/ResponseError';
@@ -15,16 +17,18 @@ import type {
1517
} from '../../components/VirtualTable';
1618
import {ResizeableVirtualTable} from '../../components/VirtualTable/ResizeableVirtualTable';
1719
import type {NodesPreparedEntity} from '../../store/reducers/nodes/types';
18-
import {ProblemFilterValues} from '../../store/reducers/settings/settings';
20+
import {ProblemFilterValues, changeFilter} from '../../store/reducers/settings/settings';
1921
import type {ProblemFilterValue} from '../../store/reducers/settings/types';
2022
import type {AdditionalNodesProps} from '../../types/additionalProps';
2123
import {cn} from '../../utils/cn';
24+
import {useTypedDispatch, useTypedSelector} from '../../utils/hooks';
2225
import {
2326
NodesUptimeFilterValues,
2427
getProblemParamValue,
2528
getUptimeParamValue,
2629
isSortableNodesProperty,
2730
isUnavailableNode,
31+
nodesUptimeFilterValuesSchema,
2832
} from '../../utils/nodes';
2933
import type {NodesSortValue} from '../../utils/nodes';
3034

@@ -43,13 +47,16 @@ interface NodesProps {
4347
}
4448

4549
export const VirtualNodes = ({path, parentContainer, additionalNodesProps}: NodesProps) => {
46-
const [searchValue, setSearchValue] = React.useState('');
47-
const [problemFilter, setProblemFilter] = React.useState<ProblemFilterValue>(
48-
ProblemFilterValues.ALL,
49-
);
50-
const [uptimeFilter, setUptimeFilter] = React.useState<NodesUptimeFilterValues>(
51-
NodesUptimeFilterValues.All,
52-
);
50+
const [queryParams, setQueryParams] = useQueryParams({
51+
uptimeFilter: StringParam,
52+
search: StringParam,
53+
});
54+
const uptimeFilter = nodesUptimeFilterValuesSchema.parse(queryParams.uptimeFilter);
55+
const searchValue = queryParams.search ?? '';
56+
57+
const dispatch = useTypedDispatch();
58+
59+
const problemFilter = useTypedSelector((state) => state.settings.problemFilter);
5360

5461
const filters = React.useMemo(() => {
5562
return [path, searchValue, problemFilter, uptimeFilter];
@@ -76,16 +83,28 @@ export const VirtualNodes = ({path, parentContainer, additionalNodesProps}: Node
7683
};
7784

7885
const renderControls: RenderControls = ({totalEntities, foundEntities, inited}) => {
86+
const handleSearchQueryChange = (value: string) => {
87+
setQueryParams({search: value || undefined}, 'replaceIn');
88+
};
89+
90+
const handleProblemFilterChange = (value: ProblemFilterValue) => {
91+
dispatch(changeFilter(value));
92+
};
93+
94+
const handleUptimeFilterChange = (value: NodesUptimeFilterValues) => {
95+
setQueryParams({uptimeFilter: value}, 'replaceIn');
96+
};
97+
7998
return (
8099
<React.Fragment>
81100
<Search
82-
onChange={setSearchValue}
101+
onChange={handleSearchQueryChange}
83102
placeholder="Host name"
84103
className={b('search')}
85104
value={searchValue}
86105
/>
87-
<ProblemFilter value={problemFilter} onChange={setProblemFilter} />
88-
<UptimeFilter value={uptimeFilter} onChange={setUptimeFilter} />
106+
<ProblemFilter value={problemFilter} onChange={handleProblemFilterChange} />
107+
<UptimeFilter value={uptimeFilter} onChange={handleUptimeFilterChange} />
89108
<EntitiesCount
90109
total={totalEntities}
91110
current={foundEntities}

src/containers/Storage/Storage.tsx

+54-40
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import React from 'react';
22

3+
import {ArrayParam, StringParam, useQueryParams, withDefault} from 'use-query-params';
4+
35
import {AccessDenied} from '../../components/Errors/403';
46
import {ResponseError} from '../../components/Errors/ResponseError';
57
import {TableWithControlsLayout} from '../../components/TableWithControlsLayout/TableWithControlsLayout';
@@ -10,20 +12,9 @@ import {
1012
filterGroups,
1113
filterNodes,
1214
getUsageFilterOptions,
13-
selectGroupsSortParams,
14-
selectNodesSortParams,
1515
} from '../../store/reducers/storage/selectors';
16-
import {
17-
setGroupsSortParams,
18-
setInitialState,
19-
setNodesSortParams,
20-
setStorageTextFilter,
21-
setStorageType,
22-
setUptimeFilter,
23-
setUsageFilter,
24-
setVisibleEntities,
25-
storageApi,
26-
} from '../../store/reducers/storage/storage';
16+
import {storageApi} from '../../store/reducers/storage/storage';
17+
import {storageTypeSchema, visibleEntitiesSchema} from '../../store/reducers/storage/types';
2718
import type {
2819
StorageSortParams,
2920
StorageType,
@@ -35,39 +26,69 @@ import {
3526
useNodesRequestParams,
3627
useStorageRequestParams,
3728
useTableSort,
38-
useTypedDispatch,
3929
useTypedSelector,
4030
} from '../../utils/hooks';
41-
import {NodesUptimeFilterValues} from '../../utils/nodes';
31+
import {NodesUptimeFilterValues, nodesUptimeFilterValuesSchema} from '../../utils/nodes';
4232

4333
import {StorageControls} from './StorageControls/StorageControls';
4434
import {StorageGroups} from './StorageGroups/StorageGroups';
4535
import {StorageNodes} from './StorageNodes/StorageNodes';
4636
import {b} from './shared';
37+
import {defaultSortNode, getDefaultSortGroup} from './utils';
4738

4839
import './Storage.scss';
4940

41+
const UsageFilterParam = withDefault(
42+
{
43+
encode: ArrayParam.encode,
44+
decode: (input) => {
45+
if (input === null || input === undefined) {
46+
return input;
47+
}
48+
49+
if (!Array.isArray(input)) {
50+
return input ? [input] : [];
51+
}
52+
return input.filter(Boolean) as string[];
53+
},
54+
},
55+
[],
56+
);
57+
5058
interface StorageProps {
5159
additionalNodesProps?: AdditionalNodesProps;
5260
tenant?: string;
5361
nodeId?: string;
5462
}
5563

5664
export const Storage = ({additionalNodesProps, tenant, nodeId}: StorageProps) => {
57-
const dispatch = useTypedDispatch();
58-
5965
const {autorefresh} = useTypedSelector((state) => state.schema);
60-
const {
61-
type,
62-
visible: visibleEntities,
63-
filter,
64-
usageFilter,
65-
uptimeFilter,
66-
} = useTypedSelector((state) => state.storage);
66+
const [queryParams, setQueryParams] = useQueryParams({
67+
type: StringParam,
68+
visible: StringParam,
69+
search: StringParam,
70+
uptimeFilter: StringParam,
71+
usageFilter: UsageFilterParam,
72+
});
73+
const type = storageTypeSchema.parse(queryParams.type);
74+
const visibleEntities = visibleEntitiesSchema.parse(queryParams.visible);
75+
const filter = queryParams.search ?? '';
76+
const uptimeFilter = nodesUptimeFilterValuesSchema.parse(queryParams.uptimeFilter);
77+
const usageFilter = queryParams.usageFilter;
6778

6879
const nodesMap = useTypedSelector(selectNodesMap);
69-
const nodesSortParams = useTypedSelector(selectNodesSortParams);
70-
const groupsSortParams = useTypedSelector(selectGroupsSortParams);
80+
81+
const [nodeSort, setNodeSort] = React.useState<NodesSortParams>({
82+
sortOrder: undefined,
83+
sortValue: undefined,
84+
});
85+
const nodesSortParams = nodeSort.sortValue ? nodeSort : defaultSortNode;
86+
87+
const [groupSort, setGroupSort] = React.useState<StorageSortParams>({
88+
sortOrder: undefined,
89+
sortValue: undefined,
90+
});
91+
const groupsSortParams = groupSort.sortOrder ? groupSort : getDefaultSortGroup(visibleEntities);
7192

7293
// Do not display Nodes table for Node page (NodeId present)
7394
const isNodePage = nodeId !== undefined;
@@ -120,38 +141,31 @@ export const Storage = ({additionalNodesProps, tenant, nodeId}: StorageProps) =>
120141

121142
const usageFilterOptions = React.useMemo(() => getUsageFilterOptions(groups), [groups]);
122143

123-
React.useEffect(() => {
124-
return () => {
125-
// Clean data on component unmount
126-
dispatch(setInitialState());
127-
};
128-
}, [dispatch]);
129-
130144
const [nodesSort, handleNodesSort] = useTableSort(nodesSortParams, (params) =>
131-
dispatch(setNodesSortParams(params as NodesSortParams)),
145+
setNodeSort(params as NodesSortParams),
132146
);
133147
const [groupsSort, handleGroupsSort] = useTableSort(groupsSortParams, (params) =>
134-
dispatch(setGroupsSortParams(params as StorageSortParams)),
148+
setGroupSort(params as StorageSortParams),
135149
);
136150

137151
const handleUsageFilterChange = (value: string[]) => {
138-
dispatch(setUsageFilter(value));
152+
setQueryParams({usageFilter: value.length ? value : undefined}, 'replaceIn');
139153
};
140154

141155
const handleTextFilterChange = (value: string) => {
142-
dispatch(setStorageTextFilter(value));
156+
setQueryParams({search: value || undefined}, 'replaceIn');
143157
};
144158

145159
const handleGroupVisibilityChange = (value: VisibleEntities) => {
146-
dispatch(setVisibleEntities(value));
160+
setQueryParams({visible: value}, 'replaceIn');
147161
};
148162

149163
const handleStorageTypeChange = (value: StorageType) => {
150-
dispatch(setStorageType(value));
164+
setQueryParams({type: value}, 'replaceIn');
151165
};
152166

153167
const handleUptimeFilterChange = (value: NodesUptimeFilterValues) => {
154-
dispatch(setUptimeFilter(value));
168+
setQueryParams({uptimeFilter: value}, 'replaceIn');
155169
};
156170

157171
const handleShowAllNodes = () => {

0 commit comments

Comments
 (0)