ydb-embedded-ui 1.9.0 → 1.10.2
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 +43 -0
- package/dist/components/IndexInfoViewer/IndexInfoViewer.tsx +10 -7
- package/dist/components/InfoViewer/InfoViewer.scss +1 -2
- package/dist/components/InfoViewer/utils.ts +18 -10
- 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/DetailedOverview/DetailedOverview.tsx +15 -14
- 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/QueryEditor/QueryEditor.js +12 -2
- package/dist/containers/Tenant/Schema/SchemaInfoViewer/SchemaInfoViewer.js +164 -42
- package/dist/containers/Tenant/Schema/SchemaInfoViewer/SchemaInfoViewer.scss +18 -0
- package/dist/containers/Tenant/utils/schema.ts +73 -28
- package/dist/containers/Tenant/utils/schemaActions.ts +45 -32
- package/dist/services/api.js +13 -9
- package/dist/store/reducers/executeQuery.js +4 -3
- 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/settings.js +20 -13
- package/dist/store/reducers/shardsWorkload.js +32 -4
- package/dist/types/api/schema.ts +123 -4
- package/dist/types/api/storage.ts +1 -1
- package/dist/utils/constants.js +4 -0
- package/dist/utils/index.js +7 -3
- package/dist/utils/pdisk.ts +2 -2
- package/package.json +2 -2
@@ -3,63 +3,185 @@ import PropTypes from 'prop-types';
|
|
3
3
|
import cn from 'bem-cn-lite';
|
4
4
|
import './SchemaInfoViewer.scss';
|
5
5
|
|
6
|
-
import {formatCPU, formatBytes} from '../../../../utils';
|
6
|
+
import {formatCPU, formatBytes, formatNumber, formatBps} from '../../../../utils';
|
7
7
|
|
8
|
-
import InfoViewer from '../../../../components/InfoViewer
|
8
|
+
import {InfoViewer, createInfoFormatter} from '../../../../components/InfoViewer';
|
9
9
|
|
10
10
|
const b = cn('schema-info-viewer');
|
11
11
|
|
12
|
+
const formatTabletMetricsItem = createInfoFormatter({
|
13
|
+
values: {
|
14
|
+
CPU: formatCPU,
|
15
|
+
Memory: formatBytes,
|
16
|
+
Storage: formatBytes,
|
17
|
+
Network: formatBps,
|
18
|
+
ReadThroughput: formatBps,
|
19
|
+
WriteThroughput: formatBps,
|
20
|
+
},
|
21
|
+
defaultValueFormatter: formatNumber,
|
22
|
+
});
|
23
|
+
|
24
|
+
const formatFollowerGroupItem = createInfoFormatter({
|
25
|
+
values: {
|
26
|
+
FollowerCount: formatNumber,
|
27
|
+
},
|
28
|
+
});
|
29
|
+
|
30
|
+
const formatPartitionConfigItem = createInfoFormatter({
|
31
|
+
values: {
|
32
|
+
FollowerCount: formatNumber,
|
33
|
+
CrossDataCenterFollowerCount: formatNumber,
|
34
|
+
},
|
35
|
+
});
|
36
|
+
|
37
|
+
const formatTableStatsItem = createInfoFormatter({
|
38
|
+
values: {
|
39
|
+
DataSize: formatBytes,
|
40
|
+
IndexSize: formatBytes,
|
41
|
+
LastAccessTime: (value) => value > 0 ? new Date(Number(value)).toUTCString() : 'N/A',
|
42
|
+
LastUpdateTime: (value) => value > 0 ? new Date(Number(value)).toUTCString() : 'N/A',
|
43
|
+
},
|
44
|
+
defaultValueFormatter: formatNumber,
|
45
|
+
});
|
46
|
+
|
47
|
+
const formatTableStats = (fields) => Object.entries(fields)
|
48
|
+
.map(([label, value]) => formatTableStatsItem(label, value))
|
49
|
+
.filter(({value}) => Boolean(value));
|
50
|
+
|
12
51
|
class SchemaInfoViewer extends React.Component {
|
13
52
|
static propTypes = {
|
14
53
|
data: PropTypes.object.isRequired,
|
15
54
|
};
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
return formatBytes(value);
|
21
|
-
} else {
|
22
|
-
return value;
|
55
|
+
|
56
|
+
renderItem(itemData, title) {
|
57
|
+
if (!Array.isArray(itemData) || !itemData.length) {
|
58
|
+
return null;
|
23
59
|
}
|
24
|
-
|
60
|
+
|
61
|
+
return (
|
62
|
+
<div className={b('item')}>
|
63
|
+
<InfoViewer
|
64
|
+
title={title}
|
65
|
+
info={itemData}
|
66
|
+
/>
|
67
|
+
</div>
|
68
|
+
);
|
69
|
+
}
|
70
|
+
|
71
|
+
renderContent(data) {
|
72
|
+
const {PathDescription = {}} = data;
|
73
|
+
const {TableStats = {}, TabletMetrics = {}, Table: {PartitionConfig = {}} = {}} = PathDescription;
|
74
|
+
const {
|
75
|
+
PartCount,
|
76
|
+
RowCount,
|
77
|
+
DataSize,
|
78
|
+
IndexSize,
|
79
|
+
|
80
|
+
LastAccessTime,
|
81
|
+
LastUpdateTime,
|
82
|
+
|
83
|
+
ImmediateTxCompleted,
|
84
|
+
PlannedTxCompleted,
|
85
|
+
TxRejectedByOverload,
|
86
|
+
TxRejectedBySpace,
|
87
|
+
TxCompleteLagMsec,
|
88
|
+
InFlightTxCount,
|
89
|
+
|
90
|
+
RowUpdates,
|
91
|
+
RowDeletes,
|
92
|
+
RowReads,
|
93
|
+
RangeReads,
|
94
|
+
RangeReadRows,
|
95
|
+
|
96
|
+
...restTableStats
|
97
|
+
} = TableStats;
|
98
|
+
const {FollowerGroups, FollowerCount, CrossDataCenterFollowerCount} = PartitionConfig;
|
99
|
+
|
100
|
+
const tableStatsInfo = [
|
101
|
+
formatTableStats({
|
102
|
+
PartCount,
|
103
|
+
RowCount,
|
104
|
+
DataSize,
|
105
|
+
IndexSize,
|
106
|
+
}),
|
107
|
+
formatTableStats({
|
108
|
+
LastAccessTime,
|
109
|
+
LastUpdateTime,
|
110
|
+
}),
|
111
|
+
formatTableStats({
|
112
|
+
ImmediateTxCompleted,
|
113
|
+
PlannedTxCompleted,
|
114
|
+
TxRejectedByOverload,
|
115
|
+
TxRejectedBySpace,
|
116
|
+
TxCompleteLagMsec,
|
117
|
+
InFlightTxCount,
|
118
|
+
}),
|
119
|
+
formatTableStats({
|
120
|
+
RowUpdates,
|
121
|
+
RowDeletes,
|
122
|
+
RowReads,
|
123
|
+
RangeReads,
|
124
|
+
RangeReadRows,
|
125
|
+
}),
|
126
|
+
formatTableStats(restTableStats),
|
127
|
+
];
|
128
|
+
|
129
|
+
const tabletMetricsInfo = Object.keys(TabletMetrics).map((key) =>
|
130
|
+
formatTabletMetricsItem(key, TabletMetrics[key])
|
131
|
+
);
|
132
|
+
|
133
|
+
const partitionConfigInfo = [];
|
134
|
+
|
135
|
+
if (Array.isArray(FollowerGroups) && FollowerGroups.length > 0) {
|
136
|
+
partitionConfigInfo.push(...Object.keys(FollowerGroups[0]).map((key) =>
|
137
|
+
formatFollowerGroupItem(key, FollowerGroups[0][key])
|
138
|
+
));
|
139
|
+
} else if (FollowerCount !== undefined) {
|
140
|
+
partitionConfigInfo.push(
|
141
|
+
formatPartitionConfigItem('FollowerCount', FollowerCount)
|
142
|
+
);
|
143
|
+
} else if (CrossDataCenterFollowerCount !== undefined) {
|
144
|
+
partitionConfigInfo.push(
|
145
|
+
formatPartitionConfigItem('CrossDataCenterFollowerCount', CrossDataCenterFollowerCount)
|
146
|
+
);
|
147
|
+
}
|
148
|
+
|
149
|
+
if ([
|
150
|
+
tabletMetricsInfo,
|
151
|
+
partitionConfigInfo,
|
152
|
+
tableStatsInfo.flat(),
|
153
|
+
].flat().length === 0) {
|
154
|
+
return (
|
155
|
+
<div className={b('item')}>Empty</div>
|
156
|
+
);
|
157
|
+
}
|
158
|
+
|
159
|
+
return (
|
160
|
+
<div className={b('row')}>
|
161
|
+
{tabletMetricsInfo.length > 0 || partitionConfigInfo.length > 0 ? (
|
162
|
+
<div className={b('col')}>
|
163
|
+
{this.renderItem(tabletMetricsInfo, 'Tablet Metrics')}
|
164
|
+
{this.renderItem(partitionConfigInfo, 'Partition Config')}
|
165
|
+
</div>
|
166
|
+
) : null}
|
167
|
+
<div className={b('col')}>
|
168
|
+
{tableStatsInfo.map((info, index) => (
|
169
|
+
<React.Fragment key={index}>
|
170
|
+
{this.renderItem(info, index === 0 ? 'Table Stats' : undefined)}
|
171
|
+
</React.Fragment>
|
172
|
+
))}
|
173
|
+
</div>
|
174
|
+
</div>
|
175
|
+
);
|
176
|
+
}
|
177
|
+
|
25
178
|
render() {
|
26
179
|
const {data} = this.props;
|
27
180
|
|
28
181
|
if (data) {
|
29
|
-
const {PathDescription = {}} = data;
|
30
|
-
const {TableStats = {}, TabletMetrics = {}} = PathDescription;
|
31
|
-
const {PartCount, ...restTableStats} = TableStats;
|
32
|
-
|
33
|
-
const priorityInfo = [{
|
34
|
-
label: 'PartCount',
|
35
|
-
value: PartCount,
|
36
|
-
}].filter(({value}) => value !== undefined);
|
37
|
-
|
38
|
-
const tableStatsInfo = Object.keys(restTableStats).map((key) => ({
|
39
|
-
label: key,
|
40
|
-
value: TableStats[key].toString(),
|
41
|
-
}));
|
42
|
-
|
43
|
-
const tabletMetricsInfo = Object.keys(TabletMetrics).map((key) => ({
|
44
|
-
label: key,
|
45
|
-
value: this.formatTabletMetricsValue(key, TabletMetrics[key].toString()),
|
46
|
-
}));
|
47
|
-
|
48
|
-
const generalInfo = [
|
49
|
-
...priorityInfo,
|
50
|
-
...tabletMetricsInfo,
|
51
|
-
...tableStatsInfo,
|
52
|
-
];
|
53
|
-
|
54
182
|
return (
|
55
183
|
<div className={b()}>
|
56
|
-
|
57
|
-
{generalInfo.length ? (
|
58
|
-
<InfoViewer info={generalInfo}></InfoViewer>
|
59
|
-
) : (
|
60
|
-
<div>Empty</div>
|
61
|
-
)}
|
62
|
-
</div>
|
184
|
+
{this.renderContent(data)}
|
63
185
|
</div>
|
64
186
|
);
|
65
187
|
} else {
|
@@ -1,6 +1,24 @@
|
|
1
1
|
.schema-info-viewer {
|
2
2
|
overflow: auto;
|
3
3
|
|
4
|
+
&__row {
|
5
|
+
display: flex;
|
6
|
+
flex-wrap: wrap;
|
7
|
+
justify-content: flex-start;
|
8
|
+
align-items: flex-start;
|
9
|
+
}
|
10
|
+
|
11
|
+
&__col {
|
12
|
+
display: flex;
|
13
|
+
flex-direction: column;
|
14
|
+
justify-content: flex-start;
|
15
|
+
align-items: flex-start;
|
16
|
+
|
17
|
+
& + & {
|
18
|
+
margin-left: 50px;
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
4
22
|
&__item {
|
5
23
|
margin-bottom: 20px;
|
6
24
|
|
@@ -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,46 @@ 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
|
+
|
118
|
+
table: TABLE_SET,
|
119
|
+
column_table: TABLE_SET,
|
120
|
+
|
121
|
+
index_table: JUST_COPY,
|
122
|
+
topic: JUST_COPY,
|
123
|
+
|
124
|
+
index: EMPTY_SET,
|
125
|
+
};
|
126
|
+
|
127
|
+
return nodeTypeToActions[type];
|
115
128
|
};
|
package/dist/services/api.js
CHANGED
@@ -83,7 +83,6 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
|
|
83
83
|
path,
|
84
84
|
enums: true,
|
85
85
|
backup: false,
|
86
|
-
partition_config: false,
|
87
86
|
partition_stats: false,
|
88
87
|
partitioning_info: false,
|
89
88
|
},
|
@@ -147,14 +146,19 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
|
|
147
146
|
state: 0,
|
148
147
|
});
|
149
148
|
}
|
150
|
-
sendQuery(query, database, action, stats) {
|
151
|
-
return this.post(
|
152
|
-
query,
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
149
|
+
sendQuery({query, database, action, stats}, {concurrentId} = {}) {
|
150
|
+
return this.post(
|
151
|
+
this.getPath('/viewer/json/query'),
|
152
|
+
{
|
153
|
+
query,
|
154
|
+
database,
|
155
|
+
action,
|
156
|
+
stats,
|
157
|
+
timeout: 600000,
|
158
|
+
},
|
159
|
+
null,
|
160
|
+
{concurrentId},
|
161
|
+
);
|
158
162
|
}
|
159
163
|
getExplainQuery(query, database) {
|
160
164
|
return this.post(this.getPath('/viewer/json/query'), {
|
@@ -1,7 +1,8 @@
|
|
1
1
|
import {createRequestActionTypes, createApiRequest} from '../utils';
|
2
2
|
import '../../services/api';
|
3
3
|
import {getValueFromLS, parseJson} from '../../utils/utils';
|
4
|
-
import {QUERIES_HISTORY_KEY} from '../../utils/constants';
|
4
|
+
import {QUERIES_HISTORY_KEY, QUERY_INITIAL_RUN_ACTION_KEY} from '../../utils/constants';
|
5
|
+
import {readSavedSettingsValue} from './settings';
|
5
6
|
|
6
7
|
const MAXIMUM_QUERIES_IN_HISTORY = 20;
|
7
8
|
|
@@ -39,7 +40,7 @@ const initialState = {
|
|
39
40
|
? MAXIMUM_QUERIES_IN_HISTORY - 1
|
40
41
|
: queriesHistoryInitial.length - 1,
|
41
42
|
},
|
42
|
-
runAction: RUN_ACTIONS_VALUES.script,
|
43
|
+
runAction: readSavedSettingsValue(QUERY_INITIAL_RUN_ACTION_KEY, RUN_ACTIONS_VALUES.script),
|
43
44
|
monacoHotKey: null,
|
44
45
|
};
|
45
46
|
|
@@ -141,7 +142,7 @@ const executeQuery = (state = initialState, action) => {
|
|
141
142
|
|
142
143
|
export const sendQuery = ({query, database, action}) => {
|
143
144
|
return createApiRequest({
|
144
|
-
request: window.api.sendQuery(query, database, action, 'profile'),
|
145
|
+
request: window.api.sendQuery({query, database, action, stats: 'profile'}),
|
145
146
|
actions: SEND_QUERY,
|
146
147
|
dataHandler: (result) => {
|
147
148
|
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)) {
|
@@ -1,4 +1,11 @@
|
|
1
|
-
import {
|
1
|
+
import {
|
2
|
+
defaultUserSettings,
|
3
|
+
ALL,
|
4
|
+
SAVED_QUERIES_KEY,
|
5
|
+
THEME_KEY,
|
6
|
+
TENANT_INITIAL_TAB_KEY,
|
7
|
+
QUERY_INITIAL_RUN_ACTION_KEY,
|
8
|
+
} from '../../utils/constants';
|
2
9
|
import '../../services/api';
|
3
10
|
import {getValueFromLS} from '../../utils/utils';
|
4
11
|
|
@@ -7,24 +14,24 @@ const SET_SETTING_VALUE = 'settings/SET_VALUE';
|
|
7
14
|
|
8
15
|
const userSettings = window.userSettings || {};
|
9
16
|
const systemSettings = window.systemSettings || {};
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
: getValueFromLS(TENANT_INITIAL_TAB_KEY);
|
17
|
+
|
18
|
+
export function readSavedSettingsValue(key, defaultValue) {
|
19
|
+
const savedValue = window.web_version
|
20
|
+
? userSettings[key]
|
21
|
+
: getValueFromLS(key);
|
22
|
+
|
23
|
+
return savedValue ?? defaultValue;
|
24
|
+
}
|
19
25
|
|
20
26
|
export const initialState = {
|
21
27
|
problemFilter: ALL,
|
22
28
|
userSettings: {
|
23
29
|
...defaultUserSettings,
|
24
30
|
...userSettings,
|
25
|
-
theme,
|
26
|
-
[SAVED_QUERIES_KEY]:
|
27
|
-
[TENANT_INITIAL_TAB_KEY]:
|
31
|
+
theme: readSavedSettingsValue(THEME_KEY, 'light'),
|
32
|
+
[SAVED_QUERIES_KEY]: readSavedSettingsValue(SAVED_QUERIES_KEY, '[]'),
|
33
|
+
[TENANT_INITIAL_TAB_KEY]: readSavedSettingsValue(TENANT_INITIAL_TAB_KEY),
|
34
|
+
[QUERY_INITIAL_RUN_ACTION_KEY]: readSavedSettingsValue(QUERY_INITIAL_RUN_ACTION_KEY),
|
28
35
|
},
|
29
36
|
systemSettings,
|
30
37
|
};
|
@@ -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') {
|