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.
Files changed (60) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/components/ProgressViewer/ProgressViewer.tsx +1 -1
  3. package/dist/components/VirtualTable/TableChunk.tsx +2 -1
  4. package/dist/components/VirtualTable/VirtualTable.tsx +1 -1
  5. package/dist/containers/Cluster/Cluster.tsx +2 -0
  6. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.scss +14 -5
  7. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.tsx +104 -24
  8. package/dist/containers/Cluster/ClusterInfoSkeleton/ClusterInfoSkeleton.tsx +1 -1
  9. package/dist/containers/Cluster/i18n/en.json +16 -0
  10. package/dist/containers/Cluster/i18n/index.ts +11 -0
  11. package/dist/containers/Cluster/i18n/ru.json +16 -0
  12. package/dist/containers/Node/NodeStructure/Pdisk.tsx +4 -1
  13. package/dist/containers/Nodes/getNodesColumns.tsx +57 -13
  14. package/dist/containers/Tenant/Diagnostics/Network/Network.js +5 -10
  15. package/dist/containers/Tenant/Diagnostics/Network/utils.ts +6 -0
  16. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByCpu.tsx +18 -3
  17. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByLoad.tsx +18 -3
  18. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopQueries.tsx +17 -1
  19. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopShards.tsx +20 -1
  20. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TopNodesByMemory.tsx +18 -3
  21. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverviewTableLayout.tsx +2 -1
  22. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopGroups.tsx +19 -2
  23. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopTables.tsx +8 -1
  24. package/dist/containers/Tenant/Diagnostics/TenantOverview/getSectionTitle.tsx +28 -0
  25. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json +17 -1
  26. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/ru.json +17 -1
  27. package/dist/containers/Tenant/Query/ExecuteResult/ExecuteResult.scss +13 -5
  28. package/dist/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx +72 -18
  29. package/dist/containers/Tenant/Query/ExplainResult/ExplainResult.js +2 -1
  30. package/dist/containers/Tenant/Query/ExplainResult/utils.ts +6 -0
  31. package/dist/containers/Tenant/Query/QueryEditor/QueryEditor.js +11 -24
  32. package/dist/containers/Tenant/Query/utils/getPreparedResult.ts +4 -5
  33. package/dist/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx +19 -11
  34. package/dist/containers/UserSettings/i18n/en.json +3 -0
  35. package/dist/containers/UserSettings/i18n/ru.json +3 -0
  36. package/dist/containers/UserSettings/settings.ts +7 -0
  37. package/dist/store/reducers/cluster/__test__/parseGroupsStatsQueryResponse.test.ts +121 -0
  38. package/dist/store/reducers/cluster/cluster.ts +46 -2
  39. package/dist/store/reducers/cluster/types.ts +29 -4
  40. package/dist/store/reducers/cluster/utils.ts +88 -0
  41. package/dist/store/reducers/executeQuery.ts +4 -3
  42. package/dist/store/reducers/nodes/types.ts +11 -1
  43. package/dist/store/reducers/nodes/utils.ts +6 -0
  44. package/dist/store/reducers/settings/settings.ts +2 -0
  45. package/dist/types/api/cluster.ts +3 -0
  46. package/dist/types/api/netInfo.ts +1 -1
  47. package/dist/types/api/nodes.ts +24 -0
  48. package/dist/types/api/query.ts +23 -8
  49. package/dist/types/store/query.ts +6 -0
  50. package/dist/utils/constants.ts +3 -0
  51. package/dist/utils/developerUI/__test__/developerUI.test.ts +50 -0
  52. package/dist/utils/developerUI/developerUI.ts +42 -0
  53. package/dist/utils/diagnostics.ts +1 -0
  54. package/dist/utils/hooks/index.ts +1 -0
  55. package/dist/utils/hooks/useSearchQuery.ts +9 -0
  56. package/dist/utils/query.ts +60 -12
  57. package/package.json +1 -1
  58. package/dist/utils/developerUI.ts +0 -32
  59. package/dist/utils/index.js +0 -9
  60. /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({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) => {
@@ -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
- return row.Id && keyColumnIds.includes(row.Id) ? 1 : 0;
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={tableData}
98
+ data={columns}
91
99
  columns={dataTableColumns}
92
100
  settings={DEFAULT_TABLE_SETTINGS}
93
- initialSortOrder={{columnId: SchemaViewerColumns.key, order: DataTable.DESCENDING}}
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: action.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: window.api.getClusterInfo(clusterName),
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<typeof FETCH_CLUSTER, TClusterInfo, IResponseError>;
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: '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',
@@ -32,6 +32,9 @@ export interface TClusterInfo {
32
32
  /** uint64 */
33
33
  Tablets?: string;
34
34
 
35
+ /** Cluster root database */
36
+ Domain?: string;
37
+
35
38
  Balancer?: string; // additional
36
39
  Solomon?: string; // additional
37
40
  }
@@ -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;