ydb-embedded-ui 4.19.3 → 4.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/components/LinkToSchemaObject/LinkToSchemaObject.tsx +20 -0
  3. package/dist/components/QueryExecutionStatus/QueryExecutionStatus.tsx +3 -2
  4. package/dist/components/UsageLabel/UsageLabel.scss +6 -0
  5. package/dist/components/UsageLabel/UsageLabel.tsx +22 -0
  6. package/dist/containers/AsideNavigation/AsideNavigation.tsx +13 -8
  7. package/dist/containers/AsideNavigation/i18n/en.json +13 -0
  8. package/dist/containers/AsideNavigation/i18n/index.ts +11 -0
  9. package/dist/containers/AsideNavigation/i18n/ru.json +13 -0
  10. package/dist/containers/Node/NodeStructure/Pdisk.tsx +74 -68
  11. package/dist/containers/Node/NodeStructure/Vdisk.tsx +9 -33
  12. package/dist/containers/Nodes/Nodes.tsx +10 -2
  13. package/dist/containers/Nodes/getNodesColumns.tsx +206 -122
  14. package/dist/containers/Storage/Storage.tsx +9 -2
  15. package/dist/containers/Storage/utils/index.ts +1 -22
  16. package/dist/containers/Tenant/Diagnostics/Describe/Describe.tsx +2 -3
  17. package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.scss +0 -1
  18. package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.tsx +4 -2
  19. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +1 -0
  20. package/dist/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckDetails.tsx +8 -1
  21. package/dist/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckPreview.tsx +11 -1
  22. package/dist/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/IssuesViewer/IssueTree.tsx +0 -1
  23. package/dist/containers/Tenant/Diagnostics/TenantOverview/MetricsCards/MetricsCards.tsx +3 -0
  24. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx +21 -0
  25. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByCpu.tsx +53 -0
  26. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByLoad.tsx +53 -0
  27. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopQueries.tsx +85 -0
  28. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopShards.tsx +53 -0
  29. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx +9 -0
  30. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TopNodesByMemory.tsx +55 -0
  31. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss +40 -0
  32. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +35 -19
  33. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverviewTableLayout.tsx +53 -0
  34. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopTables.tsx +15 -7
  35. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json +3 -3
  36. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/ru.json +3 -3
  37. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss +0 -2
  38. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +13 -61
  39. package/dist/containers/Tenant/Diagnostics/TopQueries/getTopQueriesColumns.tsx +82 -0
  40. package/dist/containers/Tenant/Diagnostics/TopShards/Filters/Filters.tsx +2 -2
  41. package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.tsx +18 -97
  42. package/dist/containers/Tenant/Diagnostics/TopShards/getTopShardsColumns.tsx +138 -0
  43. package/dist/containers/Tenant/Info/ExternalTable/ExternalTable.tsx +2 -4
  44. package/dist/containers/Tenant/Query/ExecuteResult/{ExecuteResult.js → ExecuteResult.tsx} +51 -31
  45. package/dist/containers/Tenant/Query/Issues/Issues.tsx +4 -6
  46. package/dist/containers/Tenant/Query/QueryDuration/QueryDuration.tsx +1 -1
  47. package/dist/containers/Tenant/utils/paneVisibilityToggleHelpers.tsx +1 -1
  48. package/dist/routes.ts +6 -0
  49. package/dist/store/reducers/{executeTopQueries.ts → executeTopQueries/executeTopQueries.ts} +23 -75
  50. package/dist/{types/store/executeTopQueries.ts → store/reducers/executeTopQueries/types.ts} +3 -7
  51. package/dist/store/reducers/executeTopQueries/utils.ts +36 -0
  52. package/dist/store/reducers/index.ts +12 -2
  53. package/dist/store/reducers/nodes/types.ts +1 -0
  54. package/dist/store/reducers/nodes/utils.ts +16 -6
  55. package/dist/store/reducers/{shardsWorkload.ts → shardsWorkload/shardsWorkload.ts} +5 -11
  56. package/dist/{types/store/shardsWorkload.ts → store/reducers/shardsWorkload/types.ts} +3 -7
  57. package/dist/store/reducers/tenantOverview/topNodesByCpu/topNodesByCpu.ts +87 -0
  58. package/dist/store/reducers/tenantOverview/topNodesByCpu/types.ts +29 -0
  59. package/dist/store/reducers/tenantOverview/topNodesByLoad/topNodesByLoad.ts +87 -0
  60. package/dist/store/reducers/tenantOverview/topNodesByLoad/types.ts +29 -0
  61. package/dist/store/reducers/tenantOverview/topNodesByMemory/topNodesByMemory.ts +87 -0
  62. package/dist/store/reducers/tenantOverview/topNodesByMemory/types.ts +29 -0
  63. package/dist/store/reducers/tenantOverview/topQueries/tenantOverviewTopQueries.ts +93 -0
  64. package/dist/store/reducers/tenantOverview/topQueries/types.ts +14 -0
  65. package/dist/store/reducers/tenantOverview/topShards/tenantOverviewTopShards.ts +103 -0
  66. package/dist/store/reducers/tenantOverview/topShards/types.ts +14 -0
  67. package/dist/store/reducers/tenantOverview/topShards/utils.ts +3 -0
  68. package/dist/styles/mixins.scss +4 -0
  69. package/dist/types/additionalProps.ts +3 -1
  70. package/dist/types/api/compute.ts +1 -1
  71. package/dist/types/react-json-inspector.d.ts +21 -0
  72. package/dist/utils/diagnostics.ts +11 -0
  73. package/dist/utils/generateEvaluator.ts +21 -0
  74. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
