ydb-embedded-ui 4.20.0 → 4.20.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
-
}
|