ydb-embedded-ui 4.13.0 → 4.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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",