ydb-embedded-ui 1.9.0 → 1.10.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/components/IndexInfoViewer/IndexInfoViewer.tsx +10 -7
  3. package/dist/components/InfoViewer/InfoViewer.scss +1 -2
  4. package/dist/components/InfoViewer/utils.ts +18 -10
  5. package/dist/containers/Storage/Pdisk/Pdisk.tsx +25 -33
  6. package/dist/containers/Storage/Vdisk/Vdisk.js +2 -0
  7. package/dist/containers/Tablet/Tablet.js +2 -2
  8. package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.tsx +15 -14
  9. package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +24 -14
  10. package/dist/containers/Tenant/Diagnostics/HotKeys/HotKeys.js +3 -3
  11. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +20 -13
  12. package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.js +80 -10
  13. package/dist/containers/Tenant/QueryEditor/QueryEditor.js +12 -2
  14. package/dist/containers/Tenant/Schema/SchemaInfoViewer/SchemaInfoViewer.js +164 -42
  15. package/dist/containers/Tenant/Schema/SchemaInfoViewer/SchemaInfoViewer.scss +18 -0
  16. package/dist/containers/Tenant/utils/schema.ts +73 -28
  17. package/dist/containers/Tenant/utils/schemaActions.ts +45 -32
  18. package/dist/services/api.js +13 -9
  19. package/dist/store/reducers/executeQuery.js +4 -3
  20. package/dist/store/reducers/executeTopQueries.js +1 -1
  21. package/dist/store/reducers/olapStats.js +5 -1
  22. package/dist/store/reducers/preview.js +1 -1
  23. package/dist/store/reducers/settings.js +20 -13
  24. package/dist/store/reducers/shardsWorkload.js +32 -4
  25. package/dist/types/api/schema.ts +123 -4
  26. package/dist/types/api/storage.ts +1 -1
  27. package/dist/utils/constants.js +4 -0
  28. package/dist/utils/index.js +7 -3
  29. package/dist/utils/pdisk.ts +2 -2
  30. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.10.2](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.10.1...v1.10.2) (2022-08-17)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * convert bytes on decimal scale ([db9b0a7](https://github.com/ydb-platform/ydb-embedded-ui/commit/db9b0a71fc5334f5a40992cc6abc0688782ad5d2))
9
+ * display HDD instead of ROT as pdisk type ([bd9e5ba](https://github.com/ydb-platform/ydb-embedded-ui/commit/bd9e5ba4e594cb3a1f6a964f619f9824e083ae7c))
10
+ * **InfoViewer:** accept default value formatter ([e03d8cc](https://github.com/ydb-platform/ydb-embedded-ui/commit/e03d8cc5de76e4ac00b05586ae6f6522a9708fb0))
11
+ * **InfoViewer:** allow longer labels ([89060a3](https://github.com/ydb-platform/ydb-embedded-ui/commit/89060a381858b5beaa3c3cf3402c13c917705676))
12
+ * **Overview:** display table r/o replicas ([6dbe0b4](https://github.com/ydb-platform/ydb-embedded-ui/commit/6dbe0b45fc5e3867f9d6141d270c15508a693e35))
13
+ * **Overview:** format & group table info in overview ([1a35cfc](https://github.com/ydb-platform/ydb-embedded-ui/commit/1a35cfcd2075454c4a1f1fc4961a4b3106b6d225))
14
+ * **QueryEditor:** save chosen run action ([b0fb436](https://github.com/ydb-platform/ydb-embedded-ui/commit/b0fb43651e0c6d1dc5d6a25f92716703402b556d))
15
+ * use current i18n lang for numeral formatting ([5d58fcf](https://github.com/ydb-platform/ydb-embedded-ui/commit/5d58fcffde21924f3cbe6c28946c7a9f755a8490))
16
+
17
+ ## [1.10.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.10.0...v1.10.1) (2022-08-10)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * **Tenant:** fix actions set for topics ([0c75bf4](https://github.com/ydb-platform/ydb-embedded-ui/commit/0c75bf4561966dd663ab1cd7c7b81ef6b4632e50))
23
+
24
+ ## [1.10.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.9.0...v1.10.0) (2022-08-10)
25
+
26
+
27
+ ### Features
28
+
29
+ * **TopShards:** add DataSize column ([cbcd047](https://github.com/ydb-platform/ydb-embedded-ui/commit/cbcd047d277f699a67bc002a5542f3b9f6a0c942))
30
+ * **TopShards:** sort table data on backend ([dc28c5c](https://github.com/ydb-platform/ydb-embedded-ui/commit/dc28c5c75b0036480bf804d49f82fc54eac98c8e))
31
+
32
+
33
+ ### Bug Fixes
34
+
35
+ * add concurrentId for sendQuery request ([dc6b32a](https://github.com/ydb-platform/ydb-embedded-ui/commit/dc6b32a8fd51064ddeca2fc60a0f08a725216334))
36
+ * **Storage:** display pdisk type in tooltip ([2b03a35](https://github.com/ydb-platform/ydb-embedded-ui/commit/2b03a35fc11ddeae3bdd30a0690b324ae917f5c3))
37
+ * **Tablet:** change Kill to Restart ([dd585b1](https://github.com/ydb-platform/ydb-embedded-ui/commit/dd585b1d1a6a5ddb484a702523773b169900f582))
38
+ * **Tenant:** add missing schema node types ([62a0ecb](https://github.com/ydb-platform/ydb-embedded-ui/commit/62a0ecb848dbcee53e18535cbf7c03a731d0cfeb))
39
+ * **Tenant:** ensure correct behavior for new schema node types ([f80c381](https://github.com/ydb-platform/ydb-embedded-ui/commit/f80c38152656e8bbbe51ec38b29fc0d954c361cc))
40
+ * **Tenant:** use new schema icons ([389a921](https://github.com/ydb-platform/ydb-embedded-ui/commit/389a9214c64b1adb183fa0c6caa6f2ec536dbef3))
41
+ * **TopShards:** disable virtualization for table ([006d3d9](https://github.com/ydb-platform/ydb-embedded-ui/commit/006d3d9fb9a4744b8bb4ad03e53693199213f80e))
42
+ * **TopShards:** format DataSize value ([c51ce66](https://github.com/ydb-platform/ydb-embedded-ui/commit/c51ce66286f6454f7252d1194628ee5a50aafba2))
43
+ * **TopShards:** only allow DESC sort ([6aa326f](https://github.com/ydb-platform/ydb-embedded-ui/commit/6aa326fc4b8165f00f8b3ecf5becdb0943ed57af))
44
+ * **TopShards:** substring tenant name out of shards path ([9e57672](https://github.com/ydb-platform/ydb-embedded-ui/commit/9e5767222c7dac7734c68abd08067cea507b1e15))
45
+
3
46
  ## [1.9.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.8.8...v1.9.0) (2022-07-29)
4
47
 
5
48
 
@@ -10,13 +10,16 @@ const DISPLAYED_FIELDS: Set<keyof TIndexDescription> = new Set([
10
10
  ]);
11
11
 
12
12
  const formatItem = createInfoFormatter<TIndexDescription>({
13
- Type: (value) => value?.substring(10), // trims EIndexType prefix
14
- State: (value) => value?.substring(11), // trims EIndexState prefix
15
- KeyColumnNames: (value) => value?.join(', '),
16
- DataColumnNames: (value) => value?.join(', '),
17
- }, {
18
- KeyColumnNames: 'Columns',
19
- DataColumnNames: 'Includes',
13
+ values: {
14
+ Type: (value) => value?.substring(10), // trims EIndexType prefix
15
+ State: (value) => value?.substring(11), // trims EIndexState prefix
16
+ KeyColumnNames: (value) => value?.join(', '),
17
+ DataColumnNames: (value) => value?.join(', '),
18
+ },
19
+ labels: {
20
+ KeyColumnNames: 'Columns',
21
+ DataColumnNames: 'Includes',
22
+ },
20
23
  });
21
24
 
22
25
  interface IndexInfoViewerProps {
@@ -27,11 +27,10 @@
27
27
 
28
28
  &__label {
29
29
  display: flex;
30
- flex: 1 1 auto;
30
+ flex: 0 1 auto;
31
31
  align-items: baseline;
32
32
 
33
33
  min-width: 200px;
34
- max-width: 200px;
35
34
 
36
35
  white-space: nowrap;
37
36
 
@@ -2,7 +2,7 @@ type LabelMap<T> = {
2
2
  [label in keyof T]?: string;
3
3
  }
4
4
 
5
- type FieldMappers<T> = {
5
+ type ValueFormatters<T> = {
6
6
  [label in keyof T]?: (value: T[label]) => string | undefined;
7
7
  }
8
8
 
@@ -13,20 +13,28 @@ function formatLabel<Shape>(label: keyof Shape, map: LabelMap<Shape>) {
13
13
  function formatValue<Shape, Key extends keyof Shape>(
14
14
  label: Key,
15
15
  value: Shape[Key],
16
- mappers: FieldMappers<Shape>,
16
+ formatters: ValueFormatters<Shape>,
17
+ defaultFormatter?: (value: Shape[Key]) => string | undefined,
17
18
  ) {
18
- const mapper = mappers[label];
19
- const mappedValue = mapper ? mapper(value) : value;
19
+ const formatter = formatters[label] || defaultFormatter;
20
+ const formattedValue = formatter ? formatter(value) : value;
20
21
 
21
- return String(mappedValue ?? '');
22
+ return String(formattedValue ?? '');
22
23
  }
23
24
 
24
- export function createInfoFormatter<Shape extends Record<string, any>>(
25
- fieldMappers?: FieldMappers<Shape>,
26
- labelMap?: LabelMap<Shape>,
27
- ) {
25
+ interface CreateInfoFormatterOptions<Shape> {
26
+ values?: ValueFormatters<Shape>,
27
+ labels?: LabelMap<Shape>,
28
+ defaultValueFormatter?: (value: Shape[keyof Shape]) => string | undefined,
29
+ }
30
+
31
+ export function createInfoFormatter<Shape extends Record<string, any>>({
32
+ values: valueFormatters,
33
+ labels: labelMap,
34
+ defaultValueFormatter,
35
+ }: CreateInfoFormatterOptions<Shape>) {
28
36
  return <Key extends keyof Shape>(label: Key, value: Shape[Key]) => ({
29
37
  label: formatLabel(label, labelMap || {}),
30
- value: formatValue(label, value, fieldMappers || {}),
38
+ value: formatValue(label, value, valueFormatters || {}, defaultValueFormatter),
31
39
  });
32
40
  }
@@ -8,6 +8,8 @@ import {bytesToGB} from '../../../utils/utils';
8
8
  import routes, {createHref} from '../../../routes';
9
9
  //@ts-ignore
10
10
  import {getPDiskId} from '../../../utils';
11
+ import {getPDiskType} from '../../../utils/pdisk';
12
+ import {TPDiskStateInfo, TPDiskState} from '../../../types/api/storage';
11
13
  import DiskStateProgressBar, {
12
14
  diskProgressColors,
13
15
  } from '../DiskStateProgressBar/DiskStateProgressBar';
@@ -20,38 +22,29 @@ import './Pdisk.scss';
20
22
  const b = cn('pdisk-storage');
21
23
 
22
24
  const stateSeverity = {
23
- Initial: 0,
24
- Normal: 1,
25
- InitialFormatRead: 3,
26
- InitialSysLogRead: 3,
27
- InitialCommonLogRead: 3,
28
- InitialFormatReadError: 5,
29
- InitialSysLogReadError: 5,
30
- InitialSysLogParseError: 5,
31
- InitialCommonLogReadError: 5,
32
- InitialCommonLogParseError: 5,
33
- CommonLoggerInitError: 5,
34
- OpenFileError: 5,
35
- ChunkQuotaError: 5,
36
- DeviceIoError: 5,
25
+ [TPDiskState.Initial]: 0,
26
+ [TPDiskState.Normal]: 1,
27
+ [TPDiskState.InitialFormatRead]: 3,
28
+ [TPDiskState.InitialSysLogRead]: 3,
29
+ [TPDiskState.InitialCommonLogRead]: 3,
30
+ [TPDiskState.InitialFormatReadError]: 5,
31
+ [TPDiskState.InitialSysLogReadError]: 5,
32
+ [TPDiskState.InitialSysLogParseError]: 5,
33
+ [TPDiskState.InitialCommonLogReadError]: 5,
34
+ [TPDiskState.InitialCommonLogParseError]: 5,
35
+ [TPDiskState.CommonLoggerInitError]: 5,
36
+ [TPDiskState.OpenFileError]: 5,
37
+ [TPDiskState.ChunkQuotaError]: 5,
38
+ [TPDiskState.DeviceIoError]: 5,
37
39
  };
38
40
 
39
- type PDiskState = keyof typeof stateSeverity;
40
-
41
- interface PDiskProps {
42
- NodeId: number;
43
- Host?: string;
44
- Path?: string;
45
- Realtime?: string;
46
- Device?: string;
47
- AvailableSize?: string;
48
- TotalSize?: string;
49
- State?: PDiskState;
50
- PDiskId: number;
51
- }
41
+ type PDiskProps = TPDiskStateInfo;
42
+
43
+ const isSeverityKey = (key?: TPDiskState): key is keyof typeof stateSeverity =>
44
+ key !== undefined && key in stateSeverity;
52
45
 
53
- const getStateSeverity = (pDiskState?: PDiskState) => {
54
- return pDiskState ? stateSeverity[pDiskState] : NOT_AVAILABLE_SEVERITY;
46
+ const getStateSeverity = (pDiskState?: TPDiskState) => {
47
+ return isSeverityKey(pDiskState) ? stateSeverity[pDiskState] : NOT_AVAILABLE_SEVERITY;
55
48
  };
56
49
 
57
50
  function Pdisk(props: PDiskProps) {
@@ -76,8 +69,7 @@ function Pdisk(props: PDiskProps) {
76
69
  };
77
70
  /* eslint-disable */
78
71
  const preparePdiskData = () => {
79
- const {AvailableSize, TotalSize, State, PDiskId, NodeId, Host, Path, Realtime, Device} =
80
- props;
72
+ const {AvailableSize, TotalSize, State, PDiskId, NodeId, Path, Realtime, Device} = props;
81
73
  const errorColors = [
82
74
  diskProgressColors[colorSeverity.Orange as keyof typeof diskProgressColors],
83
75
  diskProgressColors[colorSeverity.Red as keyof typeof diskProgressColors],
@@ -89,9 +81,9 @@ function Pdisk(props: PDiskProps) {
89
81
  ];
90
82
 
91
83
  pdiskData.push({property: 'State', value: State || 'not available'});
84
+ pdiskData.push({property: 'Type', value: getPDiskType(props) || 'unknown'});
92
85
  NodeId && pdiskData.push({property: 'Node Id', value: NodeId});
93
86
 
94
- Host && pdiskData.push({property: 'Host', value: Host});
95
87
  Path && pdiskData.push({property: 'Path', value: Path});
96
88
  pdiskData.push({
97
89
  property: 'Available',
@@ -151,7 +143,7 @@ function Pdisk(props: PDiskProps) {
151
143
  href={createHref(
152
144
  routes.node,
153
145
  {id: props.NodeId, activeTab: STRUCTURE},
154
- {pdiskId: props.PDiskId},
146
+ {pdiskId: props.PDiskId || ''},
155
147
  )}
156
148
  />
157
149
  </div>
@@ -7,6 +7,7 @@ import {Popup} from '@yandex-cloud/uikit';
7
7
  import {bytesToGB, bytesToSpeed} from '../../../utils/utils';
8
8
  import routes, {createHref} from '../../../routes';
9
9
  import {stringifyVdiskId, getPDiskId} from '../../../utils';
10
+ import {getPDiskType} from '../../../utils/pdisk';
10
11
  import DiskStateProgressBar, {
11
12
  diskProgressColors,
12
13
  } from '../DiskStateProgressBar/DiskStateProgressBar';
@@ -169,6 +170,7 @@ function Vdisk(props) {
169
170
  property: 'State',
170
171
  value: PDisk.State || 'not available',
171
172
  });
173
+ pdiskData.push({property: 'Type', value: getPDiskType(PDisk) || 'unknown'});
172
174
  PDisk.NodeId && pdiskData.push({property: 'Node Id', value: PDisk.NodeId});
173
175
  PDisk.NodeId &&
174
176
  nodes[PDisk.NodeId] &&
@@ -199,7 +199,7 @@ class Tablet extends React.Component {
199
199
  return (
200
200
  <CriticalActionDialog
201
201
  visible={dialogVisible}
202
- text="The tablet will be killed. Do you want to proceed?"
202
+ text="The tablet will be restarted. Do you want to proceed?"
203
203
  onClose={this.hideDialog}
204
204
  onConfirm={this._onKillClick}
205
205
  />
@@ -363,7 +363,7 @@ class Tablet extends React.Component {
363
363
  disabled={this.isDisabledKill()}
364
364
  className={b('control')}
365
365
  >
366
- Kill
366
+ Restart
367
367
  </Button>
368
368
  {this.hasHiveId() ? (
369
369
  <React.Fragment>
@@ -60,20 +60,21 @@ function DetailedOverview(props: DetailedOverviewProps) {
60
60
  const isTenant = tenantName === currentSchemaPath;
61
61
  return (
62
62
  <div className={b()}>
63
- <div className={b('section')}>
64
- {!isTenant && (
65
- <Overview type={type} tenantName={tenantName} />
66
- )}
67
- {isTenant && <TenantOverview tenantName={tenantName} additionalTenantInfo={additionalTenantInfo}/>}
68
- </div>
69
- {isTenant && (
70
- <div className={b('section')}>
71
- <Healthcheck
72
- tenant={tenantName}
73
- preview={true}
74
- showMoreHandler={openModalHandler}
75
- />
76
- </div>
63
+ {isTenant ? (
64
+ <>
65
+ <div className={b('section')}>
66
+ <TenantOverview tenantName={tenantName} additionalTenantInfo={additionalTenantInfo} />
67
+ </div>
68
+ <div className={b('section')}>
69
+ <Healthcheck
70
+ tenant={tenantName}
71
+ preview={true}
72
+ showMoreHandler={openModalHandler}
73
+ />
74
+ </div>
75
+ </>
76
+ ) : (
77
+ <Overview type={type} tenantName={tenantName} />
77
78
  )}
78
79
  </div>
79
80
  );
@@ -13,6 +13,11 @@ export enum GeneralPagesIds {
13
13
  'graph' = 'graph',
14
14
  }
15
15
 
16
+ type Page = {
17
+ id: GeneralPagesIds,
18
+ title: string,
19
+ };
20
+
16
21
  const overview = {
17
22
  id: GeneralPagesIds.overview,
18
23
  title: 'Overview',
@@ -76,17 +81,22 @@ export const TABLE_PAGES = [overview, topShards, graph, tablets, hotKeys, descri
76
81
 
77
82
  export const DIR_PAGES = [overview, topShards, describe];
78
83
 
79
- export const getPagesByType = (type?: EPathType) => {
80
- switch (type) {
81
- case EPathType.EPathTypeColumnStore:
82
- case EPathType.EPathTypeSubDomain:
83
- return DATABASE_PAGES;
84
- case EPathType.EPathTypeColumnTable:
85
- case EPathType.EPathTypeTable:
86
- return TABLE_PAGES;
87
- case EPathType.EPathTypeDir:
88
- case EPathType.EPathTypeTableIndex:
89
- default:
90
- return DIR_PAGES;
91
- }
92
- }
84
+ // verbose mapping to guarantee correct tabs for new path types
85
+ // TS will error when a new type is added but not mapped here
86
+ const pathTypeToPages: Record<EPathType, Page[] | undefined> = {
87
+ [EPathType.EPathTypeInvalid]: undefined,
88
+
89
+ [EPathType.EPathTypeSubDomain]: DATABASE_PAGES,
90
+ [EPathType.EPathTypeExtSubDomain]: DATABASE_PAGES,
91
+ [EPathType.EPathTypeColumnStore]: DATABASE_PAGES,
92
+
93
+ [EPathType.EPathTypeTable]: TABLE_PAGES,
94
+ [EPathType.EPathTypeColumnTable]: TABLE_PAGES,
95
+
96
+ [EPathType.EPathTypeDir]: DIR_PAGES,
97
+ [EPathType.EPathTypeTableIndex]: DIR_PAGES,
98
+ [EPathType.EPathTypeCdcStream]: DIR_PAGES,
99
+ };
100
+
101
+ export const getPagesByType = (type?: EPathType) =>
102
+ (type && pathTypeToPages[type]) || DIR_PAGES;
@@ -8,9 +8,10 @@ import Icon from '../../../../components/Icon/Icon';
8
8
 
9
9
  import {AutoFetcher} from '../../../../utils/autofetcher';
10
10
  import {getHotKeys, setHotKeysOptions} from '../../../../store/reducers/hotKeys';
11
- import {EPathType} from '../../../../types/api/schema';
12
11
  import {prepareQueryError} from '../../../../utils';
13
12
 
13
+ import {isColumnEntityType, isTableType} from '../../utils/schema';
14
+
14
15
  import './HotKeys.scss';
15
16
 
16
17
  const b = cn('hot-keys');
@@ -42,8 +43,7 @@ function HotKeys({
42
43
  type,
43
44
  }) {
44
45
  const fetchData = () => {
45
- // ColumnTables excluded intentionally
46
- if (type === EPathType.EPathTypeTable) {
46
+ if (isTableType(type) && !isColumnEntityType(type)) {
47
47
  getHotKeys(currentSchemaPath);
48
48
  }
49
49
  };
@@ -1,4 +1,4 @@
1
- import {useEffect, useMemo} from 'react';
1
+ import {ReactNode, useEffect, useMemo} from 'react';
2
2
  import {useDispatch, useSelector} from 'react-redux';
3
3
  import cn from 'bem-cn-lite';
4
4
 
@@ -8,8 +8,8 @@ import {Loader} from '@yandex-cloud/uikit';
8
8
  import SchemaInfoViewer from '../../Schema/SchemaInfoViewer/SchemaInfoViewer';
9
9
  import {IndexInfoViewer} from '../../../../components/IndexInfoViewer/IndexInfoViewer';
10
10
 
11
- import type {EPathType} from '../../../../types/api/schema';
12
- import {isColumnEntityType, isTableType, mapPathTypeToNavigationTreeType} from '../../utils/schema';
11
+ import {EPathType} from '../../../../types/api/schema';
12
+ import {isColumnEntityType, isTableType} from '../../utils/schema';
13
13
  import {AutoFetcher} from '../../../../utils/autofetcher';
14
14
  //@ts-ignore
15
15
  import {getSchema} from '../../../../store/reducers/schema';
@@ -114,16 +114,23 @@ function Overview(props: OverviewProps) {
114
114
  };
115
115
 
116
116
  const renderContent = () => {
117
- switch (mapPathTypeToNavigationTreeType(props.type)) {
118
- case 'index':
119
- return (
120
- <IndexInfoViewer data={schemaData} />
121
- );
122
- default:
123
- return (
124
- <SchemaInfoViewer fullPath={currentItem.Path} data={schemaData} />
125
- );
126
- }
117
+ // verbose mapping to guarantee a correct render for new path types
118
+ // TS will error when a new type is added but not mapped here
119
+ const pathTypeToComponent: Record<EPathType, (() => ReactNode) | undefined> = {
120
+ [EPathType.EPathTypeInvalid]: undefined,
121
+ [EPathType.EPathTypeDir]: undefined,
122
+ [EPathType.EPathTypeTable]: undefined,
123
+ [EPathType.EPathTypeSubDomain]: undefined,
124
+ [EPathType.EPathTypeTableIndex]: () => <IndexInfoViewer data={schemaData} />,
125
+ [EPathType.EPathTypeExtSubDomain]: undefined,
126
+ [EPathType.EPathTypeColumnStore]: undefined,
127
+ [EPathType.EPathTypeColumnTable]: undefined,
128
+ [EPathType.EPathTypeCdcStream]: undefined,
129
+ };
130
+
131
+ return (props.type && pathTypeToComponent[props.type]?.()) || (
132
+ <SchemaInfoViewer fullPath={currentItem.Path} data={schemaData} />
133
+ );
127
134
  }
128
135
 
129
136
  return loading && !wasLoaded ? (
@@ -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
  ) : (
@@ -33,6 +33,7 @@ import {
33
33
  DEFAULT_SIZE_RESULT_PANE_KEY,
34
34
  DEFAULT_TABLE_SETTINGS,
35
35
  SAVED_QUERIES_KEY,
36
+ QUERY_INITIAL_RUN_ACTION_KEY,
36
37
  } from '../../../utils/constants';
37
38
  import {prepareQueryResponse} from '../../../utils/index';
38
39
 
@@ -538,7 +539,13 @@ function QueryEditor(props) {
538
539
  };
539
540
 
540
541
  const renderControls = () => {
541
- const {executeQuery, explainQuery, savedQueries, selectRunAction} = props;
542
+ const {
543
+ executeQuery,
544
+ explainQuery,
545
+ savedQueries,
546
+ selectRunAction,
547
+ setSettingValue,
548
+ } = props;
542
549
  const {runAction} = executeQuery;
543
550
  const runIsDisabled = !executeQuery.input || executeQuery.loading;
544
551
  const runText = _.find(RUN_ACTIONS, {value: runAction}).content;
@@ -546,7 +553,10 @@ function QueryEditor(props) {
546
553
  const menuItems = RUN_ACTIONS.map((action) => {
547
554
  return {
548
555
  text: action.content,
549
- action: () => selectRunAction(action.value),
556
+ action: () => {
557
+ selectRunAction(action.value);
558
+ setSettingValue(QUERY_INITIAL_RUN_ACTION_KEY, action.value);
559
+ },
550
560
  };
551
561
  });
552
562