ydb-embedded-ui 4.19.3 → 4.20.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/components/CellWithPopover/CellWithPopover.scss +13 -0
  3. package/dist/components/CellWithPopover/CellWithPopover.tsx +26 -0
  4. package/dist/components/LinkToSchemaObject/LinkToSchemaObject.tsx +20 -0
  5. package/dist/components/NodeHostWrapper/NodeHostWrapper.scss +0 -2
  6. package/dist/components/NodeHostWrapper/NodeHostWrapper.tsx +28 -29
  7. package/dist/components/QueryExecutionStatus/QueryExecutionStatus.tsx +3 -2
  8. package/dist/components/TruncatedQuery/TruncatedQuery.scss +8 -0
  9. package/dist/components/TruncatedQuery/TruncatedQuery.tsx +15 -1
  10. package/dist/components/UsageLabel/UsageLabel.scss +6 -0
  11. package/dist/components/UsageLabel/UsageLabel.tsx +22 -0
  12. package/dist/containers/AsideNavigation/AsideNavigation.tsx +13 -8
  13. package/dist/containers/AsideNavigation/i18n/en.json +13 -0
  14. package/dist/containers/AsideNavigation/i18n/index.ts +11 -0
  15. package/dist/containers/AsideNavigation/i18n/ru.json +13 -0
  16. package/dist/containers/Node/NodeStructure/Pdisk.tsx +74 -68
  17. package/dist/containers/Node/NodeStructure/Vdisk.tsx +9 -33
  18. package/dist/containers/Nodes/Nodes.tsx +10 -2
  19. package/dist/containers/Nodes/getNodesColumns.tsx +207 -123
  20. package/dist/containers/Storage/Storage.tsx +9 -2
  21. package/dist/containers/Storage/StorageGroups/StorageGroups.scss +0 -11
  22. package/dist/containers/Storage/StorageGroups/getStorageGroupsColumns.tsx +11 -11
  23. package/dist/containers/Storage/utils/index.ts +1 -22
  24. package/dist/containers/Tenant/Diagnostics/Describe/Describe.tsx +2 -3
  25. package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.scss +0 -1
  26. package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.tsx +4 -2
  27. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +1 -0
  28. package/dist/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckDetails.tsx +8 -1
  29. package/dist/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckPreview.tsx +11 -1
  30. package/dist/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/IssuesViewer/IssueTree.tsx +0 -1
  31. package/dist/containers/Tenant/Diagnostics/TenantOverview/MetricsCards/MetricsCards.tsx +3 -0
  32. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx +21 -0
  33. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByCpu.tsx +53 -0
  34. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByLoad.tsx +53 -0
  35. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopQueries.tsx +83 -0
  36. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopShards.tsx +53 -0
  37. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx +9 -0
  38. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TopNodesByMemory.tsx +55 -0
  39. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss +44 -0
  40. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +35 -19
  41. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverviewTableLayout.tsx +53 -0
  42. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx +4 -2
  43. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopGroups.tsx +9 -36
  44. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopTables.tsx +22 -41
  45. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json +3 -3
  46. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/ru.json +3 -3
  47. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss +0 -2
  48. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +19 -61
  49. package/dist/containers/Tenant/Diagnostics/TopQueries/getTopQueriesColumns.tsx +102 -0
  50. package/dist/containers/Tenant/Diagnostics/TopShards/Filters/Filters.tsx +2 -2
  51. package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.tsx +18 -97
  52. package/dist/containers/Tenant/Diagnostics/TopShards/getTopShardsColumns.tsx +138 -0
  53. package/dist/containers/Tenant/Info/ExternalTable/ExternalTable.tsx +2 -4
  54. package/dist/containers/Tenant/Query/ExecuteResult/{ExecuteResult.js → ExecuteResult.tsx} +51 -31
  55. package/dist/containers/Tenant/Query/Issues/Issues.tsx +4 -6
  56. package/dist/containers/Tenant/Query/QueryDuration/QueryDuration.tsx +1 -1
  57. package/dist/containers/Tenant/utils/paneVisibilityToggleHelpers.tsx +1 -1
  58. package/dist/routes.ts +26 -1
  59. package/dist/store/reducers/{executeTopQueries.ts → executeTopQueries/executeTopQueries.ts} +23 -75
  60. package/dist/{types/store/executeTopQueries.ts → store/reducers/executeTopQueries/types.ts} +3 -7
  61. package/dist/store/reducers/executeTopQueries/utils.ts +36 -0
  62. package/dist/store/reducers/index.ts +12 -2
  63. package/dist/store/reducers/nodes/types.ts +1 -0
  64. package/dist/store/reducers/nodes/utils.ts +16 -6
  65. package/dist/store/reducers/{shardsWorkload.ts → shardsWorkload/shardsWorkload.ts} +5 -11
  66. package/dist/{types/store/shardsWorkload.ts → store/reducers/shardsWorkload/types.ts} +3 -7
  67. package/dist/store/reducers/tenantOverview/topNodesByCpu/topNodesByCpu.ts +87 -0
  68. package/dist/store/reducers/tenantOverview/topNodesByCpu/types.ts +29 -0
  69. package/dist/store/reducers/tenantOverview/topNodesByLoad/topNodesByLoad.ts +87 -0
  70. package/dist/store/reducers/tenantOverview/topNodesByLoad/types.ts +29 -0
  71. package/dist/store/reducers/tenantOverview/topNodesByMemory/topNodesByMemory.ts +87 -0
  72. package/dist/store/reducers/tenantOverview/topNodesByMemory/types.ts +29 -0
  73. package/dist/store/reducers/tenantOverview/topQueries/tenantOverviewTopQueries.ts +93 -0
  74. package/dist/store/reducers/tenantOverview/topQueries/types.ts +14 -0
  75. package/dist/store/reducers/tenantOverview/topShards/tenantOverviewTopShards.ts +103 -0
  76. package/dist/store/reducers/tenantOverview/topShards/types.ts +14 -0
  77. package/dist/store/reducers/tenantOverview/topShards/utils.ts +3 -0
  78. package/dist/styles/mixins.scss +4 -0
  79. package/dist/types/additionalProps.ts +3 -1
  80. package/dist/types/api/compute.ts +1 -1
  81. package/dist/types/react-json-inspector.d.ts +21 -0
  82. package/dist/utils/diagnostics.ts +23 -0
  83. package/dist/utils/generateEvaluator.ts +21 -0
  84. package/dist/utils/generateHash.ts +11 -0
  85. package/package.json +3 -2
  86. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.scss +0 -41