- import React, {useEffect, useState} from 'react';
2
- import {useDispatch, useSelector} from 'react-redux';
1
+ import React, {type ReactNode, useEffect, useState} from 'react';
2
+ import {useDispatch} from 'react-redux';
3
3
  import cn from 'bem-cn-lite';
4
4
  import JSONTree from 'react-json-inspector';
5
5
 
@@ -11,13 +11,15 @@ import EnableFullscreenButton from '../../../../components/EnableFullscreenButto
11
11
  import Fullscreen from '../../../../components/Fullscreen/Fullscreen';
12
12
  import {QueryExecutionStatus} from '../../../../components/QueryExecutionStatus';
13
13
 
14
+ import type {ValueOf} from '../../../../types/common';
15
+ import type {IQueryResult, QueryErrorResponse} from '../../../../types/store/query';
14
16
  import {disableFullscreen} from '../../../../store/reducers/fullscreen';
15
-
16
17
  import {prepareQueryError} from '../../../../utils/query';
18
+ import {useTypedSelector} from '../../../../utils/hooks';
17
19
 
18
20
  import {PaneVisibilityToggleButtons} from '../../utils/paneVisibilityToggleHelpers';
19
21
 
20
- import ResultIssues from '../Issues/Issues';
22
+ import {ResultIssues} from '../Issues/Issues';
21
23
  import {QueryDuration} from '../QueryDuration/QueryDuration';
22
24
 
23
25
  import './ExecuteResult.scss';
@@ -27,31 +29,51 @@ const b = cn('ydb-query-execute-result');
27
29
  const resultOptionsIds = {
28
30
  result: 'result',
29
31
  stats: 'stats',
30
- };
32
+ } as const;
33
+
34
+ type SectionID = ValueOf<typeof resultOptionsIds>;
31
35
 
32
36
  const resultOptions = [
33
37
  {value: resultOptionsIds.result, content: 'Result'},
34
38
  {value: resultOptionsIds.stats, content: 'Stats'},
35
39
  ];
36
40
 
37
- export function ExecuteResult(props) {
38
- const [activeSection, setActiveSection] = useState(resultOptionsIds.result);
39
- const isFullscreen = useSelector((state) => state.fullscreen);
41
+ interface ExecuteResultProps {
42
+ textResults: string;
43
+ result: ReactNode;
44
+ stats: IQueryResult['stats'] | undefined;
45
+ error: string | QueryErrorResponse | undefined;
46
+ copyDisabled?: boolean;
47
+ isResultsCollapsed?: boolean;
48
+ onCollapseResults: VoidFunction;
49
+ onExpandResults: VoidFunction;
50
+ }
51
+
52
+ export function ExecuteResult({
53
+ textResults,
54
+ result,
55
+ stats,
56
+ error,
57
+ copyDisabled,
58
+ isResultsCollapsed,
59
+ onCollapseResults,
60
+ onExpandResults,
61
+ }: ExecuteResultProps) {
62
+ const [activeSection, setActiveSection] = useState<SectionID>(resultOptionsIds.result);
63
+ const isFullscreen = useTypedSelector((state) => state.fullscreen);
40
64
  const dispatch = useDispatch();
41
65
 
42
66
  useEffect(() => {
43
67
  return () => {
44
68
  dispatch(disableFullscreen());
45
69
  };
46
- }, []);
70
+ }, [dispatch]);
47
71
 
