ydb-embedded-ui 1.8.7 → 1.10.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 (51) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/components/BasicNodeViewer/BasicNodeViewer.scss +43 -0
  3. package/dist/components/BasicNodeViewer/BasicNodeViewer.tsx +53 -0
  4. package/dist/components/BasicNodeViewer/index.ts +1 -0
  5. package/dist/components/EntityStatus/EntityStatus.js +15 -3
  6. package/dist/components/FullNodeViewer/FullNodeViewer.js +29 -48
  7. package/dist/components/FullNodeViewer/FullNodeViewer.scss +0 -45
  8. package/dist/components/IndexInfoViewer/IndexInfoViewer.tsx +52 -0
  9. package/dist/components/InfoViewer/index.ts +4 -0
  10. package/dist/components/InfoViewer/utils.ts +32 -0
  11. package/dist/components/ProgressViewer/ProgressViewer.js +1 -1
  12. package/dist/components/TabletsOverall/TabletsOverall.tsx +1 -1
  13. package/dist/containers/Node/Node.scss +5 -1
  14. package/dist/containers/Node/Node.tsx +7 -1
  15. package/dist/containers/Node/NodeOverview/NodeOverview.tsx +1 -3
  16. package/dist/containers/Node/NodeStructure/NodeStructure.scss +30 -1
  17. package/dist/containers/Node/NodeStructure/PDiskTitleBadge.tsx +25 -0
  18. package/dist/containers/Node/NodeStructure/Pdisk.tsx +24 -2
  19. package/dist/containers/Nodes/Nodes.js +1 -0
  20. package/dist/containers/ReduxTooltip/ReduxTooltip.js +1 -1
  21. package/dist/containers/Storage/Pdisk/Pdisk.tsx +25 -33
  22. package/dist/containers/Storage/Vdisk/Vdisk.js +2 -0
  23. package/dist/containers/Tablet/Tablet.js +2 -2
  24. package/dist/containers/Tablets/Tablets.js +1 -1
  25. package/dist/containers/Tablets/Tablets.scss +0 -6
  26. package/dist/containers/TabletsFilters/TabletsFilters.scss +0 -7
  27. package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.scss +6 -2
  28. package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +24 -14
  29. package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.js +6 -2
  30. package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.scss +6 -2
  31. package/dist/containers/Tenant/Diagnostics/HotKeys/HotKeys.js +3 -3
  32. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +24 -3
  33. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js +15 -13
  34. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss +22 -6
  35. package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.js +80 -10
  36. package/dist/containers/Tenant/Schema/SchemaInfoViewer/SchemaInfoViewer.js +20 -16
  37. package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +1 -0
  38. package/dist/containers/Tenant/utils/schema.ts +73 -28
  39. package/dist/containers/Tenant/utils/schemaActions.ts +42 -32
  40. package/dist/services/api.js +13 -8
  41. package/dist/store/reducers/executeQuery.js +1 -1
  42. package/dist/store/reducers/executeTopQueries.js +1 -1
  43. package/dist/store/reducers/olapStats.js +5 -1
  44. package/dist/store/reducers/preview.js +1 -1
  45. package/dist/store/reducers/shardsWorkload.js +32 -4
  46. package/dist/types/api/schema.ts +43 -1
  47. package/dist/types/api/storage.ts +54 -0
  48. package/dist/utils/getNodesColumns.js +2 -0
  49. package/dist/utils/pdisk.ts +74 -0
  50. package/dist/utils/tooltip.js +27 -0
  51. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
- import {useContext, useEffect, useMemo} from 'react';
1
+ import {useState, useContext, useEffect, useMemo} from 'react';
2
2
  import cn from 'bem-cn-lite';
3
3
  import {connect} from 'react-redux';
4
4
  import {Loader} from '@yandex-cloud/uikit';
@@ -14,15 +14,25 @@ import HistoryContext from '../../../../contexts/HistoryContext';
14
14
  import {DEFAULT_TABLE_SETTINGS} from '../../../../utils/constants';
15
15
  import {isColumnEntityType} from '../../utils/schema';
16
16
  import {prepareQueryError} from '../../../../utils';
17
+ import {i18n} from '../../../../utils/i18n';
17
18
 
18
19
  import './TopShards.scss';
19
20
 
20
21
  const b = cn('top-shards');
