ydb-embedded-ui 4.21.1 → 4.22.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 +14 -0
- package/dist/components/VirtualTable/TableChunk.tsx +2 -1
- package/dist/components/VirtualTable/VirtualTable.tsx +1 -1
- package/dist/containers/Node/NodeStructure/Pdisk.tsx +4 -1
- package/dist/containers/Nodes/getNodesColumns.tsx +57 -13
- package/dist/containers/Tenant/Diagnostics/Network/Network.js +5 -10
- package/dist/containers/Tenant/Diagnostics/Network/utils.ts +6 -0
- package/dist/containers/Tenant/Query/ExecuteResult/ExecuteResult.scss +13 -5
- package/dist/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx +72 -18
- package/dist/containers/Tenant/Query/ExplainResult/ExplainResult.js +2 -1
- package/dist/containers/Tenant/Query/ExplainResult/utils.ts +6 -0
- package/dist/containers/Tenant/Query/QueryEditor/QueryEditor.js +11 -24
- package/dist/containers/Tenant/Query/utils/getPreparedResult.ts +4 -5
- package/dist/containers/UserSettings/i18n/en.json +3 -0
- package/dist/containers/UserSettings/i18n/ru.json +3 -0
- package/dist/containers/UserSettings/settings.ts +7 -0
- package/dist/store/reducers/executeQuery.ts +4 -3
- package/dist/store/reducers/nodes/types.ts +11 -1
- package/dist/store/reducers/nodes/utils.ts +6 -0
- package/dist/store/reducers/settings/settings.ts +2 -0
- package/dist/types/api/netInfo.ts +1 -1
- package/dist/types/api/nodes.ts +24 -0
- package/dist/types/api/query.ts +23 -8
- package/dist/types/store/query.ts +6 -0
- package/dist/utils/constants.ts +3 -0
- package/dist/utils/developerUI/__test__/developerUI.test.ts +50 -0
- package/dist/utils/developerUI/developerUI.ts +42 -0
- package/dist/utils/diagnostics.ts +1 -0
- package/dist/utils/query.ts +60 -12
- package/package.json +1 -1
- package/dist/utils/developerUI.ts +0 -32
- package/dist/utils/index.js +0 -9
- /package/dist/{components/VirtualTable/utils.ts → utils/index.ts} +0 -0
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [4.22.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.21.1...v4.22.0) (2023-11-27)
|
4
|
+
|
5
|
+
|
6
|
+
### Features
|
7
|
+
|
8
|
+
* **Query:** enable queries with multiple resultsets ([#595](https://github.com/ydb-platform/ydb-embedded-ui/issues/595)) ([2eedfb6](https://github.com/ydb-platform/ydb-embedded-ui/commit/2eedfb6ec3be932c7399bb67de901798c0b31b50))
|
9
|
+
* **TenantOverview:** add columns to memory table ([#593](https://github.com/ydb-platform/ydb-embedded-ui/issues/593)) ([6379577](https://github.com/ydb-platform/ydb-embedded-ui/commit/6379577782cfa69de9fb39640d2a143f1670be39))
|
10
|
+
|
11
|
+
|
12
|
+
### Bug Fixes
|
13
|
+
|
14
|
+
* fix disks developer UI links for paths with nodeId ([#594](https://github.com/ydb-platform/ydb-embedded-ui/issues/594)) ([7f5a783](https://github.com/ydb-platform/ydb-embedded-ui/commit/7f5a78393d0c23e584ad73040fd0e73d404e5d01))
|
15
|
+
* **TopShards:** sort by InFlightTxCount ([#591](https://github.com/ydb-platform/ydb-embedded-ui/issues/591)) ([eb3592d](https://github.com/ydb-platform/ydb-embedded-ui/commit/eb3592d69a465814de27e8b1e368b34cc60fed2f))
|
16
|
+
|
3
17
|
## [4.21.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.21.0...v4.21.1) (2023-11-17)
|
4
18
|
|
5
19
|
|
@@ -1,8 +1,9 @@
|
|
1
1
|
import {useEffect, useRef, memo} from 'react';
|
2
2
|
|
3
|
+
import {getArray} from '../../utils';
|
4
|
+
|
3
5
|
import type {Column, Chunk, GetRowClassName} from './types';
|
4
6
|
import {LoadingTableRow, TableRow} from './TableRow';
|
5
|
-
import {getArray} from './utils';
|
6
7
|
|
7
8
|
// With original memo generic types are lost
|
8
9
|
const typedMemo: <T>(Component: T) => T = memo;
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import {useState, useReducer, useRef, useCallback, useEffect} from 'react';
|
2
2
|
|
3
3
|
import type {IResponseError} from '../../types/api/error';
|
4
|
+
import {getArray} from '../../utils';
|
4
5
|
|
5
6
|
import {TableWithControlsLayout} from '../TableWithControlsLayout/TableWithControlsLayout';
|
6
7
|
import {ResponseError} from '../Errors/ResponseError';
|
@@ -31,7 +32,6 @@ import {TableHead} from './TableHead';
|
|
31
32
|
import {TableChunk} from './TableChunk';
|
32
33
|
import {EmptyTableRow} from './TableRow';
|
33
34
|
import {useIntersectionObserver} from './useIntersectionObserver';
|
34
|
-
import {getArray} from './utils';
|
35
35
|
import i18n from './i18n';
|
36
36
|
import {b} from './shared';
|
37
37
|
|
@@ -16,7 +16,10 @@ import {bytesToGB} from '../../../utils/utils';
|
|
16
16
|
import {formatStorageValuesToGb} from '../../../utils/dataFormatters/dataFormatters';
|
17
17
|
import {getPDiskType} from '../../../utils/pdisk';
|
18
18
|
import {DEFAULT_TABLE_SETTINGS} from '../../../utils/constants';
|
19
|
-
import {
|
19
|
+
import {
|
20
|
+
createPDiskDeveloperUILink,
|
21
|
+
createVDiskDeveloperUILink,
|
22
|
+
} from '../../../utils/developerUI/developerUI';
|
20
23
|
import EntityStatus from '../../../components/EntityStatus/EntityStatus';
|
21
24
|
import InfoViewer, {type InfoViewerItem} from '../../../components/InfoViewer/InfoViewer';
|
22
25
|
import {ProgressViewer} from '../../../components/ProgressViewer/ProgressViewer';
|
@@ -28,6 +28,9 @@ const NODES_COLUMNS_IDS = {
|
|
28
28
|
Tablets: 'Tablets',
|
29
29
|
TopNodesLoadAverage: 'TopNodesLoadAverage',
|
30
30
|
TopNodesMemory: 'TopNodesMemory',
|
31
|
+
SharedCacheUsage: 'SharedCacheUsage',
|
32
|
+
MemoryUsedInAlloc: 'MemoryUsedInAlloc',
|
33
|
+
TotalSessions: 'TotalSessions',
|
31
34
|
};
|
32
35
|
|
33
36
|
interface GetNodesColumnsProps {
|
@@ -184,23 +187,61 @@ const topNodesLoadAverageColumn: NodesColumn = {
|
|
184
187
|
|
185
188
|
const topNodesMemoryColumn: NodesColumn = {
|
186
189
|
name: NODES_COLUMNS_IDS.TopNodesMemory,
|
187
|
-
header: '
|
188
|
-
render: ({row}) =>
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
190
|
+
header: 'Process',
|
191
|
+
render: ({row}) => (
|
192
|
+
<ProgressViewer
|
193
|
+
value={row.MemoryUsed}
|
194
|
+
capacity={row.MemoryLimit}
|
195
|
+
formatValues={formatStorageValuesToGb}
|
196
|
+
colorizeProgress={true}
|
197
|
+
/>
|
198
|
+
),
|
199
|
+
align: DataTable.LEFT,
|
200
|
+
width: 140,
|
201
|
+
sortable: false,
|
202
|
+
};
|
203
|
+
|
204
|
+
const sharedCacheUsageColumn: NodesColumn = {
|
205
|
+
name: NODES_COLUMNS_IDS.SharedCacheUsage,
|
206
|
+
header: 'Tablet Cache',
|
207
|
+
render: ({row}) => (
|
208
|
+
<ProgressViewer
|
209
|
+
value={row.SharedCacheUsed}
|
210
|
+
capacity={row.SharedCacheLimit}
|
211
|
+
formatValues={formatStorageValuesToGb}
|
212
|
+
colorizeProgress={true}
|
213
|
+
/>
|
214
|
+
),
|
215
|
+
align: DataTable.LEFT,
|
216
|
+
width: 140,
|
217
|
+
sortable: false,
|
218
|
+
};
|
219
|
+
|
220
|
+
const memoryUsedInAllocColumn: NodesColumn = {
|
221
|
+
name: NODES_COLUMNS_IDS.MemoryUsedInAlloc,
|
222
|
+
header: 'Query Runtime',
|
223
|
+
render: ({row}) => (
|
224
|
+
<ProgressViewer
|
225
|
+
value={row.MemoryUsedInAlloc}
|
226
|
+
capacity={row.MemoryLimit}
|
227
|
+
formatValues={formatStorageValuesToGb}
|
228
|
+
colorizeProgress={true}
|
229
|
+
/>
|
230
|
+
),
|
199
231
|
align: DataTable.LEFT,
|
200
232
|
width: 140,
|
201
233
|
sortable: false,
|
202
234
|
};
|
203
235
|
|
236
|
+
const sessionsColumn: NodesColumn = {
|
237
|
+
name: NODES_COLUMNS_IDS.TotalSessions,
|
238
|
+
header: 'Sessions',
|
239
|
+
render: ({row}) => row.TotalSessions ?? '—',
|
240
|
+
align: DataTable.RIGHT,
|
241
|
+
width: 100,
|
242
|
+
sortable: false,
|
243
|
+
};
|
244
|
+
|
204
245
|
export function getNodesColumns({tabletsPath, getNodeRef}: GetNodesColumnsProps): NodesColumn[] {
|
205
246
|
return [
|
206
247
|
nodeIdColumn,
|
@@ -241,8 +282,11 @@ export function getTopNodesByMemoryColumns({
|
|
241
282
|
nodeIdColumn,
|
242
283
|
getHostColumn(getNodeRef),
|
243
284
|
uptimeColumn,
|
244
|
-
topNodesMemoryColumn,
|
245
285
|
topNodesLoadAverageColumn,
|
286
|
+
topNodesMemoryColumn,
|
287
|
+
sharedCacheUsageColumn,
|
288
|
+
memoryUsedInAllocColumn,
|
289
|
+
sessionsColumn,
|
246
290
|
getTabletsColumn(tabletsPath),
|
247
291
|
];
|
248
292
|
}
|
@@ -18,6 +18,8 @@ import {changeFilter, ProblemFilterValues} from '../../../../store/reducers/sett
|
|
18
18
|
import {AutoFetcher} from '../../../../utils/autofetcher';
|
19
19
|
import {getDefaultNodePath} from '../../../Node/NodePages';
|
20
20
|
|
21
|
+
import {getConnectedNodesCount} from './utils';
|
22
|
+
|
21
23
|
import './Network.scss';
|
22
24
|
|
23
25
|
const b = cn('network');
|
@@ -145,11 +147,6 @@ class Network extends React.Component {
|
|
145
147
|
);
|
146
148
|
};
|
147
149
|
|
148
|
-
getConnectedNodesCount = (peers) => {
|
149
|
-
const res = peers?.reduce((acc, item) => (item.Connected ? acc + 1 : acc), 0);
|
150
|
-
return res;
|
151
|
-
};
|
152
|
-
|
153
150
|
renderNodes = (nodes, isRight) => {
|
154
151
|
const {showId, showRacks, clickedNode} = this.state;
|
155
152
|
let problemNodesCount = 0;
|
@@ -171,9 +168,7 @@ class Network extends React.Component {
|
|
171
168
|
let capacity, connected;
|
172
169
|
if (!isRight && nodeInfo?.Peers) {
|
173
170
|
capacity = Object.keys(nodeInfo?.Peers).length;
|
174
|
-
connected =
|
175
|
-
nodeInfo?.Peers,
|
176
|
-
);
|
171
|
+
connected = getConnectedNodesCount(nodeInfo?.Peers);
|
177
172
|
}
|
178
173
|
|
179
174
|
if (
|
@@ -214,7 +209,7 @@ class Network extends React.Component {
|
|
214
209
|
let capacity, connected;
|
215
210
|
if (!isRight) {
|
216
211
|
capacity = nodeInfo?.Peers?.length;
|
217
|
-
connected =
|
212
|
+
connected = getConnectedNodesCount(nodeInfo?.Peers);
|
218
213
|
}
|
219
214
|
|
220
215
|
if (
|
@@ -234,7 +229,7 @@ class Network extends React.Component {
|
|
234
229
|
capacity={nodeInfo?.Peers && nodeInfo?.Peers.length}
|
235
230
|
connected={
|
236
231
|
nodeInfo?.Peers &&
|
237
|
-
|
232
|
+
getConnectedNodesCount(nodeInfo?.Peers)
|
238
233
|
}
|
239
234
|
onMouseEnter={showTooltip}
|
240
235
|
onMouseLeave={hideTooltip}
|
@@ -0,0 +1,6 @@
|
|
1
|
+
import type {TNetNodePeerInfo} from '../../../../types/api/netInfo';
|
2
|
+
|
3
|
+
// determine how many nodes have status Connected "true"
|
4
|
+
export const getConnectedNodesCount = (peers: TNetNodePeerInfo[] | undefined) => {
|
5
|
+
return peers?.reduce((acc, item) => (item.Connected ? acc + 1 : acc), 0);
|
6
|
+
};
|
@@ -13,11 +13,19 @@
|
|
13
13
|
& .data-table__table-wrapper {
|
14
14
|
padding-bottom: 0;
|
15
15
|
}
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
}
|
17
|
+
|
18
|
+
&__result-fullscreen-wrapper {
|
19
|
+
display: flex;
|
20
|
+
flex-direction: column;
|
21
|
+
|
22
|
+
width: 100%;
|
23
|
+
margin-top: 10px;
|
24
|
+
padding: 0 10px 10px;
|
25
|
+
}
|
26
|
+
|
27
|
+
&__result-tabs {
|
28
|
+
padding-left: 10px;
|
21
29
|
}
|
22
30
|
|
23
31
|
&__error {
|
@@ -1,26 +1,30 @@
|
|
1
|
-
import React, {
|
1
|
+
import React, {useEffect, useState} from 'react';
|
2
2
|
import {useDispatch} from 'react-redux';
|
3
3
|
import cn from 'bem-cn-lite';
|
4
4
|
import JSONTree from 'react-json-inspector';
|
5
5
|
|
6
|
-
import {RadioButton} from '@gravity-ui/uikit';
|
6
|
+
import {RadioButton, Tabs} from '@gravity-ui/uikit';
|
7
7
|
|
8
8
|
import CopyToClipboard from '../../../../components/CopyToClipboard/CopyToClipboard';
|
9
9
|
import Divider from '../../../../components/Divider/Divider';
|
10
10
|
import EnableFullscreenButton from '../../../../components/EnableFullscreenButton/EnableFullscreenButton';
|
11
11
|
import Fullscreen from '../../../../components/Fullscreen/Fullscreen';
|
12
12
|
import {QueryExecutionStatus} from '../../../../components/QueryExecutionStatus';
|
13
|
+
import {QueryResultTable} from '../../../../components/QueryResultTable/QueryResultTable';
|
13
14
|
|
14
15
|
import type {ValueOf} from '../../../../types/common';
|
15
16
|
import type {IQueryResult, QueryErrorResponse} from '../../../../types/store/query';
|
17
|
+
import type {ColumnType, KeyValueRow} from '../../../../types/api/query';
|
16
18
|
import {disableFullscreen} from '../../../../store/reducers/fullscreen';
|
17
19
|
import {prepareQueryError} from '../../../../utils/query';
|
18
20
|
import {useTypedSelector} from '../../../../utils/hooks';
|
21
|
+
import {getArray} from '../../../../utils';
|
19
22
|
|
20
23
|
import {PaneVisibilityToggleButtons} from '../../utils/paneVisibilityToggleHelpers';
|
21
24
|
|
22
25
|
import {ResultIssues} from '../Issues/Issues';
|
23
26
|
import {QueryDuration} from '../QueryDuration/QueryDuration';
|
27
|
+
import {getPreparedResult} from '../utils/getPreparedResult';
|
24
28
|
|
25
29
|
import './ExecuteResult.scss';
|
26
30
|
|
@@ -39,30 +43,35 @@ const resultOptions = [
|
|
39
43
|
];
|
40
44
|
|
41
45
|
interface ExecuteResultProps {
|
42
|
-
|
43
|
-
result: ReactNode;
|
46
|
+
data: IQueryResult | undefined;
|
44
47
|
stats: IQueryResult['stats'] | undefined;
|
45
48
|
error: string | QueryErrorResponse | undefined;
|
46
|
-
copyDisabled?: boolean;
|
47
49
|
isResultsCollapsed?: boolean;
|
48
50
|
onCollapseResults: VoidFunction;
|
49
51
|
onExpandResults: VoidFunction;
|
50
52
|
}
|
51
53
|
|
52
54
|
export function ExecuteResult({
|
53
|
-
|
54
|
-
result,
|
55
|
+
data,
|
55
56
|
stats,
|
56
57
|
error,
|
57
|
-
copyDisabled,
|
58
58
|
isResultsCollapsed,
|
59
59
|
onCollapseResults,
|
60
60
|
onExpandResults,
|
61
61
|
}: ExecuteResultProps) {
|
62
|
+
const [selectedResultSet, setSelectedResultSet] = useState(0);
|
62
63
|
const [activeSection, setActiveSection] = useState<SectionID>(resultOptionsIds.result);
|
64
|
+
|
63
65
|
const isFullscreen = useTypedSelector((state) => state.fullscreen);
|
64
66
|
const dispatch = useDispatch();
|
65
67
|
|
68
|
+
const resultsSetsCount = data?.resultSets?.length;
|
69
|
+
const isMulti = resultsSetsCount && resultsSetsCount > 0;
|
70
|
+
const currentResult = isMulti ? data?.resultSets?.[selectedResultSet].result : data?.result;
|
71
|
+
const currentColumns = isMulti ? data?.resultSets?.[selectedResultSet].columns : data?.columns;
|
72
|
+
const textResults = getPreparedResult(currentResult);
|
73
|
+
const copyDisabled = !textResults.length;
|
74
|
+
|
66
75
|
useEffect(() => {
|
67
76
|
return () => {
|
68
77
|
dispatch(disableFullscreen());
|
@@ -73,6 +82,37 @@ export function ExecuteResult({
|
|
73
82
|
setActiveSection(value as SectionID);
|
74
83
|
};
|
75
84
|
|
85
|
+
const renderResultTable = (
|
86
|
+
result: KeyValueRow[] | undefined,
|
87
|
+
columns: ColumnType[] | undefined,
|
88
|
+
) => {
|
89
|
+
return <QueryResultTable data={result} columns={columns} settings={{sortable: false}} />;
|
90
|
+
};
|
91
|
+
|
92
|
+
const renderContent = () => {
|
93
|
+
return (
|
94
|
+
<>
|
95
|
+
{isMulti && resultsSetsCount > 1 && (
|
96
|
+
<div>
|
97
|
+
<Tabs
|
98
|
+
className={b('result-tabs')}
|
99
|
+
size="l"
|
100
|
+
items={getArray(resultsSetsCount).map((item) => ({
|
101
|
+
id: String(item),
|
102
|
+
title: `Result #${item + 1}`,
|
103
|
+
}))}
|
104
|
+
activeTab={String(selectedResultSet)}
|
105
|
+
onSelectTab={(tabId) => setSelectedResultSet(Number(tabId))}
|
106
|
+
/>
|
107
|
+
</div>
|
108
|
+
)}
|
109
|
+
<div className={b('result')}>
|
110
|
+
{renderResultTable(currentResult, currentColumns)}
|
111
|
+
</div>
|
112
|
+
</>
|
113
|
+
);
|
114
|
+
};
|
115
|
+
|
76
116
|
const renderClipboardButton = () => {
|
77
117
|
return (
|
78
118
|
<CopyToClipboard
|
@@ -108,12 +148,14 @@ export function ExecuteResult({
|
|
108
148
|
};
|
109
149
|
|
110
150
|
const renderResult = () => {
|
151
|
+
const content = renderContent();
|
152
|
+
|
111
153
|
return (
|
112
154
|
<React.Fragment>
|
113
|
-
{
|
155
|
+
{content}
|
114
156
|
{isFullscreen && (
|
115
157
|
<Fullscreen>
|
116
|
-
<div className={b('result'
|
158
|
+
<div className={b('result-fullscreen-wrapper')}>{content}</div>
|
117
159
|
</Fullscreen>
|
118
160
|
)}
|
119
161
|
</React.Fragment>
|
@@ -126,13 +168,15 @@ export function ExecuteResult({
|
|
126
168
|
}
|
127
169
|
|
128
170
|
if (typeof error === 'object' && error.data?.issues && Array.isArray(error.data.issues)) {
|
171
|
+
const content = <ResultIssues data={error.data} />;
|
172
|
+
|
129
173
|
return (
|
130
174
|
<React.Fragment>
|
131
|
-
|
175
|
+
{content}
|
132
176
|
{isFullscreen && (
|
133
177
|
<Fullscreen>
|
134
|
-
<div className={b('result',
|
135
|
-
|
178
|
+
<div className={b('result-fullscreen-wrapper', b('result'))}>
|
179
|
+
{content}
|
136
180
|
</div>
|
137
181
|
</Fullscreen>
|
138
182
|
)}
|
@@ -145,6 +189,19 @@ export function ExecuteResult({
|
|
145
189
|
return <div className={b('error')}>{parsedError}</div>;
|
146
190
|
};
|
147
191
|
|
192
|
+
const renderResultSection = () => {
|
193
|
+
if (activeSection === resultOptionsIds.result && !error) {
|
194
|
+
return renderResult();
|
195
|
+
}
|
196
|
+
|
197
|
+
return (
|
198
|
+
<div className={b('result')}>
|
199
|
+
{activeSection === resultOptionsIds.stats && !error && renderStats()}
|
200
|
+
{renderIssues()}
|
201
|
+
</div>
|
202
|
+
);
|
203
|
+
};
|
204
|
+
|
148
205
|
return (
|
149
206
|
<React.Fragment>
|
150
207
|
<div className={b('controls')}>
|
@@ -174,11 +231,8 @@ export function ExecuteResult({
|
|
174
231
|
/>
|
175
232
|
</div>
|
176
233
|
</div>
|
177
|
-
|
178
|
-
|
179
|
-
{activeSection === resultOptionsIds.stats && !error && renderStats()}
|
180
|
-
{renderIssues()}
|
181
|
-
</div>
|
234
|
+
|
235
|
+
{renderResultSection()}
|
182
236
|
</React.Fragment>
|
183
237
|
);
|
184
238
|
}
|
@@ -16,11 +16,12 @@ import {QueryExecutionStatus} from '../../../../components/QueryExecutionStatus'
|
|
16
16
|
import {explainVersions} from '../../../../store/reducers/explainQuery';
|
17
17
|
import {disableFullscreen} from '../../../../store/reducers/fullscreen';
|
18
18
|
|
19
|
-
import {renderExplainNode} from '../../../../utils';
|
20
19
|
import {LANGUAGE_S_EXPRESSION_ID} from '../../../../utils/monaco';
|
21
20
|
|
22
21
|
import {PaneVisibilityToggleButtons} from '../../utils/paneVisibilityToggleHelpers';
|
23
22
|
|
23
|
+
import {renderExplainNode} from './utils';
|
24
|
+
|
24
25
|
import './ExplainResult.scss';
|
25
26
|
|
26
27
|
const b = cn('ydb-query-explain-result');
|
@@ -6,7 +6,6 @@ import _ from 'lodash';
|
|
6
6
|
import MonacoEditor from 'react-monaco-editor';
|
7
7
|
|
8
8
|
import SplitPane from '../../../../components/SplitPane';
|
9
|
-
import {QueryResultTable} from '../../../../components/QueryResultTable';
|
10
9
|
|
11
10
|
import {
|
12
11
|
sendExecuteQuery,
|
@@ -26,6 +25,7 @@ import {
|
|
26
25
|
SAVED_QUERIES_KEY,
|
27
26
|
ENABLE_ADDITIONAL_QUERY_MODES,
|
28
27
|
LAST_USED_QUERY_ACTION_KEY,
|
28
|
+
QUERY_USE_MULTI_SCHEMA_KEY,
|
29
29
|
} from '../../../../utils/constants';
|
30
30
|
import {useSetting, useQueryModes} from '../../../../utils/hooks';
|
31
31
|
import {QUERY_ACTIONS, QUERY_MODES, isNewQueryMode} from '../../../../utils/query';
|
@@ -40,14 +40,8 @@ import {ExecuteResult} from '../ExecuteResult/ExecuteResult';
|
|
40
40
|
import {ExplainResult} from '../ExplainResult/ExplainResult';
|
41
41
|
import {QueryEditorControls} from '../QueryEditorControls/QueryEditorControls';
|
42
42
|
|
43
|
-
import {getPreparedResult} from '../utils/getPreparedResult';
|
44
|
-
|
45
43
|
import './QueryEditor.scss';
|
46
44
|
|
47
|
-
const TABLE_SETTINGS = {
|
48
|
-
sortable: false,
|
49
|
-
};
|
50
|
-
|
51
45
|
const EDITOR_OPTIONS = {
|
52
46
|
automaticLayout: true,
|
53
47
|
selectOnLineNumbers: true,
|
@@ -92,6 +86,7 @@ function QueryEditor(props) {
|
|
92
86
|
const [isResultLoaded, setIsResultLoaded] = useState(false);
|
93
87
|
const [queryMode, setQueryMode] = useQueryModes();
|
94
88
|
const [enableAdditionalQueryModes] = useSetting(ENABLE_ADDITIONAL_QUERY_MODES);
|
89
|
+
const [useMultiSchema] = useSetting(QUERY_USE_MULTI_SCHEMA_KEY);
|
95
90
|
const [lastUsedQueryAction, setLastUsedQueryAction] = useSetting(LAST_USED_QUERY_ACTION_KEY);
|
96
91
|
|
97
92
|
useEffect(() => {
|
@@ -256,9 +251,16 @@ function QueryEditor(props) {
|
|
256
251
|
setShowPreview,
|
257
252
|
} = props;
|
258
253
|
|
254
|
+
const schema = useMultiSchema ? 'multi' : 'modern';
|
255
|
+
|
259
256
|
setLastUsedQueryAction(QUERY_ACTIONS.execute);
|
260
257
|
setResultType(RESULT_TYPES.EXECUTE);
|
261
|
-
sendExecuteQuery({
|
258
|
+
sendExecuteQuery({
|
259
|
+
query: input,
|
260
|
+
database: path,
|
261
|
+
mode,
|
262
|
+
schema,
|
263
|
+
});
|
262
264
|
setIsResultLoaded(true);
|
263
265
|
setShowPreview(false);
|
264
266
|
|
@@ -314,26 +316,11 @@ function QueryEditor(props) {
|
|
314
316
|
executeQuery: {data, error, stats},
|
315
317
|
} = props;
|
316
318
|
|
317
|
-
let content;
|
318
|
-
if (data) {
|
319
|
-
content = (
|
320
|
-
<QueryResultTable
|
321
|
-
data={data.result}
|
322
|
-
columns={data.columns}
|
323
|
-
settings={TABLE_SETTINGS}
|
324
|
-
/>
|
325
|
-
);
|
326
|
-
}
|
327
|
-
const textResults = getPreparedResult(data);
|
328
|
-
const disabled = !textResults.length || resultType !== RESULT_TYPES.EXECUTE;
|
329
|
-
|
330
319
|
return data || error ? (
|
331
320
|
<ExecuteResult
|
332
|
-
|
321
|
+
data={data}
|
333
322
|
stats={stats}
|
334
323
|
error={error}
|
335
|
-
textResults={textResults}
|
336
|
-
copyDisabled={disabled}
|
337
324
|
isResultsCollapsed={resultVisibilityState.collapsed}
|
338
325
|
onExpandResults={onExpandResultHandler}
|
339
326
|
onCollapseResults={onCollapseResultHandler}
|
@@ -1,16 +1,15 @@
|
|
1
1
|
import type {KeyValueRow} from '../../../../types/api/query';
|
2
|
-
import type {IQueryResult} from '../../../../types/store/query';
|
3
2
|
|
4
|
-
export const getPreparedResult = (data:
|
3
|
+
export const getPreparedResult = (data: KeyValueRow[] | undefined) => {
|
5
4
|
const columnDivider = '\t';
|
6
5
|
const rowDivider = '\n';
|
7
6
|
|
8
|
-
if (!data?.
|
7
|
+
if (!data?.length) {
|
9
8
|
return '';
|
10
9
|
}
|
11
10
|
|
12
|
-
const columnHeaders = Object.keys(data
|
13
|
-
const rows = Array<string[] | KeyValueRow[]>(columnHeaders).concat(data
|
11
|
+
const columnHeaders = Object.keys(data[0]);
|
12
|
+
const rows = Array<string[] | KeyValueRow[]>(columnHeaders).concat(data);
|
14
13
|
|
15
14
|
return rows
|
16
15
|
.map((item) => {
|
@@ -25,6 +25,9 @@
|
|
25
25
|
"settings.enableAdditionalQueryModes.title": "Enable additional query modes",
|
26
26
|
"settings.enableAdditionalQueryModes.popover": "Adds 'Data', 'YQL - QueryService' and 'PostgreSQL' modes. May not work on some versions",
|
27
27
|
|
28
|
+
"settings.queryUseMultiSchema.title": "Allow queries with multiple result sets",
|
29
|
+
"settings.queryUseMultiSchema.popover": "Use 'multi' schema for queries that enables queries with multiple result sets. Returns nothing on versions 23-3 and older",
|
30
|
+
|
28
31
|
"settings.tenantDiagnostics.title": "Display metrics cards for database diagnostics",
|
29
32
|
"settings.tenantDiagnostics.popover": "Adds indicators of database resources usage. Incomplete data may be displayed for some databases"
|
30
33
|
}
|
@@ -25,6 +25,9 @@
|
|
25
25
|
"settings.enableAdditionalQueryModes.title": "Включить дополнительные режимы выполнения запросов",
|
26
26
|
"settings.enableAdditionalQueryModes.popover": "Добавляет режимы 'Data', 'YQL - QueryService' и 'PostgreSQL'. Может работать некорректно на некоторых версиях",
|
27
27
|
|
28
|
+
"settings.queryUseMultiSchema.title": "Разрешить запросы с несколькими результатами",
|
29
|
+
"settings.queryUseMultiSchema.popover": "Использовать для запросов схему 'multi', которая позволяет выполнять запросы с несколькими результатами. На версиях 23-3 и старше результат не возвращается вообще",
|
30
|
+
|
28
31
|
"settings.tenantDiagnostics.title": "Показывать карточки с метриками в диагностике базы данных",
|
29
32
|
"settings.tenantDiagnostics.popover": "Добавляет индикаторы использования ресурсов базы данных. Для некоторых баз могут отображаться неполные данные"
|
30
33
|
}
|
@@ -11,6 +11,7 @@ import {
|
|
11
11
|
THEME_KEY,
|
12
12
|
USE_BACKEND_PARAMS_FOR_TABLES_KEY,
|
13
13
|
USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,
|
14
|
+
QUERY_USE_MULTI_SCHEMA_KEY,
|
14
15
|
} from '../../utils/constants';
|
15
16
|
import {Lang, defaultLang} from '../../utils/i18n';
|
16
17
|
|
@@ -95,6 +96,11 @@ export const enableQueryModesForExplainSetting: SettingProps = {
|
|
95
96
|
title: i18n('settings.enableAdditionalQueryModes.title'),
|
96
97
|
helpPopoverContent: i18n('settings.enableAdditionalQueryModes.popover'),
|
97
98
|
};
|
99
|
+
export const queryUseMultiSchemaSetting: SettingProps = {
|
100
|
+
settingKey: QUERY_USE_MULTI_SCHEMA_KEY,
|
101
|
+
title: i18n('settings.queryUseMultiSchema.title'),
|
102
|
+
helpPopoverContent: i18n('settings.queryUseMultiSchema.popover'),
|
103
|
+
};
|
98
104
|
export const enableNewTenantDiagnosticsDesign: SettingProps = {
|
99
105
|
settingKey: DISPLAY_METRICS_CARDS_FOR_TENANT_DIAGNOSTICS,
|
100
106
|
title: i18n('settings.tenantDiagnostics.title'),
|
@@ -113,6 +119,7 @@ export const experimentsSection: SettingsSection = {
|
|
113
119
|
useNodesEndpointSetting,
|
114
120
|
useBackendParamsForTables,
|
115
121
|
enableQueryModesForExplainSetting,
|
122
|
+
queryUseMultiSchemaSetting,
|
116
123
|
enableNewTenantDiagnosticsDesign,
|
117
124
|
],
|
118
125
|
};
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import type {Reducer} from 'redux';
|
2
2
|
|
3
|
-
import type {ExecuteActions} from '../../types/api/query';
|
3
|
+
import type {ExecuteActions, Schemas} from '../../types/api/query';
|
4
4
|
import type {
|
5
5
|
ExecuteQueryAction,
|
6
6
|
ExecuteQueryState,
|
@@ -154,9 +154,10 @@ const executeQuery: Reducer<ExecuteQueryState, ExecuteQueryAction> = (
|
|
154
154
|
|
155
155
|
interface SendQueryParams extends QueryRequestParams {
|
156
156
|
mode?: QueryMode;
|
157
|
+
schema?: Schemas;
|
157
158
|
}
|
158
159
|
|
159
|
-
export const sendExecuteQuery = ({query, database, mode}: SendQueryParams) => {
|
160
|
+
export const sendExecuteQuery = ({query, database, mode, schema = 'modern'}: SendQueryParams) => {
|
160
161
|
let action: ExecuteActions = 'execute';
|
161
162
|
let syntax: QuerySyntax = QUERY_SYNTAX.yql;
|
162
163
|
|
@@ -169,7 +170,7 @@ export const sendExecuteQuery = ({query, database, mode}: SendQueryParams) => {
|
|
169
170
|
|
170
171
|
return createApiRequest({
|
171
172
|
request: window.api.sendQuery({
|
172
|
-
schema
|
173
|
+
schema,
|
173
174
|
query,
|
174
175
|
database,
|
175
176
|
action,
|
@@ -30,15 +30,25 @@ export interface NodesPreparedEntity {
|
|
30
30
|
DataCenter?: string;
|
31
31
|
Rack?: string;
|
32
32
|
Version?: string;
|
33
|
+
TenantName?: string;
|
34
|
+
|
33
35
|
StartTime?: string;
|
34
36
|
Uptime: string;
|
37
|
+
DisconnectTime?: string;
|
38
|
+
|
35
39
|
MemoryUsed?: string;
|
40
|
+
MemoryUsedInAlloc?: string;
|
36
41
|
MemoryLimit?: string;
|
42
|
+
|
43
|
+
SharedCacheUsed?: string;
|
44
|
+
SharedCacheLimit?: string | number;
|
45
|
+
|
37
46
|
PoolStats?: TPoolStats[];
|
38
47
|
LoadAverage?: number[];
|
39
48
|
Tablets?: TFullTabletStateInfo[] | TComputeTabletStateInfo[];
|
40
|
-
TenantName?: string;
|
41
49
|
Endpoints?: TEndpoint[];
|
50
|
+
|
51
|
+
TotalSessions?: number;
|
42
52
|
}
|
43
53
|
|
44
54
|
export interface NodesState {
|
@@ -50,12 +50,18 @@ export const prepareNodesData = (data: TNodesInfo): NodesHandledResponse => {
|
|
50
50
|
const rawNodes = data.Nodes || [];
|
51
51
|
|
52
52
|
const preparedNodes = rawNodes.map((node) => {
|
53
|
+
// 0 limit means that limit is not set, so it should be undefined
|
54
|
+
const sharedCacheLimit = Number(node.SystemState.SharedCacheStats?.LimitBytes) || undefined;
|
55
|
+
|
53
56
|
return {
|
54
57
|
...node.SystemState,
|
55
58
|
Tablets: node.Tablets,
|
56
59
|
NodeId: node.NodeId,
|
57
60
|
Uptime: calcUptime(node.SystemState?.StartTime),
|
58
61
|
TenantName: node.SystemState?.Tenants?.[0],
|
62
|
+
|
63
|
+
SharedCacheUsed: node.SystemState.SharedCacheStats?.UsedBytes,
|
64
|
+
SharedCacheLimit: sharedCacheLimit,
|
59
65
|
};
|
60
66
|
});
|
61
67
|
|
@@ -16,6 +16,7 @@ import {
|
|
16
16
|
USE_BACKEND_PARAMS_FOR_TABLES_KEY,
|
17
17
|
LANGUAGE_KEY,
|
18
18
|
DISPLAY_METRICS_CARDS_FOR_TENANT_DIAGNOSTICS,
|
19
|
+
QUERY_USE_MULTI_SCHEMA_KEY,
|
19
20
|
} from '../../../utils/constants';
|
20
21
|
import '../../../services/api';
|
21
22
|
import {parseJson} from '../../../utils/utils';
|
@@ -56,6 +57,7 @@ export const initialState = {
|
|
56
57
|
ENABLE_ADDITIONAL_QUERY_MODES,
|
57
58
|
'true',
|
58
59
|
),
|
60
|
+
[QUERY_USE_MULTI_SCHEMA_KEY]: readSavedSettingsValue(QUERY_USE_MULTI_SCHEMA_KEY, 'false'),
|
59
61
|
[DISPLAY_METRICS_CARDS_FOR_TENANT_DIAGNOSTICS]: readSavedSettingsValue(
|
60
62
|
DISPLAY_METRICS_CARDS_FOR_TENANT_DIAGNOSTICS,
|
61
63
|
'true',
|
package/dist/types/api/nodes.ts
CHANGED
@@ -26,6 +26,9 @@ export interface TNodeInfo {
|
|
26
26
|
Tablets?: TTabletStateInfo[];
|
27
27
|
}
|
28
28
|
|
29
|
+
/**
|
30
|
+
* source: https://github.com/ydb-platform/ydb/blob/main/ydb/core/protos/node_whiteboard.proto
|
31
|
+
*/
|
29
32
|
export interface TSystemStateInfo {
|
30
33
|
/** uint64 */
|
31
34
|
StartTime?: string;
|
@@ -62,6 +65,20 @@ export interface TSystemStateInfo {
|
|
62
65
|
/** double */
|
63
66
|
MaxDiskUsage?: number;
|
64
67
|
Location?: TNodeLocation;
|
68
|
+
|
69
|
+
/**
|
70
|
+
* int64
|
71
|
+
*
|
72
|
+
* a positive value means the peer is ahead in time; a negative value means it's behind
|
73
|
+
*/
|
74
|
+
MaxClockSkewWithPeerUs?: string;
|
75
|
+
MaxClockSkewPeerId?: number;
|
76
|
+
|
77
|
+
/** uint64 */
|
78
|
+
DisconnectTime?: string;
|
79
|
+
|
80
|
+
SharedCacheStats?: TNodeSharedCache;
|
81
|
+
TotalSessions?: number;
|
65
82
|
}
|
66
83
|
|
67
84
|
export interface TPoolStats {
|
@@ -97,6 +114,13 @@ interface TNodeLocation {
|
|
97
114
|
Unit?: string;
|
98
115
|
}
|
99
116
|
|
117
|
+
interface TNodeSharedCache {
|
118
|
+
/** uint64 */
|
119
|
+
UsedBytes: string;
|
120
|
+
/** uint64 */
|
121
|
+
LimitBytes: string;
|
122
|
+
}
|
123
|
+
|
100
124
|
enum EConfigState {
|
101
125
|
'Consistent' = 'Consistent',
|
102
126
|
'Outdated' = 'Outdated',
|
package/dist/types/api/query.ts
CHANGED
@@ -180,7 +180,7 @@ export interface ColumnType {
|
|
180
180
|
}
|
181
181
|
|
182
182
|
/** undefined = 'classic' */
|
183
|
-
export type Schemas = 'classic' | 'modern' | 'ydb' | undefined;
|
183
|
+
export type Schemas = 'classic' | 'modern' | 'ydb' | 'multi' | undefined;
|
184
184
|
|
185
185
|
/** undefined = 'execute' */
|
186
186
|
export type ExecuteActions =
|
@@ -231,16 +231,29 @@ export type ExplainResponse<Action extends ExplainActions> = Action extends 'exp
|
|
231
231
|
? ExplainScriptResponse
|
232
232
|
: ExplainQueryResponse;
|
233
233
|
|
234
|
+
// ==== Execute Results ====
|
235
|
+
|
236
|
+
interface ModernSchemaResult {
|
237
|
+
result?: ArrayRow[];
|
238
|
+
columns?: ColumnType[];
|
239
|
+
}
|
240
|
+
interface MultiSchemaResult {
|
241
|
+
result?: {
|
242
|
+
rows?: ArrayRow[] | null;
|
243
|
+
columns?: ColumnType[];
|
244
|
+
}[];
|
245
|
+
}
|
246
|
+
interface DefaultSchemaResult {
|
247
|
+
result?: KeyValueRow[];
|
248
|
+
}
|
249
|
+
|
234
250
|
// ==== Execute Responses ====
|
235
251
|
|
236
252
|
type ResultFields<Schema extends Schemas> = Schema extends 'modern'
|
237
|
-
?
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
: {
|
242
|
-
result?: KeyValueRow[];
|
243
|
-
};
|
253
|
+
? ModernSchemaResult
|
254
|
+
: Schema extends 'multi'
|
255
|
+
? MultiSchemaResult
|
256
|
+
: DefaultSchemaResult;
|
244
257
|
|
245
258
|
/**
|
246
259
|
* meta.type = 'query'
|
@@ -287,6 +300,7 @@ export type AnyExplainResponse = ExplainQueryResponse | ExplainScriptResponse;
|
|
287
300
|
export type ExecuteModernResponse =
|
288
301
|
| ExecuteQueryResponse<'modern'>
|
289
302
|
| ExecuteScriptResponse<'modern'>;
|
303
|
+
export type ExecuteMultiResponse = ExecuteQueryResponse<'multi'> | ExecuteScriptResponse<'multi'>;
|
290
304
|
export type ExecuteClassicResponse =
|
291
305
|
| ExecuteQueryResponse<'classic'>
|
292
306
|
| ExecuteScriptResponse<'classic'>;
|
@@ -294,5 +308,6 @@ export type ExecuteYdbResponse = ExecuteQueryResponse<'ydb'> | ExecuteScriptResp
|
|
294
308
|
|
295
309
|
export type AnyExecuteResponse =
|
296
310
|
| ExecuteModernResponse
|
311
|
+
| ExecuteMultiResponse
|
297
312
|
| ExecuteClassicResponse
|
298
313
|
| ExecuteYdbResponse;
|
@@ -11,7 +11,13 @@ import type {
|
|
11
11
|
} from '../api/query';
|
12
12
|
import type {ValueOf} from '../common';
|
13
13
|
|
14
|
+
export interface ParsedResultSet {
|
15
|
+
columns?: ColumnType[];
|
16
|
+
result?: KeyValueRow[];
|
17
|
+
}
|
18
|
+
|
14
19
|
export interface IQueryResult {
|
20
|
+
resultSets?: ParsedResultSet[];
|
15
21
|
result?: KeyValueRow[];
|
16
22
|
columns?: ColumnType[];
|
17
23
|
stats?: TKqpStatsQuery;
|
package/dist/utils/constants.ts
CHANGED
@@ -133,3 +133,6 @@ export const TENANT_INITIAL_PAGE_KEY = 'saved_tenant_initial_tab';
|
|
133
133
|
|
134
134
|
// Send filters and sort params to backend for Nodes and Storage tables
|
135
135
|
export const USE_BACKEND_PARAMS_FOR_TABLES_KEY = 'useBackendParamsForTables';
|
136
|
+
|
137
|
+
// Enable schema that supports multiple resultsets
|
138
|
+
export const QUERY_USE_MULTI_SCHEMA_KEY = 'queryUseMultiSchema';
|
@@ -0,0 +1,50 @@
|
|
1
|
+
import {
|
2
|
+
createDeveloperUILinkWithNodeId,
|
3
|
+
createPDiskDeveloperUILink,
|
4
|
+
createVDiskDeveloperUILink,
|
5
|
+
} from '../developerUI';
|
6
|
+
|
7
|
+
describe('Developer UI links generators', () => {
|
8
|
+
describe('createDeveloperUILinkWithNodeId', () => {
|
9
|
+
it('should create relative link with no host', () => {
|
10
|
+
expect(createDeveloperUILinkWithNodeId(1)).toBe('/node/1/');
|
11
|
+
});
|
12
|
+
it('should create relative link with existing relative path with nodeId', () => {
|
13
|
+
expect(createDeveloperUILinkWithNodeId(1, '/node/3/')).toBe('/node/1/');
|
14
|
+
});
|
15
|
+
it('should create full link with host', () => {
|
16
|
+
expect(
|
17
|
+
createDeveloperUILinkWithNodeId(
|
18
|
+
1,
|
19
|
+
'http://ydb-vla-dev02-001.search.yandex.net:8765',
|
20
|
+
),
|
21
|
+
).toBe('http://ydb-vla-dev02-001.search.yandex.net:8765/node/1/');
|
22
|
+
});
|
23
|
+
it('should create full link with host with existing node path with nodeId', () => {
|
24
|
+
expect(
|
25
|
+
createDeveloperUILinkWithNodeId(
|
26
|
+
1,
|
27
|
+
'http://ydb-vla-dev02-001.search.yandex.net:8765/node/3',
|
28
|
+
),
|
29
|
+
).toBe('http://ydb-vla-dev02-001.search.yandex.net:8765/node/1/');
|
30
|
+
});
|
31
|
+
});
|
32
|
+
describe('createPDiskDeveloperUILink', () => {
|
33
|
+
it('should create link with pDiskId and nodeId', () => {
|
34
|
+
expect(createPDiskDeveloperUILink({nodeId: 1, pDiskId: 1})).toBe(
|
35
|
+
'/node/1/actors/pdisks/pdisk000000001',
|
36
|
+
);
|
37
|
+
});
|
38
|
+
});
|
39
|
+
describe('createVDiskDeveloperUILink', () => {
|
40
|
+
it('should create link with pDiskId, vDiskSlotId nodeId', () => {
|
41
|
+
expect(
|
42
|
+
createVDiskDeveloperUILink({
|
43
|
+
nodeId: 1,
|
44
|
+
pDiskId: 1,
|
45
|
+
vDiskSlotId: 1,
|
46
|
+
}),
|
47
|
+
).toBe('/node/1/actors/vdisks/vdisk000000001_000000001');
|
48
|
+
});
|
49
|
+
});
|
50
|
+
});
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import {backend} from '../../store';
|
2
|
+
import {pad9} from '../utils';
|
3
|
+
|
4
|
+
// Current node connects with target node by itself using nodeId
|
5
|
+
export const createDeveloperUILinkWithNodeId = (nodeId: number | string, host = backend) => {
|
6
|
+
const nodePathRegexp = /\/node\/\d+\/?$/g;
|
7
|
+
|
8
|
+
// In case current backend is already relative node path ({host}/node/{nodeId})
|
9
|
+
// We replace existing nodeId path with new nodeId path
|
10
|
+
if (nodePathRegexp.test(String(host))) {
|
11
|
+
return String(host).replace(nodePathRegexp, `/node/${nodeId}/`);
|
12
|
+
}
|
13
|
+
|
14
|
+
return `${host ?? ''}/node/${nodeId}/`;
|
15
|
+
};
|
16
|
+
|
17
|
+
interface PDiskDeveloperUILinkParams {
|
18
|
+
nodeId: number | string;
|
19
|
+
pDiskId: number | string;
|
20
|
+
host?: string;
|
21
|
+
}
|
22
|
+
|
23
|
+
export const createPDiskDeveloperUILink = ({nodeId, pDiskId, host}: PDiskDeveloperUILinkParams) => {
|
24
|
+
const pdiskPath = 'actors/pdisks/pdisk' + pad9(pDiskId);
|
25
|
+
|
26
|
+
return createDeveloperUILinkWithNodeId(nodeId, host) + pdiskPath;
|
27
|
+
};
|
28
|
+
|
29
|
+
interface VDiskDeveloperUILinkParams extends PDiskDeveloperUILinkParams {
|
30
|
+
vDiskSlotId: number | string;
|
31
|
+
}
|
32
|
+
|
33
|
+
export const createVDiskDeveloperUILink = ({
|
34
|
+
nodeId,
|
35
|
+
pDiskId,
|
36
|
+
vDiskSlotId,
|
37
|
+
host,
|
38
|
+
}: VDiskDeveloperUILinkParams) => {
|
39
|
+
const vdiskPath = 'actors/vdisks/vdisk' + pad9(pDiskId) + '_' + pad9(vDiskSlotId);
|
40
|
+
|
41
|
+
return createDeveloperUILinkWithNodeId(nodeId, host) + vdiskPath;
|
42
|
+
};
|
package/dist/utils/query.ts
CHANGED
@@ -2,7 +2,10 @@ import {YQLType} from '../types';
|
|
2
2
|
import type {
|
3
3
|
AnyExecuteResponse,
|
4
4
|
AnyExplainResponse,
|
5
|
+
ArrayRow,
|
6
|
+
ColumnType,
|
5
7
|
ExecuteModernResponse,
|
8
|
+
ExecuteMultiResponse,
|
6
9
|
KeyValueRow,
|
7
10
|
QueryPlan,
|
8
11
|
ScriptPlan,
|
@@ -76,34 +79,74 @@ export const getColumnType = (type: string) => {
|
|
76
79
|
}
|
77
80
|
};
|
78
81
|
|
79
|
-
/** parse response result
|
82
|
+
/** parse response result from ArrayRow to KeyValueRow */
|
83
|
+
const parseModernResult = (rows: ArrayRow[], columns: ColumnType[]) => {
|
84
|
+
return rows.map((row) => {
|
85
|
+
return row.reduce<KeyValueRow>((newRow, cellData, columnIndex) => {
|
86
|
+
const {name} = columns[columnIndex];
|
87
|
+
newRow[name] = cellData;
|
88
|
+
return newRow;
|
89
|
+
}, {});
|
90
|
+
});
|
91
|
+
};
|
92
|
+
|
80
93
|
const parseExecuteModernResponse = (data: ExecuteModernResponse): IQueryResult => {
|
81
94
|
const {result, columns, ...restData} = data;
|
82
95
|
|
83
96
|
return {
|
84
|
-
result:
|
85
|
-
result &&
|
86
|
-
columns &&
|
87
|
-
result.map((row) => {
|
88
|
-
return row.reduce((newRow, cellData, columnIndex) => {
|
89
|
-
const {name} = columns[columnIndex];
|
90
|
-
newRow[name] = cellData;
|
91
|
-
return newRow;
|
92
|
-
}, {} as KeyValueRow);
|
93
|
-
}),
|
97
|
+
result: result && columns && parseModernResult(result, columns),
|
94
98
|
columns,
|
95
99
|
...restData,
|
96
100
|
};
|
97
101
|
};
|
98
102
|
|
103
|
+
const parseExecuteMultiResponse = (data: ExecuteMultiResponse): IQueryResult => {
|
104
|
+
const {result, ...restData} = data;
|
105
|
+
|
106
|
+
const parsedResult = result?.map((resultSet) => {
|
107
|
+
const {rows, columns} = resultSet;
|
108
|
+
|
109
|
+
let parsedRows: KeyValueRow[] | undefined;
|
110
|
+
|
111
|
+
if (columns) {
|
112
|
+
// Result shouldn't be null if there are columns
|
113
|
+
parsedRows = [];
|
114
|
+
}
|
115
|
+
|
116
|
+
if (rows && columns) {
|
117
|
+
parsedRows = parseModernResult(rows, columns);
|
118
|
+
}
|
119
|
+
|
120
|
+
return {
|
121
|
+
columns: columns,
|
122
|
+
result: parsedRows,
|
123
|
+
};
|
124
|
+
});
|
125
|
+
|
126
|
+
return {
|
127
|
+
resultSets: parsedResult, // use a separate field to make result compatible
|
128
|
+
...restData,
|
129
|
+
};
|
130
|
+
};
|
131
|
+
|
99
132
|
const isModern = (response: AnyExecuteResponse): response is ExecuteModernResponse =>
|
100
133
|
Boolean(
|
101
134
|
response &&
|
102
135
|
!Array.isArray(response) &&
|
103
|
-
Array.isArray(
|
136
|
+
Array.isArray(response.result) &&
|
104
137
|
Array.isArray((response as ExecuteModernResponse).columns),
|
105
138
|
);
|
106
139
|
|
140
|
+
const isMulti = (response: AnyExecuteResponse): response is ExecuteMultiResponse =>
|
141
|
+
Boolean(
|
142
|
+
response &&
|
143
|
+
!Array.isArray(response) &&
|
144
|
+
Array.isArray(response.result) &&
|
145
|
+
typeof response.result[0] === 'object' &&
|
146
|
+
'rows' in response.result[0] &&
|
147
|
+
'columns' in response.result[0],
|
148
|
+
);
|
149
|
+
|
107
150
|
type UnsupportedQueryResponseFormat =
|
108
151
|
| Array<unknown>
|
109
152
|
| string
|
@@ -122,12 +165,17 @@ const isUnsupportedType = (
|
|
122
165
|
);
|
123
166
|
};
|
124
167
|
|
168
|
+
// Although schema is set in request, if schema is not supported default schema for the version will be used
|
169
|
+
// So we should additionally parse response
|
125
170
|
export const parseQueryAPIExecuteResponse = (
|
126
171
|
data: AnyExecuteResponse | UnsupportedQueryResponseFormat,
|
127
172
|
): IQueryResult => {
|
128
173
|
if (isUnsupportedType(data)) {
|
129
174
|
return {};
|
130
175
|
}
|
176
|
+
if (isMulti(data)) {
|
177
|
+
return parseExecuteMultiResponse(data);
|
178
|
+
}
|
131
179
|
if (isModern(data)) {
|
132
180
|
return parseExecuteModernResponse(data);
|
133
181
|
}
|
package/package.json
CHANGED
@@ -1,32 +0,0 @@
|
|
1
|
-
import {backend} from '../store';
|
2
|
-
import {pad9} from './utils';
|
3
|
-
|
4
|
-
// Current node connects with target node by itself using nodeId
|
5
|
-
export const createDeveloperUILinkWithNodeId = (nodeId: number | string) => {
|
6
|
-
return `${backend}/node/${nodeId}/`;
|
7
|
-
};
|
8
|
-
|
9
|
-
interface PDiskDeveloperUILinkParams {
|
10
|
-
nodeId: number | string;
|
11
|
-
pDiskId: number | string;
|
12
|
-
}
|
13
|
-
|
14
|
-
export const createPDiskDeveloperUILink = ({nodeId, pDiskId}: PDiskDeveloperUILinkParams) => {
|
15
|
-
const pdiskPath = 'actors/pdisks/pdisk' + pad9(pDiskId);
|
16
|
-
|
17
|
-
return createDeveloperUILinkWithNodeId(nodeId) + pdiskPath;
|
18
|
-
};
|
19
|
-
|
20
|
-
interface VDiskDeveloperUILinkParams extends PDiskDeveloperUILinkParams {
|
21
|
-
vDiskSlotId: number | string;
|
22
|
-
}
|
23
|
-
|
24
|
-
export const createVDiskDeveloperUILink = ({
|
25
|
-
nodeId,
|
26
|
-
pDiskId,
|
27
|
-
vDiskSlotId,
|
28
|
-
}: VDiskDeveloperUILinkParams) => {
|
29
|
-
const vdiskPath = 'actors/vdisks/vdisk' + pad9(pDiskId) + '_' + pad9(vDiskSlotId);
|
30
|
-
|
31
|
-
return createDeveloperUILinkWithNodeId(nodeId) + vdiskPath;
|
32
|
-
};
|
package/dist/utils/index.js
DELETED
@@ -1,9 +0,0 @@
|
|
1
|
-
// determine how many nodes have status Connected "true"
|
2
|
-
export const getConnectedNodesCount = (nodeStateInfo) => {
|
3
|
-
return nodeStateInfo?.reduce((acc, item) => (item.Connected ? acc + 1 : acc), 0);
|
4
|
-
};
|
5
|
-
|
6
|
-
export const renderExplainNode = (node) => {
|
7
|
-
const parts = node.name.split('|');
|
8
|
-
return parts.length > 1 ? parts[1] : node.name;
|
9
|
-
};
|
File without changes
|