ydb-embedded-ui 4.21.1 → 4.23.0
Sign up to get free protection for your applications and to get access to all the features.
- 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',
|