ydb-embedded-ui 4.20.0 → 4.20.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/CHANGELOG.md +20 -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/components/UsageLabel/UsageLabel.tsx +2 -0
  9. package/dist/containers/Nodes/getNodesColumns.tsx +2 -2
  10. package/dist/containers/Storage/StorageGroups/StorageGroups.scss +0 -17
  11. package/dist/containers/Storage/StorageGroups/getStorageGroupsColumns.tsx +13 -17
  12. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopQueries.tsx +2 -4
  13. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss +4 -0
  14. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx +5 -3
  15. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopGroups.tsx +9 -36
  16. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopTables.tsx +15 -42
  17. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss +2 -0
  18. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +7 -1
  19. package/dist/containers/Tenant/Diagnostics/TopQueries/getTopQueriesColumns.tsx +22 -2
  20. package/dist/routes.ts +20 -1
  21. package/dist/utils/diagnostics.ts +14 -2
  22. package/dist/utils/generateHash.ts +11 -0
  23. package/package.json +3 -2
  24. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.scss +0 -41
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.20.2](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.20.1...v4.20.2) (2023-10-25)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * fix diagnostics top queries width ([#574](https://github.com/ydb-platform/ydb-embedded-ui/issues/574)) ([afa17f2](https://github.com/ydb-platform/ydb-embedded-ui/commit/afa17f236331692167a0a37936b090a8baa772df))
9
+ * fix sticky storage info ([#573](https://github.com/ydb-platform/ydb-embedded-ui/issues/573)) ([4b923d1](https://github.com/ydb-platform/ydb-embedded-ui/commit/4b923d1db73c53c63e95f43487127b4c2c1e4cac))
10
+ * use UsageLabel in top groups by usage table ([#572](https://github.com/ydb-platform/ydb-embedded-ui/issues/572)) ([752888d](https://github.com/ydb-platform/ydb-embedded-ui/commit/752888d26ac2cab75307011fb1354830b1cb6db6))
11
+
12
+ ## [4.20.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.20.0...v4.20.1) (2023-10-24)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * fix createExternalUILink ([#571](https://github.com/ydb-platform/ydb-embedded-ui/issues/571)) ([52546f1](https://github.com/ydb-platform/ydb-embedded-ui/commit/52546f17dbfdb255b2429836e880d6812b19d66a))
18
+ * fix incorrect truncate strings with popover ([#567](https://github.com/ydb-platform/ydb-embedded-ui/issues/567)) ([d82e65b](https://github.com/ydb-platform/ydb-embedded-ui/commit/d82e65b925b76dc539a76520eccf601951654e94))
19
+ * fix top queries table row height ([#565](https://github.com/ydb-platform/ydb-embedded-ui/issues/565)) ([b12dceb](https://github.com/ydb-platform/ydb-embedded-ui/commit/b12dcebdb0167fd5852c247bca48844ef6ab35af))
20
+ * refactor metrics storage section ([#568](https://github.com/ydb-platform/ydb-embedded-ui/issues/568)) ([db5d922](https://github.com/ydb-platform/ydb-embedded-ui/commit/db5d922d06b88c9d8a792220d2a178c81806c09e))
21
+ * update @types/react ([#570](https://github.com/ydb-platform/ydb-embedded-ui/issues/570)) ([1e38c5b](https://github.com/ydb-platform/ydb-embedded-ui/commit/1e38c5bb3b4b2139b2141636d6434c2a2ec65772))
22
+
3
23
  ## [4.20.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.19.3...v4.20.0) (2023-10-19)
4
24
 
5
25
 
@@ -0,0 +1,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
+ };
@@ -2,6 +2,8 @@ import cn from 'bem-cn-lite';
2
2
 
3
3
  import {Label, type LabelProps} from '@gravity-ui/uikit';
4
4
 
5
+ import './UsageLabel.scss';
6
+
5
7
  const b = cn('ydb-usage-label');
6
8
 
7
9
  interface UsageLabelProps extends LabelProps {
@@ -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,26 +28,9 @@
28
28
  }
29
29
  }
30
30
  &__pool-name-wrapper {
31
- display: flex;
32
- align-items: flex-end;
33
-
34
31
  width: 230px;
35
32
  }
36
- &__pool-name {
37
- display: inline-block;
38
- overflow: hidden;
39
-
40
- max-width: 230px;
41
33
 
42
- vertical-align: top;
43
- text-overflow: ellipsis;
44
- }
45
- &__usage-label {
46
- &_overload {
47
- color: var(--yc-color-text-light-primary);
48
- background-color: var(--yc-color-base-danger-heavy);
49
- }
50
- }
51
34
  &__group-id {
52
35
  font-weight: 500;
53
36
  }
@@ -12,6 +12,8 @@ import {bytesToGB, bytesToSpeed} from '../../../utils/utils';
12
12
  import {stringifyVdiskId} from '../../../utils/dataFormatters/dataFormatters';
13
13
  import EntityStatus from '../../../components/EntityStatus/EntityStatus';
14
14
  import {Stack} from '../../../components/Stack/Stack';
15
+ import {CellWithPopover} from '../../../components/CellWithPopover/CellWithPopover';
16
+ import {UsageLabel} from '../../../components/UsageLabel/UsageLabel';
15
17
  import {VDisk} from '../VDisk';
16
18
  import {getDegradedSeverity, getUsageSeverityForStorageGroup} from '../utils';
17
19
  import i18n from './i18n';
@@ -58,17 +60,16 @@ const poolNameColumn: Column<PreparedStorageGroup> = {
58
60
  render: ({row}) => {
59
61
  const splitted = row.PoolName?.split('/');
60
62
  return (
61
- <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>
63
+ splitted && (
64
+ <CellWithPopover
65
+ wrapperClassName={b('pool-name-wrapper')}
66
+ content={row.PoolName}
67
+ placement={['right']}
68
+ behavior={PopoverBehavior.Immediate}
69
+ >
70
+ {splitted[splitted.length - 1]}
71
+ </CellWithPopover>
72
+ )
72
73
  );
73
74
  },
74
75
  align: DataTable.LEFT,
@@ -128,12 +129,7 @@ const usageColumn: Column<PreparedStorageGroup> = {
128
129
  // without a limit the usage can be evaluated as 0,
129
130
  // but the absence of a value is more clear
130
131
  return row.Limit ? (
131
- <Label
132
- theme={getUsageSeverityForStorageGroup(row.Usage)}
133
- className={b('usage-label', {overload: row.Usage >= 90})}
134
- >
135
- {row.Usage}%
136
- </Label>
132
+ <UsageLabel value={row.Usage} theme={getUsageSeverityForStorageGroup(row.Usage)} />
137
133
  ) : (
138
134
  '-'
139
135
  );
@@ -1,4 +1,3 @@
1
- import qs from 'qs';
2
1
  import {useDispatch} from 'react-redux';
3
2
  import {useHistory, useLocation} from 'react-router';
4
3
  import {useCallback} from 'react';
@@ -14,6 +13,7 @@ import {
14
13
  } from '../../../../../store/reducers/tenantOverview/topQueries/tenantOverviewTopQueries';
15
14
  import {changeUserInput} from '../../../../../store/reducers/executeQuery';
16
15
  import {useAutofetcher, useTypedSelector} from '../../../../../utils/hooks';
16
+ import {parseQuery} from '../../../../../routes';
17
17
  import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
18
18
  import {getTenantOverviewTopQueriesColumns} from '../../TopQueries/getTopQueriesColumns';
19
19
  import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
@@ -55,9 +55,7 @@ export function TopQueries({path}: TopQueriesProps) {
55
55
 
56
56
  dispatch(changeUserInput({input}));
57
57
 
58
- const queryParams = 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
+ &__storage-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;
@@ -60,7 +62,7 @@ export function TenantStorage({tenantName, metrics}: TenantStorageProps) {
60
62
  ];
61
63
  return (
62
64
  <>
63
- <InfoViewer className={b('info')} title="Storage details" info={info} />
65
+ <InfoViewer className={b('storage-info')} title="Storage details" info={info} />
64
66
  <TopTables path={tenantName} />
65
67
  <TopGroups tenant={tenantName} />
66
68
  </>
@@ -1,25 +1,14 @@
1
- import cn from 'bem-cn-lite';
2
1
  import {useCallback} from 'react';
3
2
  import {useDispatch} from 'react-redux';
4
3
 
5
- import DataTable from '@gravity-ui/react-data-table';
6
-
7
4
  import {useAutofetcher, useTypedSelector} from '../../../../../utils/hooks';
8
- import {
9
- TENANT_OVERVIEW_TABLES_LIMIT,
10
- TENANT_OVERVIEW_TABLES_SETTINGS,
11
- } from '../../../../../utils/constants';
12
5
  import {
13
6
  setDataWasNotLoaded,
14
7
  getTopStorageGroups,
15
8
  selectTopStorageGroups,
16
9
  } from '../../../../../store/reducers/tenantOverview/topStorageGroups/topStorageGroups';
17
- import {ResponseError} from '../../../../../components/Errors/ResponseError';
18
- import {TableSkeleton} from '../../../../../components/TableSkeleton/TableSkeleton';
19
10
  import {getStorageTopGroupsColumns} from '../../../../Storage/StorageGroups/getStorageGroupsColumns';
20
- import 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
  }
@@ -39,6 +39,8 @@
39
39
  &__query {
40
40
  overflow: hidden;
41
41
 
42
+ width: 500px;
43
+
42
44
  vertical-align: top;
43
45
  white-space: pre-wrap;
44
46
  text-overflow: ellipsis;
@@ -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.2",
4
4
  "files": [
5
5
  "dist"
6
6
  ],
@@ -15,9 +15,11 @@
15
15
  "@gravity-ui/navigation": "^0.4.0",
16
16
  "@gravity-ui/paranoid": "^1.4.0",
17
17
  "@gravity-ui/react-data-table": "^1.0.3",
18
+ "@types/react": "^17.0.58",
18
19
  "axios": "0.19.2",
19
20
  "bem-cn-lite": "4.0.0",
20
21
  "copy-to-clipboard": "^3.3.3",
22
+ "crc-32": "^1.2.2",
21
23
  "history": "4.10.1",
22
24
  "js-cookie": "2.2.1",
23
25
  "lodash": "4.17.11",
@@ -117,7 +119,6 @@
117
119
  "@types/lodash": "^4.14.178",
118
120
  "@types/numeral": "^2.0.2",
119
121
  "@types/qs": "^6.9.7",
120
- "@types/react": "^17.0.44",
121
122
  "@types/react-dom": "^17.0.11",
122
123
  "@types/react-router": "^5.1.17",
123
124
  "@types/react-router-dom": "^5.3.2",
@@ -1,41 +0,0 @@
1
- @import '../../../../../styles/mixins.scss';
2
-
3
- .tenant-overview-storage {
4
- &__info {
5
- margin-bottom: 36px;
6
- }
7
-
8
- &__title {
9
- margin-bottom: 10px;
10
-
11
- font-size: var(--yc-text-body-2-font-size);
12
- font-weight: 700;
13
- line-height: var(--yc-text-body-2-line-height);
14
- }
15
-
16
- &__table {
17
- width: var(--diagnostics-section-table-width);
18
- @include table-styles;
19
-
20
- &:not(:last-child) {
21
- margin-bottom: var(--diagnostics-section-margin);
22
- }
23
-
24
- th {
25
- height: 40px;
26
-
27
- vertical-align: middle;
28
- }
29
- }
30
-
31
- &__cell-with-popover-wrapper {
32
- display: flex;
33
- }
34
-
35
- &__cell-with-popover {
36
- overflow: hidden;
37
-
38
- white-space: nowrap;
39
- text-overflow: ellipsis;
40
- }
41
- }