ydb-embedded-ui 4.21.1 → 4.23.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 +27 -0
- package/dist/components/ProgressViewer/ProgressViewer.tsx +1 -1
- package/dist/components/VirtualTable/TableChunk.tsx +2 -1
- package/dist/components/VirtualTable/VirtualTable.tsx +1 -1
- package/dist/containers/Cluster/Cluster.tsx +2 -0
- package/dist/containers/Cluster/ClusterInfo/ClusterInfo.scss +14 -5
- package/dist/containers/Cluster/ClusterInfo/ClusterInfo.tsx +104 -24
- package/dist/containers/Cluster/ClusterInfoSkeleton/ClusterInfoSkeleton.tsx +1 -1
- package/dist/containers/Cluster/i18n/en.json +16 -0
- package/dist/containers/Cluster/i18n/index.ts +11 -0
- package/dist/containers/Cluster/i18n/ru.json +16 -0
- 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/Diagnostics/TenantOverview/TenantCpu/TopNodesByCpu.tsx +18 -3
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByLoad.tsx +18 -3
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopQueries.tsx +17 -1
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopShards.tsx +20 -1
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TopNodesByMemory.tsx +18 -3
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverviewTableLayout.tsx +2 -1
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopGroups.tsx +19 -2
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopTables.tsx +8 -1
- package/dist/containers/Tenant/Diagnostics/TenantOverview/getSectionTitle.tsx +28 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json +17 -1
- package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/ru.json +17 -1
- 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/Tenant/Schema/SchemaViewer/SchemaViewer.tsx +19 -11
- 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/cluster/__test__/parseGroupsStatsQueryResponse.test.ts +121 -0
- package/dist/store/reducers/cluster/cluster.ts +46 -2
- package/dist/store/reducers/cluster/types.ts +29 -4
- package/dist/store/reducers/cluster/utils.ts +88 -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/cluster.ts +3 -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/hooks/index.ts +1 -0
- package/dist/utils/hooks/useSearchQuery.ts +9 -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
@@ -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) => {
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import {useMemo} from 'react';
|
1
2
|
import cn from 'bem-cn-lite';
|
2
3
|
|
3
4
|
import DataTable, {Column} from '@gravity-ui/react-data-table';
|
@@ -28,6 +29,16 @@ interface SchemaViewerProps {
|
|
28
29
|
}
|
29
30
|
|
30
31
|
export const SchemaViewer = ({keyColumnIds = [], columns = [], type}: SchemaViewerProps) => {
|
32
|
+
// Keys should be displayd by their order in keyColumnIds (Primary Key)
|
33
|
+
const keyColumnsOrderValues = useMemo(() => {
|
34
|
+
return keyColumnIds.reduce<Record<number, number>>((result, keyColumnId, index) => {
|
35
|
+
// Put columns with negative values, so they will be the first with ascending sort
|
36
|
+
// Minus keyColumnIds.length for the first key, -1 for the last
|
37
|
+
result[keyColumnId] = index - keyColumnIds.length;
|
38
|
+
return result;
|
39
|
+
}, {});
|
40
|
+
}, [keyColumnIds]);
|
41
|
+
|
31
42
|
let dataTableColumns: Column<TColumnDescription>[] = [
|
32
43
|
{
|
33
44
|
name: SchemaViewerColumns.id,
|
@@ -36,8 +47,11 @@ export const SchemaViewer = ({keyColumnIds = [], columns = [], type}: SchemaView
|
|
36
47
|
{
|
37
48
|
name: SchemaViewerColumns.key,
|
38
49
|
width: 40,
|
50
|
+
// Table should start with key columns on sort click
|
51
|
+
defaultOrder: DataTable.ASCENDING,
|
39
52
|
sortAccessor: (row) => {
|
40
|
-
|
53
|
+
// Values in keyColumnsOrderValues are always negative, so it will be 1 for not key columns
|
54
|
+
return (row.Id && keyColumnsOrderValues[row.Id]) || 1;
|
41
55
|
},
|
42
56
|
render: ({row}) => {
|
43
57
|
return row.Id && keyColumnIds.includes(row.Id) ? (
|
@@ -58,6 +72,8 @@ export const SchemaViewer = ({keyColumnIds = [], columns = [], type}: SchemaView
|
|
58
72
|
{
|
59
73
|
name: SchemaViewerColumns.notNull,
|
60
74
|
width: 100,
|
75
|
+
// Table should start with notNull columns on sort click
|
76
|
+
defaultOrder: DataTable.DESCENDING,
|
61
77
|
render: ({row}) => {
|
62
78
|
if (row.NotNull) {
|
63
79
|
return '\u2713';
|
@@ -75,22 +91,14 @@ export const SchemaViewer = ({keyColumnIds = [], columns = [], type}: SchemaView
|
|
75
91
|
);
|
76
92
|
}
|
77
93
|
|
78
|
-
// Display key columns first
|
79
|
-
const tableData = columns.sort((column) => {
|
80
|
-
if (column.Id && keyColumnIds.includes(column.Id)) {
|
81
|
-
return 1;
|
82
|
-
}
|
83
|
-
return -1;
|
84
|
-
});
|
85
|
-
|
86
94
|
return (
|
87
95
|
<div className={b()}>
|
88
96
|
<DataTable
|
89
97
|
theme="yandex-cloud"
|
90
|
-
data={
|
98
|
+
data={columns}
|
91
99
|
columns={dataTableColumns}
|
92
100
|
settings={DEFAULT_TABLE_SETTINGS}
|
93
|
-
initialSortOrder={{columnId: SchemaViewerColumns.key, order: DataTable.
|
101
|
+
initialSortOrder={{columnId: SchemaViewerColumns.key, order: DataTable.ASCENDING}}
|
94
102
|
/>
|
95
103
|
</div>
|
96
104
|
);
|
@@ -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
|
};
|
@@ -0,0 +1,121 @@
|
|
1
|
+
import {parseGroupsStatsQueryResponse} from '../utils';
|
2
|
+
|
3
|
+
describe('parseGroupsStatsQueryResponse', () => {
|
4
|
+
const columns = [
|
5
|
+
{
|
6
|
+
name: 'PDiskFilter',
|
7
|
+
type: 'Utf8?',
|
8
|
+
},
|
9
|
+
{
|
10
|
+
name: 'ErasureSpecies',
|
11
|
+
type: 'Utf8?',
|
12
|
+
},
|
13
|
+
{
|
14
|
+
name: 'CurrentAvailableSize',
|
15
|
+
type: 'Uint64?',
|
16
|
+
},
|
17
|
+
{
|
18
|
+
name: 'CurrentAllocatedSize',
|
19
|
+
type: 'Uint64?',
|
20
|
+
},
|
21
|
+
{
|
22
|
+
name: 'CurrentGroupsCreated',
|
23
|
+
type: 'Uint32?',
|
24
|
+
},
|
25
|
+
{
|
26
|
+
name: 'AvailableGroupsToCreate',
|
27
|
+
type: 'Uint32?',
|
28
|
+
},
|
29
|
+
];
|
30
|
+
|
31
|
+
// 2 disk types and 2 erasure types
|
32
|
+
const dataSet1 = {
|
33
|
+
columns,
|
34
|
+
result: [
|
35
|
+
['Type:SSD', 'block-4-2', '1000', '2000', 100, 50],
|
36
|
+
['Type:ROT', 'block-4-2', '2000', '1000', 50, 0],
|
37
|
+
['Type:ROT', 'mirror-3of4', '1000', '0', 15, 0],
|
38
|
+
['Type:SSD', 'mirror-3of4', '1000', '0', 5, 50],
|
39
|
+
['Type:ROT', 'mirror-3-dc', null, null, null, 0],
|
40
|
+
['Type:SSD', 'mirror-3-dc', null, null, null, 0],
|
41
|
+
],
|
42
|
+
};
|
43
|
+
|
44
|
+
// 2 disk types and 1 erasure types, but with additional disks params
|
45
|
+
const dataSet2 = {
|
46
|
+
columns,
|
47
|
+
result: [
|
48
|
+
['Type:ROT,SharedWithOs:0,ReadCentric:0,Kind:0', 'mirror-3-dc', '1000', '500', 16, 16],
|
49
|
+
['Type:ROT,SharedWithOs:1,ReadCentric:0,Kind:0', 'mirror-3-dc', '2000', '1000', 8, 24],
|
50
|
+
['Type:SSD', 'mirror-3-dc', '3000', '400', 2, 10],
|
51
|
+
['Type:ROT', 'mirror-3-dc', null, null, null, 32],
|
52
|
+
['Type:ROT', 'block-4-2', null, null, null, 20],
|
53
|
+
['Type:SSD', 'block-4-2', null, null, null, 0],
|
54
|
+
],
|
55
|
+
};
|
56
|
+
const parsedDataSet1 = {
|
57
|
+
SSD: {
|
58
|
+
'block-4-2': {
|
59
|
+
diskType: 'SSD',
|
60
|
+
erasure: 'block-4-2',
|
61
|
+
createdGroups: 100,
|
62
|
+
totalGroups: 150,
|
63
|
+
allocatedSize: 2000,
|
64
|
+
availableSize: 1000,
|
65
|
+
},
|
66
|
+
'mirror-3of4': {
|
67
|
+
diskType: 'SSD',
|
68
|
+
erasure: 'mirror-3of4',
|
69
|
+
createdGroups: 5,
|
70
|
+
totalGroups: 55,
|
71
|
+
allocatedSize: 0,
|
72
|
+
availableSize: 1000,
|
73
|
+
},
|
74
|
+
},
|
75
|
+
HDD: {
|
76
|
+
'block-4-2': {
|
77
|
+
diskType: 'HDD',
|
78
|
+
erasure: 'block-4-2',
|
79
|
+
createdGroups: 50,
|
80
|
+
totalGroups: 50,
|
81
|
+
allocatedSize: 1000,
|
82
|
+
availableSize: 2000,
|
83
|
+
},
|
84
|
+
'mirror-3of4': {
|
85
|
+
diskType: 'HDD',
|
86
|
+
erasure: 'mirror-3of4',
|
87
|
+
createdGroups: 15,
|
88
|
+
totalGroups: 15,
|
89
|
+
allocatedSize: 0,
|
90
|
+
availableSize: 1000,
|
91
|
+
},
|
92
|
+
},
|
93
|
+
};
|
94
|
+
|
95
|
+
const parsedDataSet2 = {
|
96
|
+
HDD: {
|
97
|
+
'mirror-3-dc': {
|
98
|
+
diskType: 'HDD',
|
99
|
+
erasure: 'mirror-3-dc',
|
100
|
+
createdGroups: 24,
|
101
|
+
totalGroups: 64,
|
102
|
+
allocatedSize: 1500,
|
103
|
+
availableSize: 3000,
|
104
|
+
},
|
105
|
+
},
|
106
|
+
SSD: {
|
107
|
+
'mirror-3-dc': {
|
108
|
+
diskType: 'SSD',
|
109
|
+
erasure: 'mirror-3-dc',
|
110
|
+
createdGroups: 2,
|
111
|
+
totalGroups: 12,
|
112
|
+
allocatedSize: 400,
|
113
|
+
availableSize: 3000,
|
114
|
+
},
|
115
|
+
},
|
116
|
+
};
|
117
|
+
it('should correctly parse data', () => {
|
118
|
+
expect(parseGroupsStatsQueryResponse(dataSet1)).toEqual(parsedDataSet1);
|
119
|
+
expect(parseGroupsStatsQueryResponse(dataSet2)).toEqual(parsedDataSet2);
|
120
|
+
});
|
121
|
+
});
|
@@ -3,6 +3,7 @@ import type {Reducer} from 'redux';
|
|
3
3
|
import '../../../services/api';
|
4
4
|
import {createRequestActionTypes, createApiRequest} from '../../utils';
|
5
5
|
import type {ClusterAction, ClusterState} from './types';
|
6
|
+
import {createSelectClusterGroupsQuery, parseGroupsStatsQueryResponse} from './utils';
|
6
7
|
|
7
8
|
export const FETCH_CLUSTER = createRequestActionTypes('cluster', 'FETCH_CLUSTER');
|
8
9
|
|
@@ -17,9 +18,12 @@ const cluster: Reducer<ClusterState, ClusterAction> = (state = initialState, act
|
|
17
18
|
};
|
18
19
|
}
|
19
20
|
case FETCH_CLUSTER.SUCCESS: {
|
21
|
+
const {clusterData, groupsStats} = action.data;
|
22
|
+
|
20
23
|
return {
|
21
24
|
...state,
|
22
|
-
data:
|
25
|
+
data: clusterData,
|
26
|
+
groupsStats,
|
23
27
|
loading: false,
|
24
28
|
wasLoaded: true,
|
25
29
|
error: undefined,
|
@@ -42,8 +46,48 @@ const cluster: Reducer<ClusterState, ClusterAction> = (state = initialState, act
|
|
42
46
|
};
|
43
47
|
|
44
48
|
export function getClusterInfo(clusterName?: string) {
|
49
|
+
async function requestClusterData() {
|
50
|
+
// Error here is handled by createApiRequest
|
51
|
+
const clusterData = await window.api.getClusterInfo(clusterName);
|
52
|
+
|
53
|
+
try {
|
54
|
+
const clusterRoot = clusterData.Domain;
|
55
|
+
|
56
|
+
// Without domain we cannot get stats from system tables
|
57
|
+
if (!clusterRoot) {
|
58
|
+
return {
|
59
|
+
clusterData,
|
60
|
+
};
|
61
|
+
}
|
62
|
+
|
63
|
+
const query = createSelectClusterGroupsQuery(clusterRoot);
|
64
|
+
|
65
|
+
// Normally query request should be fulfilled within 300-400ms even on very big clusters
|
66
|
+
// Table with stats is supposed to be very small (less than 10 rows)
|
67
|
+
// So we batch this request with cluster request to prevent possible layout shifts, if data is missing
|
68
|
+
const groupsStatsResponse = await window.api.sendQuery({
|
69
|
+
schema: 'modern',
|
70
|
+
query: query,
|
71
|
+
database: clusterRoot,
|
72
|
+
action: 'execute-scan',
|
73
|
+
});
|
74
|
+
|
75
|
+
return {
|
76
|
+
clusterData,
|
77
|
+
groupsStats: parseGroupsStatsQueryResponse(groupsStatsResponse),
|
78
|
+
};
|
79
|
+
} catch {
|
80
|
+
// Doesn't return groups stats on error
|
81
|
+
// It could happen if user doesn't have access rights
|
82
|
+
// Or there are no system tables in cluster root
|
83
|
+
return {
|
84
|
+
clusterData,
|
85
|
+
};
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
45
89
|
return createApiRequest({
|
46
|
-
request:
|
90
|
+
request: requestClusterData(),
|
47
91
|
actions: FETCH_CLUSTER,
|
48
92
|
});
|
49
93
|
}
|
@@ -1,14 +1,39 @@
|
|
1
|
-
import {FETCH_CLUSTER} from './cluster';
|
2
|
-
|
3
1
|
import type {TClusterInfo} from '../../../types/api/cluster';
|
4
|
-
import type {ApiRequestAction} from '../../utils';
|
5
2
|
import type {IResponseError} from '../../../types/api/error';
|
3
|
+
import type {ApiRequestAction} from '../../utils';
|
4
|
+
|
5
|
+
import {FETCH_CLUSTER} from './cluster';
|
6
|
+
|
7
|
+
export interface DiskErasureGroupsStats {
|
8
|
+
diskType: string;
|
9
|
+
erasure: string;
|
10
|
+
createdGroups: number;
|
11
|
+
totalGroups: number;
|
12
|
+
allocatedSize: number;
|
13
|
+
availableSize: number;
|
14
|
+
}
|
15
|
+
|
16
|
+
/** Keys - erasure types */
|
17
|
+
export type DiskGroupsStats = Record<string, DiskErasureGroupsStats>;
|
18
|
+
|
19
|
+
/** Keys - PDisks types */
|
20
|
+
export type ClusterGroupsStats = Record<string, DiskGroupsStats>;
|
6
21
|
|
7
22
|
export interface ClusterState {
|
8
23
|
loading: boolean;
|
9
24
|
wasLoaded: boolean;
|
10
25
|
data?: TClusterInfo;
|
11
26
|
error?: IResponseError;
|
27
|
+
groupsStats?: ClusterGroupsStats;
|
28
|
+
}
|
29
|
+
|
30
|
+
export interface HandledClusterResponse {
|
31
|
+
clusterData: TClusterInfo;
|
32
|
+
groupsStats: ClusterGroupsStats;
|
12
33
|
}
|
13
34
|
|
14
|
-
export type ClusterAction = ApiRequestAction<
|
35
|
+
export type ClusterAction = ApiRequestAction<
|
36
|
+
typeof FETCH_CLUSTER,
|
37
|
+
HandledClusterResponse,
|
38
|
+
IResponseError
|
39
|
+
>;
|
@@ -0,0 +1,88 @@
|
|
1
|
+
import type {ExecuteQueryResponse} from '../../../types/api/query';
|
2
|
+
import {parseQueryAPIExecuteResponse} from '../../../utils/query';
|
3
|
+
|
4
|
+
import type {ClusterGroupsStats} from './types';
|
5
|
+
|
6
|
+
export const createSelectClusterGroupsQuery = (clusterRoot: string) => {
|
7
|
+
return `
|
8
|
+
SELECT
|
9
|
+
PDiskFilter,
|
10
|
+
ErasureSpecies,
|
11
|
+
CurrentAvailableSize,
|
12
|
+
CurrentAllocatedSize,
|
13
|
+
CurrentGroupsCreated,
|
14
|
+
AvailableGroupsToCreate
|
15
|
+
FROM \`${clusterRoot}/.sys/ds_storage_stats\`
|
16
|
+
ORDER BY CurrentGroupsCreated DESC;
|
17
|
+
`;
|
18
|
+
};
|
19
|
+
|
20
|
+
const getDiskType = (rawTypeString: string) => {
|
21
|
+
// Check if value math regexp and put disk type in type group
|
22
|
+
const diskTypeRe = /^Type:(?<type>[A-Za-z]+)/;
|
23
|
+
|
24
|
+
const diskType = rawTypeString.match(diskTypeRe)?.groups?.['type'];
|
25
|
+
|
26
|
+
if (diskType === 'ROT') {
|
27
|
+
// Display ROT as HDD
|
28
|
+
return 'HDD';
|
29
|
+
}
|
30
|
+
|
31
|
+
return diskType;
|
32
|
+
};
|
33
|
+
|
34
|
+
export const parseGroupsStatsQueryResponse = (
|
35
|
+
data: ExecuteQueryResponse<'modern'>,
|
36
|
+
): ClusterGroupsStats => {
|
37
|
+
const parsedData = parseQueryAPIExecuteResponse(data).result;
|
38
|
+
const result: ClusterGroupsStats = {};
|
39
|
+
|
40
|
+
parsedData?.forEach((stats) => {
|
41
|
+
const {
|
42
|
+
PDiskFilter,
|
43
|
+
ErasureSpecies: erasure,
|
44
|
+
CurrentAvailableSize,
|
45
|
+
CurrentAllocatedSize,
|
46
|
+
CurrentGroupsCreated,
|
47
|
+
AvailableGroupsToCreate,
|
48
|
+
} = stats;
|
49
|
+
|
50
|
+
const createdGroups = Number(CurrentGroupsCreated) || 0;
|
51
|
+
const availableGroupsToCreate = Number(AvailableGroupsToCreate) || 0;
|
52
|
+
const totalGroups = createdGroups + availableGroupsToCreate;
|
53
|
+
const allocatedSize = Number(CurrentAllocatedSize) || 0;
|
54
|
+
const availableSize = Number(CurrentAvailableSize) || 0;
|
55
|
+
const diskType = PDiskFilter && typeof PDiskFilter === 'string' && getDiskType(PDiskFilter);
|
56
|
+
|
57
|
+
if (diskType && erasure && typeof erasure === 'string' && createdGroups) {
|
58
|
+
const preparedStats = {
|
59
|
+
diskType,
|
60
|
+
erasure,
|
61
|
+
createdGroups,
|
62
|
+
totalGroups,
|
63
|
+
allocatedSize,
|
64
|
+
availableSize,
|
65
|
+
};
|
66
|
+
|
67
|
+
if (result[diskType]) {
|
68
|
+
if (result[diskType][erasure]) {
|
69
|
+
const currentValue = {...result[diskType][erasure]};
|
70
|
+
result[diskType][erasure] = {
|
71
|
+
diskType,
|
72
|
+
erasure,
|
73
|
+
createdGroups: currentValue.createdGroups + createdGroups,
|
74
|
+
totalGroups: currentValue.totalGroups + totalGroups,
|
75
|
+
allocatedSize: currentValue.allocatedSize + allocatedSize,
|
76
|
+
availableSize: currentValue.availableSize + availableSize,
|
77
|
+
};
|
78
|
+
} else {
|
79
|
+
result[diskType][erasure] = preparedStats;
|
80
|
+
}
|
81
|
+
} else {
|
82
|
+
result[diskType] = {[erasure]: preparedStats};
|
83
|
+
}
|
84
|
+
}
|
85
|
+
});
|
86
|
+
|
87
|
+
return result;
|
88
|
+
};
|
@@ -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',
|