21
22
  const bLink = cn('yc-link');
22
23
 
24
+ const TABLE_SETTINGS = {
25
+ ...DEFAULT_TABLE_SETTINGS,
26
+ dynamicRender: false, // no more than 20 rows
27
+ externalSort: true,
28
+ disableSortReset: true,
29
+ defaultOrder: DataTable.DESCENDING,
30
+ };
31
+
23
32
  const tableColumnsNames = {
24
33
  TabletId: 'TabletId',
25
34
  CPUCores: 'CPUCores',
35
+ DataSize: 'DataSize',
26
36
  Path: 'Path',
27
37
  };
28
38
 
@@ -32,6 +42,28 @@ function prepareCPUWorkloadValue(value) {
32
42
  return `${(value * 100).toFixed(2)}%`;
33
43
  }
34
44
 
45
+ function prepareDateSizeValue(value) {
46
+ return new Intl.NumberFormat(i18n.lang).format(value);
47
+ }
48
+
49
+ function stringToDataTableSortOrder(value) {
50
+ return value && value.split(',').map((columnId) => ({
51
+ columnId,
52
+ order: DataTable.DESCENDING,
53
+ }));
54
+ }
55
+
56
+ function stringToQuerySortOrder(value) {
57
+ return value && value.split(',').map((columnId) => ({
58
+ columnId,
59
+ order: 'DESC',
60
+ }));
61
+ }
62
+
63
+ function dataTableToStringSortOrder(value = []) {
64
+ return value.map(({columnId}) => columnId).join(',');
65
+ }
66
+
35
67
  function TopShards({
36
68
  sendShardQuery,
37
69
  currentSchemaPath,
@@ -46,25 +78,40 @@ function TopShards({
46
78
  setShardQueryOptions,
47
79
  type,
48
80
  }) {
81
+ const [sortOrder, setSortOrder] = useState(tableColumnsNames.CPUCores);
82
+
49
83
  useEffect(() => {
84
+ autofetcher.stop();
85
+
50
86
  if (autorefresh) {
51
87
  autofetcher.start();
52
- autofetcher.fetch(() => sendShardQuery({database: path, path: currentSchemaPath}));
53
- } else {
54
- autofetcher.stop();
88
+ autofetcher.fetch(() => sendShardQuery({
89
+ database: path,
90
+ path: currentSchemaPath,
91
+ sortOrder: stringToQuerySortOrder(sortOrder),
92
+ }));
55
93
  }
94
+
56
95
  return () => {
57
96
  autofetcher.stop();
58
97
  };
59
- }, [autorefresh]);
98
+ }, [autorefresh, currentSchemaPath, path, sendShardQuery, sortOrder]);
60
99
 
100
+ // don't show loader for requests triggered by table sort, only for path change
61
101
  useEffect(() => {
62
- sendShardQuery({database: path, path: currentSchemaPath});
63
102
  setShardQueryOptions({
64
103
  wasLoaded: false,
65
104
  data: undefined,
66
105
  });
67
- }, [currentSchemaPath]);
106
+ }, [currentSchemaPath, path, setShardQueryOptions]);
107
+
108
+ useEffect(() => {
109
+ sendShardQuery({
110
+ database: path,
111
+ path: currentSchemaPath,
112
+ sortOrder: stringToQuerySortOrder(sortOrder),
113
+ });
114
+ }, [currentSchemaPath, path, sendShardQuery, sortOrder]);
68
115
 
69
116
  const history = useContext(HistoryContext);
70
117
 
@@ -76,6 +123,13 @@ function TopShards({
76
123
  };
77
124
  };
78
125
 
