ydb-embedded-ui 1.13.1 → 1.14.0
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 +42 -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/Acl/Acl.js +7 -1
- 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 +1 -5
- 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 +9 -1
- package/dist/containers/Tenant/Tenant.scss +2 -50
- package/dist/containers/Tenant/Tenant.tsx +24 -22
- 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,47 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [1.14.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.13.2...v1.14.0) (2022-09-16)
|
4
|
+
|
5
|
+
|
6
|
+
### Features
|
7
|
+
|
8
|
+
* **Preview:** use modern query schema ([60bed3f](https://github.com/ydb-platform/ydb-embedded-ui/commit/60bed3fcb0fd76b869883742a2f2911201c0c226))
|
9
|
+
* **QueryEditor:** use modern query schema ([ecf38aa](https://github.com/ydb-platform/ydb-embedded-ui/commit/ecf38aa6b164ef7705e004aa77c8dab0e3164b51))
|
10
|
+
* **QueryResultTable:** component for displaying query result ([1b8be10](https://github.com/ydb-platform/ydb-embedded-ui/commit/1b8be10546ad9ae13b1043b2871b2aa110a5b6d4))
|
11
|
+
* **Storage:** experimental settings for disk colors ([b4291f4](https://github.com/ydb-platform/ydb-embedded-ui/commit/b4291f4ca19c588bc17eca50da51e898e6ccf581))
|
12
|
+
* **Tenant:** cdc streams info ([4cc773f](https://github.com/ydb-platform/ydb-embedded-ui/commit/4cc773f0351e3f1f6e279d1bebbb78329695e9ae))
|
13
|
+
* **Tenant:** cdc streams overview ([d1aed44](https://github.com/ydb-platform/ydb-embedded-ui/commit/d1aed4467135adaf01a06f8c4c4a4b3eb0b53106))
|
14
|
+
* **Tenant:** pq groups info & overview ([e1878a6](https://github.com/ydb-platform/ydb-embedded-ui/commit/e1878a6353933f74e62b204bf210f56a18a16c49))
|
15
|
+
* **Tenants:** display tenant nodes count ([72aef25](https://github.com/ydb-platform/ydb-embedded-ui/commit/72aef250006aae53d7704ff539b9eb537e6bfbd4))
|
16
|
+
* use schema param in sendQuery api ([01f9c71](https://github.com/ydb-platform/ydb-embedded-ui/commit/01f9c71190622279f03cd1c01d6b6e8e6739362a))
|
17
|
+
|
18
|
+
|
19
|
+
### UI Updates
|
20
|
+
|
21
|
+
* **Storage:** new disks design ([26033d2](https://github.com/ydb-platform/ydb-embedded-ui/commit/26033d21e994c6ece7b3b8999d0fabbf82b43021))
|
22
|
+
* **Tenant:** consistent paddings for query results ([7f0a7c2](https://github.com/ydb-platform/ydb-embedded-ui/commit/7f0a7c28d18e48013223239b5780dbaca18f68a8))
|
23
|
+
|
24
|
+
|
25
|
+
### Bug Fixes
|
26
|
+
|
27
|
+
* always parse query error to string ([0fcabf7](https://github.com/ydb-platform/ydb-embedded-ui/commit/0fcabf7042adfc728f1ec651ebae50e8c40e9199))
|
28
|
+
* correct types & parsing for query api response ([d6a177c](https://github.com/ydb-platform/ydb-embedded-ui/commit/d6a177cd0e726f1d19e27c642e0a9c1d2832bbe0))
|
29
|
+
* **Preview:** display "table is empty" only for tables ([21a93c1](https://github.com/ydb-platform/ydb-embedded-ui/commit/21a93c1a070dbd04f7338537200cd2cb9849ff88))
|
30
|
+
* **Preview:** fix action type id ([7793fad](https://github.com/ydb-platform/ydb-embedded-ui/commit/7793fad6b618bfc4c35b85481b2a0b794698eaa1))
|
31
|
+
* **QueryResultTable:** don't display absent result as empty ([e2e5bfa](https://github.com/ydb-platform/ydb-embedded-ui/commit/e2e5bfaf0dbb89ec64766bf4ed5a4fab10ae8844))
|
32
|
+
* **QueryResultTable:** don't require theme prop ([c9686d4](https://github.com/ydb-platform/ydb-embedded-ui/commit/c9686d46eb2efdeb4bc093ecd44619e6c1a9c2fd))
|
33
|
+
* **Tenant:** input working query for 'select query' action in schema ([de152bd](https://github.com/ydb-platform/ydb-embedded-ui/commit/de152bdcc38fd6f4b1e5a5e6102c621f0155be36))
|
34
|
+
* **Tenant:** rename tab overview -> info ([2d13ffe](https://github.com/ydb-platform/ydb-embedded-ui/commit/2d13ffeb149765680c2887ea7ffb86d68ac92d5c))
|
35
|
+
|
36
|
+
## [1.13.2](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.13.1...v1.13.2) (2022-09-05)
|
37
|
+
|
38
|
+
|
39
|
+
### Bug Fixes
|
40
|
+
|
41
|
+
* **Tenant:** fix acl scroll ([161bc8d](https://github.com/ydb-platform/ydb-embedded-ui/commit/161bc8d507de126c1383a10713e2ffaaaf13301d))
|
42
|
+
* **Tenant:** fix layout after moving tabs ([6abfded](https://github.com/ydb-platform/ydb-embedded-ui/commit/6abfdedb97345b555be306d49ea2454f35de9bb4))
|
43
|
+
* **Tenant:** load root if cahced path is not in tree ([2d86044](https://github.com/ydb-platform/ydb-embedded-ui/commit/2d8604464711a638dbd20cf8a14142b0de3e3a95))
|
44
|
+
|
3
45
|
## [1.13.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.13.0...v1.13.1) (2022-09-02)
|
4
46
|
|
5
47
|
|
@@ -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
|
}
|