Skip to content

Commit 2e8ab8e

Browse files
feat(Nodes): filter and sort on backend (#503)
1 parent 65cdf9e commit 2e8ab8e

File tree

18 files changed

+308
-79
lines changed

18 files changed

+308
-79
lines changed

src/containers/Nodes/Nodes.tsx

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import cn from 'bem-cn-lite';
33
import {useDispatch} from 'react-redux';
44

55
import DataTable from '@gravity-ui/react-data-table';
6+
import {ASCENDING} from '@gravity-ui/react-data-table/build/esm/lib/constants';
67

78
import type {EPathType} from '../../types/api/schema';
89
import type {ProblemFilterValue} from '../../store/reducers/settings/types';
10+
import type {NodesSortParams} from '../../store/reducers/nodes/types';
911

1012
import {AccessDenied} from '../../components/Errors/403';
1113
import {Illustration} from '../../components/Illustration';
@@ -17,7 +19,13 @@ import {TableWithControlsLayout} from '../../components/TableWithControlsLayout/
1719
import {ResponseError} from '../../components/Errors/ResponseError';
1820

1921
import {DEFAULT_TABLE_SETTINGS, USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY} from '../../utils/constants';
20-
import {useAutofetcher, useSetting, useTypedSelector} from '../../utils/hooks';
22+
import {
23+
useAutofetcher,
24+
useSetting,
25+
useTypedSelector,
26+
useNodesRequestParams,
27+
useTableSort,
28+
} from '../../utils/hooks';
2129
import {AdditionalNodesInfo, isUnavailableNode, NodesUptimeFilterValues} from '../../utils/nodes';
2230

2331
import {
@@ -26,6 +34,8 @@ import {
2634
setSearchValue,
2735
resetNodesState,
2836
getComputeNodes,
37+
setDataWasNotLoaded,
38+
setSort,
2939
} from '../../store/reducers/nodes/nodes';
3040
import {selectFilteredNodes} from '../../store/reducers/nodes/selectors';
3141
import {changeFilter, ProblemFilterValues} from '../../store/reducers/settings/settings';
@@ -58,27 +68,56 @@ export const Nodes = ({path, type, additionalNodesInfo = {}}: NodesProps) => {
5868
dispatch(resetNodesState());
5969
}, [dispatch, path]);
6070

61-
const {wasLoaded, loading, error, nodesUptimeFilter, searchValue, totalNodes} =
62-
useTypedSelector((state) => state.nodes);
71+
const {
72+
wasLoaded,
73+
loading,
74+
error,
75+
nodesUptimeFilter,
76+
searchValue,
77+
sortOrder = ASCENDING,
78+
sortValue = 'NodeId',
79+
totalNodes,
80+
} = useTypedSelector((state) => state.nodes);
6381
const problemFilter = useTypedSelector((state) => state.settings.problemFilter);
6482
const {autorefresh} = useTypedSelector((state) => state.schema);
6583

6684
const nodes = useTypedSelector(selectFilteredNodes);
6785

6886
const [useNodesEndpoint] = useSetting(USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY);
6987

70-
const fetchNodes = useCallback(() => {
71-
// For not DB entities we always use /compute endpoint instead of /nodes
72-
// since /nodes can return data only for tenants
73-
if (path && (!useNodesEndpoint || !isDatabaseEntityType(type))) {
74-
dispatch(getComputeNodes({path}));
75-
} else {
76-
dispatch(getNodes({tenant: path}));
77-
}
78-
}, [dispatch, path, type, useNodesEndpoint]);
88+
const requestParams = useNodesRequestParams({
89+
filter: searchValue,
90+
problemFilter,
91+
nodesUptimeFilter,
92+
sortOrder,
93+
sortValue,
94+
});
95+
96+
const fetchNodes = useCallback(
97+
(isBackground) => {
98+
if (!isBackground) {
99+
dispatch(setDataWasNotLoaded());
100+
}
101+
102+
const params = requestParams || {};
103+
104+
// For not DB entities we always use /compute endpoint instead of /nodes
105+
// since /nodes can return data only for tenants
106+
if (path && (!useNodesEndpoint || !isDatabaseEntityType(type))) {
107+
dispatch(getComputeNodes({path, ...params}));
108+
} else {
109+
dispatch(getNodes({tenant: path, ...params}));
110+
}
111+
},
112+
[dispatch, path, type, useNodesEndpoint, requestParams],
113+
);
79114

80115
useAutofetcher(fetchNodes, [fetchNodes], isClusterNodes ? true : autorefresh);
81116

117+
const [sort, handleSort] = useTableSort({sortValue, sortOrder}, (sortParams) =>
118+
dispatch(setSort(sortParams as NodesSortParams)),
119+
);
120+
82121
const handleSearchQueryChange = (value: string) => {
83122
dispatch(setSearchValue(value));
84123
};
@@ -132,10 +171,8 @@ export const Nodes = ({path, type, additionalNodesInfo = {}}: NodesProps) => {
132171
data={nodes || []}
133172
columns={columns}
134173
settings={DEFAULT_TABLE_SETTINGS}
135-
initialSortOrder={{
136-
columnId: 'NodeId',
137-
order: DataTable.ASCENDING,
138-
}}
174+
sortOrder={sort}
175+
onSort={handleSort}
139176
emptyDataMessage={i18n('empty.default')}
140177
rowClassName={(row) => b('node', {unavailable: isUnavailableNode(row)})}
141178
/>

src/containers/Nodes/getNodesColumns.tsx

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,63 +6,79 @@ import ProgressViewer from '../../components/ProgressViewer/ProgressViewer';
66
import {TabletsStatistic} from '../../components/TabletsStatistic';
77
import {NodeHostWrapper} from '../../components/NodeHostWrapper/NodeHostWrapper';
88

9-
import type {NodeAddress} from '../../utils/nodes';
9+
import {isSortableNodesProperty, type NodeAddress} from '../../utils/nodes';
1010
import {formatBytesToGigabyte} from '../../utils/index';
1111

1212
import type {NodesPreparedEntity} from '../../store/reducers/nodes/types';
1313

14+
const NODES_COLUMNS_IDS = {
15+
NodeId: 'NodeId',
16+
Host: 'Host',
17+
DC: 'DC',
18+
Rack: 'Rack',
19+
Version: 'Version',
20+
Uptime: 'Uptime',
21+
Memory: 'Memory',
22+
CPU: 'CPU',
23+
LoadAverage: 'LoadAverage',
24+
Tablets: 'Tablets',
25+
};
26+
1427
interface GetNodesColumnsProps {
1528
tabletsPath?: string;
1629
getNodeRef?: (node?: NodeAddress) => string | null;
1730
}
1831

19-
export function getNodesColumns({tabletsPath, getNodeRef}: GetNodesColumnsProps) {
32+
export function getNodesColumns({
33+
tabletsPath,
34+
getNodeRef,
35+
}: GetNodesColumnsProps): Column<NodesPreparedEntity>[] {
2036
const columns: Column<NodesPreparedEntity>[] = [
2137
{
22-
name: 'NodeId',
38+
name: NODES_COLUMNS_IDS.NodeId,
2339
header: '#',
2440
width: '80px',
2541
align: DataTable.RIGHT,
2642
},
2743
{
28-
name: 'Host',
44+
name: NODES_COLUMNS_IDS.Host,
2945
render: ({row}) => {
3046
return <NodeHostWrapper node={row} getNodeRef={getNodeRef} />;
3147
},
3248
width: '350px',
3349
align: DataTable.LEFT,
3450
},
3551
{
36-
name: 'DataCenter',
52+
name: NODES_COLUMNS_IDS.DC,
3753
header: 'DC',
3854
align: DataTable.LEFT,
3955
render: ({row}) => (row.DataCenter ? row.DataCenter : '—'),
4056
width: '60px',
4157
},
4258
{
43-
name: 'Rack',
59+
name: NODES_COLUMNS_IDS.Rack,
4460
header: 'Rack',
4561
align: DataTable.LEFT,
4662
render: ({row}) => (row.Rack ? row.Rack : '—'),
4763
width: '80px',
4864
},
4965
{
50-
name: 'Version',
66+
name: NODES_COLUMNS_IDS.Version,
5167
width: '200px',
5268
align: DataTable.LEFT,
5369
render: ({row}) => {
5470
return <Popover content={row.Version}>{row.Version}</Popover>;
5571
},
5672
},
5773
{
58-
name: 'Uptime',
74+
name: NODES_COLUMNS_IDS.Uptime,
5975
header: 'Uptime',
6076
sortAccessor: ({StartTime}) => StartTime && -StartTime,
6177
align: DataTable.RIGHT,
6278
width: '110px',
6379
},
6480
{
65-
name: 'MemoryUsed',
81+
name: NODES_COLUMNS_IDS.Memory,
6682
header: 'Memory',
6783
sortAccessor: ({MemoryUsed = 0}) => Number(MemoryUsed),
6884
defaultOrder: DataTable.DESCENDING,
@@ -77,7 +93,7 @@ export function getNodesColumns({tabletsPath, getNodeRef}: GetNodesColumnsProps)
7793
width: '120px',
7894
},
7995
{
80-
name: 'PoolStats',
96+
name: NODES_COLUMNS_IDS.CPU,
8197
header: 'CPU',
8298
sortAccessor: ({PoolStats = []}) =>
8399
PoolStats.reduce((acc, item) => {
@@ -93,7 +109,7 @@ export function getNodesColumns({tabletsPath, getNodeRef}: GetNodesColumnsProps)
93109
width: '120px',
94110
},
95111
{
96-
name: 'LoadAverage',
112+
name: NODES_COLUMNS_IDS.LoadAverage,
97113
header: 'Load average',
98114
sortAccessor: ({LoadAverage = []}) =>
99115
LoadAverage.slice(0, 1).reduce((acc, item) => acc + item, 0),
@@ -112,7 +128,7 @@ export function getNodesColumns({tabletsPath, getNodeRef}: GetNodesColumnsProps)
112128
width: '140px',
113129
},
114130
{
115-
name: 'Tablets',
131+
name: NODES_COLUMNS_IDS.Tablets,
116132
width: '430px',
117133
render: ({row}) => {
118134
return row.Tablets ? (
@@ -129,5 +145,7 @@ export function getNodesColumns({tabletsPath, getNodeRef}: GetNodesColumnsProps)
129145
},
130146
];
131147

132-
return columns;
148+
return columns.map((column) => {
149+
return {...column, sortable: isSortableNodesProperty(column.name)};
150+
});
133151
}

src/containers/UserSettings/i18n/en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
"settings.useNodesEndpoint.title": "Break the Nodes tab in Diagnostics",
1616
"settings.useNodesEndpoint.popover": "Use /viewer/json/nodes endpoint for Nodes Tab in diagnostics. It returns incorrect data on versions before 23-1",
1717

18+
"settings.useBackendParamsForTables.title": "Offload tables filters and sorting to backend",
19+
"settings.useBackendParamsForTables.popover": "Filter and sort Nodes table with request params. May increase performance, but could causes additional fetches and longer loading time on older versions",
20+
1821
"settings.enableAdditionalQueryModes.title": "Enable additional query modes",
1922
"settings.enableAdditionalQueryModes.popover": "Adds 'Data' and 'YQL - QueryService' modes. May not work on some versions"
2023
}

src/containers/UserSettings/i18n/ru.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
"settings.useNodesEndpoint.title": "Сломать вкладку Nodes в диагностике",
1616
"settings.useNodesEndpoint.popover": "Использовать эндпоинт /viewer/json/nodes для вкладки Nodes в диагностике. Может возвращать некорректные данные на версиях до 23-1",
1717

18+
"settings.useBackendParamsForTables.title": "Перенести фильтры и сортировку таблиц на бэкенд",
19+
"settings.useBackendParamsForTables.popover": "Добавляет фильтрацию и сортировку таблицы Nodes с использованием параметров запроса. Может улушить производительность, но на старых версиях может привести к дополнительным запросам и большему времени ожидания загрузки",
20+
1821
"settings.enableAdditionalQueryModes.title": "Включить дополнительные режимы выполнения запросов",
1922
"settings.enableAdditionalQueryModes.popover": "Добавляет режимы 'Data' и 'YQL - QueryService'. Может работать некорректно на некоторых версиях"
2023
}

src/containers/UserSettings/settings.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
ENABLE_ADDITIONAL_QUERY_MODES,
88
INVERTED_DISKS_KEY,
99
THEME_KEY,
10+
USE_BACKEND_PARAMS_FOR_TABLES_KEY,
1011
USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,
1112
} from '../../utils/constants';
1213

@@ -58,6 +59,11 @@ export const useNodesEndpointSetting: SettingProps = {
5859
title: i18n('settings.useNodesEndpoint.title'),
5960
helpPopoverContent: i18n('settings.useNodesEndpoint.popover'),
6061
};
62+
export const useBackendParamsForTables: SettingProps = {
63+
settingKey: USE_BACKEND_PARAMS_FOR_TABLES_KEY,
64+
title: i18n('settings.useBackendParamsForTables.title'),
65+
helpPopoverContent: i18n('settings.useBackendParamsForTables.popover'),
66+
};
6167
export const enableQueryModesForExplainSetting: SettingProps = {
6268
settingKey: ENABLE_ADDITIONAL_QUERY_MODES,
6369
title: i18n('settings.enableAdditionalQueryModes.title'),
@@ -72,7 +78,12 @@ export const generalSection: SettingsSection = {
7278
export const experimentsSection: SettingsSection = {
7379
id: 'experimentsSection',
7480
title: i18n('section.experiments'),
75-
settings: [invertedDisksSetting, useNodesEndpointSetting, enableQueryModesForExplainSetting],
81+
settings: [
82+
invertedDisksSetting,
83+
useNodesEndpointSetting,
84+
useBackendParamsForTables,
85+
enableQueryModesForExplainSetting,
86+
],
7687
};
7788

7889
export const generalPage: SettingsPage = {

src/services/api.ts

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import type {ComputeApiRequestParams, NodesApiRequestParams} from '../store/redu
3232
import type {StorageApiRequestParams} from '../store/reducers/storage/types';
3333

3434
import {backend as BACKEND} from '../store';
35+
import {prepareSortValue} from '../utils/filters';
3536

3637
const config = {withCredentials: !window.custom_backend};
3738

@@ -82,24 +83,35 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
8283
});
8384
}
8485
getNodes(
85-
{visibleEntities, type = 'any', tablets = true, ...params}: NodesApiRequestParams,
86+
{
87+
visibleEntities,
88+
type = 'any',
89+
tablets = true,
90+
sortOrder,
91+
sortValue,
92+
...params
93+
}: NodesApiRequestParams,
8694
{concurrentId}: AxiosOptions = {},
8795
) {
96+
const sort = prepareSortValue(sortValue, sortOrder);
97+
8898
return this.get<TNodesInfo>(
8999
this.getPath('/viewer/json/nodes?enums=true'),
90-
{
91-
with: visibleEntities,
92-
type,
93-
tablets,
94-
...params,
95-
},
96-
{
97-
concurrentId,
98-
},
100+
{with: visibleEntities, type, tablets, sort, ...params},
101+
{concurrentId},
99102
);
100103
}
101-
getCompute(params: ComputeApiRequestParams) {
102-
return this.get<TComputeInfo>(this.getPath('/viewer/json/compute?enums=true'), params);
104+
getCompute(
105+
{sortOrder, sortValue, ...params}: ComputeApiRequestParams,
106+
{concurrentId}: AxiosOptions = {},
107+
) {
108+
const sort = prepareSortValue(sortValue, sortOrder);
109+
110+
return this.get<TComputeInfo>(
111+
this.getPath('/viewer/json/compute?enums=true'),
112+
{sort, ...params},
113+
{concurrentId},
114+
);
103115
}
104116
getStorageInfo(
105117
{tenant, visibleEntities, nodeId}: StorageApiRequestParams,

0 commit comments

Comments
 (0)