ydb-embedded-ui 1.13.2 → 1.14.1
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +40 -0
- package/dist/assets/icons/flask.svg +3 -0
- package/dist/components/InfoViewer/formatters/common.ts +15 -0
- package/dist/components/InfoViewer/formatters/index.ts +2 -0
- package/dist/components/InfoViewer/formatters/schema.ts +43 -0
- package/dist/components/InfoViewer/schemaInfo/CDCStreamInfo.tsx +44 -0
- package/dist/components/InfoViewer/schemaInfo/PersQueueGroupInfo.tsx +34 -0
- package/dist/components/{IndexInfoViewer/IndexInfoViewer.tsx → InfoViewer/schemaInfo/TableIndexInfo.tsx} +7 -18
- package/dist/components/InfoViewer/schemaInfo/index.ts +3 -0
- package/dist/components/InfoViewer/schemaOverview/CDCStreamOverview.tsx +44 -0
- package/dist/components/InfoViewer/schemaOverview/PersQueueGroupOverview.tsx +35 -0
- package/dist/components/InfoViewer/schemaOverview/index.ts +2 -0
- package/dist/components/QueryResultTable/Cell/Cell.tsx +33 -0
- package/dist/components/QueryResultTable/Cell/index.ts +1 -0
- package/dist/components/QueryResultTable/QueryResultTable.scss +11 -0
- package/dist/components/QueryResultTable/QueryResultTable.tsx +115 -0
- package/dist/components/QueryResultTable/i18n/en.json +3 -0
- package/dist/components/QueryResultTable/i18n/index.ts +11 -0
- package/dist/components/QueryResultTable/i18n/ru.json +3 -0
- package/dist/components/QueryResultTable/index.ts +1 -0
- package/dist/containers/App/App.scss +1 -0
- package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.scss +39 -14
- package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx +18 -7
- package/dist/containers/Storage/Pdisk/__tests__/colors.tsx +4 -3
- package/dist/containers/Storage/Vdisk/__tests__/colors.tsx +7 -7
- package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +6 -2
- package/dist/containers/Tenant/Diagnostics/HotKeys/HotKeys.js +1 -1
- package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +8 -3
- package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.js +1 -1
- package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.js +1 -1
- package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +36 -10
- package/dist/containers/Tenant/Preview/Preview.js +15 -57
- package/dist/containers/Tenant/Preview/Preview.scss +4 -8
- package/dist/containers/Tenant/QueryEditor/QueryEditor.js +12 -41
- package/dist/containers/Tenant/QueryEditor/QueryEditor.scss +0 -4
- package/dist/containers/Tenant/QueryEditor/QueryExplain/QueryExplain.scss +1 -2
- package/dist/containers/Tenant/QueryEditor/QueryResult/QueryResult.scss +2 -2
- package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +1 -1
- package/dist/containers/Tenant/utils/schema.ts +3 -0
- package/dist/containers/Tenant/utils/schemaActions.ts +1 -2
- package/dist/containers/Tenants/Tenants.js +12 -2
- package/dist/containers/UserSettings/UserSettings.tsx +26 -3
- package/dist/services/api.d.ts +19 -2
- package/dist/services/api.js +2 -2
- package/dist/setupTests.js +4 -0
- package/dist/store/reducers/executeQuery.js +4 -9
- package/dist/store/reducers/{preview.js → preview.ts} +22 -18
- package/dist/store/reducers/settings.js +3 -1
- package/dist/store/utils.ts +88 -0
- package/dist/types/api/query.ts +147 -0
- package/dist/types/api/schema.ts +235 -2
- package/dist/types/index.ts +33 -0
- package/dist/types/store/query.ts +9 -0
- package/dist/utils/{constants.js → constants.ts} +11 -6
- package/dist/utils/index.js +0 -24
- package/dist/utils/query.test.ts +189 -0
- package/dist/utils/query.ts +156 -0
- package/dist/utils/tests/providers.tsx +29 -0
- package/package.json +2 -2
- package/dist/store/utils.js +0 -51
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,45 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [1.14.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.14.0...v1.14.1) (2022-09-16)
|
4
|
+
|
5
|
+
|
6
|
+
### Bug Fixes
|
7
|
+
|
8
|
+
* **Tenants:** display nodes count 0 for undefined NodeIds ([4be42ec](https://github.com/ydb-platform/ydb-embedded-ui/commit/4be42eca84557929837e799d7d8dcebd858470d4))
|
9
|
+
|
10
|
+
## [1.14.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.13.2...v1.14.0) (2022-09-16)
|
11
|
+
|
12
|
+
|
13
|
+
### Features
|
14
|
+
|
15
|
+
* **Preview:** use modern query schema ([60bed3f](https://github.com/ydb-platform/ydb-embedded-ui/commit/60bed3fcb0fd76b869883742a2f2911201c0c226))
|
16
|
+
* **QueryEditor:** use modern query schema ([ecf38aa](https://github.com/ydb-platform/ydb-embedded-ui/commit/ecf38aa6b164ef7705e004aa77c8dab0e3164b51))
|
17
|
+
* **QueryResultTable:** component for displaying query result ([1b8be10](https://github.com/ydb-platform/ydb-embedded-ui/commit/1b8be10546ad9ae13b1043b2871b2aa110a5b6d4))
|
18
|
+
* **Storage:** experimental settings for disk colors ([b4291f4](https://github.com/ydb-platform/ydb-embedded-ui/commit/b4291f4ca19c588bc17eca50da51e898e6ccf581))
|
19
|
+
* **Tenant:** cdc streams info ([4cc773f](https://github.com/ydb-platform/ydb-embedded-ui/commit/4cc773f0351e3f1f6e279d1bebbb78329695e9ae))
|
20
|
+
* **Tenant:** cdc streams overview ([d1aed44](https://github.com/ydb-platform/ydb-embedded-ui/commit/d1aed4467135adaf01a06f8c4c4a4b3eb0b53106))
|
21
|
+
* **Tenant:** pq groups info & overview ([e1878a6](https://github.com/ydb-platform/ydb-embedded-ui/commit/e1878a6353933f74e62b204bf210f56a18a16c49))
|
22
|
+
* **Tenants:** display tenant nodes count ([72aef25](https://github.com/ydb-platform/ydb-embedded-ui/commit/72aef250006aae53d7704ff539b9eb537e6bfbd4))
|
23
|
+
* use schema param in sendQuery api ([01f9c71](https://github.com/ydb-platform/ydb-embedded-ui/commit/01f9c71190622279f03cd1c01d6b6e8e6739362a))
|
24
|
+
|
25
|
+
|
26
|
+
### UI Updates
|
27
|
+
|
28
|
+
* **Storage:** new disks design ([26033d2](https://github.com/ydb-platform/ydb-embedded-ui/commit/26033d21e994c6ece7b3b8999d0fabbf82b43021))
|
29
|
+
* **Tenant:** consistent paddings for query results ([7f0a7c2](https://github.com/ydb-platform/ydb-embedded-ui/commit/7f0a7c28d18e48013223239b5780dbaca18f68a8))
|
30
|
+
|
31
|
+
|
32
|
+
### Bug Fixes
|
33
|
+
|
34
|
+
* always parse query error to string ([0fcabf7](https://github.com/ydb-platform/ydb-embedded-ui/commit/0fcabf7042adfc728f1ec651ebae50e8c40e9199))
|
35
|
+
* correct types & parsing for query api response ([d6a177c](https://github.com/ydb-platform/ydb-embedded-ui/commit/d6a177cd0e726f1d19e27c642e0a9c1d2832bbe0))
|
36
|
+
* **Preview:** display "table is empty" only for tables ([21a93c1](https://github.com/ydb-platform/ydb-embedded-ui/commit/21a93c1a070dbd04f7338537200cd2cb9849ff88))
|
37
|
+
* **Preview:** fix action type id ([7793fad](https://github.com/ydb-platform/ydb-embedded-ui/commit/7793fad6b618bfc4c35b85481b2a0b794698eaa1))
|
38
|
+
* **QueryResultTable:** don't display absent result as empty ([e2e5bfa](https://github.com/ydb-platform/ydb-embedded-ui/commit/e2e5bfaf0dbb89ec64766bf4ed5a4fab10ae8844))
|
39
|
+
* **QueryResultTable:** don't require theme prop ([c9686d4](https://github.com/ydb-platform/ydb-embedded-ui/commit/c9686d46eb2efdeb4bc093ecd44619e6c1a9c2fd))
|
40
|
+
* **Tenant:** input working query for 'select query' action in schema ([de152bd](https://github.com/ydb-platform/ydb-embedded-ui/commit/de152bdcc38fd6f4b1e5a5e6102c621f0155be36))
|
41
|
+
* **Tenant:** rename tab overview -> info ([2d13ffe](https://github.com/ydb-platform/ydb-embedded-ui/commit/2d13ffeb149765680c2887ea7ffb86d68ac92d5c))
|
42
|
+
|
3
43
|
## [1.13.2](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.13.1...v1.13.2) (2022-09-05)
|
4
44
|
|
5
45
|
|
@@ -0,0 +1,3 @@
|
|
1
|
+
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
|
2
|
+
<path d="M437.2 403.5L320 215V64h8c13.3 0 24-10.7 24-24V24c0-13.3-10.7-24-24-24H120c-13.3 0-24 10.7-24 24v16c0 13.3 10.7 24 24 24h8v151L10.8 403.5C-18.5 450.6 15.3 512 70.9 512h306.2c55.7 0 89.4-61.5 60.1-108.5zM137.9 320l48.2-77.6c3.7-5.2 5.8-11.6 5.8-18.4V64h64v160c0 6.9 2.2 13.2 5.8 18.4l48.2 77.6h-172z"/>
|
3
|
+
</svg>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import type {TDirEntry} from '../../../types/api/schema';
|
2
|
+
import {formatDateTime} from '../../../utils';
|
3
|
+
|
4
|
+
import {createInfoFormatter} from '../utils';
|
5
|
+
|
6
|
+
export const formatCommonItem = createInfoFormatter<TDirEntry>({
|
7
|
+
values: {
|
8
|
+
PathType: (value) => value?.substring('EPathType'.length),
|
9
|
+
CreateStep: formatDateTime,
|
10
|
+
},
|
11
|
+
labels: {
|
12
|
+
PathType: 'Type',
|
13
|
+
CreateStep: 'Created',
|
14
|
+
},
|
15
|
+
});
|
@@ -0,0 +1,43 @@
|
|
1
|
+
import type {
|
2
|
+
TCdcStreamDescription,
|
3
|
+
TIndexDescription,
|
4
|
+
TPersQueueGroupDescription,
|
5
|
+
} from '../../../types/api/schema';
|
6
|
+
import {formatNumber} from '../../../utils';
|
7
|
+
import {HOUR_IN_SECONDS} from '../../../utils/constants';
|
8
|
+
|
9
|
+
import {createInfoFormatter} from '../utils';
|
10
|
+
|
11
|
+
export const formatTableIndexItem = createInfoFormatter<TIndexDescription>({
|
12
|
+
values: {
|
13
|
+
Type: (value) => value?.substring(10), // trims EIndexType prefix
|
14
|
+
State: (value) => value?.substring(11), // trims EIndexState prefix
|
15
|
+
KeyColumnNames: (value) => value?.join(', '),
|
16
|
+
DataColumnNames: (value) => value?.join(', '),
|
17
|
+
},
|
18
|
+
labels: {
|
19
|
+
KeyColumnNames: 'Columns',
|
20
|
+
DataColumnNames: 'Includes',
|
21
|
+
},
|
22
|
+
});
|
23
|
+
|
24
|
+
export const formatCdcStreamItem = createInfoFormatter<TCdcStreamDescription>({
|
25
|
+
values: {
|
26
|
+
Mode: (value) => value?.substring('ECdcStreamMode'.length),
|
27
|
+
Format: (value) => value?.substring('ECdcStreamFormat'.length),
|
28
|
+
},
|
29
|
+
});
|
30
|
+
|
31
|
+
export const formatPQGroupItem = createInfoFormatter<TPersQueueGroupDescription>({
|
32
|
+
values: {
|
33
|
+
Partitions: (value) => formatNumber(value?.length || 0),
|
34
|
+
PQTabletConfig: (value) => {
|
35
|
+
const hours = Math.round(value.PartitionConfig.LifetimeSeconds / HOUR_IN_SECONDS * 100) / 100;
|
36
|
+
return `${formatNumber(hours)} hours`;
|
37
|
+
},
|
38
|
+
},
|
39
|
+
labels: {
|
40
|
+
Partitions: 'Partitions count',
|
41
|
+
PQTabletConfig: 'Retention',
|
42
|
+
},
|
43
|
+
});
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import type {TEvDescribeSchemeResult, TCdcStreamDescription} from '../../../types/api/schema';
|
2
|
+
|
3
|
+
import {formatCdcStreamItem, formatCommonItem} from '../formatters';
|
4
|
+
import {InfoViewer, InfoViewerItem} from '..';
|
5
|
+
|
6
|
+
const DISPLAYED_FIELDS: Set<keyof TCdcStreamDescription> = new Set([
|
7
|
+
'Mode',
|
8
|
+
'Format',
|
9
|
+
]);
|
10
|
+
|
11
|
+
interface CDCStreamInfoProps {
|
12
|
+
data?: TEvDescribeSchemeResult;
|
13
|
+
}
|
14
|
+
|
15
|
+
export const CDCStreamInfo = ({data}: CDCStreamInfoProps) => {
|
16
|
+
if (!data) {
|
17
|
+
return (
|
18
|
+
<div className="error">No CDC Stream data</div>
|
19
|
+
);
|
20
|
+
}
|
21
|
+
|
22
|
+
const TableIndex = data.PathDescription?.CdcStreamDescription;
|
23
|
+
const info: Array<InfoViewerItem> = [];
|
24
|
+
|
25
|
+
info.push(formatCommonItem('PathType', data.PathDescription?.Self?.PathType));
|
26
|
+
info.push(formatCommonItem('CreateStep', data.PathDescription?.Self?.CreateStep));
|
27
|
+
|
28
|
+
let key: keyof TCdcStreamDescription;
|
29
|
+
for (key in TableIndex) {
|
30
|
+
if (DISPLAYED_FIELDS.has(key)) {
|
31
|
+
info.push(formatCdcStreamItem(key, TableIndex?.[key]));
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
return (
|
36
|
+
<>
|
37
|
+
{info.length ? (
|
38
|
+
<InfoViewer info={info}></InfoViewer>
|
39
|
+
) : (
|
40
|
+
<>Empty</>
|
41
|
+
)}
|
42
|
+
</>
|
43
|
+
);
|
44
|
+
};
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import type {TEvDescribeSchemeResult} from '../../../types/api/schema';
|
2
|
+
|
3
|
+
import {formatCommonItem, formatPQGroupItem} from '../formatters';
|
4
|
+
import {InfoViewer, InfoViewerItem} from '..';
|
5
|
+
|
6
|
+
interface PersQueueGrouopInfoProps {
|
7
|
+
data?: TEvDescribeSchemeResult;
|
8
|
+
}
|
9
|
+
|
10
|
+
export const PersQueueGroupInfo = ({data}: PersQueueGrouopInfoProps) => {
|
11
|
+
if (!data) {
|
12
|
+
return (
|
13
|
+
<div className="error">No PersQueueGroup data</div>
|
14
|
+
);
|
15
|
+
}
|
16
|
+
|
17
|
+
const pqGroup = data.PathDescription?.PersQueueGroup;
|
18
|
+
const info: Array<InfoViewerItem> = [];
|
19
|
+
|
20
|
+
info.push(formatCommonItem('PathType', data.PathDescription?.Self?.PathType));
|
21
|
+
|
22
|
+
info.push(formatPQGroupItem('Partitions', pqGroup?.Partitions || []));
|
23
|
+
info.push(formatPQGroupItem('PQTabletConfig', pqGroup?.PQTabletConfig || {PartitionConfig: {LifetimeSeconds: 0}}));
|
24
|
+
|
25
|
+
return (
|
26
|
+
<>
|
27
|
+
{info.length ? (
|
28
|
+
<InfoViewer info={info}></InfoViewer>
|
29
|
+
) : (
|
30
|
+
<>Empty</>
|
31
|
+
)}
|
32
|
+
</>
|
33
|
+
);
|
34
|
+
};
|
@@ -1,5 +1,7 @@
|
|
1
|
-
import type {TEvDescribeSchemeResult, TIndexDescription} from '
|
2
|
-
|
1
|
+
import type {TEvDescribeSchemeResult, TIndexDescription} from '../../../types/api/schema';
|
2
|
+
|
3
|
+
import {formatTableIndexItem} from '../formatters';
|
4
|
+
import {InfoViewer, InfoViewerItem} from '..';
|
3
5
|
|
4
6
|
const DISPLAYED_FIELDS: Set<keyof TIndexDescription> = new Set([
|
5
7
|
'Type',
|
@@ -9,24 +11,11 @@ const DISPLAYED_FIELDS: Set<keyof TIndexDescription> = new Set([
|
|
9
11
|
'DataColumnNames',
|
10
12
|
]);
|
11
13
|
|
12
|
-
|
13
|
-
values: {
|
14
|
-
Type: (value) => value?.substring(10), // trims EIndexType prefix
|
15
|
-
State: (value) => value?.substring(11), // trims EIndexState prefix
|
16
|
-
KeyColumnNames: (value) => value?.join(', '),
|
17
|
-
DataColumnNames: (value) => value?.join(', '),
|
18
|
-
},
|
19
|
-
labels: {
|
20
|
-
KeyColumnNames: 'Columns',
|
21
|
-
DataColumnNames: 'Includes',
|
22
|
-
},
|
23
|
-
});
|
24
|
-
|
25
|
-
interface IndexInfoViewerProps {
|
14
|
+
interface TableIndexInfoProps {
|
26
15
|
data?: TEvDescribeSchemeResult;
|
27
16
|
}
|
28
17
|
|
29
|
-
export const
|
18
|
+
export const TableIndexInfo = ({data}: TableIndexInfoProps) => {
|
30
19
|
if (!data) {
|
31
20
|
return (
|
32
21
|
<div className="error">no index data</div>
|
@@ -39,7 +28,7 @@ export const IndexInfoViewer = ({data}: IndexInfoViewerProps) => {
|
|
39
28
|
let key: keyof TIndexDescription;
|
40
29
|
for (key in TableIndex) {
|
41
30
|
if (DISPLAYED_FIELDS.has(key)) {
|
42
|
-
info.push(
|
31
|
+
info.push(formatTableIndexItem(key, TableIndex?.[key]));
|
43
32
|
}
|
44
33
|
}
|
45
34
|
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import type {TEvDescribeSchemeResult, TCdcStreamDescription} from '../../../types/api/schema';
|
2
|
+
|
3
|
+
import {InfoViewer, InfoViewerItem} from '..';
|
4
|
+
import {formatCdcStreamItem, formatCommonItem} from '../formatters';
|
5
|
+
|
6
|
+
const DISPLAYED_FIELDS: Set<keyof TCdcStreamDescription> = new Set([
|
7
|
+
'Mode',
|
8
|
+
'Format',
|
9
|
+
]);
|
10
|
+
|
11
|
+
interface CDCStreamOverviewProps {
|
12
|
+
data?: TEvDescribeSchemeResult;
|
13
|
+
}
|
14
|
+
|
15
|
+
export const CDCStreamOverview = ({data}: CDCStreamOverviewProps) => {
|
16
|
+
if (!data) {
|
17
|
+
return (
|
18
|
+
<div className="error">No CDC Stream data</div>
|
19
|
+
);
|
20
|
+
}
|
21
|
+
|
22
|
+
const TableIndex = data.PathDescription?.CdcStreamDescription;
|
23
|
+
const info: Array<InfoViewerItem> = [];
|
24
|
+
|
25
|
+
info.push(formatCommonItem('PathType', data.PathDescription?.Self?.PathType));
|
26
|
+
info.push(formatCommonItem('CreateStep', data.PathDescription?.Self?.CreateStep));
|
27
|
+
|
28
|
+
let key: keyof TCdcStreamDescription;
|
29
|
+
for (key in TableIndex) {
|
30
|
+
if (DISPLAYED_FIELDS.has(key)) {
|
31
|
+
info.push(formatCdcStreamItem(key, TableIndex?.[key]));
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
return (
|
36
|
+
<>
|
37
|
+
{info.length ? (
|
38
|
+
<InfoViewer info={info}></InfoViewer>
|
39
|
+
) : (
|
40
|
+
<>Empty</>
|
41
|
+
)}
|
42
|
+
</>
|
43
|
+
);
|
44
|
+
};
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import type {TEvDescribeSchemeResult} from '../../../types/api/schema';
|
2
|
+
|
3
|
+
import {formatCommonItem, formatPQGroupItem} from '../formatters';
|
4
|
+
import {InfoViewer, InfoViewerItem} from '..';
|
5
|
+
|
6
|
+
interface PersQueueGroupOverviewProps {
|
7
|
+
data?: TEvDescribeSchemeResult;
|
8
|
+
}
|
9
|
+
|
10
|
+
export const PersQueueGroupOverview = ({data}: PersQueueGroupOverviewProps) => {
|
11
|
+
if (!data) {
|
12
|
+
return (
|
13
|
+
<div className="error">No PersQueueGroup data</div>
|
14
|
+
);
|
15
|
+
}
|
16
|
+
|
17
|
+
const pqGroup = data.PathDescription?.PersQueueGroup;
|
18
|
+
const info: Array<InfoViewerItem> = [];
|
19
|
+
|
20
|
+
info.push(formatCommonItem('PathType', data.PathDescription?.Self?.PathType));
|
21
|
+
info.push(formatCommonItem('CreateStep', data.PathDescription?.Self?.CreateStep));
|
22
|
+
|
23
|
+
info.push(formatPQGroupItem('Partitions', pqGroup?.Partitions || []));
|
24
|
+
info.push(formatPQGroupItem('PQTabletConfig', pqGroup?.PQTabletConfig || {PartitionConfig: {LifetimeSeconds: 0}}));
|
25
|
+
|
26
|
+
return (
|
27
|
+
<>
|
28
|
+
{info.length ? (
|
29
|
+
<InfoViewer info={info}></InfoViewer>
|
30
|
+
) : (
|
31
|
+
<>Empty</>
|
32
|
+
)}
|
33
|
+
</>
|
34
|
+
);
|
35
|
+
};
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import React, {useEffect} from 'react';
|
2
|
+
import {useDispatch} from 'react-redux';
|
3
|
+
|
4
|
+
import {showTooltip, hideTooltip} from '../../../store/reducers/tooltip';
|
5
|
+
|
6
|
+
import {b} from '../QueryResultTable';
|
7
|
+
|
8
|
+
interface CellProps {
|
9
|
+
className?: string;
|
10
|
+
value: string;
|
11
|
+
}
|
12
|
+
|
13
|
+
export const Cell = React.memo(function Cell(props: CellProps) {
|
14
|
+
const {
|
15
|
+
className,
|
16
|
+
value,
|
17
|
+
} = props;
|
18
|
+
|
19
|
+
const dispatch = useDispatch();
|
20
|
+
|
21
|
+
useEffect(() => () => {
|
22
|
+
dispatch(hideTooltip());
|
23
|
+
}, [dispatch]);
|
24
|
+
|
25
|
+
return (
|
26
|
+
<span
|
27
|
+
className={b('cell', className)}
|
28
|
+
onClick={(e) => dispatch(showTooltip(e.target, value, 'cell'))}
|
29
|
+
>
|
30
|
+
{value}
|
31
|
+
</span>
|
32
|
+
)
|
33
|
+
});
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './Cell';
|
@@ -0,0 +1,115 @@
|
|
1
|
+
import {useMemo} from 'react';
|
2
|
+
import cn from 'bem-cn-lite';
|
3
|
+
|
4
|
+
import DataTable from '@yandex-cloud/react-data-table';
|
5
|
+
import type {Column, DataTableProps, Settings} from '@yandex-cloud/react-data-table';
|
6
|
+
|
7
|
+
import type {ColumnType, KeyValueRow} from '../../types/api/query';
|
8
|
+
import {DEFAULT_TABLE_SETTINGS} from '../../utils/constants';
|
9
|
+
import {getColumnType, prepareQueryResponse} from '../../utils/query';
|
10
|
+
import {isNumeric} from '../../utils/utils';
|
11
|
+
|
12
|
+
import {Cell} from './Cell';
|
13
|
+
|
14
|
+
import i18n from './i18n';
|
15
|
+
import './QueryResultTable.scss';
|
16
|
+
|
17
|
+
const TABLE_SETTINGS: Settings = {
|
18
|
+
...DEFAULT_TABLE_SETTINGS,
|
19
|
+
stripedRows: true,
|
20
|
+
};
|
21
|
+
|
22
|
+
export const b = cn('ydb-query-result-table');
|
23
|
+
|
24
|
+
const prepareTypedColumns = (columns: ColumnType[]) => {
|
25
|
+
if (!columns.length) {
|
26
|
+
return [];
|
27
|
+
}
|
28
|
+
|
29
|
+
return columns.map(({name, type}) => {
|
30
|
+
const columnType = getColumnType(type);
|
31
|
+
|
32
|
+
const column: Column<KeyValueRow> = {
|
33
|
+
name,
|
34
|
+
align: columnType === 'number' ? DataTable.RIGHT : DataTable.LEFT,
|
35
|
+
sortAccessor: (row) => {
|
36
|
+
const value = row[name];
|
37
|
+
if (value === undefined || value === null) return null;
|
38
|
+
return columnType === 'number' ? BigInt(value) : value;
|
39
|
+
},
|
40
|
+
render: ({value}) => <Cell value={value as string} />,
|
41
|
+
};
|
42
|
+
|
43
|
+
return column;
|
44
|
+
});
|
45
|
+
};
|
46
|
+
|
47
|
+
const prepareGenericColumns = (data: KeyValueRow[]) => {
|
48
|
+
if (!data.length) {
|
49
|
+
return [];
|
50
|
+
}
|
51
|
+
|
52
|
+
return Object.keys(data[0]).map((name) => {
|
53
|
+
const column: Column<KeyValueRow> = {
|
54
|
+
name,
|
55
|
+
align: isNumeric(data[0][name]) ? DataTable.RIGHT : DataTable.LEFT,
|
56
|
+
sortAccessor: (row) => isNumeric(row[name]) ? Number(row[name]) : row[name],
|
57
|
+
render: ({value}) => <Cell value={value as string} />,
|
58
|
+
};
|
59
|
+
|
60
|
+
return column;
|
61
|
+
});
|
62
|
+
};
|
63
|
+
|
64
|
+
const getRowIndex = (_: unknown, index: number) => index
|
65
|
+
|
66
|
+
interface QueryResultTableProps extends Omit<DataTableProps<KeyValueRow>, 'data' | 'columns' | 'theme'> {
|
67
|
+
data?: KeyValueRow[];
|
68
|
+
columns?: ColumnType[];
|
69
|
+
}
|
70
|
+
|
71
|
+
export const QueryResultTable = (props: QueryResultTableProps) => {
|
72
|
+
const {
|
73
|
+
columns: rawColumns,
|
74
|
+
data: rawData,
|
75
|
+
settings: settingsMix,
|
76
|
+
...restProps
|
77
|
+
} = props;
|
78
|
+
|
79
|
+
const data = useMemo(() => prepareQueryResponse(rawData), [rawData]);
|
80
|
+
const columns = useMemo(() => {
|
81
|
+
return rawColumns ?
|
82
|
+
prepareTypedColumns(rawColumns) :
|
83
|
+
prepareGenericColumns(data);
|
84
|
+
}, [data, rawColumns]);
|
85
|
+
const settings = useMemo(() => ({
|
86
|
+
...TABLE_SETTINGS,
|
87
|
+
...settingsMix,
|
88
|
+
}), [settingsMix]);
|
89
|
+
|
90
|
+
// empty data is expected to be be an empty array
|
91
|
+
// undefined data is not rendered at all
|
92
|
+
if (!Array.isArray(rawData)) {
|
93
|
+
return null;
|
94
|
+
}
|
95
|
+
|
96
|
+
if (!columns.length) {
|
97
|
+
return (
|
98
|
+
<div className={b('message')}>
|
99
|
+
{i18n('empty')}
|
100
|
+
</div>
|
101
|
+
);
|
102
|
+
}
|
103
|
+
|
104
|
+
return (
|
105
|
+
<DataTable
|
106
|
+
theme="yandex-cloud"
|
107
|
+
data={data}
|
108
|
+
columns={columns}
|
109
|
+
settings={settings}
|
110
|
+
// prevent accessing row.id in case it is present but is not the PK (i.e. may repeat)
|
111
|
+
rowKey={getRowIndex}
|
112
|
+
{...restProps}
|
113
|
+
/>
|
114
|
+
);
|
115
|
+
};
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import {i18n, Lang} from '../../../utils/i18n';
|
2
|
+
|
3
|
+
import en from './en.json';
|
4
|
+
import ru from './ru.json';
|
5
|
+
|
6
|
+
const COMPONENT = 'ydb-query-result-table';
|
7
|
+
|
8
|
+
i18n.registerKeyset(Lang.En, COMPONENT, en);
|
9
|
+
i18n.registerKeyset(Lang.Ru, COMPONENT, ru);
|
10
|
+
|
11
|
+
export default i18n.keyset(COMPONENT);
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './QueryResultTable';
|
@@ -1,4 +1,8 @@
|
|
1
1
|
.storage-disk-progress-bar {
|
2
|
+
$border-width: 2px;
|
3
|
+
$outer-border-radius: 4px;
|
4
|
+
$inner-border-radius: $outer-border-radius - $border-width;
|
5
|
+
|
2
6
|
$block: &;
|
3
7
|
position: relative;
|
4
8
|
|
@@ -9,8 +13,8 @@
|
|
9
13
|
|
10
14
|
vertical-align: top;
|
11
15
|
|
12
|
-
border:
|
13
|
-
border-radius:
|
16
|
+
border: $border-width solid var(--yc-color-infographics-misc-heavy);
|
17
|
+
border-radius: $outer-border-radius;
|
14
18
|
background-color: var(--yc-color-infographics-misc-light);
|
15
19
|
|
16
20
|
#{$block}__filled {
|
@@ -18,15 +22,20 @@
|
|
18
22
|
}
|
19
23
|
|
20
24
|
&_green {
|
21
|
-
border:
|
25
|
+
border-color: var(--yc-color-infographics-positive-heavy);
|
22
26
|
background-color: var(--yc-color-infographics-positive-light);
|
23
27
|
#{$block}__filled {
|
24
|
-
background-color: var(--yc-color-infographics-positive-
|
28
|
+
background-color: var(--yc-color-infographics-positive-medium);
|
29
|
+
|
30
|
+
.yc-root_theme_dark & {
|
31
|
+
// the common medium green is too bright for this case
|
32
|
+
background-color: rgb(124, 227, 121, 0.4);
|
33
|
+
}
|
25
34
|
}
|
26
35
|
}
|
27
36
|
|
28
37
|
&_blue {
|
29
|
-
border:
|
38
|
+
border-color: var(--yc-color-infographics-info-heavy);
|
30
39
|
background-color: var(--yc-color-infographics-info-light);
|
31
40
|
#{$block}__filled {
|
32
41
|
background-color: var(--yc-color-infographics-info-medium);
|
@@ -34,25 +43,25 @@
|
|
34
43
|
}
|
35
44
|
|
36
45
|
&_yellow {
|
37
|
-
border:
|
46
|
+
border-color: var(--yc-color-infographics-warning-heavy);
|
38
47
|
background-color: var(--yc-color-infographics-yellow-light);
|
39
48
|
#{$block}__filled {
|
40
|
-
background-color: var(--yc-color-infographics-
|
49
|
+
background-color: var(--yc-color-infographics-yellow-medium);
|
41
50
|
}
|
42
51
|
}
|
43
52
|
|
44
53
|
&_orange {
|
45
|
-
border:
|
54
|
+
border-color: var(--yc-color-base-warning-orange);
|
46
55
|
background-color: var(--yc-color-infographics-warning-light);
|
47
56
|
#{$block}__filled {
|
48
|
-
background-color: var(--yc-color-
|
57
|
+
background-color: var(--yc-color-infographics-warning-medium);
|
49
58
|
}
|
50
59
|
}
|
51
60
|
&_red {
|
52
|
-
border:
|
61
|
+
border-color: var(--yc-color-infographics-danger-heavy);
|
53
62
|
background-color: var(--yc-color-infographics-danger-light);
|
54
63
|
#{$block}__filled {
|
55
|
-
background-color: var(--yc-color-infographics-danger-
|
64
|
+
background-color: var(--yc-color-infographics-danger-medium);
|
56
65
|
}
|
57
66
|
}
|
58
67
|
|
@@ -63,14 +72,22 @@
|
|
63
72
|
|
64
73
|
height: 100%;
|
65
74
|
|
66
|
-
border-radius:
|
75
|
+
border-radius: $inner-border-radius 0 0 $inner-border-radius;
|
76
|
+
|
77
|
+
#{$block}_inverted & {
|
78
|
+
right: 0;
|
79
|
+
left: auto;
|
80
|
+
|
81
|
+
border-radius: 0 $inner-border-radius $inner-border-radius 0;
|
82
|
+
}
|
67
83
|
}
|
68
84
|
&__filled-title {
|
69
85
|
position: absolute;
|
70
86
|
z-index: 2;
|
71
87
|
|
72
88
|
font-size: var(--yc-text-body-1-font-size);
|
73
|
-
|
89
|
+
// bar height minus borders
|
90
|
+
line-height: calc(var(--yc-text-body-2-line-height) - #{$border-width * 2});
|
74
91
|
|
75
92
|
color: inherit;
|
76
93
|
}
|
@@ -79,8 +96,16 @@
|
|
79
96
|
display: flex;
|
80
97
|
justify-content: center;
|
81
98
|
|
82
|
-
|
99
|
+
// extend active area to include borders
|
100
|
+
height: var(--yc-text-body-2-line-height);
|
101
|
+
margin: -$border-width;
|
102
|
+
padding: $border-width;
|
83
103
|
|
84
104
|
color: inherit;
|
105
|
+
border-radius: $outer-border-radius;
|
106
|
+
|
107
|
+
&:hover {
|
108
|
+
color: inherit;
|
109
|
+
}
|
85
110
|
}
|
86
111
|
}
|