ydb-embedded-ui 4.20.0 → 4.20.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (22) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/components/CellWithPopover/CellWithPopover.scss +13 -0
  3. package/dist/components/CellWithPopover/CellWithPopover.tsx +26 -0
  4. package/dist/components/NodeHostWrapper/NodeHostWrapper.scss +0 -2
  5. package/dist/components/NodeHostWrapper/NodeHostWrapper.tsx +28 -29
  6. package/dist/components/TruncatedQuery/TruncatedQuery.scss +8 -0
  7. package/dist/components/TruncatedQuery/TruncatedQuery.tsx +15 -1
  8. package/dist/containers/Nodes/getNodesColumns.tsx +2 -2
  9. package/dist/containers/Storage/StorageGroups/StorageGroups.scss +0 -11
  10. package/dist/containers/Storage/StorageGroups/getStorageGroupsColumns.tsx +11 -11
  11. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopQueries.tsx +2 -4
  12. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss +4 -0
  13. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx +4 -2
  14. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopGroups.tsx +9 -36
  15. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopTables.tsx +15 -42
  16. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +7 -1
  17. package/dist/containers/Tenant/Diagnostics/TopQueries/getTopQueriesColumns.tsx +22 -2
  18. package/dist/routes.ts +20 -1
  19. package/dist/utils/diagnostics.ts +14 -2
  20. package/dist/utils/generateHash.ts +11 -0
  21. package/package.json +3 -2
  22. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.scss +0 -41
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.20.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.20.0...v4.20.1) (2023-10-24)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * fix createExternalUILink ([#571](https://github.com/ydb-platform/ydb-embedded-ui/issues/571)) ([52546f1](https://github.com/ydb-platform/ydb-embedded-ui/commit/52546f17dbfdb255b2429836e880d6812b19d66a))
9
+ * 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))
10
+ * 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))
11
+ * 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))
12
+ * 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))
13
+
3
14
  ## [4.20.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.19.3...v4.20.0) (2023-10-19)
4
15
 
5
16
 
@@ -0,0 +1,13 @@
1
+ .ydb-cell-with-popover {
2
+ display: flex;
3
+
4
+ &__popover {
5
+ display: inline-block;
6
+ overflow: hidden;
7
+
8
+ max-width: 100%;
9
+
10
+ white-space: nowrap;
11
+ text-overflow: ellipsis;
12
+ }
13
+ }
@@ -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,4 @@
1
1
  .ydb-node-host-wrapper {
2
- display: flex;
3
-
4
2
  &__host-wrapper {
5
3
  display: flex;
6
4
 
@@ -1,6 +1,6 @@
1
1
  import block from 'bem-cn-lite';
2
2
 
3
- import {Button, Popover, PopoverBehavior} from '@gravity-ui/uikit';
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
- <div className={b()}>
38
- <Popover
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
- </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
  };
@@ -14,4 +14,12 @@
14
14
  }
15
15
  }
16
16
  }
17
+
18
+ &__popover-content {
19
+ overflow: hidden;
20
+
21
+ max-width: 600px;
22
+
23
+ white-space: pre;
24
+ }
17
25
  }
@@ -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: string | undefined;
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 <Popover content={row.Version}>{row.Version}</Popover>;
79
+ return <CellWithPopover content={row.Version}>{row.Version}</CellWithPopover>;
80
80
  },
81
81
  sortable: false,
82
82
  };
@@ -28,20 +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
33
 
40
- max-width: 230px;
41
-
42
- vertical-align: top;
43
- text-overflow: ellipsis;
44
- }
45
34
  &__usage-label {
46
35
  &_overload {
47
36
  color: var(--yc-color-text-light-primary);
@@ -12,6 +12,7 @@ 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';
15
16
  import {VDisk} from '../VDisk';
16
17
  import {getDegradedSeverity, getUsageSeverityForStorageGroup} from '../utils';
17
18
  import i18n from './i18n';
@@ -58,17 +59,16 @@ const poolNameColumn: Column<PreparedStorageGroup> = {
58
59
  render: ({row}) => {
59
60
  const splitted = row.PoolName?.split('/');
60
61
  return (
61
- <div className={b('pool-name-wrapper')}>
62
- {splitted && (
63
- <Popover
64
- content={row.PoolName}
65
- placement={['right']}
66
- behavior={PopoverBehavior.Immediate}
67
- >
68
- <span className={b('pool-name')}>{splitted[splitted.length - 1]}</span>
69
- </Popover>
70
- )}
71
- </div>
62
+ splitted && (
63
+ <CellWithPopover
64
+ wrapperClassName={b('pool-name-wrapper')}
65
+ content={row.PoolName}
66
+ placement={['right']}
67
+ behavior={PopoverBehavior.Immediate}
68
+ >
69
+ {splitted[splitted.length - 1]}
70
+ </CellWithPopover>
71
+ )
72
72
  );
73
73
  },
74
74
  align: DataTable.LEFT,
@@ -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 = qs.parse(location.search, {
59
- ignoreQueryPrefix: true,
60
- });
58
+ const queryParams = parseQuery(location);
61
59
 
62
60
  const queryPath = getTenantPath({
63
61
  ...queryParams,
@@ -118,4 +118,8 @@
118
118
  cursor: pointer;
119
119
  }
120
120
  }
121
+
122
+ &__info {
123
+ margin-bottom: 36px;
124
+ }
121
125
  }
@@ -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-storage');
13
+ const b = cn('tenant-overview');
12
14
 
13
15
  export interface TenantStorageMetrics {
14
16
  blobStorageUsed?: number;
@@ -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 i18n from '../i18n';
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
- <div className={b('title')}>Top groups by usage</div>
73
- <div className={b('table')}>{renderContent()}</div>
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 './TenantStorage.scss';
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
- <LinkToSchemaObject
76
- className={b('cell-with-popover-wrapper')}
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
- </Popover>
83
- </LinkToSchemaObject>
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
- <div className={b('title')}>Top tables by size</div>
110
- <div className={b('table')}>{renderContent()}</div>
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 columns = getTopQueriesColumns();
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 {TruncatedQuery} from '../../../../components/TruncatedQuery/TruncatedQuery';
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 [queryTextColumn, cpuTimeUsColumn];
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
- return `${compile(route)(params)}${search}`;
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
- export const TOP_SHARDS_SORT_VALUES = {
3
+ const TOP_SHARDS_SORT_VALUES = {
4
4
  CPUCores: 'CPUCores',
5
5
  DataSize: 'DataSize',
6
6
  } as const;
7
7
 
8
- export type TopShardsSortValue = ValueOf<typeof TOP_SHARDS_SORT_VALUES>;
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.0",
3
+ "version": "4.20.1",
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
- }