@@ -0,0 +1,138 @@
1
+ import type {Location} from 'history';
2
+
3
+ import DataTable, {type Column} from '@gravity-ui/react-data-table';
4
+
5
+ import type {KeyValueRow} from '../../../../types/api/query';
6
+ import type {ValueOf} from '../../../../types/common';
7
+ import {formatNumber, roundToPrecision} from '../../../../utils/dataFormatters/dataFormatters';
8
+ import {getLoadSeverityForShard} from '../../../../store/reducers/tenantOverview/topShards/utils';
9
+ import {InternalLink} from '../../../../components/InternalLink';
10
+ import routes, {createHref} from '../../../../routes';
11
+ import {getDefaultNodePath} from '../../../Node/NodePages';
12
+ import {UsageLabel} from '../../../../components/UsageLabel/UsageLabel';
13
+ import {LinkToSchemaObject} from '../../../../components/LinkToSchemaObject/LinkToSchemaObject';
14
+
15
+ const TOP_SHARDS_COLUMNS_IDS = {
16
+ TabletId: 'TabletId',
17
+ CPUCores: 'CPUCores',
18
+ DataSize: 'DataSize',
19
+ Path: 'Path',
20
+ NodeId: 'NodeId',
21
+ PeakTime: 'PeakTime',
22
+ InFlightTxCount: 'InFlightTxCount',
23
+ IntervalEnd: 'IntervalEnd',
24
+ } as const;
25
+
26
+ type TopShardsColumns = ValueOf<typeof TOP_SHARDS_COLUMNS_IDS>;
27
+
28
+ const tableColumnsNames: Record<TopShardsColumns, string> = {
29
+ TabletId: 'TabletId',
30
+ CPUCores: 'CPUCores',
31
+ DataSize: 'DataSize (B)',
32
+ Path: 'Path',
33
+ NodeId: 'NodeId',
34
+ PeakTime: 'PeakTime',
35
+ InFlightTxCount: 'InFlightTxCount',
36
+ IntervalEnd: 'IntervalEnd',
37
+ };
38
+
39
+ function prepareCPUWorkloadValue(value: string | number) {
40
+ return `${roundToPrecision(Number(value) * 100, 2)}%`;
41
+ }
42
+
43
+ const getPathColumn = (schemaPath: string, location: Location): Column<KeyValueRow> => ({
44
+ name: TOP_SHARDS_COLUMNS_IDS.Path,
45
+ header: tableColumnsNames[TOP_SHARDS_COLUMNS_IDS.Path],
46
+ render: ({row}) => {
47
+ // row.Path - relative schema path
48
+ return (
49
+ <LinkToSchemaObject path={schemaPath + row.Path} location={location}>
50
+ {row.Path}
51
+ </LinkToSchemaObject>
52
+ );
53
+ },
54
+ sortable: false,
55
+ });
56
+
57
+ const cpuCoresColumn: Column<KeyValueRow> = {
58
+ name: TOP_SHARDS_COLUMNS_IDS.CPUCores,
59
+ header: tableColumnsNames[TOP_SHARDS_COLUMNS_IDS.CPUCores],
60
+ render: ({row}) => {
61
+ return prepareCPUWorkloadValue(row.CPUCores || 0);
62
+ },
63
+ align: DataTable.RIGHT,
64
+ };
65
+
66
+ const dataSizeColumn: Column<KeyValueRow> = {
67
+ name: TOP_SHARDS_COLUMNS_IDS.DataSize,
68
+ header: tableColumnsNames[TOP_SHARDS_COLUMNS_IDS.DataSize],
69
+ render: ({row}) => {
70
+ return formatNumber(row.DataSize);
71
+ },
72
+ align: DataTable.RIGHT,
73
+ };
74
+
75
+ const tabletIdColumn: Column<KeyValueRow> = {
76
+ name: TOP_SHARDS_COLUMNS_IDS.TabletId,
77
+ header: tableColumnsNames[TOP_SHARDS_COLUMNS_IDS.TabletId],
78
+ render: ({row}) => {
79
+ if (!row.TabletId) {
80
+ return '–';
81
+ }
82
+ return (
83
+ <InternalLink to={createHref(routes.tablet, {id: row.TabletId})}>
84
+ {row.TabletId}
85
+ </InternalLink>
86
+ );
87
+ },
88
+ sortable: false,
89
+ };
90
+
91
+ const nodeIdColumn: Column<KeyValueRow> = {
92
+ name: TOP_SHARDS_COLUMNS_IDS.NodeId,
93
+ header: tableColumnsNames[TOP_SHARDS_COLUMNS_IDS.NodeId],
94
+ render: ({row}) => {
95
+ if (!row.NodeId) {
96
+ return '–';
97
+ }
98
+ return <InternalLink to={getDefaultNodePath(row.NodeId)}>{row.NodeId}</InternalLink>;
99
+ },
100
+ align: DataTable.RIGHT,
101
+ };
102
+
103
+ const topShardsCpuCoresColumn: Column<KeyValueRow> = {
104
+ name: TOP_SHARDS_COLUMNS_IDS.CPUCores,
105
+ header: tableColumnsNames[TOP_SHARDS_COLUMNS_IDS.CPUCores],
106
+ render: ({row}) => {
107
+ return (
108
+ <UsageLabel
109
+ value={roundToPrecision(Number(row.CPUCores) * 100, 2)}
110
+ theme={getLoadSeverityForShard(Number(row.CPUCores) * 100)}
111
+ />
112
+ );
113
+ },
114
+ align: DataTable.RIGHT,
115
+ sortable: false,
116
+ };
117
+
118
+ const inFlightTxCountColumn: Column<KeyValueRow> = {
119
+ name: TOP_SHARDS_COLUMNS_IDS.InFlightTxCount,
120
+ header: tableColumnsNames[TOP_SHARDS_COLUMNS_IDS.InFlightTxCount],
121
+ render: ({row}) => formatNumber(row.InFlightTxCount),
122
+ align: DataTable.RIGHT,
123
+ };
124
+
125
+ export const getShardsWorkloadColumns = (schemaPath: string, location: Location) => {
126
+ return [
127
+ getPathColumn(schemaPath, location),
128
+ cpuCoresColumn,
129
+ dataSizeColumn,
130
+ tabletIdColumn,
131
+ nodeIdColumn,
132
+ inFlightTxCountColumn,
133
+ ];
134
+ };
135
+
136
+ export const getTopShardsColumns = (schemaPath: string, location: Location) => {
137
+ return [tabletIdColumn, getPathColumn(schemaPath, location), topShardsCpuCoresColumn];
138
+ };
@@ -3,7 +3,7 @@ import block from 'bem-cn-lite';
3
3
 
