ydb-embedded-ui 4.21.1 → 4.22.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/components/VirtualTable/TableChunk.tsx +2 -1
  3. package/dist/components/VirtualTable/VirtualTable.tsx +1 -1
  4. package/dist/containers/Node/NodeStructure/Pdisk.tsx +4 -1
  5. package/dist/containers/Nodes/getNodesColumns.tsx +57 -13
  6. package/dist/containers/Tenant/Diagnostics/Network/Network.js +5 -10
  7. package/dist/containers/Tenant/Diagnostics/Network/utils.ts +6 -0
  8. package/dist/containers/Tenant/Query/ExecuteResult/ExecuteResult.scss +13 -5
  9. package/dist/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx +72 -18
  10. package/dist/containers/Tenant/Query/ExplainResult/ExplainResult.js +2 -1
  11. package/dist/containers/Tenant/Query/ExplainResult/utils.ts +6 -0
  12. package/dist/containers/Tenant/Query/QueryEditor/QueryEditor.js +11 -24
  13. package/dist/containers/Tenant/Query/utils/getPreparedResult.ts +4 -5
  14. package/dist/containers/UserSettings/i18n/en.json +3 -0
  15. package/dist/containers/UserSettings/i18n/ru.json +3 -0
  16. package/dist/containers/UserSettings/settings.ts +7 -0
  17. package/dist/store/reducers/executeQuery.ts +4 -3
  18. package/dist/store/reducers/nodes/types.ts +11 -1
  19. package/dist/store/reducers/nodes/utils.ts +6 -0
  20. package/dist/store/reducers/settings/settings.ts +2 -0
  21. package/dist/types/api/netInfo.ts +1 -1
  22. package/dist/types/api/nodes.ts +24 -0
  23. package/dist/types/api/query.ts +23 -8
  24. package/dist/types/store/query.ts +6 -0
  25. package/dist/utils/constants.ts +3 -0
  26. package/dist/utils/developerUI/__test__/developerUI.test.ts +50 -0
  27. package/dist/utils/developerUI/developerUI.ts +42 -0
  28. package/dist/utils/diagnostics.ts +1 -0
  29. package/dist/utils/query.ts +60 -12
  30. package/package.json +1 -1
  31. package/dist/utils/developerUI.ts +0 -32
  32. package/dist/utils/index.js +0 -9
  33. /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 {createPDiskDeveloperUILink, createVDiskDeveloperUILink} from '../../../utils/developerUI';
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: 'Memory',
188
- render: ({row}) =>
189
- row.MemoryUsed ? (
190
- <ProgressViewer
191
- value={row.MemoryUsed}
192
- capacity={row.MemoryLimit}
193
- formatValues={formatStorageValuesToGb}
194
- colorizeProgress={true}
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 = this.getConnectedNodesCount(
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 = this.getConnectedNodesCount(nodeInfo?.Peers);
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
- this.getConnectedNodesCount(nodeInfo?.Peers)
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
- &_fullscreen {
17
- width: 100%;
18
- margin-top: 10px;
19
- padding: 0 10px 10px;
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, {type ReactNode, useEffect, useState} from '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
- textResults: string;
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
- textResults,
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
- {result}
155
+ {content}
114
156
  {isFullscreen && (
115
157
  <Fullscreen>
116
- <div className={b('result', {fullscreen: true})}>{result}</div>
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
- <ResultIssues data={error.data} />
175
+ {content}
132
176
  {isFullscreen && (
133
177
  <Fullscreen>
134
- <div className={b('result', {fullscreen: true})}>
135
- <ResultIssues data={error.data} />
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
- <div className={b('result')}>
178
- {activeSection === resultOptionsIds.result && !error && renderResult()}
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');
@@ -0,0 +1,6 @@
1
+ import type {GraphNode} from '@gravity-ui/paranoid';
2
+
3
+ export const renderExplainNode = (node: GraphNode): string => {
4
+ const parts = node.name.split('|');
5
+ return parts.length > 1 ? parts[1] : node.name;
6
+ };
@@ -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({query: input, database: path, mode});
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
- result={content}
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: IQueryResult) => {
3
+ export const getPreparedResult = (data: KeyValueRow[] | undefined) => {
5
4
  const columnDivider = '\t';
6
5
  const rowDivider = '\n';
7
6
 
8
- if (!data?.result?.length) {
7
+ if (!data?.length) {
9
8
  return '';
10
9
  }
11
10
 
12
- const columnHeaders = Object.keys(data.result[0]);
13
- const rows = Array<string[] | KeyValueRow[]>(columnHeaders).concat(data.result);
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: 'modern',
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',
@@ -27,7 +27,7 @@ interface TNetNodeInfo {
27
27
  Port: number;
28
28
  }
29
29
 
30
- interface TNetNodePeerInfo {
30
+ export interface TNetNodePeerInfo {
31
31
  NodeId: number;
32
32
  PeerName: string;
33
33
  Connected: boolean;
@@ -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',
@@ -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
- result?: ArrayRow[];
239
- columns?: ColumnType[];
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;
@@ -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
+ };
@@ -3,6 +3,7 @@ import {ValueOf} from '../types/common';
3
3
  const TOP_SHARDS_SORT_VALUES = {
4
4
  CPUCores: 'CPUCores',
5
5
  DataSize: 'DataSize',
6
+ InFlightTxCount: 'InFlightTxCount',
6
7
  } as const;
7
8
 
8
9
  const TOP_QUERIES_SORT_VALUES = {
@@ -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 field from ArrayRow to KeyValueRow */
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((response as ExecuteModernResponse).result) &&
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,6 +1,6 @@
1
1
  {
2
2
  "name": "ydb-embedded-ui",
3
- "version": "4.21.1",
3
+ "version": "4.22.0",
4
4
  "files": [
5
5
  "dist"
6
6
  ],
@@ -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
- };
@@ -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
- };