ydb-embedded-ui 4.13.0 → 4.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/components/Tablet/Tablet.scss +1 -16
  3. package/dist/components/Tablet/Tablet.tsx +5 -5
  4. package/dist/components/TabletIcon/TabletIcon.scss +17 -0
  5. package/dist/components/TabletIcon/TabletIcon.tsx +18 -0
  6. package/dist/containers/Header/Header.scss +2 -0
  7. package/dist/containers/Header/Header.tsx +2 -7
  8. package/dist/containers/Header/{breadcrumbs.ts → breadcrumbs.tsx} +19 -8
  9. package/dist/containers/Nodes/Nodes.tsx +53 -16
  10. package/dist/containers/Nodes/getNodesColumns.tsx +31 -13
  11. package/dist/containers/Tablet/Tablet.tsx +9 -3
  12. package/dist/containers/Tenant/Query/QueryDuration/QueryDuration.scss +8 -0
  13. package/dist/containers/Tenant/Query/QueryDuration/QueryDuration.tsx +13 -1
  14. package/dist/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.scss +3 -1
  15. package/dist/containers/Tenant/Query/i18n/en.json +6 -4
  16. package/dist/containers/Tenant/Query/i18n/ru.json +6 -4
  17. package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +4 -0
  18. package/dist/containers/Tenant/utils/schemaActions.ts +3 -10
  19. package/dist/containers/Tenant/utils/schemaControls.tsx +69 -0
  20. package/dist/containers/UserSettings/i18n/en.json +3 -0
  21. package/dist/containers/UserSettings/i18n/ru.json +3 -0
  22. package/dist/containers/UserSettings/settings.ts +12 -1
  23. package/dist/services/api.ts +24 -12
  24. package/dist/store/reducers/header/types.ts +2 -0
  25. package/dist/store/reducers/nodes/nodes.ts +23 -6
  26. package/dist/store/reducers/nodes/selectors.ts +2 -2
  27. package/dist/store/reducers/nodes/types.ts +15 -5
  28. package/dist/store/reducers/settings/settings.ts +5 -0
  29. package/dist/types/api/compute.ts +0 -12
  30. package/dist/types/api/nodes.ts +0 -12
  31. package/dist/utils/constants.ts +3 -0
  32. package/dist/utils/filters.ts +23 -0
  33. package/dist/utils/hooks/index.ts +3 -0
  34. package/dist/utils/hooks/useNodesRequestParams.ts +46 -0
  35. package/dist/utils/hooks/useTableSort.ts +37 -0
  36. package/dist/utils/nodes.ts +25 -0
  37. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.14.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.13.0...v4.14.0) (2023-08-11)
