ydb-embedded-ui 1.9.0 → 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.
- package/CHANGELOG.md +22 -0
- package/dist/containers/Storage/Pdisk/Pdisk.tsx +25 -33
- package/dist/containers/Storage/Vdisk/Vdisk.js +2 -0
- package/dist/containers/Tablet/Tablet.js +2 -2
- package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +24 -14
- package/dist/containers/Tenant/Diagnostics/HotKeys/HotKeys.js +3 -3
- package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +20 -13
- package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.js +80 -10
- package/dist/containers/Tenant/utils/schema.ts +73 -28
- package/dist/containers/Tenant/utils/schemaActions.ts +42 -32
- package/dist/services/api.js +13 -8
- package/dist/store/reducers/executeQuery.js +1 -1
- package/dist/store/reducers/executeTopQueries.js +1 -1
- package/dist/store/reducers/olapStats.js +5 -1
- package/dist/store/reducers/preview.js +1 -1
- package/dist/store/reducers/shardsWorkload.js +32 -4
- package/dist/types/api/schema.ts +7 -1
- package/dist/types/api/storage.ts +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,27 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [1.10.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.9.0...v1.10.0) (2022-08-10)
|
4
|
+
|
5
|
+
|
6
|
+
### Features
|
7
|
+
|
8
|
+
* **TopShards:** add DataSize column ([cbcd047](https://github.com/ydb-platform/ydb-embedded-ui/commit/cbcd047d277f699a67bc002a5542f3b9f6a0c942))
|
9
|
+
* **TopShards:** sort table data on backend ([dc28c5c](https://github.com/ydb-platform/ydb-embedded-ui/commit/dc28c5c75b0036480bf804d49f82fc54eac98c8e))
|
10
|
+
|
11
|
+
|
12
|
+
### Bug Fixes
|
13
|
+
|
14
|
+
* add concurrentId for sendQuery request ([dc6b32a](https://github.com/ydb-platform/ydb-embedded-ui/commit/dc6b32a8fd51064ddeca2fc60a0f08a725216334))
|
15
|
+
* **Storage:** display pdisk type in tooltip ([2b03a35](https://github.com/ydb-platform/ydb-embedded-ui/commit/2b03a35fc11ddeae3bdd30a0690b324ae917f5c3))
|
16
|
+
* **Tablet:** change Kill to Restart ([dd585b1](https://github.com/ydb-platform/ydb-embedded-ui/commit/dd585b1d1a6a5ddb484a702523773b169900f582))
|
17
|
+
* **Tenant:** add missing schema node types ([62a0ecb](https://github.com/ydb-platform/ydb-embedded-ui/commit/62a0ecb848dbcee53e18535cbf7c03a731d0cfeb))
|
18
|
+
* **Tenant:** ensure correct behavior for new schema node types ([f80c381](https://github.com/ydb-platform/ydb-embedded-ui/commit/f80c38152656e8bbbe51ec38b29fc0d954c361cc))
|
19
|
+
* **Tenant:** use new schema icons ([389a921](https://github.com/ydb-platform/ydb-embedded-ui/commit/389a9214c64b1adb183fa0c6caa6f2ec536dbef3))
|
20
|
+
* **TopShards:** disable virtualization for table ([006d3d9](https://github.com/ydb-platform/ydb-embedded-ui/commit/006d3d9fb9a4744b8bb4ad03e53693199213f80e))
|
21
|
+
* **TopShards:** format DataSize value ([c51ce66](https://github.com/ydb-platform/ydb-embedded-ui/commit/c51ce66286f6454f7252d1194628ee5a50aafba2))
|
22
|
+
* **TopShards:** only allow DESC sort ([6aa326f](https://github.com/ydb-platform/ydb-embedded-ui/commit/6aa326fc4b8165f00f8b3ecf5becdb0943ed57af))
|
23
|
+
* **TopShards:** substring tenant name out of shards path ([9e57672](https://github.com/ydb-platform/ydb-embedded-ui/commit/9e5767222c7dac7734c68abd08067cea507b1e15))
|
24
|
+
|
3
25
|
## [1.9.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.8.8...v1.9.0) (2022-07-29)
|
4
26
|
|
5
27
|
|
@@ -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
|
40
|
-
|
41
|
-
|
42
|
-
|
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?:
|
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,
|
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
|
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
|
-
|
366
|
+
Restart
|
367
367
|
</Button>
|
368
368
|
{this.hasHiveId() ? (
|
369
369
|
<React.Fragment>
|
@@ -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
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
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
|
12
|
-
import {isColumnEntityType, isTableType
|
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
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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({
|
53
|
-
|
54
|
-
|
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
|
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={
|
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
|
) : (
|
@@ -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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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 = (
|
36
|
-
|
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
|
-
|
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
|
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
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
};
|
package/dist/services/api.js
CHANGED
@@ -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(
|
152
|
-
query,
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
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(
|
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
|
13
|
-
return
|
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(
|
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') {
|
package/dist/types/api/schema.ts
CHANGED
@@ -108,10 +108,16 @@ export enum EPathType {
|
|
108
108
|
EPathTypeInvalid = 'EPathTypeInvalid',
|
109
109
|
EPathTypeDir = 'EPathTypeDir',
|
110
110
|
EPathTypeTable = 'EPathTypeTable',
|
111
|
+
|
111
112
|
EPathTypeSubDomain = 'EPathTypeSubDomain',
|
113
|
+
|
114
|
+
EPathTypeTableIndex = 'EPathTypeTableIndex',
|
115
|
+
EPathTypeExtSubDomain = 'EPathTypeExtSubDomain',
|
116
|
+
|
112
117
|
EPathTypeColumnStore = 'EPathTypeColumnStore',
|
113
118
|
EPathTypeColumnTable = 'EPathTypeColumnTable',
|
114
|
-
|
119
|
+
EPathTypeCdcStream = 'EPathTypeCdcStream',
|
120
|
+
|
115
121
|
}
|
116
122
|
|
117
123
|
export enum EPathSubType {
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "ydb-embedded-ui",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.10.0",
|
4
4
|
"files": [
|
5
5
|
"dist"
|
6
6
|
],
|
@@ -40,7 +40,7 @@
|
|
40
40
|
"reselect": "4.0.0",
|
41
41
|
"sass": "1.32.8",
|
42
42
|
"web-vitals": "1.1.2",
|
43
|
-
"ydb-ui-components": "2.4.
|
43
|
+
"ydb-ui-components": "2.4.1"
|
44
44
|
},
|
45
45
|
"scripts": {
|
46
46
|
"start": "react-app-rewired start",
|