4
4
  import type {TEvDescribeSchemeResult} from '../../../../types/api/schema';
5
5
  import {useTypedSelector} from '../../../../utils/hooks';
6
- import {createHref, parseQuery} from '../../../../routes';
6
+ import {createExternalUILink, parseQuery} from '../../../../routes';
7
7
  import {formatCommonItem} from '../../../../components/InfoViewer/formatters';
8
8
  import {InfoViewer, InfoViewerItem} from '../../../../components/InfoViewer';
9
9
  import {ExternalLinkWithIcon} from '../../../../components/ExternalLinkWithIcon/ExternalLinkWithIcon';
@@ -72,9 +72,7 @@ const ExternalTable = ({data, prepareData}: ExternalTableProps) => {
72
72
  const location = useLocation();
73
73
  const query = parseQuery(location);
74
74
 
75
- // embedded version could be located in some folder (e.g. host/some_folder/app_router_path)
76
- // window.location has the full pathname, while location from router ignores path to project
77
- const pathToDataSource = createHref(window.location.pathname, undefined, {
75
+ const pathToDataSource = createExternalUILink({
78
76
  ...query,
79
77
  schema: data?.PathDescription?.ExternalTableDescription?.DataSourcePath,
80
78
  });
@@ -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
@@ -25,6 +25,23 @@ export const parseQuery = (location: Location) => {
25
25
  });
26
26
  };
27
27
 
28
+ const prepareRoute = (route: string) => {
29
+ let preparedRoute = route;
30
+ const portRegExp = /:\d{3, 5}/g;
31
+ const portMatch = route.match(portRegExp);
32
+
33
+ // if port exists in route we escape port to avoid errors in function compile()
34
+ // compile(preparedRoute) parses prepared root by symbol ":"
35
+ // if we pass raw route and there is a port in route, compile()
36
+ // will try to parse the port and throw an error
37
+ if (portMatch) {
38
+ const port = portMatch[0];
39
+ preparedRoute = route.replace(portRegExp, ':\\' + port.slice(1));
40
+ }
41
+
42
+ return preparedRoute;
43
+ };
44
+
28
45
  export type Query = Record<string | number, string | number | string[] | number[] | undefined>;
29
46
 
30
47
  export function createHref(
@@ -46,7 +63,15 @@ export function createHref(
46
63
 
47
64
  const search = isEmpty(extendedQuery) ? '' : `?${qs.stringify(extendedQuery, {encode: false})}`;
48
65
 
49
- return `${compile(route)(params)}${search}`;
66
+ const preparedRoute = prepareRoute(route);
67
+
68
+ return `${compile(preparedRoute)(params)}${search}`;
50
69
  }
51
70
 
71
+ // embedded version could be located in some folder (e.g. host/some_folder/app_router_path)
72
+ // window.location has the full pathname, while location from router ignores path to project
73
+ // this navigation assumes page reloading
74
+ export const createExternalUILink = (query = {}) =>
75
+ createHref(window.location.pathname, undefined, query);
76
+
52
77
  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[];