4
+
5
+
6
+ ### Features
7
+
8
+ * **Nodes:** filter and sort on backend ([#503](https://github.com/ydb-platform/ydb-embedded-ui/issues/503)) ([2e8ab8e](https://github.com/ydb-platform/ydb-embedded-ui/commit/2e8ab8e9965db61ec281f7340b89dd3967b639df))
9
+ * **Query:** add explanation to query duration ([#501](https://github.com/ydb-platform/ydb-embedded-ui/issues/501)) ([a5f5140](https://github.com/ydb-platform/ydb-embedded-ui/commit/a5f5140a23864147d8495e3c6b94709e5e710a9b))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * **Header:** add icons for nodes and tablets ([#500](https://github.com/ydb-platform/ydb-embedded-ui/issues/500)) ([862660c](https://github.com/ydb-platform/ydb-embedded-ui/commit/862660c1928c2c2b626e4417cd043f0bd5a65df9))
15
+ * **Query:** fix query method selector help text ([#504](https://github.com/ydb-platform/ydb-embedded-ui/issues/504)) ([65cdf9e](https://github.com/ydb-platform/ydb-embedded-ui/commit/65cdf9ee93277c193cc1ad036b2cb38d2ae15b71))
16
+ * **Query:** transfer API calls to a new line ([#499](https://github.com/ydb-platform/ydb-embedded-ui/issues/499)) ([de3d540](https://github.com/ydb-platform/ydb-embedded-ui/commit/de3d5404310f32ba05598bb99a1afb1b65ab45a1))
17
+ * **SchemaTree:** transfer Show Preview to SchemaTree ([#505](https://github.com/ydb-platform/ydb-embedded-ui/issues/505)) ([46220c4](https://github.com/ydb-platform/ydb-embedded-ui/commit/46220c4b2cd111acf12712b4693744c52aaf7231))
18
+
3
19
  ## [4.13.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.12.0...v4.13.0) (2023-08-04)
4
20
 
5
21
 
@@ -1,17 +1,8 @@
1
1
  .tablet {
2
- display: flex;
3
- justify-content: center;
4
-
5
- width: 23px;
6
- height: 18px;
7
-
8
- font-size: 10px;
9
2
  cursor: pointer;
10
- text-transform: uppercase;
11
3
 
12
4
  color: var(--yc-color-text-complementary);
13
- border: 1px solid var(--yc-color-base-generic-medium-hover);
14
- border-radius: 4px;
5
+ border-color: var(--yc-color-base-generic-medium-hover);
15
6
 
16
7
  &__wrapper {
17
8
  margin-right: 2px;
@@ -25,12 +16,6 @@
25
16
  padding: 10px;
26
17
  }
27
18
 
28
- &__type {
29
- line-height: 17px;
30
-
31
- color: var(--yc-color-text-complementary);
32
- }
33
-
34
19
  &_status_gray {
35
20
  background-color: var(--yc-color-text-complementary);
36
21
  }
@@ -5,6 +5,7 @@ import {getTabletLabel} from '../../utils/constants';
5
5
  import routes, {createHref} from '../../routes';
6
6
 
7
7
  import {ContentWithPopup} from '../ContentWithPopup/ContentWithPopup';
8
+ import {TabletIcon} from '../TabletIcon/TabletIcon';
8
9
  import {InternalLink} from '../InternalLink';
9
10
  import {TabletTooltipContent} from '../TooltipsContent';
10
11
 
@@ -18,10 +19,11 @@ interface TabletProps {
18
19
  }
19
20
 
20
21
  export const Tablet = ({tablet = {}, tenantName}: TabletProps) => {
21
- const {TabletId: id, NodeId} = tablet;
22
+ const {TabletId: id, NodeId, Type} = tablet;
22
23
  const status = tablet.Overall?.toLowerCase();
23
24
 
24
- const tabletPath = id && createHref(routes.tablet, {id}, {nodeId: NodeId, tenantName});
25
+ const tabletPath =
26
+ id && createHref(routes.tablet, {id}, {nodeId: NodeId, tenantName, type: Type});
25
27
 
26
28
  return (
27
29
  <ContentWithPopup
@@ -29,9 +31,7 @@ export const Tablet = ({tablet = {}, tenantName}: TabletProps) => {
29
31
  content={<TabletTooltipContent data={tablet} className={b('popup-content')} />}
30
32
  >
31
33
  <InternalLink to={tabletPath}>
32
- <div className={b({status})}>
33
- <div className={b('type')}>{[getTabletLabel(tablet.Type)]}</div>
34
- </div>
34
+ <TabletIcon className={b({status})} text={getTabletLabel(tablet.Type)} />
35
35
  </InternalLink>
36
36
  </ContentWithPopup>
37
37
  );
@@ -0,0 +1,17 @@
1
+ .tablet-icon {
2
+ display: flex;
3
+ justify-content: center;
4
+
5
+ width: 23px;
6
+ height: 16px;
7
+
8
+ font-size: 10px;
9
+ text-transform: uppercase;
10
+
11
+ border: 1px solid;
12
+ border-radius: 4px;
13
+
14
+ &__type {
15
+ line-height: 14px;
16
+ }
17
+ }
@@ -0,0 +1,18 @@
1
+ import cn from 'bem-cn-lite';
2
+
3
+ import './TabletIcon.scss';
4
+
5
+ interface TabletIconProps {
6
+ text?: string;
7
+ className?: string;
8
+ }
9
+
10
+ const b = cn('tablet-icon');
11
+
12
+ export const TabletIcon = ({text, className}: TabletIconProps) => {
13
+ return (
14
+ <div className={b(null, className)}>
15
+ <div className={b('type')}>{text || 'T'}</div>
16
+ </div>
17
+ );
18
+ };
@@ -15,6 +15,8 @@
15
15
  align-items: center;
16
16
 
17
17
  &__icon {
18
+ display: flex;
19
+
18
20
  margin-right: 3px;
19
21
  }
20
22
  }
@@ -3,7 +3,7 @@ import {useHistory, useLocation} from 'react-router';
3
3
  import {useDispatch} from 'react-redux';
4
4
  import block from 'bem-cn-lite';
5
5
 
6
- import {Breadcrumbs, Icon} from '@gravity-ui/uikit';
6
+ import {Breadcrumbs} from '@gravity-ui/uikit';
7
7
 
8
8
  import {ExternalLinkWithIcon} from '../../components/ExternalLinkWithIcon/ExternalLinkWithIcon';
9
9
 
@@ -91,12 +91,7 @@ function Header({mainPage}: HeaderProps) {
91
91
  }
92
92
  return (
93
93
  <span className={b('breadcrumb')}>
94
- <Icon
95
- width={16}
96
- height={16}
97
- data={icon}
98
- className={b('breadcrumb__icon')}
99
- />
94
+ <div className={b('breadcrumb__icon')}>{icon}</div>
100
95
  {text}
101
96
  </span>
102
97
  );
@@ -1,5 +1,9 @@
1
- import nodesRightIcon from '@gravity-ui/icons/svgs/nodes-right.svg';
2
- import databaseIcon from '@gravity-ui/icons/svgs/database.svg';
1
+ import {
2
+ NodesRight as ClusterIcon,
3
+ Database as DatabaseIcon,
4
+ Cpu as ComputeNodeIcon,
5
+ HardDrive as StorageNodeIcon,
6
+ } from '@gravity-ui/icons';
3
7
 
4
8
  import type {
5
9
  BreadcrumbsOptions,
@@ -15,8 +19,9 @@ import {
15
19
  TENANT_PAGE,
16
20
  TENANT_PAGES_IDS,
17
21
  } from '../../store/reducers/tenant/constants';
22
+ import {TabletIcon} from '../../components/TabletIcon/TabletIcon';
18
23
  import routes, {createHref} from '../../routes';
19
- import {CLUSTER_DEFAULT_TITLE} from '../../utils/constants';
24
+ import {CLUSTER_DEFAULT_TITLE, getTabletLabel} from '../../utils/constants';
20
25
 
21
26
  import {getClusterPath} from '../Cluster/utils';
22
27
  import {TenantTabsGroups, getTenantPath} from '../Tenant/TenantPages';
@@ -29,7 +34,7 @@ const prepareTenantName = (tenantName: string) => {
29
34
  export interface RawBreadcrumbItem {
30
35
  text: string;
31
36
  link?: string;
32
- icon?: SVGIconData;
37
+ icon?: JSX.Element;
33
38
  }
34
39
 
35
40
  const getClusterBreadcrumbs = (
@@ -42,7 +47,7 @@ const getClusterBreadcrumbs = (
42
47
  {
43
48
  text: clusterName || CLUSTER_DEFAULT_TITLE,
44
49
  link: getClusterPath(clusterTab, query),
45
- icon: nodesRightIcon,
50
+ icon: <ClusterIcon />,
46
51
  },
47
52
  ];
48
53
  };
@@ -56,7 +61,7 @@ const getTenantBreadcrumbs = (
56
61
  const text = tenantName ? prepareTenantName(tenantName) : 'Tenant';
57
62
  const link = tenantName ? getTenantPath({...query, name: tenantName}) : undefined;
58
63
 
59
- return [...getClusterBreadcrumbs(options, query), {text, link, icon: databaseIcon}];
64
+ return [...getClusterBreadcrumbs(options, query), {text, link, icon: <DatabaseIcon />}];
60
65
  };
61
66
 
62
67
  const getNodeBreadcrumbs = (options: NodeBreadcrumbsOptions, query = {}): RawBreadcrumbItem[] => {
@@ -81,8 +86,13 @@ const getNodeBreadcrumbs = (options: NodeBreadcrumbsOptions, query = {}): RawBre
81
86
 
82
87
  const text = nodeId ? `Node ${nodeId}` : 'Node';
83
88
  const link = nodeId ? getDefaultNodePath(nodeId, query) : undefined;
89
+ const icon = isStorageNode ? <StorageNodeIcon /> : <ComputeNodeIcon />;
84
90
 
85
- breadcrumbs.push({text, link});
91
+ breadcrumbs.push({
92
+ text,
93
+ link,
94
+ icon,
95
+ });
86
96
 
87
97
  return breadcrumbs;
88
98
  };
@@ -123,12 +133,13 @@ const getTabletBreadcrubms = (
123
133
  options: TabletBreadcrumbsOptions,
124
134
  query = {},
125
135
  ): RawBreadcrumbItem[] => {
126
- const {tabletId} = options;
136
+ const {tabletId, tabletType} = options;
127
137
 
128
138
  const breadcrumbs = getTabletsBreadcrubms(options, query);
129
139
 
130
140
  breadcrumbs.push({
131
141
  text: tabletId || 'Tablet',
142
+ icon: <TabletIcon text={getTabletLabel(tabletType)} />,
132
143
  });
133
144
 
134
145
  return breadcrumbs;
@@ -3,9 +3,11 @@ import cn from 'bem-cn-lite';
3
3
  import {useDispatch} from 'react-redux';
4
4
 
5
5
  import DataTable from '@gravity-ui/react-data-table';
6
+ import {ASCENDING} from '@gravity-ui/react-data-table/build/esm/lib/constants';
6
7
 
7
8
  import type {EPathType} from '../../types/api/schema';
8
9
  import type {ProblemFilterValue} from '../../store/reducers/settings/types';
10
+ import type {NodesSortParams} from '../../store/reducers/nodes/types';
9
11
 
10
12
  import {AccessDenied} from '../../components/Errors/403';
11
13
  import {Illustration} from '../../components/Illustration';
@@ -17,7 +19,13 @@ import {TableWithControlsLayout} from '../../components/TableWithControlsLayout/
17
19
  import {ResponseError} from '../../components/Errors/ResponseError';
18
20
 
19
21
  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';
21
29
  import {AdditionalNodesInfo, isUnavailableNode, NodesUptimeFilterValues} from '../../utils/nodes';
22
30
 
23
31
  import {
@@ -26,6 +34,8 @@ import {
26
34
  setSearchValue,
27
35
  resetNodesState,
28
36
  getComputeNodes,
37
+ setDataWasNotLoaded,
38
+ setSort,
29
39
  } from '../../store/reducers/nodes/nodes';
30
40
  import {selectFilteredNodes} from '../../store/reducers/nodes/selectors';
31
41
  import {changeFilter, ProblemFilterValues} from '../../store/reducers/settings/settings';
@@ -58,8 +68,16 @@ export const Nodes = ({path, type, additionalNodesInfo = {}}: NodesProps) => {
58
68
  dispatch(resetNodesState());
59
69
  }, [dispatch, path]);
60
70
 
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);
63
81
  const problemFilter = useTypedSelector((state) => state.settings.problemFilter);
64
82
  const {autorefresh} = useTypedSelector((state) => state.schema);
65
83
 
@@ -67,18 +85,39 @@ export const Nodes = ({path, type, additionalNodesInfo = {}}: NodesProps) => {
67
85
 
68
86
  const [useNodesEndpoint] = useSetting(USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY);
69
87
 
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
+ );
79
114
 
80
115
  useAutofetcher(fetchNodes, [fetchNodes], isClusterNodes ? true : autorefresh);
81
116
 
117
+ const [sort, handleSort] = useTableSort({sortValue, sortOrder}, (sortParams) =>
118
+ dispatch(setSort(sortParams as NodesSortParams)),
119
+ );
120
+
82
121
  const handleSearchQueryChange = (value: string) => {
83
122
  dispatch(setSearchValue(value));
84
123
  };
@@ -132,10 +171,8 @@ export const Nodes = ({path, type, additionalNodesInfo = {}}: NodesProps) => {
132
171
  data={nodes || []}
133
172
  columns={columns}
134
173
  settings={DEFAULT_TABLE_SETTINGS}
135
- initialSortOrder={{
136
- columnId: 'NodeId',
137
- order: DataTable.ASCENDING,
138
- }}
174
+ sortOrder={sort}
175
+ onSort={handleSort}
139
176
  emptyDataMessage={i18n('empty.default')}
140
177
  rowClassName={(row) => b('node', {unavailable: isUnavailableNode(row)})}
141
178
  />
@@ -6,26 +6,42 @@ import ProgressViewer from '../../components/ProgressViewer/ProgressViewer';
6
6
  import {TabletsStatistic} from '../../components/TabletsStatistic';
7
7
  import {NodeHostWrapper} from '../../components/NodeHostWrapper/NodeHostWrapper';
8
8
 
9
- import type {NodeAddress} from '../../utils/nodes';
9
+ import {isSortableNodesProperty, type NodeAddress} from '../../utils/nodes';
10
10
  import {formatBytesToGigabyte} from '../../utils/index';
11
11
 
12
12
  import type {NodesPreparedEntity} from '../../store/reducers/nodes/types';
13
13
 
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
+
14
27
  interface GetNodesColumnsProps {
15
28
  tabletsPath?: string;
16
29
  getNodeRef?: (node?: NodeAddress) => string | null;
17
30
  }
18
31
 
19
- export function getNodesColumns({tabletsPath, getNodeRef}: GetNodesColumnsProps) {
32
+ export function getNodesColumns({
33
+ tabletsPath,
34
+ getNodeRef,
35
+ }: GetNodesColumnsProps): Column<NodesPreparedEntity>[] {
20
36
  const columns: Column<NodesPreparedEntity>[] = [
21
37
  {
22
- name: 'NodeId',
38
+ name: NODES_COLUMNS_IDS.NodeId,
23
39
  header: '#',
24
40
  width: '80px',
25
41
  align: DataTable.RIGHT,
26
42
  },
27
43
  {
28
- name: 'Host',
44
+ name: NODES_COLUMNS_IDS.Host,
29
45
  render: ({row}) => {
30
46
  return <NodeHostWrapper node={row} getNodeRef={getNodeRef} />;
31
47
  },
@@ -33,21 +49,21 @@ export function getNodesColumns({tabletsPath, getNodeRef}: GetNodesColumnsProps)
33
49
  align: DataTable.LEFT,
34
50
  },
35
51
  {
36
- name: 'DataCenter',
52
+ name: NODES_COLUMNS_IDS.DC,
37
53
  header: 'DC',
38
54
  align: DataTable.LEFT,
39
55
  render: ({row}) => (row.DataCenter ? row.DataCenter : '—'),
40
56
  width: '60px',
41
57
  },
42
58
  {
43
- name: 'Rack',
59
+ name: NODES_COLUMNS_IDS.Rack,
44
60
  header: 'Rack',
45
61
  align: DataTable.LEFT,
46
62
  render: ({row}) => (row.Rack ? row.Rack : '—'),
47
63
  width: '80px',
48
64
  },
49
65
  {
50
- name: 'Version',
66
+ name: NODES_COLUMNS_IDS.Version,
51
67
  width: '200px',
52
68
  align: DataTable.LEFT,
53
69
  render: ({row}) => {
@@ -55,14 +71,14 @@ export function getNodesColumns({tabletsPath, getNodeRef}: GetNodesColumnsProps)
55
71
  },
56
72
  },
57
73
  {
58
- name: 'Uptime',
74
+ name: NODES_COLUMNS_IDS.Uptime,
59
75
  header: 'Uptime',
60
76
  sortAccessor: ({StartTime}) => StartTime && -StartTime,
61
77
  align: DataTable.RIGHT,
62
78
  width: '110px',
63
79
  },
64
80
  {
65
- name: 'MemoryUsed',
81
+ name: NODES_COLUMNS_IDS.Memory,
66
82
  header: 'Memory',
67
83
  sortAccessor: ({MemoryUsed = 0}) => Number(MemoryUsed),
68
84
  defaultOrder: DataTable.DESCENDING,
@@ -77,7 +93,7 @@ export function getNodesColumns({tabletsPath, getNodeRef}: GetNodesColumnsProps)
77
93
  width: '120px',
78
94
  },
79
95
  {
80
- name: 'PoolStats',
96
+ name: NODES_COLUMNS_IDS.CPU,
81
97
  header: 'CPU',
82
98
  sortAccessor: ({PoolStats = []}) =>
83
99
  PoolStats.reduce((acc, item) => {
@@ -93,7 +109,7 @@ export function getNodesColumns({tabletsPath, getNodeRef}: GetNodesColumnsProps)
93
109
  width: '120px',
94
110
  },
95
111
  {
96
- name: 'LoadAverage',
112
+ name: NODES_COLUMNS_IDS.LoadAverage,
97
113
  header: 'Load average',
98
114
  sortAccessor: ({LoadAverage = []}) =>
99
115
  LoadAverage.slice(0, 1).reduce((acc, item) => acc + item, 0),
@@ -112,7 +128,7 @@ export function getNodesColumns({tabletsPath, getNodeRef}: GetNodesColumnsProps)
112
128
  width: '140px',
113
129
  },
114
130
  {
115
- name: 'Tablets',
131
+ name: NODES_COLUMNS_IDS.Tablets,
116
132
  width: '430px',
117
133
  render: ({row}) => {
118
134
  return row.Tablets ? (
@@ -129,5 +145,7 @@ export function getNodesColumns({tabletsPath, getNodeRef}: GetNodesColumnsProps)
129
145
  },
130
146
  ];
131
147
 
132
- return columns;
148
+ return columns.map((column) => {
149
+ return {...column, sortable: isSortableNodesProperty(column.name)};
150
+ });
133
151
  }
@@ -13,6 +13,7 @@ import {DEVELOPER_UI_TITLE} from '../../utils/constants';
13
13
  import '../../services/api';
14
14
  import {parseQuery} from '../../routes';
15
15
 
16
+ import type {EType} from '../../types/api/tablet';
16
17
  import EntityStatus from '../../components/EntityStatus/EntityStatus';
17
18
  import {ResponseError} from '../../components/Errors/ResponseError';
18
19
  import {Tag} from '../../components/Tag';
@@ -48,10 +49,14 @@ export const Tablet = () => {
48
49
  error,
49
50
  } = useTypedSelector((state) => state.tablet);
50
51
 
51
- const {nodeId: queryNodeId, tenantName: queryTenantName} = parseQuery(location);
52
-
52
+ const {
53
+ nodeId: queryNodeId,
54
+ tenantName: queryTenantName,
55
+ type: queryTabletType,
56
+ } = parseQuery(location);
53
57
  const nodeId = tablet.NodeId?.toString() || queryNodeId?.toString();
54
58
  const tenantName = tenantPath || queryTenantName?.toString();
59
+ const type = tablet.Type || (queryTabletType?.toString() as EType | undefined);
55
60
 
56
61
  // NOTE: should be reviewed when migrating to React 18
57
62
  useEffect(() => {
@@ -79,9 +84,10 @@ export const Tablet = () => {
79
84
  nodeIds: nodeId ? [nodeId] : [],
80
85
  tenantName,
81
86
  tabletId: id,
87
+ tabletType: type,
82
88
  }),
83
89
  );
84
- }, [dispatch, tenantName, id, nodeId]);
90
+ }, [dispatch, tenantName, id, nodeId, type]);
85
91
 
86
92
  const renderExternalLinks = (link: {name: string; path: string}, index: number) => {
87
93
  return (
@@ -5,4 +5,12 @@
5
5
  margin-left: 10px;
6
6
 
7
7
  color: var(--yc-color-text-complementary);
8
+
9
+ &__item-with-popover {
10
+ white-space: nowrap;
11
+ }
12
+
13
+ &__popover {
14
+ max-width: 300px;
15
+ }
8
16
  }
@@ -1,6 +1,9 @@
1
1
  import block from 'bem-cn-lite';
2
2
 
3
3
  import {formatDurationToShortTimeFormat, parseUsToMs} from '../../../../utils/timeParsers';
4
+ import {LabelWithPopover} from '../../../../components/LabelWithPopover';
5
+
6
+ import i18n from '../i18n';
4
7
 
5
8
  import './QueryDuration.scss';
6
9
 
@@ -17,5 +20,14 @@ export const QueryDuration = ({duration}: QueryDurationProps) => {
17
20
 
18
21
  const parsedDuration = formatDurationToShortTimeFormat(parseUsToMs(duration), 1);
19
22
 
20
- return <span className={b()}>{parsedDuration}</span>;
23
+ return (
24
+ <span className={b()}>
25
+ <LabelWithPopover
26
+ className={b('item-with-popover')}
27
+ contentClassName={b('popover')}
28
+ text={parsedDuration}
29
+ popoverContent={i18n('query-duration.description')}
30
+ />
31
+ </span>
32
+ );
21
33
  };
@@ -67,6 +67,8 @@
67
67
  }
68
68
 
69
69
  &__popover {
70
- max-width: 340px;
70
+ max-width: 420px;
71
+
72
+ white-space: pre-wrap;
71
73
  }
72
74
  }
@@ -17,8 +17,10 @@
17
17
  "preview.not-available": "Preview is not available",
18
18
  "preview.close": "Close preview",
19
19
 
20
- "method-description.script": "For YQL-scripts combining DDL and DML. API call: schema.scripting",
21
- "method-description.scan": "Read-only queries, potentially reading a lot of data. API call: table.ExecuteScan",
22
- "method-description.data": "DML queries for changing and fetching data in serialization mode. API call: table.executeDataQuery",
23
- "method-description.query": "Any queries. An experimental API call supposed to replace all existing methods. API Call: query.ExecuteScript"
20
+ "method-description.script": "For YQL-scripts combining DDL and DML.\nAPI call: schema.scripting",
21
+ "method-description.scan": "Read-only queries, potentially reading a lot of data.\nAPI call: table.ExecuteScan",
22
+ "method-description.data": "DML queries for changing and fetching data in serialization mode.\nAPI call: table.executeDataQuery",
23
+ "method-description.query": "Any query. An experimental API call supposed to replace all existing methods.\nAPI Call: query.ExecuteScript",
24
+
25
+ "query-duration.description": "Duration of server-side query execution"
24
26
  }
@@ -17,8 +17,10 @@
17
17
  "preview.not-available": "Предпросмотр недоступен",
18
18
  "preview.close": "Закрыть предпросмотр",
19
19
 
20
- "method-description.script": "Для скриптов, совмещающих DDL и DML-конструкции. API call: schema.scripting",
21
- "method-description.scan": "Только читающие запросы, потенциально читающие много данных. API call: table.ExecuteScan",
22
- "method-description.data": "DML-запросы для изменения и выборки данных в режиме изоляции Serializable. API call: table.executeDataQuery",
23
- "method-description.query": "Любые запросы. Экспериментальный перспективный метод, который в будущем заменит все остальные. API call: query.ExecuteScript"
20
+ "method-description.script": "Для скриптов, совмещающих DDL и DML-конструкции.\nAPI call: schema.scripting",
21
+ "method-description.scan": "Только читающие запросы, потенциально читающие много данных.\nAPI call: table.ExecuteScan",
22
+ "method-description.data": "DML-запросы для изменения и выборки данных в режиме изоляции Serializable.\nAPI call: table.executeDataQuery",
23
+ "method-description.query": "Любые запросы. Экспериментальный перспективный метод, который в будущем заменит все остальные.\nAPI call: query.ExecuteScript",
24
+
25
+ "query-duration.description": "Время выполнения запроса на стороне сервера"
24
26
  }
@@ -9,6 +9,7 @@ import {useQueryModes} from '../../../../utils/hooks';
9
9
 
10
10
  import {isChildlessPathType, mapPathTypeToNavigationTreeType} from '../../utils/schema';
11
11
  import {getActions} from '../../utils/schemaActions';
12
+ import {getControls} from '../../utils/schemaControls';
12
13
 
13
14
  interface SchemaTreeProps {
14
15
  rootPath: string;
@@ -78,6 +79,9 @@ export function SchemaTree(props: SchemaTreeProps) {
78
79
  setActivePath: handleActivePathUpdate,
79
80
  setQueryMode,
80
81
  })}
82
+ renderAdditionalNodeElements={getControls(dispatch, {
83
+ setActivePath: handleActivePathUpdate,
84
+ })}
81
85
  activePath={currentPath}
82
86
  onActivePathUpdate={handleActivePathUpdate}
83
87
  cache={false}
@@ -6,7 +6,6 @@ import type {NavigationTreeNodeType, NavigationTreeProps} from 'ydb-ui-component
6
6
  import type {QueryMode} from '../../../types/store/query';
7
7
  import type {SetQueryModeIfAvailable} from '../../../utils/hooks';
8
8
  import {changeUserInput} from '../../../store/reducers/executeQuery';
9
- import {setShowPreview} from '../../../store/reducers/schema/schema';
10
9
  import {setQueryTab, setTenantPage} from '../../../store/reducers/tenant/tenant';
11
10
  import {TENANT_QUERY_TABS_ID, TENANT_PAGES_IDS} from '../../../store/reducers/tenant/constants';
12
11
  import createToast from '../../../utils/createToast';
@@ -109,12 +108,6 @@ const bindActions = (
109
108
  });
110
109
  }
111
110
  },
112
- openPreview: () => {
113
- dispatch(setShowPreview(true));
114
- dispatch(setTenantPage(TENANT_PAGES_IDS.query));
115
- dispatch(setQueryTab(TENANT_QUERY_TABS_ID.newQuery));
116
- setActivePath(path);
117
- },
118
111
  };
119
112
  };
120
113
 
@@ -125,14 +118,13 @@ export const getActions =
125
118
  (path: string, type: NavigationTreeNodeType) => {
126
119
  const actions = bindActions(path, dispatch, additionalEffects);
127
120
  const copyItem = {text: i18n('actions.copyPath'), action: actions.copyPath};
128
- const openPreview = {text: i18n('actions.openPreview'), action: actions.openPreview};
129
121
 
130
122
  const DIR_SET: ActionsSet = [
131
123
  [copyItem],
132
124
  [{text: i18n('actions.createTable'), action: actions.createTable}],
133
125
  ];
134
126
  const TABLE_SET: ActionsSet = [
135
- [openPreview, copyItem],
127
+ [copyItem],
136
128
  [
137
129
  {text: i18n('actions.alterTable'), action: actions.alterTable},
138
130
  {text: i18n('actions.selectQuery'), action: actions.selectQuery},
@@ -141,7 +133,7 @@ export const getActions =
141
133
  ];
142
134
 
143
135
  const EXTERNAL_TABLE_SET = [
144
- [openPreview, copyItem],
136
+ [copyItem],
145
137
  [
146
138
  {
147
139
  text: i18n('actions.selectQuery'),
@@ -169,6 +161,7 @@ export const getActions =
169
161
 
170
162
  index_table: JUST_COPY,
171
163
  topic: JUST_COPY,
164
+ stream: JUST_COPY,
172
165
 
173
166
  index: JUST_COPY,
174
167
 
@@ -0,0 +1,69 @@
1
+ import {Dispatch} from 'react';
2
+
3
+ import {NavigationTreeNodeType, NavigationTreeProps} from 'ydb-ui-components';
4
+ import {Button} from '@gravity-ui/uikit';
5
+
6
+ import {setShowPreview} from '../../../store/reducers/schema/schema';
7
+ import {setQueryTab, setTenantPage} from '../../../store/reducers/tenant/tenant';
8
+ import {TENANT_PAGES_IDS, TENANT_QUERY_TABS_ID} from '../../../store/reducers/tenant/constants';
9
+ import {IconWrapper} from '../../../components/Icon';
10
+
11
+ import i18n from '../i18n';
12
+
13
+ interface ControlsAdditionalEffects {
14
+ setActivePath: (path: string) => void;
15
+ }
16
+
17
+ const bindActions = (
18
+ path: string,
19
+ dispatch: Dispatch<any>,
20
+ additionalEffects: ControlsAdditionalEffects,
21
+ ) => {
22
+ const {setActivePath} = additionalEffects;
23
+
24
+ return {
25
+ openPreview: () => {
26
+ dispatch(setShowPreview(true));
27
+ dispatch(setTenantPage(TENANT_PAGES_IDS.query));
28
+ dispatch(setQueryTab(TENANT_QUERY_TABS_ID.newQuery));
29
+ setActivePath(path);
30
+ },
31
+ };
32
+ };
33
+
34
+ type Controls = ReturnType<Required<NavigationTreeProps>['renderAdditionalNodeElements']>;
35
+
36
+ export const getControls =
37
+ (dispatch: Dispatch<any>, additionalEffects: ControlsAdditionalEffects) =>
38
+ (path: string, type: NavigationTreeNodeType) => {
39
+ const options = bindActions(path, dispatch, additionalEffects);
40
+ const openPreview = (
41
+ <Button
42
+ view="flat-secondary"
43
+ onClick={options.openPreview}
44
+ title={i18n('actions.openPreview')}
45
+ size="s"
46
+ >
47
+ <IconWrapper name="tablePreview" />
48
+ </Button>
49
+ );
50
+
51
+ const nodeTypeToControls: Record<NavigationTreeNodeType, Controls> = {
52
+ database: undefined,
53
+ directory: undefined,
54
+
55
+ table: openPreview,
56
+ column_table: openPreview,
57
+
58
+ index_table: undefined,
59
+ topic: undefined,
60
+ stream: undefined,
61
+
62
+ index: undefined,
63
+
64
+ external_table: openPreview,
65
+ external_data_source: undefined,
66
+ };
67
+
68
+ return nodeTypeToControls[type];
69
+ };
@@ -15,6 +15,9 @@
15
15
  "settings.useNodesEndpoint.title": "Break the Nodes tab in Diagnostics",
16
16
  "settings.useNodesEndpoint.popover": "Use /viewer/json/nodes endpoint for Nodes Tab in diagnostics. It returns incorrect data on versions before 23-1",
17
17
 
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
+
18
21
  "settings.enableAdditionalQueryModes.title": "Enable additional query modes",
19
22
  "settings.enableAdditionalQueryModes.popover": "Adds 'Data' and 'YQL - QueryService' modes. May not work on some versions"
20
23
  }
@@ -15,6 +15,9 @@
15
15
  "settings.useNodesEndpoint.title": "Сломать вкладку Nodes в диагностике",
16
16
  "settings.useNodesEndpoint.popover": "Использовать эндпоинт /viewer/json/nodes для вкладки Nodes в диагностике. Может возвращать некорректные данные на версиях до 23-1",
17
17
 
18
+ "settings.useBackendParamsForTables.title": "Перенести фильтры и сортировку таблиц на бэкенд",
19
+ "settings.useBackendParamsForTables.popover": "Добавляет фильтрацию и сортировку таблицы Nodes с использованием параметров запроса. Может улушить производительность, но на старых версиях может привести к дополнительным запросам и большему времени ожидания загрузки",
20
+
18
21
  "settings.enableAdditionalQueryModes.title": "Включить дополнительные режимы выполнения запросов",
19
22
  "settings.enableAdditionalQueryModes.popover": "Добавляет режимы 'Data' и 'YQL - QueryService'. Может работать некорректно на некоторых версиях"
20
23
  }
@@ -7,6 +7,7 @@ import {
7
7
  ENABLE_ADDITIONAL_QUERY_MODES,
8
8
  INVERTED_DISKS_KEY,
9
9
  THEME_KEY,
10
+ USE_BACKEND_PARAMS_FOR_TABLES_KEY,
10
11
  USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,
11
12
  } from '../../utils/constants';
12
13
 
@@ -58,6 +59,11 @@ export const useNodesEndpointSetting: SettingProps = {
58
59
  title: i18n('settings.useNodesEndpoint.title'),
59
60
  helpPopoverContent: i18n('settings.useNodesEndpoint.popover'),
60
61
  };
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
+ };
61
67
  export const enableQueryModesForExplainSetting: SettingProps = {
62
68
  settingKey: ENABLE_ADDITIONAL_QUERY_MODES,
63
69
  title: i18n('settings.enableAdditionalQueryModes.title'),
@@ -72,7 +78,12 @@ export const generalSection: SettingsSection = {
72
78
  export const experimentsSection: SettingsSection = {
73
79
  id: 'experimentsSection',
74
80
  title: i18n('section.experiments'),
75
- settings: [invertedDisksSetting, useNodesEndpointSetting, enableQueryModesForExplainSetting],
81
+ settings: [
82
+ invertedDisksSetting,
83
+ useNodesEndpointSetting,
84
+ useBackendParamsForTables,
85
+ enableQueryModesForExplainSetting,
86
+ ],
76
87
  };
77
88
 
78
89
  export const generalPage: SettingsPage = {
@@ -32,6 +32,7 @@ import type {ComputeApiRequestParams, NodesApiRequestParams} from '../store/redu
32
32
  import type {StorageApiRequestParams} from '../store/reducers/storage/types';
33
33
 
34
34
  import {backend as BACKEND} from '../store';
35
+ import {prepareSortValue} from '../utils/filters';
35
36
 
36
37
  const config = {withCredentials: !window.custom_backend};
37
38
 
@@ -82,24 +83,35 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
82
83
  });
83
84
  }
84
85
  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,
86
94
  {concurrentId}: AxiosOptions = {},
87
95
  ) {
96
+ const sort = prepareSortValue(sortValue, sortOrder);
97
+
88
98
  return this.get<TNodesInfo>(
89
99
  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},
99
102
  );
100
103
  }
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
+ );
103
115
  }
104
116
  getStorageInfo(
105
117
  {tenant, visibleEntities, nodeId}: StorageApiRequestParams,
@@ -1,4 +1,5 @@
1
1
  import type {ClusterTab} from '../../../containers/Cluster/utils';
2
+ import type {EType} from '../../../types/api/tablet';
2
3
 
3
4
  import {setHeaderBreadcrumbs} from './header';
4
5
 
@@ -23,6 +24,7 @@ export interface TabletsBreadcrumbsOptions extends TenantBreadcrumbsOptions {
23
24
 
24
25
  export interface TabletBreadcrumbsOptions extends TabletsBreadcrumbsOptions {
25
26
  tabletId?: string;
27
+ tabletType?: EType;
26
28
  }
27
29
 
28
30
  export type BreadcrumbsOptions =
@@ -10,6 +10,7 @@ import type {
10
10
  ComputeApiRequestParams,
11
11
  NodesAction,
12
12
  NodesApiRequestParams,
13
+ NodesSortParams,
13
14
  NodesState,
14
15
  } from './types';
15
16
  import {prepareComputeNodesData, prepareNodesData} from './utils';
@@ -20,6 +21,7 @@ const RESET_NODES_STATE = 'nodes/RESET_NODES_STATE';
20
21
  const SET_NODES_UPTIME_FILTER = 'nodes/SET_NODES_UPTIME_FILTER';
21
22
  const SET_DATA_WAS_NOT_LOADED = 'nodes/SET_DATA_WAS_NOT_LOADED';
22
23
  const SET_SEARCH_VALUE = 'nodes/SET_SEARCH_VALUE';
24
+ const SET_SORT = 'nodes/SET_SORT';
23
25
 
24
26
  const initialState = {
25
27
  loading: false,
@@ -47,6 +49,10 @@ const nodes: Reducer<NodesState, NodesAction> = (state = initialState, action) =
47
49
  };
48
50
  }
49
51
  case FETCH_NODES.FAILURE: {
52
+ if (action.error?.isCancelled) {
53
+ return state;
54
+ }
55
+
50
56
  return {
51
57
  ...state,
52
58
  error: action.error,
@@ -74,7 +80,13 @@ const nodes: Reducer<NodesState, NodesAction> = (state = initialState, action) =
74
80
  searchValue: action.data,
75
81
  };
76
82
  }
77
-
83
+ case SET_SORT: {
84
+ return {
85
+ ...state,
86
+ sortValue: action.data.sortValue,
87
+ sortOrder: action.data.sortOrder,
88
+ };
89
+ }
78
90
  case SET_DATA_WAS_NOT_LOADED: {
79
91
  return {
80
92
  ...state,
@@ -85,13 +97,11 @@ const nodes: Reducer<NodesState, NodesAction> = (state = initialState, action) =
85
97
  return state;
86
98
  }
87
99
  };
100
+ const concurrentId = 'getNodes';
88
101
 
89
102
  export function getNodes({type = 'any', ...params}: NodesApiRequestParams) {
90
103
  return createApiRequest({
91
- request: window.api.getNodes({
92
- type,
93
- ...params,
94
- }),
104
+ request: window.api.getNodes({type, ...params}, {concurrentId}),
95
105
  actions: FETCH_NODES,
96
106
  dataHandler: prepareNodesData,
97
107
  });
@@ -99,7 +109,7 @@ export function getNodes({type = 'any', ...params}: NodesApiRequestParams) {
99
109
 
100
110
  export function getComputeNodes({version = EVersion.v2, ...params}: ComputeApiRequestParams) {
101
111
  return createApiRequest({
102
- request: window.api.getCompute({version, ...params}),
112
+ request: window.api.getCompute({version, ...params}, {concurrentId}),
103
113
  actions: FETCH_NODES,
104
114
  dataHandler: prepareComputeNodesData,
105
115
  });
@@ -130,4 +140,11 @@ export const setSearchValue = (value: string) => {
130
140
  } as const;
131
141
  };
132
142
 
143
+ export const setSort = (sortParams: NodesSortParams) => {
144
+ return {
145
+ type: SET_SORT,
146
+ data: sortParams,
147
+ } as const;
148
+ };
149
+
133
150
  export default nodes;
@@ -1,10 +1,10 @@
1
1
  import {Selector, createSelector} from 'reselect';
2
- import {escapeRegExp} from 'lodash';
3
2
 
4
3
  import {EFlag} from '../../../types/api/enums';
5
4
  import {calcUptimeInSeconds} from '../../../utils';
6
5
  import {HOUR_IN_SECONDS} from '../../../utils/constants';
7
6
  import {NodesUptimeFilterValues} from '../../../utils/nodes';
7
+ import {prepareSearchValue} from '../../../utils/filters';
8
8
 
9
9
  import type {ProblemFilterValue} from '../settings/types';
10
10
  import type {NodesPreparedEntity, NodesStateSlice} from './types';
@@ -41,7 +41,7 @@ const filterNodesBySearchValue = (nodesList: NodesPreparedEntity[] = [], searchV
41
41
  if (!searchValue) {
42
42
  return nodesList;
43
43
  }
44
- const re = new RegExp(escapeRegExp(searchValue), 'i');
44
+ const re = prepareSearchValue(searchValue);
45
45
 
46
46
  return nodesList.filter((node) => {
47
47
  return node.Host ? re.test(node.Host) || re.test(String(node.NodeId)) : true;
@@ -1,3 +1,5 @@
1
+ import type {OrderType} from '@gravity-ui/react-data-table';
2
+
1
3
  import type {IResponseError} from '../../../types/api/error';
2
4
  import type {TEndpoint, TPoolStats} from '../../../types/api/nodes';
3
5
  import type {
@@ -9,13 +11,14 @@ import type {EFlag} from '../../../types/api/enums';
9
11
  import type {ApiRequestAction} from '../../utils';
10
12
  import type {VisibleEntities} from '../storage/types';
11
13
 
12
- import {NodesUptimeFilterValues} from '../../../utils/nodes';
14
+ import type {NodesSortValue, NodesUptimeFilterValues} from '../../../utils/nodes';
13
15
  import {
14
16
  FETCH_NODES,
15
17
  resetNodesState,
16
18
  setDataWasNotLoaded,
17
19
  setNodesUptimeFilter,
18
20
  setSearchValue,
21
+ setSort,
19
22
  } from './nodes';
20
23
 
21
24
  // Since nodes from different endpoints can have different types,
@@ -42,6 +45,8 @@ export interface NodesState {
42
45
  wasLoaded: boolean;
43
46
  nodesUptimeFilter: NodesUptimeFilterValues;
44
47
  searchValue: string;
48
+ sortValue?: NodesSortValue;
49
+ sortOrder?: OrderType;
45
50
  data?: NodesPreparedEntity[];
46
51
  totalNodes?: number;
47
52
  error?: IResponseError;
@@ -49,17 +54,21 @@ export interface NodesState {
49
54
 
50
55
  export type NodeType = 'static' | 'dynamic' | 'any';
51
56
 
52
- interface RequestParams {
57
+ export interface NodesSortParams {
58
+ sortOrder?: OrderType;
59
+ sortValue?: NodesSortValue;
60
+ }
61
+
62
+ export interface NodesGeneralRequestParams extends NodesSortParams {
53
63
  filter?: string; // NodeId or Host
54
64
  uptime?: number; // return nodes with less uptime in seconds
55
65
  problems_only?: boolean; // return nodes with SystemState !== EFlag.Green
56
- sort?: string; // Sort by one of ESort params (may differ for /nodes and /compute)
57
66
 
58
67
  offser?: number;
59
68
  limit?: number;
60
69
  }
61
70
 
62
- export interface NodesApiRequestParams extends RequestParams {
71
+ export interface NodesApiRequestParams extends NodesGeneralRequestParams {
63
72
  tenant?: string;
64
73
  type?: NodeType;
65
74
  visibleEntities?: VisibleEntities; // "with" param
@@ -67,7 +76,7 @@ export interface NodesApiRequestParams extends RequestParams {
67
76
  tablets?: boolean;
68
77
  }
69
78
 
70
- export interface ComputeApiRequestParams extends RequestParams {
79
+ export interface ComputeApiRequestParams extends NodesGeneralRequestParams {
71
80
  path: string;
72
81
  version?: EVersion; // only v2 works with filters
73
82
  }
@@ -90,6 +99,7 @@ export type NodesAction =
90
99
  | ReturnType<typeof setDataWasNotLoaded>
91
100
  | ReturnType<typeof setNodesUptimeFilter>
92
101
  | ReturnType<typeof setSearchValue>
102
+ | ReturnType<typeof setSort>
93
103
  | ReturnType<typeof resetNodesState>
94
104
  );
95
105
 
@@ -13,6 +13,7 @@ import {
13
13
  ENABLE_ADDITIONAL_QUERY_MODES,
14
14
  CLUSTER_INFO_HIDDEN_KEY,
15
15
  LAST_USED_QUERY_ACTION_KEY,
16
+ USE_BACKEND_PARAMS_FOR_TABLES_KEY,
16
17
  } from '../../../utils/constants';
17
18
  import '../../../services/api';
18
19
  import {getValueFromLS, parseJson} from '../../../utils/utils';
@@ -76,6 +77,10 @@ export const initialState = {
76
77
  [ASIDE_HEADER_COMPACT_KEY]: readSavedSettingsValue(ASIDE_HEADER_COMPACT_KEY, 'true'),
77
78
  [PARTITIONS_HIDDEN_COLUMNS_KEY]: readSavedSettingsValue(PARTITIONS_HIDDEN_COLUMNS_KEY),
78
79
  [CLUSTER_INFO_HIDDEN_KEY]: readSavedSettingsValue(CLUSTER_INFO_HIDDEN_KEY, 'true'),
80
+ [USE_BACKEND_PARAMS_FOR_TABLES_KEY]: readSavedSettingsValue(
81
+ USE_BACKEND_PARAMS_FOR_TABLES_KEY,
82
+ 'false',
83
+ ),
79
84
  },
80
85
  systemSettings,
81
86
  };
@@ -65,15 +65,3 @@ export enum EVersion {
65
65
  v1 = 'v1',
66
66
  v2 = 'v2', // only this versions works with sorting
67
67
  }
68
-
69
- export enum ESort {
70
- NodeId = 'NodeId',
71
- Host = 'Host',
72
- DC = 'DC',
73
- Rack = 'Rack',
74
- Version = 'Version',
75
- Uptime = 'Uptime',
76
- Memory = 'Memory',
77
- CPU = 'CPU',
78
- LoadAverage = 'LoadAverage',
79
- }
@@ -101,15 +101,3 @@ enum EConfigState {
101
101
  'Consistent' = 'Consistent',
102
102
  'Outdated' = 'Outdated',
103
103
  }
104
-
105
- export enum ESort {
106
- NodeId = 'NodeId',
107
- Host = 'Host',
108
- DC = 'DC',
109
- Rack = 'Rack',
110
- Version = 'Version',
111
- Uptime = 'Uptime',
112
- Memory = 'Memory',
113
- CPU = 'CPU',
114
- LoadAverage = 'LoadAverage',
115
- }
@@ -119,3 +119,6 @@ export const CLUSTER_INFO_HIDDEN_KEY = 'clusterInfoHidden';
119
119
 
120
120
  // Remain "tab" in key name for backward compatibility
121
121
  export const TENANT_INITIAL_PAGE_KEY = 'saved_tenant_initial_tab';
122
+
123
+ // Send filters and sort params to backend for Nodes and Storage tables
124
+ export const USE_BACKEND_PARAMS_FOR_TABLES_KEY = 'useBackendParamsForTables';
@@ -0,0 +1,23 @@
1
+ import {escapeRegExp} from 'lodash';
2
+
3
+ import type {OrderType} from '@gravity-ui/react-data-table';
4
+ import {DESCENDING} from '@gravity-ui/react-data-table/build/esm/lib/constants';
5
+
6
+ export const prepareSortValue = (
7
+ sortValue: string | undefined,
8
+ sortOrder: OrderType = DESCENDING,
9
+ ) => {
10
+ if (!sortValue) {
11
+ return '';
12
+ }
13
+
14
+ if (sortOrder === DESCENDING) {
15
+ return '-' + sortValue;
16
+ }
17
+
18
+ return sortValue;
19
+ };
20
+
21
+ export const prepareSearchValue = (searchValue = '') => {
22
+ return new RegExp(escapeRegExp(searchValue), 'i');
23
+ };
@@ -2,3 +2,6 @@ export * from './useAutofetcher';
2
2
  export * from './useTypedSelector';
3
3
  export * from './useSetting';
4
4
  export * from './useQueryModes';
5
+ export * from './useTableSort';
6
+
7
+ export * from './useNodesRequestParams';
@@ -0,0 +1,46 @@
1
+ import {useMemo} from 'react';
2
+
3
+ import type {NodesGeneralRequestParams} from '../../store/reducers/nodes/types';
4
+ import type {ProblemFilterValue} from '../../store/reducers/settings/types';
5
+ import {ProblemFilterValues} from '../../store/reducers/settings/settings';
6
+
7
+ import {HOUR_IN_SECONDS, USE_BACKEND_PARAMS_FOR_TABLES_KEY} from '../constants';
8
+ import {NodesUptimeFilterValues} from '../nodes';
9
+ import {useSetting} from './useSetting';
10
+
11
+ interface NodesRawRequestParams
12
+ extends Omit<NodesGeneralRequestParams, 'problems_only' | 'uptime'> {
13
+ problemFilter?: ProblemFilterValue;
14
+ nodesUptimeFilter?: NodesUptimeFilterValues;
15
+ }
16
+
17
+ export const useNodesRequestParams = ({
18
+ filter,
19
+ problemFilter,
20
+ nodesUptimeFilter,
21
+ sortOrder,
22
+ sortValue,
23
+ }: NodesRawRequestParams) => {
24
+ const [useBackendParamsForTables] = useSetting<boolean>(USE_BACKEND_PARAMS_FOR_TABLES_KEY);
25
+
26
+ // If backend params are enabled, update params value to use them in fetch request
27
+ // Otherwise no params will be updated, no hooks that depend on requestParams will be triggered
28
+ return useMemo(() => {
29
+ if (useBackendParamsForTables) {
30
+ const problemsOnly = problemFilter === ProblemFilterValues.PROBLEMS;
31
+ const uptime =
32
+ nodesUptimeFilter === NodesUptimeFilterValues.SmallUptime
33
+ ? HOUR_IN_SECONDS
34
+ : undefined;
35
+
36
+ return {
37
+ filter,
38
+ problems_only: problemsOnly,
39
+ uptime,
40
+ sortOrder,
41
+ sortValue,
42
+ };
43
+ }
44
+ return undefined;
45
+ }, [useBackendParamsForTables, filter, problemFilter, nodesUptimeFilter, sortOrder, sortValue]);
46
+ };
@@ -0,0 +1,37 @@
1
+ import {useMemo} from 'react';
2
+
3
+ import {OrderType, SortOrder} from '@gravity-ui/react-data-table';
4
+ import {DESCENDING} from '@gravity-ui/react-data-table/build/esm/lib/constants';
5
+
6
+ interface SortParams {
7
+ sortValue: string | undefined;
8
+ sortOrder: OrderType | undefined;
9
+ }
10
+
11
+ type HandleSort = (rawValue: SortOrder | SortOrder[] | undefined) => void;
12
+
13
+ export const useTableSort = (
14
+ {sortValue, sortOrder = DESCENDING}: SortParams,
15
+ onSort: (params: SortParams) => void,
16
+ ): [SortOrder | undefined, HandleSort] => {
17
+ const sort: SortOrder | undefined = useMemo(() => {
18
+ if (!sortValue) {
19
+ return undefined;
20
+ }
21
+
22
+ return {
23
+ columnId: sortValue,
24
+ order: sortOrder,
25
+ };
26
+ }, [sortValue, sortOrder]);
27
+
28
+ const handleSort: HandleSort = (rawValue) => {
29
+ const value = Array.isArray(rawValue) ? rawValue[0] : rawValue;
30
+ onSort({
31
+ sortValue: value?.columnId,
32
+ sortOrder: value?.order,
33
+ });
34
+ };
35
+
36
+ return [sort, handleSort];
37
+ };
@@ -2,6 +2,7 @@ import type {TSystemStateInfo} from '../types/api/nodes';
2
2
  import type {TNodeInfo} from '../types/api/nodesList';
3
3
  import type {NodesPreparedEntity} from '../store/reducers/nodes/types';
4
4
  import type {NodesMap} from '../types/store/nodesList';
5
+ import type {ValueOf} from '../types/common';
5
6
  import {EFlag} from '../types/api/enums';
6
7
 
7
8
  export enum NodesUptimeFilterValues {
@@ -31,3 +32,27 @@ export const prepareNodesMap = (nodesList?: TNodeInfo[]) => {
31
32
  return nodesMap;
32
33
  }, new Map());
33
34
  };
35
+
36
+ /**
37
+ * Values to sort /compute v2 and /nodes responses
38
+ *
39
+ * For actual values go to:\
40
+ * /nodes: https://github.com/ydb-platform/ydb/blob/main/ydb/core/viewer/json_nodes.h\
41
+ * /compute: https://github.com/ydb-platform/ydb/blob/main/ydb/core/viewer/json_compute.h
42
+ */
43
+ export const NODES_SORT_VALUES = {
44
+ NodeId: 'NodeId',
45
+ Host: 'Host',
46
+ DC: 'DC',
47
+ Rack: 'Rack',
48
+ Version: 'Version',
49
+ Uptime: 'Uptime',
50
+ Memory: 'Memory',
51
+ CPU: 'CPU',
52
+ LoadAverage: 'LoadAverage',
53
+ } as const;
54
+
55
+ export type NodesSortValue = ValueOf<typeof NODES_SORT_VALUES>;
56
+
57
+ export const isSortableNodesProperty = (value: string): value is NodesSortValue =>
58
+ Object.values(NODES_SORT_VALUES).includes(value as NodesSortValue);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ydb-embedded-ui",
3
- "version": "4.13.0",
3
+ "version": "4.14.0",
4
4
  "files": [
5
5
  "dist"
6
6
  ],
@@ -40,7 +40,7 @@
40
40
  "reselect": "4.1.6",
41
41
  "sass": "1.32.8",
42
42
  "web-vitals": "1.1.2",
43
- "ydb-ui-components": "^3.2.2"
43
+ "ydb-ui-components": "^3.3.1"
44
44
  },
45
45
  "scripts": {
46
46
  "start": "react-app-rewired start",