126
+ const onSort = (newSortOrder) => {
127
+ // omit information about sort order to disable ASC order, only DESC makes sense for top shards
128
+ // use a string (and not the DataTable default format) to prevent reference change,
129
+ // which would cause an excess state change, to avoid repeating requests
130
+ setSortOrder(dataTableToStringSortOrder(newSortOrder));
131
+ };
132
+
79
133
  const tableColumns = useMemo(() => {
80
134
  return [
81
135
  {
@@ -83,11 +137,16 @@ function TopShards({
83
137
  // eslint-disable-next-line
84
138
  render: ({value}) => {
85
139
  return (
86
- <span onClick={onSchemaClick(value)} className={bLink({view: 'normal'})}>
140
+ <span
141
+ // tenant name is substringed out in sql query but is needed here
142
+ onClick={onSchemaClick(path + value)}
143
+ className={bLink({view: 'normal'})}
144
+ >
87
145
  {value}
88
146
  </span>
89
147
  );
90
148
  },
149
+ sortable: false,
91
150
  },
92
151
  {
93
152
  name: tableColumnsNames.CPUCores,
@@ -97,6 +156,14 @@ function TopShards({
97
156
  },
98
157
  align: DataTable.RIGHT,
99
158
  },
159
+ {
160
+ name: tableColumnsNames.DataSize,
161
+ header: 'DataSize (B)',
162
+ render: ({value}) => {
163
+ return prepareDateSizeValue(value);
164
+ },
165
+ align: DataTable.RIGHT,
166
+ },
100
167
  {
101
168
  name: tableColumnsNames.TabletId,
102
169
  // eslint-disable-next-line
@@ -107,6 +174,7 @@ function TopShards({
107
174
  </InternalLink>
108
175
  );
109
176
  },
177
+ sortable: false,
110
178
  },
111
179
  ];
112
180
  }, []);
@@ -123,7 +191,7 @@ function TopShards({
123
191
  if (isColumnEntityType(type)) {
124
192
  return 'No data';
125
193
  }
126
- if (error) {
194
+ if (error && !error.isCancelled) {
127
195
  return prepareQueryError(error);
128
196
  }
129
197
 
@@ -132,9 +200,11 @@ function TopShards({
132
200
  <DataTable
133
201
  columns={tableColumns}
134
202
  data={data}
135
- settings={DEFAULT_TABLE_SETTINGS}
203
+ settings={TABLE_SETTINGS}
136
204
  className={b('table')}
137
205
  theme="yandex-cloud"
206
+ onSort={onSort}
207
+ sortOrder={stringToDataTableSortOrder(sortOrder)}
138
208
  />
139
209
  </div>
140
210
  ) : (
@@ -28,29 +28,33 @@ class SchemaInfoViewer extends React.Component {
28
28
  if (data) {
29
29
  const {PathDescription = {}} = data;
30
30
  const {TableStats = {}, TabletMetrics = {}} = PathDescription;
31
- const tableStatsInfo =
32
- TableStats &&
33
- Object.keys(TableStats).map((key) => ({
34
- label: key,
35
- value: TableStats[key].toString(),
36
- }));
31
+ const {PartCount, ...restTableStats} = TableStats;
37
32
 
38
- const tabletMetricsInfo =
39
- TableStats &&
40
- Object.keys(TabletMetrics).map((key) => ({
41
- label: key,
42
- value: this.formatTabletMetricsValue(key, TabletMetrics[key].toString()),
43
- }));
33
+ const priorityInfo = [{
34
+ label: 'PartCount',
35
+ value: PartCount,
36
+ }].filter(({value}) => value !== undefined);
44
37
 
45
- let generalInfo = Object.assign(tableStatsInfo, tabletMetricsInfo);
46
- generalInfo = Object.assign(generalInfo);
38
+ const tableStatsInfo = Object.keys(restTableStats).map((key) => ({
39
+ label: key,
40
+ value: TableStats[key].toString(),
41
+ }));
47
42
 
48
- const infoLength = Object.keys(generalInfo).length;
43
+ const tabletMetricsInfo = Object.keys(TabletMetrics).map((key) => ({
44
+ label: key,
45
+ value: this.formatTabletMetricsValue(key, TabletMetrics[key].toString()),
46
+ }));
49
47
 
48
+ const generalInfo = [
49
+ ...priorityInfo,
50
+ ...tabletMetricsInfo,
51
+ ...tableStatsInfo,
52
+ ];
53
+
50
54
  return (
51
55
  <div className={b()}>
52
56
  <div className={b('item')}>
53
- {infoLength ? (
57
+ {generalInfo.length ? (
54
58
  <InfoViewer info={generalInfo}></InfoViewer>
55
59
  ) : (
56
60
  <div>Empty</div>
@@ -61,6 +61,7 @@ export function SchemaTree(props: SchemaTreeProps) {
61
61
  activePath={currentPath}
62
62
  onActivePathUpdate={handleActivePathUpdate}
63
63
  cache={false}
64
+ virtualize
64
65
  />
65
66
  );
66
67
  }
@@ -1,43 +1,88 @@
1
1
  import type {NavigationTreeNodeType} from 'ydb-ui-components';
2
2
  import {EPathSubType, EPathType} from '../../../types/api/schema';
3
3
 
4
- const mapTablePathSubTypeToNavigationTreeType = (subType?: EPathSubType) => {
5
- switch (subType) {
6
- case EPathSubType.EPathSubTypeSyncIndexImplTable:
7
- case EPathSubType.EPathSubTypeAsyncIndexImplTable:
8
- return 'index_table';
9
- default:
10
- return 'table';
11
- }
4
+ // this file contains verbose mappings that are typed in a way that ensures
5
+ // correctness when a new node type or a new path type is added
6
+ // TS will error if a new entity is added but not mapped here
7
+
8
+ const pathSubTypeToNodeType: Record<EPathSubType, NavigationTreeNodeType | undefined> = {
9
+ [EPathSubType.EPathSubTypeSyncIndexImplTable]: 'index_table',
10
+ [EPathSubType.EPathSubTypeAsyncIndexImplTable]: 'index_table',
11
+
12
+ [EPathSubType.EPathSubTypeStreamImpl]: undefined,
13
+ [EPathSubType.EPathSubTypeEmpty]: undefined,
14
+ };
15
+
16
+ const pathTypeToNodeType: Record<EPathType, NavigationTreeNodeType | undefined> = {
17
+ [EPathType.EPathTypeInvalid]: undefined,
18
+
19
+ [EPathType.EPathTypeSubDomain]: 'database',
20
+ [EPathType.EPathTypeExtSubDomain]: 'database',
21
+
22
+ [EPathType.EPathTypeDir]: 'directory',
23
+ [EPathType.EPathTypeColumnStore]: 'directory',
24
+
25
+ [EPathType.EPathTypeTable]: 'table',
26
+
27
+ [EPathType.EPathTypeTableIndex]: 'index',
28
+
29
+ [EPathType.EPathTypeColumnTable]: 'column_table',
30
+
31
+ [EPathType.EPathTypeCdcStream]: 'topic',
12
32
  };
13
33
 
14
34
  export const mapPathTypeToNavigationTreeType = (
15
35
  type: EPathType = EPathType.EPathTypeDir,
16
36
  subType?: EPathSubType,
17
37
  defaultType: NavigationTreeNodeType = 'directory'
18
- ): NavigationTreeNodeType => {
19
- switch (type) {
20
- case EPathType.EPathTypeSubDomain:
21
- return 'database';
22
- case EPathType.EPathTypeTable:
23
- case EPathType.EPathTypeColumnTable:
24
- return mapTablePathSubTypeToNavigationTreeType(subType);
25
- case EPathType.EPathTypeDir:
26
- case EPathType.EPathTypeColumnStore:
27
- return 'directory';
28
- case EPathType.EPathTypeTableIndex:
29
- return 'index';
30
- default:
31
- return defaultType;
32
- }
38
+ ): NavigationTreeNodeType =>
39
+ (subType && pathSubTypeToNodeType[subType]) || pathTypeToNodeType[type] || defaultType;
40
+
41
+ // ====================
42
+
43
+ const pathTypeToIsTable: Record<EPathType, boolean> = {
44
+ [EPathType.EPathTypeTable]: true,
45
+ [EPathType.EPathTypeColumnTable]: true,
46
+
47
+ [EPathType.EPathTypeInvalid]: false,
48
+ [EPathType.EPathTypeDir]: false,
49
+ [EPathType.EPathTypeSubDomain]: false,
50
+ [EPathType.EPathTypeTableIndex]: false,
51
+ [EPathType.EPathTypeExtSubDomain]: false,
52
+ [EPathType.EPathTypeColumnStore]: false,
53
+ [EPathType.EPathTypeCdcStream]: false,
33
54
  };
34
55
 
35
- export const isTableType = (type?: EPathType) =>
36
- mapPathTypeToNavigationTreeType(type) === 'table';
56
+ export const isTableType = (pathType?: EPathType) =>
57
+ (pathType && pathTypeToIsTable[pathType]) ?? false;
58
+
59
+ // ====================
60
+
61
+ const pathSubTypeToIsIndexImpl: Record<EPathSubType, boolean> = {
62
+ [EPathSubType.EPathSubTypeSyncIndexImplTable]: true,
63
+ [EPathSubType.EPathSubTypeAsyncIndexImplTable]: true,
64
+
65
+ [EPathSubType.EPathSubTypeStreamImpl]: false,
66
+ [EPathSubType.EPathSubTypeEmpty]: false,
67
+ };
37
68
 
38
69
  export const isIndexTable = (subType?: EPathSubType) =>
39
- mapTablePathSubTypeToNavigationTreeType(subType) === 'index_table';
70
+ (subType && pathSubTypeToIsIndexImpl[subType]) ?? false;
71
+
72
+ // ====================
73
+
74
+ const pathTypeToIsColumn: Record<EPathType, boolean> = {
75
+ [EPathType.EPathTypeColumnStore]: true,
76
+ [EPathType.EPathTypeColumnTable]: true,
77
+
78
+ [EPathType.EPathTypeInvalid]: false,
79
+ [EPathType.EPathTypeDir]: false,
80
+ [EPathType.EPathTypeTable]: false,
81
+ [EPathType.EPathTypeSubDomain]: false,
82
+ [EPathType.EPathTypeTableIndex]: false,
83
+ [EPathType.EPathTypeExtSubDomain]: false,
84
+ [EPathType.EPathTypeCdcStream]: false,
85
+ };
40
86
 
41
87
  export const isColumnEntityType = (type?: EPathType) =>
42
- type === EPathType.EPathTypeColumnStore ||
43
- type === EPathType.EPathTypeColumnTable;
88
+ (type && pathTypeToIsColumn[type]) ?? false;
@@ -1,5 +1,5 @@
1
1
  import {Dispatch} from 'react';
2
- import type {NavigationTreeNodeType} from 'ydb-ui-components';
2
+ import type {NavigationTreeNodeType, NavigationTreeProps} from 'ydb-ui-components';
3
3
 
4
4
  import {changeUserInput} from '../../../store/reducers/executeQuery';
5
5
  import {setShowPreview} from '../../../store/reducers/schema';
@@ -73,6 +73,8 @@ const bindActions = (
73
73
  };
74
74
  };
75
75
 
76
+ type ActionsSet = ReturnType<Required<NavigationTreeProps>['getActions']>;
77
+
76
78
  export const getActions = (
77
79
  dispatch: Dispatch<any>,
78
80
  setActivePath: (path: string) => void,
@@ -81,35 +83,43 @@ export const getActions = (
81
83
  const actions = bindActions(path, dispatch, setActivePath);
82
84
  const copyItem = {text: 'Copy path', action: actions.copyPath};
83
85
 
84
- switch (type) {
85
- case 'database':
86
- case 'directory':
87
- return [
88
- [
89
- copyItem,
90
- ],
91
- [
92
- {text: 'Create table...', action: actions.createTable},
93
- ],
94
- ];
95
- case 'table':
96
- return [
97
- [
98
- {text: 'Open preview', action: actions.openPreview},
99
- copyItem,
100
- ],
101
- [
102
- {text: 'Alter table...', action: actions.alterTable},
103
- {text: 'Select query...', action: actions.selectQuery},
104
- {text: 'Upsert query...', action: actions.upsertQuery},
105
- ],
106
- ];
107
- case 'index_table':
108
- return [
109
- copyItem,
110
- ];
111
- case 'index':
112
- default:
113
- return [];
114
- }
86
+ const DIR_SET: ActionsSet = [
87
+ [
88
+ copyItem,
89
+ ],
90
+ [
91
+ {text: 'Create table...', action: actions.createTable},
92
+ ],
93
+ ];
94
+ const TABLE_SET: ActionsSet = [
95
+ [
96
+ {text: 'Open preview', action: actions.openPreview},
97
+ copyItem,
98
+ ],
99
+ [
100
+ {text: 'Alter table...', action: actions.alterTable},
101
+ {text: 'Select query...', action: actions.selectQuery},
102
+ {text: 'Upsert query...', action: actions.upsertQuery},
103
+ ],
104
+ ];
105
+
106
+ const JUST_COPY: ActionsSet = [
107
+ copyItem,
108
+ ];
109
+
110
+ const EMPTY_SET: ActionsSet = [];
111
+
112
+ // verbose mapping to guarantee a correct actions set for new node types
113
+ // TS will error when a new type is added in the lib but is not mapped here
114
+ const nodeTypeToActions: Record<NavigationTreeNodeType, ActionsSet> = {
115
+ database: DIR_SET,
116
+ directory: DIR_SET,
117
+ table: TABLE_SET,
118
+ column_table: TABLE_SET,
119
+ index_table: JUST_COPY,
120
+ index: EMPTY_SET,
121
+ topic: DIR_SET,
122
+ };
123
+
124
+ return nodeTypeToActions[type];
115
125
  };
@@ -147,14 +147,19 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
147
147
  state: 0,
148
148
  });
149
149
  }
150
- sendQuery(query, database, action, stats) {
151
- return this.post(this.getPath('/viewer/json/query'), {
152
- query,
153
- database,
154
- action,
155
- stats,
156
- timeout: 600000,
157
- });
150
+ sendQuery({query, database, action, stats}, {concurrentId} = {}) {
151
+ return this.post(
152
+ this.getPath('/viewer/json/query'),
153
+ {
154
+ query,
155
+ database,
156
+ action,
157
+ stats,
158
+ timeout: 600000,
159
+ },
160
+ null,
161
+ {concurrentId},
162
+ );
158
163
  }
159
164
  getExplainQuery(query, database) {
160
165
  return this.post(this.getPath('/viewer/json/query'), {
@@ -141,7 +141,7 @@ const executeQuery = (state = initialState, action) => {
141
141
 
142
142
  export const sendQuery = ({query, database, action}) => {
143
143
  return createApiRequest({
144
- request: window.api.sendQuery(query, database, action, 'profile'),
144
+ request: window.api.sendQuery({query, database, action, stats: 'profile'}),
145
145
  actions: SEND_QUERY,
146
146
  dataHandler: (result) => {
147
147
  const resultData = result.result ?? result;
@@ -47,7 +47,7 @@ const executeTopQueries = (state = initialState, action) => {
47
47
 
48
48
  export const sendQuery = ({query, database, action}) => {
49
49
  return createApiRequest({
50
- request: window.api.sendQuery(query, database, action),
50
+ request: window.api.sendQuery({query, database, action}),
51
51
  actions: SEND_QUERY,
52
52
  dataHandler: (result) => {
53
53
  if (result && typeof result === 'string') {
@@ -52,7 +52,11 @@ const olapStats = (state = initialState, action) => {
52
52
 
53
53
  export const getOlapStats = ({path = ''}) => {
54
54
  return createApiRequest({
55
- request: window.api.sendQuery(createOlatStatsQuery(path), path, queryAction),
55
+ request: window.api.sendQuery({
56
+ query: createOlatStatsQuery(path),
57
+ database: path,
58
+ action: queryAction,
59
+ }),
56
60
  actions: FETCH_OLAP_STATS,
57
61
  dataHandler: (result) => {
58
62
  if (result && typeof result === 'string') {
@@ -47,7 +47,7 @@ const preview = (state = initialState, action) => {
47
47
 
48
48
  export const sendQuery = ({query, database, action}) => {
49
49
  return createApiRequest({
50
- request: window.api.sendQuery(query, database, action),
50
+ request: window.api.sendQuery({query, database, action}),
51
51
  actions: SEND_QUERY,
52
52
  dataHandler: (data) => {
53
53
  if (!Array.isArray(data)) {
@@ -9,8 +9,30 @@ const initialState = {
9
9
  wasLoaded: false,
10
10
  };
11
11
 
12
- function createShardQuery(path) {
13
- return `SELECT Path, TabletId, CPUCores FROM \`.sys/partition_stats\` WHERE Path='${path}' OR Path LIKE '${path}/%' ORDER BY CPUCores DESC LIMIT 20`;
12
+ function formatSortOrder({columnId, order}) {
13
+ return `${columnId} ${order}`;
14
+ }
15
+
16
+ function createShardQuery(path, sortOrder, tenantName) {
17
+ const orderBy = Array.isArray(sortOrder) ?
18
+ `ORDER BY ${sortOrder.map(formatSortOrder).join(', ')}` :
19
+ '';
20
+
21
+ const pathSelect = tenantName ?
22
+ `CAST(SUBSTRING(CAST(Path AS String), ${tenantName.length}) AS Utf8) AS Path` :
23
+ 'Path';
24
+
25
+ return `SELECT
26
+ ${pathSelect},
27
+ TabletId,
28
+ CPUCores,
29
+ DataSize
30
+ FROM \`.sys/partition_stats\`
31
+ WHERE
32
+ Path='${path}'
33
+ OR Path LIKE '${path}/%'
34
+ ${orderBy}
35
+ LIMIT 20`;
14
36
  }
15
37
 
16
38
  const queryAction = 'execute-scan';
@@ -51,9 +73,15 @@ const shardsWorkload = (state = initialState, action) => {
51
73
  }
52
74
  };
53
75
 
54
- export const sendShardQuery = ({database, path = ''}) => {
76
+ export const sendShardQuery = ({database, path = '', sortOrder}) => {
55
77
  return createApiRequest({
56
- request: window.api.sendQuery(createShardQuery(path), database, queryAction),
78
+ request: window.api.sendQuery({
79
+ query: createShardQuery(path, sortOrder, database),
80
+ database,
81
+ action: queryAction,
82
+ }, {
83
+ concurrentId: 'topShards',
84
+ }),
57
85
  actions: SEND_SHARD_QUERY,
58
86
  dataHandler: (result) => {
59
87
  if (result && typeof result === 'string') {
@@ -54,6 +54,8 @@ interface TPathDescription {
54
54
 
55
55
  ColumnStoreDescription?: unknown;
56
56
  ColumnTableDescription?: unknown;
57
+
58
+ TableIndex?: TIndexDescription;
57
59
  }
58
60
 
59
61
  interface TDirEntry {
@@ -80,15 +82,42 @@ interface TDirEntry {
80
82
  Version?: TPathVersion;
81
83
  }
82
84
 
85
+ export interface TIndexDescription {
86
+ Name?: string;
87
+ /** uint64 */
88
+ LocalPathId?: string;
89
+
90
+ Type?: EIndexType;
91
+ State?: EIndexState;
92
+
93
+ KeyColumnNames?: string[];
94
+
95
+ /** uint64 */
96
+ SchemaVersion?: string;
97
+
98
+ /** uint64 */
99
+ PathOwnerId?: string;
100
+
101
+ DataColumnNames?: string[];
102
+ /** uint64 */
103
+ DataSize?: string;
104
+ }
105
+
83
106
  // incomplete
84
107
  export enum EPathType {
85
108
  EPathTypeInvalid = 'EPathTypeInvalid',
86
109
  EPathTypeDir = 'EPathTypeDir',
87
110
  EPathTypeTable = 'EPathTypeTable',
111
+
88
112
  EPathTypeSubDomain = 'EPathTypeSubDomain',
113
+
114
+ EPathTypeTableIndex = 'EPathTypeTableIndex',
115
+ EPathTypeExtSubDomain = 'EPathTypeExtSubDomain',
116
+
89
117
  EPathTypeColumnStore = 'EPathTypeColumnStore',
90
118
  EPathTypeColumnTable = 'EPathTypeColumnTable',
91
- EPathTypeTableIndex = 'EPathTypeTableIndex',
119
+ EPathTypeCdcStream = 'EPathTypeCdcStream',
120
+
92
121
  }
93
122
 
94
123
  export enum EPathSubType {
@@ -112,6 +141,19 @@ enum EPathState {
112
141
  EPathStateMoving = 'EPathStateMoving',
113
142
  }
114
143
 
144
+ enum EIndexType {
145
+ EIndexTypeInvalid = 'EIndexTypeInvalid',
146
+ EIndexTypeGlobal = 'EIndexTypeGlobal',
147
+ EIndexTypeGlobalAsync = 'EIndexTypeGlobalAsync',
148
+ }
149
+
150
+ enum EIndexState {
151
+ EIndexStateInvalid = 'EIndexStateInvalid',
152
+ EIndexStateReady = 'EIndexStateReady',
153
+ EIndexStateNotReady = 'EIndexStateNotReady',
154
+ EIndexStateWriteOnly = 'EIndexStateWriteOnly',
155
+ }
156
+
115
157
  // incomplete
116
158
  interface TPathVersion {
117
159
  /** uint64 */