ydb-embedded-ui 1.13.1 → 1.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/assets/icons/flask.svg +3 -0
  3. package/dist/components/InfoViewer/formatters/common.ts +15 -0
  4. package/dist/components/InfoViewer/formatters/index.ts +2 -0
  5. package/dist/components/InfoViewer/formatters/schema.ts +43 -0
  6. package/dist/components/InfoViewer/schemaInfo/CDCStreamInfo.tsx +44 -0
  7. package/dist/components/InfoViewer/schemaInfo/PersQueueGroupInfo.tsx +34 -0
  8. package/dist/components/{IndexInfoViewer/IndexInfoViewer.tsx → InfoViewer/schemaInfo/TableIndexInfo.tsx} +7 -18
  9. package/dist/components/InfoViewer/schemaInfo/index.ts +3 -0
  10. package/dist/components/InfoViewer/schemaOverview/CDCStreamOverview.tsx +44 -0
  11. package/dist/components/InfoViewer/schemaOverview/PersQueueGroupOverview.tsx +35 -0
  12. package/dist/components/InfoViewer/schemaOverview/index.ts +2 -0
  13. package/dist/components/QueryResultTable/Cell/Cell.tsx +33 -0
  14. package/dist/components/QueryResultTable/Cell/index.ts +1 -0
  15. package/dist/components/QueryResultTable/QueryResultTable.scss +11 -0
  16. package/dist/components/QueryResultTable/QueryResultTable.tsx +115 -0
  17. package/dist/components/QueryResultTable/i18n/en.json +3 -0
  18. package/dist/components/QueryResultTable/i18n/index.ts +11 -0
  19. package/dist/components/QueryResultTable/i18n/ru.json +3 -0
  20. package/dist/components/QueryResultTable/index.ts +1 -0
  21. package/dist/containers/App/App.scss +1 -0
  22. package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.scss +39 -14
  23. package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx +18 -7
  24. package/dist/containers/Storage/Pdisk/__tests__/colors.tsx +4 -3
  25. package/dist/containers/Storage/Vdisk/__tests__/colors.tsx +7 -7
  26. package/dist/containers/Tenant/Acl/Acl.js +7 -1
  27. package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +6 -2
  28. package/dist/containers/Tenant/Diagnostics/HotKeys/HotKeys.js +1 -1
  29. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +8 -3
  30. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.js +1 -1
  31. package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.js +1 -1
  32. package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +36 -10
  33. package/dist/containers/Tenant/Preview/Preview.js +15 -57
  34. package/dist/containers/Tenant/Preview/Preview.scss +4 -8
  35. package/dist/containers/Tenant/QueryEditor/QueryEditor.js +12 -41
  36. package/dist/containers/Tenant/QueryEditor/QueryEditor.scss +1 -5
  37. package/dist/containers/Tenant/QueryEditor/QueryExplain/QueryExplain.scss +1 -2
  38. package/dist/containers/Tenant/QueryEditor/QueryResult/QueryResult.scss +2 -2
  39. package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +9 -1
  40. package/dist/containers/Tenant/Tenant.scss +2 -50
  41. package/dist/containers/Tenant/Tenant.tsx +24 -22
  42. package/dist/containers/Tenant/utils/schema.ts +3 -0
  43. package/dist/containers/Tenant/utils/schemaActions.ts +1 -2
  44. package/dist/containers/Tenants/Tenants.js +12 -2
  45. package/dist/containers/UserSettings/UserSettings.tsx +26 -3
  46. package/dist/services/api.d.ts +19 -2
  47. package/dist/services/api.js +2 -2
  48. package/dist/setupTests.js +4 -0
  49. package/dist/store/reducers/executeQuery.js +4 -9
  50. package/dist/store/reducers/{preview.js → preview.ts} +22 -18
  51. package/dist/store/reducers/settings.js +3 -1
  52. package/dist/store/utils.ts +88 -0
  53. package/dist/types/api/query.ts +147 -0
  54. package/dist/types/api/schema.ts +235 -2
  55. package/dist/types/index.ts +33 -0
  56. package/dist/types/store/query.ts +9 -0
  57. package/dist/utils/{constants.js → constants.ts} +11 -6
  58. package/dist/utils/index.js +0 -24
  59. package/dist/utils/query.test.ts +189 -0
  60. package/dist/utils/query.ts +156 -0
  61. package/dist/utils/tests/providers.tsx +29 -0
  62. package/package.json +2 -2
  63. 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,2 @@
1
+ export * from './common';
2
+ export * from './schema';
@@ -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 '../../types/api/schema';
2
- import {InfoViewer, createInfoFormatter, InfoViewerItem} from '../InfoViewer';
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
- const formatItem = createInfoFormatter<TIndexDescription>({
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 IndexInfoViewer = ({data}: IndexInfoViewerProps) => {
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(formatItem(key, TableIndex?.[key]));
31
+ info.push(formatTableIndexItem(key, TableIndex?.[key]));
43
32
  }
44
33
  }
45
34
 
@@ -0,0 +1,3 @@
1
+ export * from './CDCStreamInfo';
2
+ export * from './TableIndexInfo';
3
+ export * from './PersQueueGroupInfo';
@@ -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,2 @@
1
+ export * from './CDCStreamOverview';
2
+ export * from './PersQueueGroupOverview';
@@ -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,11 @@
1
+ @import '../../styles/mixins.scss';
2
+
3
+ .ydb-query-result-table {
4
+ &__cell {
5
+ @include cell-container;
6
+ }
7
+
8
+ &__message {
9
+ padding: 15px 10px;
10
+ }
11
+ }
@@ -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,3 @@
1
+ {
2
+ "empty": "Table is empty"
3
+ }
@@ -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,3 @@
1
+ {
2
+ "empty": "Таблица пустая"
3
+ }
@@ -0,0 +1 @@
1
+ export * from './QueryResultTable';
@@ -25,6 +25,7 @@ body,
25
25
 
26
26
  :root {
27
27
  --yc-color-infographics-yellow-light: rgba(255, 199, 0, 0.15);
28
+ --yc-color-infographics-yellow-medium: rgba(255, 219, 77, 0.4);
28
29
  --yc-color-base-warning-orange: #ff922e;
29
30
 
30
31
  --data-table-row-height: 40px;
@@ -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: 1px solid var(--yc-color-infographics-misc-medium);
13
- border-radius: 2px;
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: 1px solid var(--yc-color-infographics-positive-heavy);
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-heavy);
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: 1px solid var(--yc-color-infographics-info-medium);
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: 1px solid var(--yc-color-infographics-warning-heavy);
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-warning-heavy);
49
+ background-color: var(--yc-color-infographics-yellow-medium);
41
50
  }
42
51
  }
43
52
 
44
53
  &_orange {
45
- border: 1px solid var(--yc-color-base-warning-orange);
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-base-warning-orange);
57
+ background-color: var(--yc-color-infographics-warning-medium);
49
58
  }
50
59
  }
51
60
  &_red {
52
- border: 1px solid var(--yc-color-infographics-danger-heavy);
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-heavy);
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: 2px 0 0 2px;
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
- line-height: inherit;
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
- height: 100%;
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
  }