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.
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
- };