48
- const onSelectSection = (value) => {
49
- setActiveSection(value);
72
+ const onSelectSection = (value: string) => {
73
+ setActiveSection(value as SectionID);
50
74
  };
51
75
 
52
76
  const renderClipboardButton = () => {
53
- const {textResults, copyDisabled} = props;
54
-
55
77
  return (
56
78
  <CopyToClipboard
57
79
  text={textResults}
@@ -65,7 +87,7 @@ export function ExecuteResult(props) {
65
87
  const renderStats = () => {
66
88
  const content = (
67
89
  <JSONTree
68
- data={props.stats}
90
+ data={stats}
69
91
  isExpanded={() => true}
70
92
  className={b('inspector')}
71
93
  searchOptions={{
@@ -86,8 +108,6 @@ export function ExecuteResult(props) {
86
108
  };
87
109
 
88
110
  const renderResult = () => {
89
- const {result} = props;
90
-
91
111
  return (
92
112
  <React.Fragment>
93
113
  {result}
@@ -101,11 +121,11 @@ export function ExecuteResult(props) {
101
121
  };
102
122
 
103
123
  const renderIssues = () => {
104
- const error = props.error;
105
-
106
- const hasIssues = error?.data?.issues && Array.isArray(error.data.issues);
124
+ if (!error) {
125
+ return null;
126
+ }
107
127
 
108
- if (hasIssues) {
128
+ if (typeof error === 'object' && error.data?.issues && Array.isArray(error.data.issues)) {
109
129
  return (
110
130
  <React.Fragment>
111
131
  <ResultIssues data={error.data} />
@@ -120,20 +140,20 @@ export function ExecuteResult(props) {
120
140
  );
121
141
  }
122
142
 
123
- if (error) {
124
- return <div className={b('error')}>{prepareQueryError(error)}</div>;
125
- }
143
+ const parsedError = typeof error === 'string' ? error : prepareQueryError(error);
144
+
145
+ return <div className={b('error')}>{parsedError}</div>;
126
146
  };
127
147
 
128
148
  return (
129
149
  <React.Fragment>
130
150
  <div className={b('controls')}>
131
151
  <div className={b('controls-right')}>
132
- <QueryExecutionStatus error={props.error} />
152
+ <QueryExecutionStatus error={error} />
133
153
 
134
- {props.stats && !props.error && (
154
+ {stats && !error && (
135
155
  <React.Fragment>
136
- <QueryDuration duration={props.stats?.DurationUs} />
156
+ <QueryDuration duration={stats?.DurationUs} />
137
157
  <Divider />
138
158
  <RadioButton
139
159
  options={resultOptions}
@@ -147,16 +167,16 @@ export function ExecuteResult(props) {
147
167
  {renderClipboardButton()}
148
168
  <EnableFullscreenButton />
149
169
  <PaneVisibilityToggleButtons
150
- onCollapse={props.onCollapseResults}
151
- onExpand={props.onExpandResults}
152
- isCollapsed={props.isResultsCollapsed}
170
+ onCollapse={onCollapseResults}
171
+ onExpand={onExpandResults}
172
+ isCollapsed={isResultsCollapsed}
153
173
  initialDirection="bottom"
154
174
  />
155
175
  </div>
156
176
  </div>
157
177
  <div className={b('result')}>
158
- {activeSection === resultOptionsIds.result && !props.error && renderResult()}
159
- {activeSection === resultOptionsIds.stats && !props.error && renderStats()}
178
+ {activeSection === resultOptionsIds.result && !error && renderResult()}
179
+ {activeSection === resultOptionsIds.stats && !error && renderStats()}
160
180
  {renderIssues()}
161
181
  </div>
162
182
  </React.Fragment>
@@ -21,10 +21,9 @@ const blockIssue = cn('kv-issue');
21
21
 
22
22
  interface ResultIssuesProps {
23
23
  data: ErrorResponse | string;
24
- className: string;
25
24
  }
26
25
 
27
- export default function ResultIssues({data, className}: ResultIssuesProps) {
26
+ export function ResultIssues({data}: ResultIssuesProps) {
28
27
  const [showIssues, setShowIssues] = React.useState(false);
29
28
 
30
29
  const issues = typeof data === 'string' ? undefined : data?.issues;
@@ -59,22 +58,21 @@ export default function ResultIssues({data, className}: ResultIssuesProps) {
59
58
  </Button>
60
59
  )}
61
60
  </div>
62
- {hasIssues && showIssues && <Issues issues={issues} className={className} />}
61
+ {hasIssues && showIssues && <Issues issues={issues} />}
63
62
  </div>
64
63
  );
65
64
  }
66
65
 
67
66
  interface IssuesProps {
68
- className?: string;
69
67
  issues: IssueMessage[] | null | undefined;
70
68
  }
71
- export function Issues({issues, className}: IssuesProps) {
69
+ export function Issues({issues}: IssuesProps) {
72
70
  const mostSevereIssue = issues?.reduce((result, issue) => {
73
71
  const severity = issue.severity ?? 10;
74
72
  return Math.min(result, severity);
75
73
  }, 10);
76
74
  return (
77
- <div className={blockIssues(null, className)}>
75
+ <div className={blockIssues(null)}>
78
76
  {issues?.map((issue, index) => (
79
77
  <Issue key={index} issue={issue} expanded={issue === mostSevereIssue} />
80
78
  ))}
@@ -8,7 +8,7 @@ import i18n from '../i18n';
8
8
  import './QueryDuration.scss';
9
9
 
10
10
  interface QueryDurationProps {
11
- duration?: string;
11
+ duration?: string | number;
12
12
  }
13
13
 
14
14
  const b = block('ydb-query-duration');
@@ -67,7 +67,7 @@ export function paneVisibilityToggleReducerCreator(isPaneCollapsedKey: string) {
67
67
  interface ToggleButtonProps {
68
68
  onCollapse: VoidFunction;
69
69
  onExpand: VoidFunction;
70
- isCollapsed: boolean;
70
+ isCollapsed?: boolean;
71
71
  initialDirection?: 'right' | 'left' | 'top' | 'bottom';
72
72
  className?: string;
73
73
  }
package/dist/routes.ts CHANGED
@@ -49,4 +49,10 @@ export function createHref(
49
49
  return `${compile(route)(params)}${search}`;
50
50
  }
51
51
 
52
+ // embedded version could be located in some folder (e.g. host/some_folder/app_router_path)
53
+ // window.location has the full pathname, while location from router ignores path to project
54
+ // this navigation assumes page reloading
55
+ export const createExternalUILink = (query = {}) =>
56
+ createHref(window.location.pathname, undefined, query);
57
+
52
58
  export default routes;
@@ -1,19 +1,14 @@
1
1
  import type {AnyAction, Reducer} from 'redux';
2
2
  import type {ThunkAction} from 'redux-thunk';
3
3
 
4
- import '../../services/api';
5
- import {
6
- ITopQueriesAction,
7
- ITopQueriesFilters,
8
- ITopQueriesState,
9
- } from '../../types/store/executeTopQueries';
10
- import {IQueryResult} from '../../types/store/query';
4
+ import '../../../services/api';
5
+ import type {IQueryResult} from '../../../types/store/query';
6
+ import {parseQueryAPIExecuteResponse} from '../../../utils/query';
11
7
 
12
- import {parseQueryAPIExecuteResponse} from '../../utils/query';
13
-
14
- import {createRequestActionTypes, createApiRequest} from '../utils';
15
-
16
- import type {RootState} from '.';
8
+ import {createRequestActionTypes, createApiRequest} from '../../utils';
9
+ import type {RootState} from '..';
10
+ import type {ITopQueriesAction, ITopQueriesFilters, ITopQueriesState} from './types';
11
+ import {getFiltersConditions} from './utils';
17
12
 
18
13
  export const FETCH_TOP_QUERIES = createRequestActionTypes('top-queries', 'FETCH_TOP_QUERIES');
19
14
  const SET_TOP_QUERIES_STATE = 'top-queries/SET_TOP_QUERIES_STATE';
@@ -25,41 +20,6 @@ const initialState = {
25
20
  filters: {},
26
21
  };
27
22
 
28
- const getMaxIntervalSubquery = (path: string) => `(
29
- SELECT
30
- MAX(IntervalEnd)
31
- FROM \`${path}/.sys/top_queries_by_cpu_time_one_hour\`
32
- )`;
33
-
34
- function getFiltersConditions(path: string, filters?: ITopQueriesFilters) {
35
- const conditions: string[] = [];
36
-
37
- if (filters?.from && filters?.to && filters.from > filters.to) {
38
- throw new Error('Invalid date range');
39
- }
40
-
41
- if (filters?.from) {
42
- // matching `from` & `to` is an edge case
43
- // other cases should not include the starting point, since intervals are stored using the ending time
44
- const gt = filters.to === filters.from ? '>=' : '>';
45
- conditions.push(`IntervalEnd ${gt} Timestamp('${new Date(filters.from).toISOString()}')`);
46
- }
47
-
48
- if (filters?.to) {
49
- conditions.push(`IntervalEnd <= Timestamp('${new Date(filters.to).toISOString()}')`);
50
- }
51
-
52
- if (!filters?.from && !filters?.to) {
53
- conditions.push(`IntervalEnd IN ${getMaxIntervalSubquery(path)}`);
54
- }
55
-
56
- if (filters?.text) {
57
- conditions.push(`QueryText ILIKE '%${filters.text}%'`);
58
- }
59
-
60
- return conditions.join(' AND ');
61
- }
62
-
63
23
  const getQueryText = (path: string, filters?: ITopQueriesFilters) => {
64
24
  const filterConditions = getFiltersConditions(path, filters);
65
25
  return `
@@ -128,34 +88,22 @@ type FetchTopQueries = (params: {
128
88
  filters?: ITopQueriesFilters;
129
89
  }) => ThunkAction<Promise<IQueryResult | undefined>, RootState, unknown, AnyAction>;
130
90
 
131
- export const fetchTopQueries: FetchTopQueries =
132
- ({database, filters}) =>
133
- async (dispatch, getState) => {
134
- try {
135
- return createApiRequest({
136
- request: window.api.sendQuery(
137
- {
138
- schema: 'modern',
139
- query: getQueryText(database, filters),
140
- database,
141
- action: 'execute-scan',
142
- },
143
- {
144
- concurrentId: 'executeTopQueries',
145
- },
146
- ),
147
- actions: FETCH_TOP_QUERIES,
148
- dataHandler: parseQueryAPIExecuteResponse,
149
- })(dispatch, getState);
150
- } catch (error) {
151
- dispatch({
152
- type: FETCH_TOP_QUERIES.FAILURE,
153
- error,
154
- });
155
-
156
- throw error;
157
- }
158
- };
91
+ export const fetchTopQueries: FetchTopQueries = ({database, filters}) =>
92
+ createApiRequest({
93
+ request: window.api.sendQuery(
94
+ {
95
+ schema: 'modern',
96
+ query: getQueryText(database, filters),
97
+ database,
98
+ action: 'execute-scan',
99
+ },
100
+ {
101
+ concurrentId: 'executeTopQueries',
102
+ },
103
+ ),
104
+ actions: FETCH_TOP_QUERIES,
105
+ dataHandler: parseQueryAPIExecuteResponse,
106
+ });
159
107
 
160
108
  export function setTopQueriesState(state: Partial<ITopQueriesState>) {
161
109
  return {
@@ -1,10 +1,6 @@
1
- import {
2
- FETCH_TOP_QUERIES,
3
- setTopQueriesState,
4
- setTopQueriesFilters,
5
- } from '../../store/reducers/executeTopQueries';
6
- import type {ApiRequestAction} from '../../store/utils';
7
- import type {IQueryResult, QueryErrorResponse} from './query';
1
+ import {FETCH_TOP_QUERIES, setTopQueriesState, setTopQueriesFilters} from './executeTopQueries';
2
+ import type {ApiRequestAction} from '../../utils';
3
+ import type {IQueryResult, QueryErrorResponse} from '../../../types/store/query';
8
4
 
9
5
  export interface ITopQueriesFilters {
10
6
  /** ms from epoch */
@@ -0,0 +1,36 @@
1
+ import {ITopQueriesFilters} from './types';
2
+
3
+ const getMaxIntervalSubquery = (path: string) => `(
4
+ SELECT
5
+ MAX(IntervalEnd)
6
+ FROM \`${path}/.sys/top_queries_by_cpu_time_one_hour\`
7
+ )`;
8
+
9
+ export function getFiltersConditions(path: string, filters?: ITopQueriesFilters) {
10
+ const conditions: string[] = [];
11
+
12
+ if (filters?.from && filters?.to && filters.from > filters.to) {
13
+ throw new Error('Invalid date range');
14
+ }
15
+
16
+ if (filters?.from) {
17
+ // matching `from` & `to` is an edge case
18
+ // other cases should not include the starting point, since intervals are stored using the ending time
19
+ const gt = filters.to === filters.from ? '>=' : '>';
20
+ conditions.push(`IntervalEnd ${gt} Timestamp('${new Date(filters.from).toISOString()}')`);
21
+ }
22
+
23
+ if (filters?.to) {
24
+ conditions.push(`IntervalEnd <= Timestamp('${new Date(filters.to).toISOString()}')`);
25
+ }
26
+
27
+ if (!filters?.from && !filters?.to) {
28
+ conditions.push(`IntervalEnd IN ${getMaxIntervalSubquery(path)}`);
29
+ }
30
+
31
+ if (filters?.text) {
32
+ conditions.push(`QueryText ILIKE '%${filters.text}%'`);
33
+ }
34
+
35
+ return conditions.join(' AND ');
36
+ }
@@ -1,6 +1,9 @@
1
1
  import {combineReducers} from 'redux';
2
2
 
3
3
  import nodes from './nodes/nodes';
4
+ import {topNodesByLoad} from './tenantOverview/topNodesByLoad/topNodesByLoad';
5
+ import {topNodesByCpu} from './tenantOverview/topNodesByCpu/topNodesByCpu';
6
+ import {topNodesByMemory} from './tenantOverview/topNodesByMemory/topNodesByMemory';
4
7
  import cluster from './cluster/cluster';
5
8
  import clusterNodes from './clusterNodes/clusterNodes';
6
9
  import tenant from './tenant/tenant';
@@ -26,10 +29,12 @@ import preview from './preview';
26
29
  import nodesList from './nodesList';
27
30
  import describe from './describe';
28
31
  import schemaAcl from './schemaAcl/schemaAcl';
29
- import executeTopQueries from './executeTopQueries';
32
+ import executeTopQueries from './executeTopQueries/executeTopQueries';
33
+ import {tenantOverviewTopQueries} from './tenantOverview/topQueries/tenantOverviewTopQueries';
30
34
  import executeTopTables from './tenantOverview/executeTopTables/executeTopTables';
31
35
  import healthcheckInfo from './healthcheckInfo';
32
- import shardsWorkload from './shardsWorkload';
36
+ import shardsWorkload from './shardsWorkload/shardsWorkload';
37
+ import {tenantOverviewTopShards} from './tenantOverview/topShards/tenantOverviewTopShards';
33
38
  import hotKeys from './hotKeys';
34
39
  import olapStats from './olapStats';
35
40
  import authentication from './authentication/authentication';
@@ -41,6 +46,9 @@ import singleClusterMode from './singleClusterMode';
41
46
  export const rootReducer = {
42
47
  singleClusterMode,
43
48
  nodes,
49
+ topNodesByLoad,
50
+ topNodesByCpu,
51
+ topNodesByMemory,
44
52
  cluster,
45
53
  clusterNodes,
46
54
  tenant,
@@ -69,8 +77,10 @@ export const rootReducer = {
69
77
  schemaAcl,
70
78
  executeTopQueries,
71
79
  executeTopTables,
80
+ tenantOverviewTopQueries,
72
81
  healthcheckInfo,
73
82
  shardsWorkload,
83
+ tenantOverviewTopShards,
74
84
  hotKeys,
75
85
  authentication,
76
86
  header,
@@ -33,6 +33,7 @@ export interface NodesPreparedEntity {
33
33
  StartTime?: string;
34
34
  Uptime: string;
35
35
  MemoryUsed?: string;
36
+ MemoryLimit?: string;
36
37
  PoolStats?: TPoolStats[];
37
38
  LoadAverage?: number[];
38
39
  Tablets?: TFullTabletStateInfo[] | TComputeTabletStateInfo[];
@@ -1,6 +1,7 @@
1
- import type {TComputeInfo, TComputeNodeInfo} from '../../../types/api/compute';
1
+ import type {TComputeInfo, TComputeNodeInfo, TComputeTenantInfo} from '../../../types/api/compute';
2
2
  import type {TNodesInfo} from '../../../types/api/nodes';
3
3
  import {calcUptime} from '../../../utils/dataFormatters/dataFormatters';
4
+ import {generateEvaluator} from '../../../utils/generateEvaluator';
4
5
 
5
6
  import type {NodesHandledResponse, NodesPreparedEntity} from './types';
6
7
 
@@ -14,23 +15,30 @@ const prepareComputeNode = (node: TComputeNodeInfo, tenantName?: string) => {
14
15
  };
15
16
  };
16
17
 
17
- export const prepareComputeNodesData = (data: TComputeInfo): NodesHandledResponse => {
18
+ export const prepareComputeNodes = (nodes?: TComputeNodeInfo[], tenants?: TComputeTenantInfo[]) => {
18
19
  const preparedNodes: NodesPreparedEntity[] = [];
19
20
 
20
21
  // First try to parse v2 response in case backend supports it
21
22
  // Else parse v1 response
22
- if (data.Nodes) {
23
- data.Nodes.forEach((node) => {
23
+
24
+ if (nodes) {
25
+ nodes.forEach((node) => {
24
26
  preparedNodes.push(prepareComputeNode(node));
25
27
  });
26
- } else if (data.Tenants) {
27
- for (const tenant of data.Tenants) {
28
+ } else if (tenants) {
29
+ for (const tenant of tenants) {
28
30
  tenant.Nodes?.forEach((node) => {
29
31
  preparedNodes.push(prepareComputeNode(node, tenant.Name));
30
32
  });
31
33
  }
32
34
  }
33
35
 
36
+ return preparedNodes;
37
+ };
38
+
39
+ export const prepareComputeNodesData = (data: TComputeInfo): NodesHandledResponse => {
40
+ const preparedNodes = prepareComputeNodes(data.Nodes, data.Tenants);
41
+
34
42
  return {
35
43
  Nodes: preparedNodes,
36
44
  TotalNodes: Number(data.TotalNodes) || preparedNodes.length,
@@ -57,3 +65,5 @@ export const prepareNodesData = (data: TNodesInfo): NodesHandledResponse => {
57
65
  FoundNodes: Number(data.FoundNodes),
58
66
  };
59
67
  };
68
+
69
+ export const getLoadSeverityForNode = generateEvaluator(60, 80, ['success', 'warning', 'danger']);
@@ -1,16 +1,10 @@
1
1
  import type {Reducer} from 'redux';
2
2
 
3
- import '../../services/api';
4
- import type {
5
- IShardsWorkloadAction,
6
- IShardsWorkloadFilters,
7
- IShardsWorkloadState,
8
- } from '../../types/store/shardsWorkload';
9
- import {EShardsWorkloadMode} from '../../types/store/shardsWorkload';
10
-
11
- import {parseQueryAPIExecuteResponse} from '../../utils/query';
12
-
13
- import {createRequestActionTypes, createApiRequest} from '../utils';
3
+ import '../../../services/api';
4
+ import {parseQueryAPIExecuteResponse} from '../../../utils/query';
5
+ import {createRequestActionTypes, createApiRequest} from '../../utils';
6
+ import type {IShardsWorkloadAction, IShardsWorkloadFilters, IShardsWorkloadState} from './types';
7
+ import {EShardsWorkloadMode} from './types';
14
8
 
15
9
  export const SEND_SHARD_QUERY = createRequestActionTypes('query', 'SEND_SHARD_QUERY');
16
10
  const SET_SHARD_STATE = 'query/SET_SHARD_STATE';
@@ -1,10 +1,6 @@
1
- import {
2
- SEND_SHARD_QUERY,
3
- setShardsState,
4
- setShardsQueryFilters,
5
- } from '../../store/reducers/shardsWorkload';
6
- import type {ApiRequestAction} from '../../store/utils';
7
- import type {IQueryResult, QueryErrorResponse} from './query';
1
+ import {SEND_SHARD_QUERY, setShardsState, setShardsQueryFilters} from './shardsWorkload';
2
+ import type {ApiRequestAction} from '../../utils';
3
+ import type {IQueryResult, QueryErrorResponse} from '../../../types/store/query';
8
4
 
9
5
  export enum EShardsWorkloadMode {
10
6
  Immediate = 'immediate',
@@ -0,0 +1,87 @@
1
+ import type {Reducer} from 'redux';
2
+
3
+ import {TENANT_OVERVIEW_TABLES_LIMIT} from '../../../../utils/constants';
4
+ import {createApiRequest, createRequestActionTypes} from '../../../utils';
5
+ import {prepareNodesData} from '../../nodes/utils';
6
+ import type {NodesApiRequestParams} from '../../nodes/types';
7
+ import type {TopNodesByCpuAction, TopNodesByCpuState, TopPoolsStateSlice} from './types';
8
+
9
+ export const FETCH_TOP_NODES_BY_CPU = createRequestActionTypes(
10
+ 'topNodesByCpu',
11
+ 'FETCH_TOP_NODES_BY_CPU',
12
+ );
13
+ const SET_DATA_WAS_NOT_LOADED = 'topNodesByCpu/SET_DATA_WAS_NOT_LOADED';
14
+
15
+ const initialState = {
16
+ loading: false,
17
+ wasLoaded: false,
18
+ };
19
+
20
+ export const topNodesByCpu: Reducer<TopNodesByCpuState, TopNodesByCpuAction> = (
21
+ state = initialState,
22
+ action,
23
+ ) => {
24
+ switch (action.type) {
25
+ case FETCH_TOP_NODES_BY_CPU.REQUEST: {
26
+ return {
27
+ ...state,
28
+ loading: true,
29
+ };
30
+ }
31
+ case FETCH_TOP_NODES_BY_CPU.SUCCESS: {
32
+ return {
33
+ ...state,
34
+ data: action.data?.Nodes,
35
+ loading: false,
36
+ wasLoaded: true,
37
+ error: undefined,
38
+ };
39
+ }
40
+ case FETCH_TOP_NODES_BY_CPU.FAILURE: {
41
+ if (action.error?.isCancelled) {
42
+ return state;
43
+ }
44
+
45
+ return {
46
+ ...state,
47
+ error: action.error,
48
+ loading: false,
49
+ };
50
+ }
51
+ case SET_DATA_WAS_NOT_LOADED: {
52
+ return {
53
+ ...state,
54
+ wasLoaded: false,
55
+ };
56
+ }
57
+ default:
58
+ return state;
59
+ }
60
+ };
61
+
62
+ const concurrentId = 'getTopNodeByCpu';
63
+
64
+ export function getTopNodesByCpu({
65
+ type = 'any',
66
+ sortOrder = -1,
67
+ sortValue = 'CPU',
68
+ limit = TENANT_OVERVIEW_TABLES_LIMIT,
69
+ ...params
70
+ }: NodesApiRequestParams) {
71
+ return createApiRequest({
72
+ request: window.api.getNodes(
73
+ {type, sortOrder, sortValue, limit, ...params},
74
+ {concurrentId},
75
+ ),
76
+ actions: FETCH_TOP_NODES_BY_CPU,
77
+ dataHandler: prepareNodesData,
78
+ });
79
+ }
80
+
81
+ export const selectTopNodesByCpu = (state: TopPoolsStateSlice) => state.topNodesByCpu.data;
82
+
83
+ export const setDataWasNotLoaded = () => {
84
+ return {
85
+ type: SET_DATA_WAS_NOT_LOADED,
86
+ } as const;
87
+ };
@@ -0,0 +1,29 @@
1
+ import type {IResponseError} from '../../../../types/api/error';
2
+ import type {ApiRequestAction} from '../../../utils';
3
+ import type {NodesPreparedEntity} from '../../nodes/types';
4
+ import {FETCH_TOP_NODES_BY_CPU, setDataWasNotLoaded} from './topNodesByCpu';
5
+
6
+ export interface TopNodesByCpuState {
7
+ loading: boolean;
8
+ wasLoaded: boolean;
9
+ data?: NodesPreparedEntity[];
10
+ error?: IResponseError;
11
+ }
12
+
13
+ export interface TopNodesByCpuHandledResponse {
14
+ Nodes?: NodesPreparedEntity[];
15
+ }
16
+
17
+ type TopNodesByCpuApiRequestAction = ApiRequestAction<
18
+ typeof FETCH_TOP_NODES_BY_CPU,
19
+ TopNodesByCpuHandledResponse,
20
+ IResponseError
21
+ >;
22
+
23
+ export type TopNodesByCpuAction =
24
+ | TopNodesByCpuApiRequestAction
25
+ | ReturnType<typeof setDataWasNotLoaded>;
26
+
27
+ export interface TopPoolsStateSlice {
28
+ topNodesByCpu: TopNodesByCpuState;
29
+ }