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.
- package/CHANGELOG.md +27 -0
- package/dist/components/CellWithPopover/CellWithPopover.scss +13 -0
- package/dist/components/CellWithPopover/CellWithPopover.tsx +26 -0
- package/dist/components/LinkToSchemaObject/LinkToSchemaObject.tsx +20 -0
- package/dist/components/NodeHostWrapper/NodeHostWrapper.scss +0 -2
- package/dist/components/NodeHostWrapper/NodeHostWrapper.tsx +28 -29
- package/dist/components/QueryExecutionStatus/QueryExecutionStatus.tsx +3 -2
- package/dist/components/TruncatedQuery/TruncatedQuery.scss +8 -0
- package/dist/components/TruncatedQuery/TruncatedQuery.tsx +15 -1
- package/dist/components/UsageLabel/UsageLabel.scss +6 -0
- package/dist/components/UsageLabel/UsageLabel.tsx +22 -0
- package/dist/containers/AsideNavigation/AsideNavigation.tsx +13 -8
- package/dist/containers/AsideNavigation/i18n/en.json +13 -0
- package/dist/containers/AsideNavigation/i18n/index.ts +11 -0
- package/dist/containers/AsideNavigation/i18n/ru.json +13 -0
- package/dist/containers/Node/NodeStructure/Pdisk.tsx +74 -68
- package/dist/containers/Node/NodeStructure/Vdisk.tsx +9 -33
- package/dist/containers/Nodes/Nodes.tsx +10 -2
- package/dist/containers/Nodes/getNodesColumns.tsx +207 -123
- package/dist/containers/Storage/Storage.tsx +9 -2
- package/dist/containers/Storage/StorageGroups/StorageGroups.scss +0 -11
- package/dist/containers/Storage/StorageGroups/getStorageGroupsColumns.tsx +11 -11
- package/dist/containers/Storage/utils/index.ts +1 -22
- package/dist/containers/Tenant/Diagnostics/Describe/Describe.tsx +2 -3
- package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.scss +0 -1
- package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.tsx +4 -2
- package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +1 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckDetails.tsx +8 -1
- package/dist/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckPreview.tsx +11 -1
- package/dist/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/IssuesViewer/IssueTree.tsx +0 -1
- package/dist/containers/Tenant/Diagnostics/TenantOverview/MetricsCards/MetricsCards.tsx +3 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx +21 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByCpu.tsx +53 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByLoad.tsx +53 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopQueries.tsx +83 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopShards.tsx +53 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx +9 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TopNodesByMemory.tsx +55 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss +44 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +35 -19
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverviewTableLayout.tsx +53 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx +4 -2
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopGroups.tsx +9 -36
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopTables.tsx +22 -41
- package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json +3 -3
- package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/ru.json +3 -3
- package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss +0 -2
- package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +19 -61
- package/dist/containers/Tenant/Diagnostics/TopQueries/getTopQueriesColumns.tsx +102 -0
- package/dist/containers/Tenant/Diagnostics/TopShards/Filters/Filters.tsx +2 -2
- package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.tsx +18 -97
- package/dist/containers/Tenant/Diagnostics/TopShards/getTopShardsColumns.tsx +138 -0
- package/dist/containers/Tenant/Info/ExternalTable/ExternalTable.tsx +2 -4
- package/dist/containers/Tenant/Query/ExecuteResult/{ExecuteResult.js → ExecuteResult.tsx} +51 -31
- package/dist/containers/Tenant/Query/Issues/Issues.tsx +4 -6
- package/dist/containers/Tenant/Query/QueryDuration/QueryDuration.tsx +1 -1
- package/dist/containers/Tenant/utils/paneVisibilityToggleHelpers.tsx +1 -1
- package/dist/routes.ts +26 -1
- package/dist/store/reducers/{executeTopQueries.ts → executeTopQueries/executeTopQueries.ts} +23 -75
- package/dist/{types/store/executeTopQueries.ts → store/reducers/executeTopQueries/types.ts} +3 -7
- package/dist/store/reducers/executeTopQueries/utils.ts +36 -0
- package/dist/store/reducers/index.ts +12 -2
- package/dist/store/reducers/nodes/types.ts +1 -0
- package/dist/store/reducers/nodes/utils.ts +16 -6
- package/dist/store/reducers/{shardsWorkload.ts → shardsWorkload/shardsWorkload.ts} +5 -11
- package/dist/{types/store/shardsWorkload.ts → store/reducers/shardsWorkload/types.ts} +3 -7
- package/dist/store/reducers/tenantOverview/topNodesByCpu/topNodesByCpu.ts +87 -0
- package/dist/store/reducers/tenantOverview/topNodesByCpu/types.ts +29 -0
- package/dist/store/reducers/tenantOverview/topNodesByLoad/topNodesByLoad.ts +87 -0
- package/dist/store/reducers/tenantOverview/topNodesByLoad/types.ts +29 -0
- package/dist/store/reducers/tenantOverview/topNodesByMemory/topNodesByMemory.ts +87 -0
- package/dist/store/reducers/tenantOverview/topNodesByMemory/types.ts +29 -0
- package/dist/store/reducers/tenantOverview/topQueries/tenantOverviewTopQueries.ts +93 -0
- package/dist/store/reducers/tenantOverview/topQueries/types.ts +14 -0
- package/dist/store/reducers/tenantOverview/topShards/tenantOverviewTopShards.ts +103 -0
- package/dist/store/reducers/tenantOverview/topShards/types.ts +14 -0
- package/dist/store/reducers/tenantOverview/topShards/utils.ts +3 -0
- package/dist/styles/mixins.scss +4 -0
- package/dist/types/additionalProps.ts +3 -1
- package/dist/types/api/compute.ts +1 -1
- package/dist/types/react-json-inspector.d.ts +21 -0
- package/dist/utils/diagnostics.ts +23 -0
- package/dist/utils/generateEvaluator.ts +21 -0
- package/dist/utils/generateHash.ts +11 -0
- package/package.json +3 -2
- 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 {
|
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
|
-
|
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
|
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
|
-
|
38
|
-
|
39
|
-
|
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={
|
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
|
-
|
105
|
-
|
106
|
-
|
124
|
+
if (!error) {
|
125
|
+
return null;
|
126
|
+
}
|
107
127
|
|
108
|
-
if (
|
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
|
-
|
124
|
-
|
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={
|
152
|
+
<QueryExecutionStatus error={error} />
|
133
153
|
|
134
|
-
{
|
154
|
+
{stats && !error && (
|
135
155
|
<React.Fragment>
|
136
|
-
<QueryDuration duration={
|
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={
|
151
|
-
onExpand={
|
152
|
-
isCollapsed={
|
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 && !
|
159
|
-
{activeSection === resultOptionsIds.stats && !
|
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
|
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}
|
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
|
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
|
75
|
+
<div className={blockIssues(null)}>
|
78
76
|
{issues?.map((issue, index) => (
|
79
77
|
<Issue key={index} issue={issue} expanded={issue === mostSevereIssue} />
|
80
78
|
))}
|
@@ -67,7 +67,7 @@ export function paneVisibilityToggleReducerCreator(isPaneCollapsedKey: string) {
|
|
67
67
|
interface ToggleButtonProps {
|
68
68
|
onCollapse: VoidFunction;
|
69
69
|
onExpand: VoidFunction;
|
70
|
-
isCollapsed
|
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
|
-
|
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 '
|
5
|
-
import {
|
6
|
-
|
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 {
|
13
|
-
|
14
|
-
import {
|
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
|
-
({
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
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
|
-
|
3
|
-
|
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,
|