ydb-embedded-ui 4.20.0 → 4.20.2
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +20 -0
- package/dist/components/CellWithPopover/CellWithPopover.scss +13 -0
- package/dist/components/CellWithPopover/CellWithPopover.tsx +26 -0
- package/dist/components/NodeHostWrapper/NodeHostWrapper.scss +0 -2
- package/dist/components/NodeHostWrapper/NodeHostWrapper.tsx +28 -29
- package/dist/components/TruncatedQuery/TruncatedQuery.scss +8 -0
- package/dist/components/TruncatedQuery/TruncatedQuery.tsx +15 -1
- package/dist/components/UsageLabel/UsageLabel.tsx +2 -0
- package/dist/containers/Nodes/getNodesColumns.tsx +2 -2
- package/dist/containers/Storage/StorageGroups/StorageGroups.scss +0 -17
- package/dist/containers/Storage/StorageGroups/getStorageGroupsColumns.tsx +13 -17
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopQueries.tsx +2 -4
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss +4 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx +5 -3
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopGroups.tsx +9 -36
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopTables.tsx +15 -42
- package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss +2 -0
- package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +7 -1
- package/dist/containers/Tenant/Diagnostics/TopQueries/getTopQueriesColumns.tsx +22 -2
- package/dist/routes.ts +20 -1
- package/dist/utils/diagnostics.ts +14 -2
- package/dist/utils/generateHash.ts +11 -0
- package/package.json +3 -2
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.scss +0 -41
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,25 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [4.20.2](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.20.1...v4.20.2) (2023-10-25)
|
4
|
+
|
5
|
+
|
6
|
+
### Bug Fixes
|
7
|
+
|
8
|
+
* fix diagnostics top queries width ([#574](https://github.com/ydb-platform/ydb-embedded-ui/issues/574)) ([afa17f2](https://github.com/ydb-platform/ydb-embedded-ui/commit/afa17f236331692167a0a37936b090a8baa772df))
|
9
|
+
* fix sticky storage info ([#573](https://github.com/ydb-platform/ydb-embedded-ui/issues/573)) ([4b923d1](https://github.com/ydb-platform/ydb-embedded-ui/commit/4b923d1db73c53c63e95f43487127b4c2c1e4cac))
|
10
|
+
* use UsageLabel in top groups by usage table ([#572](https://github.com/ydb-platform/ydb-embedded-ui/issues/572)) ([752888d](https://github.com/ydb-platform/ydb-embedded-ui/commit/752888d26ac2cab75307011fb1354830b1cb6db6))
|
11
|
+
|
12
|
+
## [4.20.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.20.0...v4.20.1) (2023-10-24)
|
13
|
+
|
14
|
+
|
15
|
+
### Bug Fixes
|
16
|
+
|
17
|
+
* fix createExternalUILink ([#571](https://github.com/ydb-platform/ydb-embedded-ui/issues/571)) ([52546f1](https://github.com/ydb-platform/ydb-embedded-ui/commit/52546f17dbfdb255b2429836e880d6812b19d66a))
|
18
|
+
* fix incorrect truncate strings with popover ([#567](https://github.com/ydb-platform/ydb-embedded-ui/issues/567)) ([d82e65b](https://github.com/ydb-platform/ydb-embedded-ui/commit/d82e65b925b76dc539a76520eccf601951654e94))
|
19
|
+
* fix top queries table row height ([#565](https://github.com/ydb-platform/ydb-embedded-ui/issues/565)) ([b12dceb](https://github.com/ydb-platform/ydb-embedded-ui/commit/b12dcebdb0167fd5852c247bca48844ef6ab35af))
|
20
|
+
* refactor metrics storage section ([#568](https://github.com/ydb-platform/ydb-embedded-ui/issues/568)) ([db5d922](https://github.com/ydb-platform/ydb-embedded-ui/commit/db5d922d06b88c9d8a792220d2a178c81806c09e))
|
21
|
+
* update @types/react ([#570](https://github.com/ydb-platform/ydb-embedded-ui/issues/570)) ([1e38c5b](https://github.com/ydb-platform/ydb-embedded-ui/commit/1e38c5bb3b4b2139b2141636d6434c2a2ec65772))
|
22
|
+
|
3
23
|
## [4.20.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.19.3...v4.20.0) (2023-10-19)
|
4
24
|
|
5
25
|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import cn from 'bem-cn-lite';
|
2
|
+
|
3
|
+
import {Popover, type PopoverProps} from '@gravity-ui/uikit';
|
4
|
+
|
5
|
+
import './CellWithPopover.scss';
|
6
|
+
|
7
|
+
const b = cn('ydb-cell-with-popover');
|
8
|
+
|
9
|
+
interface CellWithPopoverProps extends PopoverProps {
|
10
|
+
wrapperClassName?: string;
|
11
|
+
}
|
12
|
+
|
13
|
+
export function CellWithPopover({
|
14
|
+
children,
|
15
|
+
className,
|
16
|
+
wrapperClassName,
|
17
|
+
...props
|
18
|
+
}: CellWithPopoverProps) {
|
19
|
+
return (
|
20
|
+
<div className={b(null, wrapperClassName)}>
|
21
|
+
<Popover className={b('popover', className)} {...props}>
|
22
|
+
{children}
|
23
|
+
</Popover>
|
24
|
+
</div>
|
25
|
+
);
|
26
|
+
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import block from 'bem-cn-lite';
|
2
2
|
|
3
|
-
import {Button,
|
3
|
+
import {Button, PopoverBehavior} from '@gravity-ui/uikit';
|
4
4
|
|
5
5
|
import type {NodesPreparedEntity} from '../../store/reducers/nodes/types';
|
6
6
|
import type {NodeAddress} from '../../types/additionalProps';
|
@@ -10,6 +10,7 @@ import {isUnavailableNode} from '../../utils/nodes';
|
|
10
10
|
import EntityStatus from '../EntityStatus/EntityStatus';
|
11
11
|
import {NodeEndpointsTooltipContent} from '../TooltipsContent';
|
12
12
|
import {IconWrapper} from '../Icon';
|
13
|
+
import {CellWithPopover} from '../CellWithPopover/CellWithPopover';
|
13
14
|
|
14
15
|
import './NodeHostWrapper.scss';
|
15
16
|
|
@@ -34,33 +35,31 @@ export const NodeHostWrapper = ({node, getNodeRef}: NodeHostWrapperProps) => {
|
|
34
35
|
: undefined;
|
35
36
|
|
36
37
|
return (
|
37
|
-
<
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
>
|
44
|
-
<
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
</Popover>
|
64
|
-
</div>
|
38
|
+
<CellWithPopover
|
39
|
+
disabled={!isNodeAvailable}
|
40
|
+
content={<NodeEndpointsTooltipContent data={node} />}
|
41
|
+
placement={['top', 'bottom']}
|
42
|
+
behavior={PopoverBehavior.Immediate}
|
43
|
+
>
|
44
|
+
<div className={b('host-wrapper')}>
|
45
|
+
<EntityStatus
|
46
|
+
name={node.Host}
|
47
|
+
status={node.SystemState}
|
48
|
+
path={nodePath}
|
49
|
+
hasClipboardButton
|
50
|
+
className={b('host')}
|
51
|
+
/>
|
52
|
+
{nodeRef && (
|
53
|
+
<Button
|
54
|
+
size="s"
|
55
|
+
href={nodeRef}
|
56
|
+
className={b('external-button')}
|
57
|
+
target="_blank"
|
58
|
+
>
|
59
|
+
<IconWrapper name="external" />
|
60
|
+
</Button>
|
61
|
+
)}
|
62
|
+
</div>
|
63
|
+
</CellWithPopover>
|
65
64
|
);
|
66
65
|
};
|
@@ -1,11 +1,13 @@
|
|
1
1
|
import cn from 'bem-cn-lite';
|
2
2
|
|
3
|
+
import {CellWithPopover} from '../CellWithPopover/CellWithPopover';
|
4
|
+
|
3
5
|
import './TruncatedQuery.scss';
|
4
6
|
|
5
7
|
const b = cn('kv-truncated-query');
|
6
8
|
|
7
9
|
interface TruncatedQueryProps {
|
8
|
-
value
|
10
|
+
value?: string;
|
9
11
|
maxQueryHeight?: number;
|
10
12
|
}
|
11
13
|
|
@@ -26,3 +28,15 @@ export const TruncatedQuery = ({value = '', maxQueryHeight = 6}: TruncatedQueryP
|
|
26
28
|
}
|
27
29
|
return <>{value}</>;
|
28
30
|
};
|
31
|
+
|
32
|
+
interface OneLineQueryWithPopoverProps {
|
33
|
+
value?: string;
|
34
|
+
}
|
35
|
+
|
36
|
+
export const OneLineQueryWithPopover = ({value = ''}: OneLineQueryWithPopoverProps) => {
|
37
|
+
return (
|
38
|
+
<CellWithPopover contentClassName={b('popover-content')} content={value}>
|
39
|
+
{value}
|
40
|
+
</CellWithPopover>
|
41
|
+
);
|
42
|
+
};
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import DataTable, {type Column} from '@gravity-ui/react-data-table';
|
2
|
-
import {Popover} from '@gravity-ui/uikit';
|
3
2
|
|
4
3
|
import {PoolsGraph} from '../../components/PoolsGraph/PoolsGraph';
|
5
4
|
import {ProgressViewer} from '../../components/ProgressViewer/ProgressViewer';
|
@@ -13,6 +12,7 @@ import type {NodesPreparedEntity} from '../../store/reducers/nodes/types';
|
|
13
12
|
import type {GetNodeRefFunc} from '../../types/additionalProps';
|
14
13
|
import {getLoadSeverityForNode} from '../../store/reducers/nodes/utils';
|
15
14
|
import {UsageLabel} from '../../components/UsageLabel/UsageLabel';
|
15
|
+
import {CellWithPopover} from '../../components/CellWithPopover/CellWithPopover';
|
16
16
|
|
17
17
|
const NODES_COLUMNS_IDS = {
|
18
18
|
NodeId: 'NodeId',
|
@@ -76,7 +76,7 @@ const versionColumn: Column<NodesPreparedEntity> = {
|
|
76
76
|
width: '200px',
|
77
77
|
align: DataTable.LEFT,
|
78
78
|
render: ({row}) => {
|
79
|
-
return <
|
79
|
+
return <CellWithPopover content={row.Version}>{row.Version}</CellWithPopover>;
|
80
80
|
},
|
81
81
|
sortable: false,
|
82
82
|
};
|
@@ -28,26 +28,9 @@
|
|
28
28
|
}
|
29
29
|
}
|
30
30
|
&__pool-name-wrapper {
|
31
|
-
display: flex;
|
32
|
-
align-items: flex-end;
|
33
|
-
|
34
31
|
width: 230px;
|
35
32
|
}
|
36
|
-
&__pool-name {
|
37
|
-
display: inline-block;
|
38
|
-
overflow: hidden;
|
39
|
-
|
40
|
-
max-width: 230px;
|
41
33
|
|
42
|
-
vertical-align: top;
|
43
|
-
text-overflow: ellipsis;
|
44
|
-
}
|
45
|
-
&__usage-label {
|
46
|
-
&_overload {
|
47
|
-
color: var(--yc-color-text-light-primary);
|
48
|
-
background-color: var(--yc-color-base-danger-heavy);
|
49
|
-
}
|
50
|
-
}
|
51
34
|
&__group-id {
|
52
35
|
font-weight: 500;
|
53
36
|
}
|
@@ -12,6 +12,8 @@ import {bytesToGB, bytesToSpeed} from '../../../utils/utils';
|
|
12
12
|
import {stringifyVdiskId} from '../../../utils/dataFormatters/dataFormatters';
|
13
13
|
import EntityStatus from '../../../components/EntityStatus/EntityStatus';
|
14
14
|
import {Stack} from '../../../components/Stack/Stack';
|
15
|
+
import {CellWithPopover} from '../../../components/CellWithPopover/CellWithPopover';
|
16
|
+
import {UsageLabel} from '../../../components/UsageLabel/UsageLabel';
|
15
17
|
import {VDisk} from '../VDisk';
|
16
18
|
import {getDegradedSeverity, getUsageSeverityForStorageGroup} from '../utils';
|
17
19
|
import i18n from './i18n';
|
@@ -58,17 +60,16 @@ const poolNameColumn: Column<PreparedStorageGroup> = {
|
|
58
60
|
render: ({row}) => {
|
59
61
|
const splitted = row.PoolName?.split('/');
|
60
62
|
return (
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
</div>
|
63
|
+
splitted && (
|
64
|
+
<CellWithPopover
|
65
|
+
wrapperClassName={b('pool-name-wrapper')}
|
66
|
+
content={row.PoolName}
|
67
|
+
placement={['right']}
|
68
|
+
behavior={PopoverBehavior.Immediate}
|
69
|
+
>
|
70
|
+
{splitted[splitted.length - 1]}
|
71
|
+
</CellWithPopover>
|
72
|
+
)
|
72
73
|
);
|
73
74
|
},
|
74
75
|
align: DataTable.LEFT,
|
@@ -128,12 +129,7 @@ const usageColumn: Column<PreparedStorageGroup> = {
|
|
128
129
|
// without a limit the usage can be evaluated as 0,
|
129
130
|
// but the absence of a value is more clear
|
130
131
|
return row.Limit ? (
|
131
|
-
<
|
132
|
-
theme={getUsageSeverityForStorageGroup(row.Usage)}
|
133
|
-
className={b('usage-label', {overload: row.Usage >= 90})}
|
134
|
-
>
|
135
|
-
{row.Usage}%
|
136
|
-
</Label>
|
132
|
+
<UsageLabel value={row.Usage} theme={getUsageSeverityForStorageGroup(row.Usage)} />
|
137
133
|
) : (
|
138
134
|
'-'
|
139
135
|
);
|
@@ -1,4 +1,3 @@
|
|
1
|
-
import qs from 'qs';
|
2
1
|
import {useDispatch} from 'react-redux';
|
3
2
|
import {useHistory, useLocation} from 'react-router';
|
4
3
|
import {useCallback} from 'react';
|
@@ -14,6 +13,7 @@ import {
|
|
14
13
|
} from '../../../../../store/reducers/tenantOverview/topQueries/tenantOverviewTopQueries';
|
15
14
|
import {changeUserInput} from '../../../../../store/reducers/executeQuery';
|
16
15
|
import {useAutofetcher, useTypedSelector} from '../../../../../utils/hooks';
|
16
|
+
import {parseQuery} from '../../../../../routes';
|
17
17
|
import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
|
18
18
|
import {getTenantOverviewTopQueriesColumns} from '../../TopQueries/getTopQueriesColumns';
|
19
19
|
import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
|
@@ -55,9 +55,7 @@ export function TopQueries({path}: TopQueriesProps) {
|
|
55
55
|
|
56
56
|
dispatch(changeUserInput({input}));
|
57
57
|
|
58
|
-
const queryParams =
|
59
|
-
ignoreQueryPrefix: true,
|
60
|
-
});
|
58
|
+
const queryParams = parseQuery(location);
|
61
59
|
|
62
60
|
const queryPath = getTenantPath({
|
63
61
|
...queryParams,
|
@@ -4,11 +4,13 @@ import InfoViewer from '../../../../../components/InfoViewer/InfoViewer';
|
|
4
4
|
import {ProgressViewer} from '../../../../../components/ProgressViewer/ProgressViewer';
|
5
5
|
import {formatStorageValues} from '../../../../../utils/dataFormatters/dataFormatters';
|
6
6
|
import {getSizeWithSignificantDigits} from '../../../../../utils/bytesParsers';
|
7
|
+
|
8
|
+
import '../TenantOverview.scss';
|
9
|
+
|
7
10
|
import {TopTables} from './TopTables';
|
8
11
|
import {TopGroups} from './TopGroups';
|
9
|
-
import './TenantStorage.scss';
|
10
12
|
|
11
|
-
const b = cn('tenant-overview
|
13
|
+
const b = cn('tenant-overview');
|
12
14
|
|
13
15
|
export interface TenantStorageMetrics {
|
14
16
|
blobStorageUsed?: number;
|
@@ -60,7 +62,7 @@ export function TenantStorage({tenantName, metrics}: TenantStorageProps) {
|
|
60
62
|
];
|
61
63
|
return (
|
62
64
|
<>
|
63
|
-
<InfoViewer className={b('info')} title="Storage details" info={info} />
|
65
|
+
<InfoViewer className={b('storage-info')} title="Storage details" info={info} />
|
64
66
|
<TopTables path={tenantName} />
|
65
67
|
<TopGroups tenant={tenantName} />
|
66
68
|
</>
|
@@ -1,25 +1,14 @@
|
|
1
|
-
import cn from 'bem-cn-lite';
|
2
1
|
import {useCallback} from 'react';
|
3
2
|
import {useDispatch} from 'react-redux';
|
4
3
|
|
5
|
-
import DataTable from '@gravity-ui/react-data-table';
|
6
|
-
|
7
4
|
import {useAutofetcher, useTypedSelector} from '../../../../../utils/hooks';
|
8
|
-
import {
|
9
|
-
TENANT_OVERVIEW_TABLES_LIMIT,
|
10
|
-
TENANT_OVERVIEW_TABLES_SETTINGS,
|
11
|
-
} from '../../../../../utils/constants';
|
12
5
|
import {
|
13
6
|
setDataWasNotLoaded,
|
14
7
|
getTopStorageGroups,
|
15
8
|
selectTopStorageGroups,
|
16
9
|
} from '../../../../../store/reducers/tenantOverview/topStorageGroups/topStorageGroups';
|
17
|
-
import {ResponseError} from '../../../../../components/Errors/ResponseError';
|
18
|
-
import {TableSkeleton} from '../../../../../components/TableSkeleton/TableSkeleton';
|
19
10
|
import {getStorageTopGroupsColumns} from '../../../../Storage/StorageGroups/getStorageGroupsColumns';
|
20
|
-
import
|
21
|
-
|
22
|
-
const b = cn('tenant-overview-storage');
|
11
|
+
import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
|
23
12
|
|
24
13
|
interface TopGroupsProps {
|
25
14
|
tenant?: string;
|
@@ -47,30 +36,14 @@ export function TopGroups({tenant}: TopGroupsProps) {
|
|
47
36
|
|
48
37
|
useAutofetcher(fetchData, [fetchData], autorefresh);
|
49
38
|
|
50
|
-
const renderContent = () => {
|
51
|
-
if (error) {
|
52
|
-
return <ResponseError error={error} />;
|
53
|
-
}
|
54
|
-
|
55
|
-
if (loading && !wasLoaded) {
|
56
|
-
return <TableSkeleton rows={TENANT_OVERVIEW_TABLES_LIMIT} />;
|
57
|
-
}
|
58
|
-
|
59
|
-
return (
|
60
|
-
<DataTable
|
61
|
-
theme="yandex-cloud"
|
62
|
-
data={topGroups || []}
|
63
|
-
columns={columns}
|
64
|
-
settings={TENANT_OVERVIEW_TABLES_SETTINGS}
|
65
|
-
emptyDataMessage={i18n('top-groups.empty-data')}
|
66
|
-
/>
|
67
|
-
);
|
68
|
-
};
|
69
|
-
|
70
39
|
return (
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
40
|
+
<TenantOverviewTableLayout
|
41
|
+
data={topGroups || []}
|
42
|
+
columns={columns}
|
43
|
+
title="Top groups by usage"
|
44
|
+
loading={loading}
|
45
|
+
wasLoaded={wasLoaded}
|
46
|
+
error={error}
|
47
|
+
/>
|
75
48
|
);
|
76
49
|
}
|
@@ -1,28 +1,20 @@
|
|
1
1
|
import {useDispatch} from 'react-redux';
|
2
2
|
import {useLocation} from 'react-router';
|
3
|
-
import cn from 'bem-cn-lite';
|
4
3
|
|
5
4
|
import DataTable, {Column} from '@gravity-ui/react-data-table';
|
6
|
-
import {Popover} from '@gravity-ui/uikit';
|
7
5
|
|
8
6
|
import {useAutofetcher, useTypedSelector} from '../../../../../utils/hooks';
|
9
7
|
import {
|
10
8
|
fetchTopTables,
|
11
9
|
setDataWasNotLoaded,
|
12
10
|
} from '../../../../../store/reducers/tenantOverview/executeTopTables/executeTopTables';
|
13
|
-
import {
|
14
|
-
TENANT_OVERVIEW_TABLES_LIMIT,
|
15
|
-
TENANT_OVERVIEW_TABLES_SETTINGS,
|
16
|
-
} from '../../../../../utils/constants';
|
17
11
|
import type {KeyValueRow} from '../../../../../types/api/query';
|
18
12
|
import {formatBytes, getSizeWithSignificantDigits} from '../../../../../utils/bytesParsers';
|
19
|
-
import {TableSkeleton} from '../../../../../components/TableSkeleton/TableSkeleton';
|
20
|
-
import {ResponseError} from '../../../../../components/Errors/ResponseError';
|
21
13
|
import {LinkToSchemaObject} from '../../../../../components/LinkToSchemaObject/LinkToSchemaObject';
|
14
|
+
import {CellWithPopover} from '../../../../../components/CellWithPopover/CellWithPopover';
|
15
|
+
import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
|
22
16
|
|
23
|
-
import '
|
24
|
-
|
25
|
-
const b = cn('tenant-overview-storage');
|
17
|
+
import '../TenantOverview.scss';
|
26
18
|
|
27
19
|
interface TopTablesProps {
|
28
20
|
path: string;
|
@@ -72,42 +64,23 @@ export function TopTables({path}: TopTablesProps) {
|
|
72
64
|
sortable: false,
|
73
65
|
render: ({row}) =>
|
74
66
|
row.Path ? (
|
75
|
-
<
|
76
|
-
|
77
|
-
path={String(row.Path)}
|
78
|
-
location={location}
|
79
|
-
>
|
80
|
-
<Popover className={b('cell-with-popover')} content={row.Path}>
|
67
|
+
<CellWithPopover content={row.Path}>
|
68
|
+
<LinkToSchemaObject path={String(row.Path)} location={location}>
|
81
69
|
{row.Path}
|
82
|
-
</
|
83
|
-
</
|
70
|
+
</LinkToSchemaObject>
|
71
|
+
</CellWithPopover>
|
84
72
|
) : null,
|
85
73
|
},
|
86
74
|
];
|
87
75
|
|
88
|
-
const renderContent = () => {
|
89
|
-
if (error) {
|
90
|
-
return <ResponseError error={error} />;
|
91
|
-
}
|
92
|
-
|
93
|
-
if (loading && !wasLoaded) {
|
94
|
-
return <TableSkeleton rows={TENANT_OVERVIEW_TABLES_LIMIT} />;
|
95
|
-
}
|
96
|
-
|
97
|
-
return (
|
98
|
-
<DataTable
|
99
|
-
theme="yandex-cloud"
|
100
|
-
columns={columns}
|
101
|
-
settings={TENANT_OVERVIEW_TABLES_SETTINGS}
|
102
|
-
data={data || []}
|
103
|
-
/>
|
104
|
-
);
|
105
|
-
};
|
106
|
-
|
107
76
|
return (
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
77
|
+
<TenantOverviewTableLayout
|
78
|
+
data={data || []}
|
79
|
+
columns={columns}
|
80
|
+
title="Top tables by size"
|
81
|
+
loading={loading}
|
82
|
+
wasLoaded={wasLoaded}
|
83
|
+
error={error}
|
84
|
+
/>
|
112
85
|
);
|
113
86
|
}
|
@@ -30,6 +30,7 @@ import {useAutofetcher, useTypedSelector} from '../../../../utils/hooks';
|
|
30
30
|
import {prepareQueryError} from '../../../../utils/query';
|
31
31
|
import {parseQuery} from '../../../../routes';
|
32
32
|
import {QUERY_TABLE_SETTINGS} from '../../utils/constants';
|
33
|
+
import {isSortableTopQueriesProperty} from '../../../../utils/diagnostics';
|
33
34
|
import {isColumnEntityType} from '../../utils/schema';
|
34
35
|
import {TenantTabsGroups, getTenantPath} from '../../TenantPages';
|
35
36
|
import {getTopQueriesColumns} from './getTopQueriesColumns';
|
@@ -58,7 +59,7 @@ export const TopQueries = ({path, type}: TopQueriesProps) => {
|
|
58
59
|
data: {result: data = undefined} = {},
|
59
60
|
filters: storeFilters,
|
60
61
|
} = useTypedSelector((state) => state.executeTopQueries);
|
61
|
-
const
|
62
|
+
const rawColumns = getTopQueriesColumns();
|
62
63
|
|
63
64
|
const preventFetch = useRef(false);
|
64
65
|
|
@@ -71,6 +72,11 @@ export const TopQueries = ({path, type}: TopQueriesProps) => {
|
|
71
72
|
dispatch(setTopQueriesFilters(filters));
|
72
73
|
}, [dispatch, filters]);
|
73
74
|
|
75
|
+
const columns = rawColumns.map((column) => ({
|
76
|
+
...column,
|
77
|
+
sortable: isSortableTopQueriesProperty(column.name),
|
78
|
+
}));
|
79
|
+
|
74
80
|
const setDefaultFiltersFromResponse = (responseData?: IQueryResult) => {
|
75
81
|
const intervalEnd = responseData?.result?.[0]?.IntervalEnd;
|
76
82
|
|
@@ -4,7 +4,11 @@ import DataTable, {type Column} from '@gravity-ui/react-data-table';
|
|
4
4
|
|
5
5
|
import type {KeyValueRow} from '../../../../types/api/query';
|
6
6
|
import {formatDateTime, formatNumber} from '../../../../utils/dataFormatters/dataFormatters';
|
7
|
-
import {
|
7
|
+
import {generateHash} from '../../../../utils/generateHash';
|
8
|
+
import {
|
9
|
+
TruncatedQuery,
|
10
|
+
OneLineQueryWithPopover,
|
11
|
+
} from '../../../../components/TruncatedQuery/TruncatedQuery';
|
8
12
|
import {MAX_QUERY_HEIGHT} from '../../utils/constants';
|
9
13
|
|
10
14
|
import './TopQueries.scss';
|
@@ -18,6 +22,8 @@ const TOP_QUERIES_COLUMNS_IDS = {
|
|
18
22
|
ReadRows: 'ReadRows',
|
19
23
|
ReadBytes: 'ReadBytes',
|
20
24
|
UserSID: 'UserSID',
|
25
|
+
OneLineQueryText: 'OneLineQueryText',
|
26
|
+
QueryHash: 'QueryHash',
|
21
27
|
};
|
22
28
|
|
23
29
|
const cpuTimeUsColumn: Column<KeyValueRow> = {
|
@@ -66,6 +72,20 @@ const userSIDColumn: Column<KeyValueRow> = {
|
|
66
72
|
align: DataTable.LEFT,
|
67
73
|
};
|
68
74
|
|
75
|
+
const oneLineQueryTextColumn: Column<KeyValueRow> = {
|
76
|
+
name: TOP_QUERIES_COLUMNS_IDS.OneLineQueryText,
|
77
|
+
header: 'QueryText',
|
78
|
+
render: ({row}) => <OneLineQueryWithPopover value={row.QueryText?.toString()} />,
|
79
|
+
sortable: false,
|
80
|
+
};
|
81
|
+
|
82
|
+
const queryHashColumn: Column<KeyValueRow> = {
|
83
|
+
name: TOP_QUERIES_COLUMNS_IDS.QueryHash,
|
84
|
+
render: ({row}) => generateHash(String(row.QueryText)),
|
85
|
+
width: 130,
|
86
|
+
sortable: false,
|
87
|
+
};
|
88
|
+
|
69
89
|
export const getTopQueriesColumns = (): Column<KeyValueRow>[] => {
|
70
90
|
return [
|
71
91
|
cpuTimeUsColumn,
|
@@ -78,5 +98,5 @@ export const getTopQueriesColumns = (): Column<KeyValueRow>[] => {
|
|
78
98
|
};
|
79
99
|
|
80
100
|
export const getTenantOverviewTopQueriesColumns = (): Column<KeyValueRow>[] => {
|
81
|
-
return [
|
101
|
+
return [queryHashColumn, oneLineQueryTextColumn, cpuTimeUsColumn];
|
82
102
|
};
|
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,9 @@ 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
|
|
52
71
|
// embedded version could be located in some folder (e.g. host/some_folder/app_router_path)
|
@@ -1,11 +1,23 @@
|
|
1
1
|
import {ValueOf} from '../types/common';
|
2
2
|
|
3
|
-
|
3
|
+
const TOP_SHARDS_SORT_VALUES = {
|
4
4
|
CPUCores: 'CPUCores',
|
5
5
|
DataSize: 'DataSize',
|
6
6
|
} as const;
|
7
7
|
|
8
|
-
|
8
|
+
const TOP_QUERIES_SORT_VALUES = {
|
9
|
+
CPUTimeUs: 'CPUTimeUs',
|
10
|
+
EndTime: 'EndTime',
|
11
|
+
ReadRows: 'ReadRows',
|
12
|
+
ReadBytes: 'ReadBytes',
|
13
|
+
UserSID: 'UserSID',
|
14
|
+
} as const;
|
15
|
+
|
16
|
+
type TopShardsSortValue = ValueOf<typeof TOP_SHARDS_SORT_VALUES>;
|
17
|
+
type TopQueriesSortValue = ValueOf<typeof TOP_QUERIES_SORT_VALUES>;
|
9
18
|
|
10
19
|
export const isSortableTopShardsProperty = (value: string): value is TopShardsSortValue =>
|
11
20
|
Object.values(TOP_SHARDS_SORT_VALUES).includes(value as TopShardsSortValue);
|
21
|
+
|
22
|
+
export const isSortableTopQueriesProperty = (value: string): value is TopQueriesSortValue =>
|
23
|
+
Object.values(TOP_QUERIES_SORT_VALUES).includes(value as TopQueriesSortValue);
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import crc32 from 'crc-32';
|
2
|
+
|
3
|
+
export const generateHash = (value: string) => {
|
4
|
+
// 1. crc32.str(value) - generate crc32 hash
|
5
|
+
// 2. (>>>) - use unsigned right shift operator (>>>) to avoid negative values
|
6
|
+
// 3. toString(16) - convert hash to hex format
|
7
|
+
// 4. toUpperCase() - convert hash to uppercase
|
8
|
+
// 5. padStart(8, '0') - fill hash with leading zeros if hash length < 8
|
9
|
+
// eslint-disable-next-line no-bitwise
|
10
|
+
return (crc32.str(value) >>> 0).toString(16).toUpperCase().padStart(8, '0');
|
11
|
+
};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "ydb-embedded-ui",
|
3
|
-
"version": "4.20.
|
3
|
+
"version": "4.20.2",
|
4
4
|
"files": [
|
5
5
|
"dist"
|
6
6
|
],
|
@@ -15,9 +15,11 @@
|
|
15
15
|
"@gravity-ui/navigation": "^0.4.0",
|
16
16
|
"@gravity-ui/paranoid": "^1.4.0",
|
17
17
|
"@gravity-ui/react-data-table": "^1.0.3",
|
18
|
+
"@types/react": "^17.0.58",
|
18
19
|
"axios": "0.19.2",
|
19
20
|
"bem-cn-lite": "4.0.0",
|
20
21
|
"copy-to-clipboard": "^3.3.3",
|
22
|
+
"crc-32": "^1.2.2",
|
21
23
|
"history": "4.10.1",
|
22
24
|
"js-cookie": "2.2.1",
|
23
25
|
"lodash": "4.17.11",
|
@@ -117,7 +119,6 @@
|
|
117
119
|
"@types/lodash": "^4.14.178",
|
118
120
|
"@types/numeral": "^2.0.2",
|
119
121
|
"@types/qs": "^6.9.7",
|
120
|
-
"@types/react": "^17.0.44",
|
121
122
|
"@types/react-dom": "^17.0.11",
|
122
123
|
"@types/react-router": "^5.1.17",
|
123
124
|
"@types/react-router-dom": "^5.3.2",
|
@@ -1,41 +0,0 @@
|
|
1
|
-
@import '../../../../../styles/mixins.scss';
|
2
|
-
|
3
|
-
.tenant-overview-storage {
|
4
|
-
&__info {
|
5
|
-
margin-bottom: 36px;
|
6
|
-
}
|
7
|
-
|
8
|
-
&__title {
|
9
|
-
margin-bottom: 10px;
|
10
|
-
|
11
|
-
font-size: var(--yc-text-body-2-font-size);
|
12
|
-
font-weight: 700;
|
13
|
-
line-height: var(--yc-text-body-2-line-height);
|
14
|
-
}
|
15
|
-
|
16
|
-
&__table {
|
17
|
-
width: var(--diagnostics-section-table-width);
|
18
|
-
@include table-styles;
|
19
|
-
|
20
|
-
&:not(:last-child) {
|
21
|
-
margin-bottom: var(--diagnostics-section-margin);
|
22
|
-
}
|
23
|
-
|
24
|
-
th {
|
25
|
-
height: 40px;
|
26
|
-
|
27
|
-
vertical-align: middle;
|
28
|
-
}
|
29
|
-
}
|
30
|
-
|
31
|
-
&__cell-with-popover-wrapper {
|
32
|
-
display: flex;
|
33
|
-
}
|
34
|
-
|
35
|
-
&__cell-with-popover {
|
36
|
-
overflow: hidden;
|
37
|
-
|
38
|
-
white-space: nowrap;
|
39
|
-
text-overflow: ellipsis;
|
40
|
-
}
|
41
|
-
}
|