ydb-embedded-ui 3.3.2 → 3.3.4

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 (73) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/components/Errors/ResponseError/ResponseError.tsx +17 -0
  3. package/dist/components/Errors/ResponseError/index.ts +1 -0
  4. package/dist/components/Errors/i18n/en.json +2 -1
  5. package/dist/components/Errors/i18n/ru.json +2 -1
  6. package/dist/components/FullGroupViewer/FullGroupViewer.js +1 -1
  7. package/dist/components/InfoViewer/InfoViewer.scss +1 -1
  8. package/dist/components/InfoViewer/InfoViewer.tsx +29 -21
  9. package/dist/components/InfoViewer/formatters/index.ts +1 -0
  10. package/dist/components/InfoViewer/formatters/table.ts +40 -0
  11. package/dist/components/QueryExecutionStatus/QueryExecutionStatus.tsx +26 -8
  12. package/dist/components/QueryExecutionStatus/index.ts +1 -0
  13. package/dist/components/QueryResultTable/QueryResultTable.tsx +2 -2
  14. package/dist/containers/App/Content.js +12 -5
  15. package/dist/containers/AsideNavigation/AsideNavigation.tsx +10 -13
  16. package/dist/containers/Authentication/Authentication.scss +6 -0
  17. package/dist/containers/Authentication/Authentication.tsx +34 -15
  18. package/dist/containers/Node/NodeStructure/Pdisk.tsx +7 -10
  19. package/dist/containers/Nodes/Nodes.tsx +1 -1
  20. package/dist/containers/Nodes/getNodesColumns.tsx +4 -4
  21. package/dist/containers/Storage/PDisk/PDisk.tsx +25 -17
  22. package/dist/containers/Storage/PDisk/__tests__/colors.tsx +64 -1
  23. package/dist/containers/Storage/Storage.js +1 -1
  24. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +12 -3
  25. package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +1 -1
  26. package/dist/containers/Storage/utils/index.ts +26 -10
  27. package/dist/containers/Tablet/Tablet.js +1 -1
  28. package/dist/containers/Tenant/Acl/Acl.js +1 -1
  29. package/dist/containers/Tenant/Diagnostics/Consumers/Consumers.tsx +2 -2
  30. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +3 -3
  31. package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +7 -7
  32. package/dist/containers/Tenant/Diagnostics/HotKeys/HotKeys.js +1 -1
  33. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +10 -21
  34. package/dist/containers/Tenant/{Schema/SchemaInfoViewer/SchemaInfoViewer.scss → Diagnostics/Overview/TableInfo/TableInfo.scss} +8 -10
  35. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/TableInfo.tsx +71 -0
  36. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/i18n/en.json +5 -0
  37. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/i18n/index.ts +11 -0
  38. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/i18n/ru.json +5 -0
  39. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/index.ts +1 -0
  40. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo.ts +96 -0
  41. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +7 -1
  42. package/dist/containers/Tenant/Diagnostics/TopShards/Filters/Filters.scss +8 -0
  43. package/dist/containers/Tenant/Diagnostics/TopShards/Filters/Filters.tsx +56 -0
  44. package/dist/containers/Tenant/Diagnostics/TopShards/Filters/index.ts +1 -0
  45. package/dist/containers/Tenant/Diagnostics/{OverloadedShards/OverloadedShards.scss → TopShards/TopShards.scss} +2 -10
  46. package/dist/containers/Tenant/Diagnostics/{OverloadedShards/OverloadedShards.tsx → TopShards/TopShards.tsx} +64 -29
  47. package/dist/containers/Tenant/Diagnostics/TopShards/i18n/en.json +6 -0
  48. package/dist/containers/Tenant/Diagnostics/{OverloadedShards → TopShards}/i18n/index.ts +1 -1
  49. package/dist/containers/Tenant/Diagnostics/TopShards/i18n/ru.json +6 -0
  50. package/dist/containers/Tenant/Diagnostics/TopShards/index.ts +1 -0
  51. package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +3 -1
  52. package/dist/containers/Tenant/QueryEditor/QueryExplain/QueryExplain.js +16 -11
  53. package/dist/containers/Tenant/QueryEditor/QueryResult/QueryResult.js +37 -23
  54. package/dist/containers/Tenant/QueryEditor/QueryResult/QueryResult.scss +4 -0
  55. package/dist/containers/Tenant/Schema/SchemaViewer/SchemaViewer.js +1 -1
  56. package/dist/containers/Tenants/Tenants.js +4 -3
  57. package/dist/routes.ts +1 -0
  58. package/dist/services/api.js +4 -1
  59. package/dist/store/reducers/authentication.js +0 -15
  60. package/dist/store/reducers/shardsWorkload.ts +30 -3
  61. package/dist/store/reducers/storage.js +1 -1
  62. package/dist/store/state-url-mapping.js +3 -0
  63. package/dist/types/store/shardsWorkload.ts +6 -0
  64. package/dist/utils/constants.ts +1 -1
  65. package/dist/utils/index.js +3 -1
  66. package/dist/utils/prepareQueryExplain.ts +1 -1
  67. package/dist/utils/typecheckers.ts +5 -0
  68. package/package.json +5 -3
  69. package/dist/containers/Tenant/Diagnostics/OverloadedShards/i18n/en.json +0 -4
  70. package/dist/containers/Tenant/Diagnostics/OverloadedShards/i18n/ru.json +0 -4
  71. package/dist/containers/Tenant/Diagnostics/OverloadedShards/index.ts +0 -1
  72. package/dist/containers/Tenant/Diagnostics/Overview/Overview.scss +0 -13
  73. package/dist/containers/Tenant/Schema/SchemaInfoViewer/SchemaInfoViewer.js +0 -201
@@ -7,7 +7,7 @@ import {TPDiskState} from '../../../../types/api/pdisk';
7
7
  import {PDisk} from '../PDisk';
8
8
 
9
9
  describe('PDisk state', () => {
10
- it('Should determine severity based on State', () => {
10
+ it('Should determine severity based on State if space severity is OK', () => {
11
11
  const {getAllByRole} = renderWithStore(
12
12
  <MemoryRouter>
13
13
  <PDisk nodeId={1} data={{State: TPDiskState.Normal}} />
@@ -20,6 +20,55 @@ describe('PDisk state', () => {
20
20
  expect(normalDisk.className).not.toBe(erroredDisk.className);
21
21
  });
22
22
 
23
+ it('Should determine severity based on space utilization if state severity is OK', () => {
24
+ const {getAllByRole} = renderWithStore(
25
+ <MemoryRouter>
26
+ <PDisk
27
+ nodeId={1}
28
+ data={{State: TPDiskState.Normal, AvailableSize: '100', TotalSize: '100'}}
29
+ />
30
+ <PDisk
31
+ nodeId={2}
32
+ data={{State: TPDiskState.Normal, AvailableSize: '14', TotalSize: '100'}}
33
+ />
34
+ <PDisk
35
+ nodeId={3}
36
+ data={{State: TPDiskState.Normal, AvailableSize: '4', TotalSize: '100'}}
37
+ />
38
+ </MemoryRouter>,
39
+ );
40
+
41
+ const [disk1, disk2, disk3] = getAllByRole('meter');
42
+
43
+ expect(disk1.className).toMatch(/_green\b/i);
44
+ expect(disk2.className).toMatch(/_yellow\b/i);
45
+ expect(disk3.className).toMatch(/_red\b/i);
46
+ });
47
+
48
+ it('Should determine severity based on max severity of state and space utilization ', () => {
49
+ const {getAllByRole} = renderWithStore(
50
+ <MemoryRouter>
51
+ <PDisk
52
+ nodeId={1}
53
+ data={{
54
+ State: TPDiskState.ChunkQuotaError,
55
+ AvailableSize: '100',
56
+ TotalSize: '100',
57
+ }}
58
+ />
59
+ <PDisk
60
+ nodeId={2}
61
+ data={{State: TPDiskState.Normal, AvailableSize: '4', TotalSize: '100'}}
62
+ />
63
+ </MemoryRouter>,
64
+ );
65
+
66
+ const [disk1, disk2] = getAllByRole('meter');
67
+
68
+ expect(disk1.className).toMatch(/_red\b/i);
69
+ expect(disk2.className).toMatch(/_red\b/i);
70
+ });
71
+
23
72
  it('Should display as unavailabe when no State is provided', () => {
24
73
  const {getAllByRole} = renderWithStore(
25
74
  <MemoryRouter>
@@ -34,4 +83,18 @@ describe('PDisk state', () => {
34
83
  expect(disk1.className).toMatch(/_grey\b/i);
35
84
  expect(disk2.className).not.toMatch(/_grey\b/i);
36
85
  });
86
+
87
+ it('Should display as unavailabe when no State is provided event if space severity is not OK', () => {
88
+ const {getAllByRole} = renderWithStore(
89
+ <MemoryRouter>
90
+ <PDisk nodeId={1} data={{AvailableSize: '14', TotalSize: '100'}} />
91
+ <PDisk nodeId={2} data={{AvailableSize: '4', TotalSize: '100'}} />
92
+ </MemoryRouter>,
93
+ );
94
+
95
+ const [disk1, disk2] = getAllByRole('meter');
96
+
97
+ expect(disk1.className).toMatch(/_grey\b/i);
98
+ expect(disk2.className).toMatch(/_grey\b/i);
99
+ });
37
100
  });
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import {connect} from 'react-redux';
4
4
  import cn from 'bem-cn-lite';
5
- import DataTable from '@yandex-cloud/react-data-table';
5
+ import DataTable from '@gravity-ui/react-data-table';
6
6
  import {RadioButton} from '@gravity-ui/uikit';
7
7
 
8
8
  import {Search} from '../../components/Search';
@@ -1,6 +1,6 @@
1
1
  import _ from 'lodash';
2
2
  import cn from 'bem-cn-lite';
3
- import DataTable, {Column, Settings, SortOrder} from '@yandex-cloud/react-data-table';
3
+ import DataTable, {Column, Settings, SortOrder} from '@gravity-ui/react-data-table';
4
4
  import {Icon, Label, Popover, PopoverBehavior} from '@gravity-ui/uikit';
5
5
 
6
6
  import shieldIcon from '../../../assets/icons/shield.svg';
@@ -20,7 +20,7 @@ import {getUsage, isFullDonorData} from '../../../utils/storage';
20
20
 
21
21
  import {EmptyFilter} from '../EmptyFilter/EmptyFilter';
22
22
  import {VDisk} from '../VDisk';
23
- import {getDegradedSeverity, getUsageSeverity} from '../utils';
23
+ import {getDegradedSeverity, getUsageSeverityForStorageGroup} from '../utils';
24
24
 
25
25
  import i18n from './i18n';
26
26
  import './StorageGroups.scss';
@@ -28,6 +28,7 @@ import './StorageGroups.scss';
28
28
  enum TableColumnsIds {
29
29
  PoolName = 'PoolName',
30
30
  Type = 'Type',
31
+ ErasureSpecies = 'ErasureSpecies',
31
32
  GroupID = 'GroupID',
32
33
  Used = 'Used',
33
34
  Limit = 'Limit',
@@ -53,6 +54,7 @@ interface StorageGroupsProps {
53
54
  const tableColumnsNames: Record<TableColumnsIdsValues, string> = {
54
55
  PoolName: 'Pool Name',
55
56
  Type: 'Type',
57
+ ErasureSpecies: 'Erasure',
56
58
  GroupID: 'Group ID',
57
59
  Used: 'Used',
58
60
  Limit: 'Limit',
@@ -127,6 +129,7 @@ function StorageGroups({
127
129
  {
128
130
  name: TableColumnsIds.Type,
129
131
  header: tableColumnsNames[TableColumnsIds.Type],
132
+ // prettier-ignore
130
133
  render: ({value, row}) => (
131
134
  <>
132
135
  <Label>{(value as string) || '—'}</Label>
@@ -145,6 +148,12 @@ function StorageGroups({
145
148
  </>
146
149
  ),
147
150
  },
151
+ {
152
+ name: TableColumnsIds.ErasureSpecies,
153
+ header: tableColumnsNames[TableColumnsIds.ErasureSpecies],
154
+ render: ({row}) => (row.ErasureSpecies ? row.ErasureSpecies : '-'),
155
+ align: DataTable.LEFT,
156
+ },
148
157
  {
149
158
  name: TableColumnsIds.Missing,
150
159
  header: tableColumnsNames[TableColumnsIds.Missing],
@@ -164,7 +173,7 @@ function StorageGroups({
164
173
  // but the absence of a value is more clear
165
174
  return row.Limit ? (
166
175
  <Label
167
- theme={getUsageSeverity(usage)}
176
+ theme={getUsageSeverityForStorageGroup(usage)}
168
177
  className={b('usage-label', {overload: usage >= 90})}
169
178
  >
170
179
  {usage}%
@@ -1,7 +1,7 @@
1
1
  import _ from 'lodash';
2
2
  import cn from 'bem-cn-lite';
3
3
 
4
- import DataTable, {Column, Settings, SortOrder} from '@yandex-cloud/react-data-table';
4
+ import DataTable, {Column, Settings, SortOrder} from '@gravity-ui/react-data-table';
5
5
  import {Popover, PopoverBehavior} from '@gravity-ui/uikit';
6
6
 
7
7
  import {VisibleEntities} from '../../../store/reducers/storage';
@@ -1,12 +1,14 @@
1
1
  import type {IStoragePoolGroup} from '../../../types/store/storage';
2
+ import {EFlag} from '../../../types/api/enums';
2
3
 
3
4
  export * from './constants';
4
5
 
5
- const generateEvaluator = <
6
- OkLevel extends string,
7
- WarnLevel extends string,
8
- CritLevel extends string
9
- >(warn: number, crit: number, levels: [OkLevel, WarnLevel, CritLevel]) =>
6
+ const generateEvaluator =
7
+ <OkLevel extends string, WarnLevel extends string, CritLevel extends string>(
8
+ warn: number,
9
+ crit: number,
10
+ levels: [OkLevel, WarnLevel, CritLevel],
11
+ ) =>
10
12
  (value: number) => {
11
13
  if (0 <= value && value < warn) {
12
14
  return levels[0];
@@ -34,12 +36,26 @@ const canEvaluateErasureSpecies = (value?: string): value is keyof typeof degrad
34
36
  value !== undefined && value in degradationEvaluators;
35
37
 
36
38
  export const getDegradedSeverity = (group: IStoragePoolGroup) => {
37
- const evaluate = canEvaluateErasureSpecies(group.ErasureSpecies) ?
38
- degradationEvaluators[group.ErasureSpecies] :
39
- defaultDegradationEvaluator;
39
+ const evaluate = canEvaluateErasureSpecies(group.ErasureSpecies)
40
+ ? degradationEvaluators[group.ErasureSpecies]
41
+ : defaultDegradationEvaluator;
40
42
 
41
43
  return evaluate(group.Missing);
42
44
  };
43
45
 
44
- export const getUsageSeverity = generateEvaluator(80, 85, ['success', 'warning', 'danger']);
45
- export const getUsageSeverityForEntityStatus = generateEvaluator(80, 85, ['Green', 'Yellow', 'Red']);
46
+ export const getUsageSeverityForStorageGroup = generateEvaluator(80, 85, [
47
+ 'success',
48
+ 'warning',
49
+ 'danger',
50
+ ]);
51
+ export const getUsageSeverityForEntityStatus = generateEvaluator(80, 85, [
52
+ 'Green',
53
+ 'Yellow',
54
+ 'Red',
55
+ ]);
56
+
57
+ export const getUsageSeverityForPDisk = generateEvaluator(85, 95, [
58
+ EFlag.Green,
59
+ EFlag.Yellow,
60
+ EFlag.Red,
61
+ ]);
@@ -16,7 +16,7 @@ import {Tag} from '../../components/Tag/Tag';
16
16
  import Icon from '../../components/Icon/Icon';
17
17
  import EmptyState from '../../components/EmptyState/EmptyState';
18
18
  import {Link as ExternalLink, Button, Loader} from '@gravity-ui/uikit';
19
- import DataTable from '@yandex-cloud/react-data-table';
19
+ import DataTable from '@gravity-ui/react-data-table';
20
20
  import CriticalActionDialog from '../../components/CriticalActionDialog/CriticalActionDialog';
21
21
  import routes, {createHref} from '../../routes';
22
22
  import {getDefaultNodePath} from '../Node/NodePages';
@@ -4,7 +4,7 @@ import cn from 'bem-cn-lite';
4
4
  import _ from 'lodash';
5
5
  import {connect} from 'react-redux';
6
6
  import {Loader} from '@gravity-ui/uikit';
7
- import DataTable from '@yandex-cloud/react-data-table';
7
+ import DataTable from '@gravity-ui/react-data-table';
8
8
  import {DEFAULT_TABLE_SETTINGS} from '../../../utils/constants';
9
9
 
10
10
  import './Acl.scss';
@@ -1,9 +1,9 @@
1
1
  import {useCallback, useEffect, useState} from 'react';
2
2
  import {useDispatch} from 'react-redux';
3
3
  import block from 'bem-cn-lite';
4
- import { escapeRegExp } from 'lodash/fp';
4
+ import {escapeRegExp} from 'lodash/fp';
5
5
 
6
- import DataTable, {Column} from '@yandex-cloud/react-data-table';
6
+ import DataTable, {Column} from '@gravity-ui/react-data-table';
7
7
 
8
8
  import type {EPathType} from '../../../../types/api/schema';
9
9
  import {Loader} from '../../../../components/Loader';
@@ -12,7 +12,7 @@ import {Loader} from '../../../components/Loader';
12
12
  import {TopQueries} from './TopQueries';
13
13
  //@ts-ignore
14
14
  import DetailedOverview from './DetailedOverview/DetailedOverview';
15
- import {OverloadedShards} from './OverloadedShards';
15
+ import {TopShards} from './TopShards';
16
16
  //@ts-ignore
17
17
  import Storage from '../../Storage/Storage';
18
18
  //@ts-ignore
@@ -124,8 +124,8 @@ function Diagnostics(props: DiagnosticsProps) {
124
124
  />
125
125
  );
126
126
  }
127
- case GeneralPagesIds.overloadedShards: {
128
- return <OverloadedShards tenantPath={tenantNameString} type={type} />;
127
+ case GeneralPagesIds.topShards: {
128
+ return <TopShards tenantPath={tenantNameString} type={type} />;
129
129
  }
130
130
  case GeneralPagesIds.nodes: {
131
131
  return (
@@ -3,7 +3,7 @@ import {EPathType} from '../../../types/api/schema';
3
3
  export enum GeneralPagesIds {
4
4
  'overview' = 'Overview',
5
5
  'topQueries' = 'topQueries',
6
- 'overloadedShards' = 'overloadedShards',
6
+ 'topShards' = 'topShards',
7
7
  'nodes' = 'Nodes',
8
8
  'tablets' = 'Tablets',
9
9
  'storage' = 'Storage',
@@ -29,9 +29,9 @@ const topQueries = {
29
29
  title: 'Top queries',
30
30
  };
31
31
 
32
- const overloadedShards = {
33
- id: GeneralPagesIds.overloadedShards,
34
- title: 'Overloaded shards',
32
+ const topShards = {
33
+ id: GeneralPagesIds.topShards,
34
+ title: 'Top shards',
35
35
  };
36
36
 
37
37
  const nodes = {
@@ -75,7 +75,7 @@ const consumers = {
75
75
  export const DATABASE_PAGES = [
76
76
  overview,
77
77
  topQueries,
78
- overloadedShards,
78
+ topShards,
79
79
  nodes,
80
80
  tablets,
81
81
  storage,
@@ -83,9 +83,9 @@ export const DATABASE_PAGES = [
83
83
  describe,
84
84
  ];
85
85
 
86
- export const TABLE_PAGES = [overview, overloadedShards, graph, tablets, hotKeys, describe];
86
+ export const TABLE_PAGES = [overview, topShards, graph, tablets, hotKeys, describe];
87
87
 
88
- export const DIR_PAGES = [overview, overloadedShards, describe];
88
+ export const DIR_PAGES = [overview, topShards, describe];
89
89
 
90
90
  export const CDC_STREAM_PAGES = [overview, consumers, describe];
91
91
  export const TOPIC_PAGES = [overview, consumers, describe];
@@ -2,7 +2,7 @@ import {useEffect, useMemo} from 'react';
2
2
  import cn from 'bem-cn-lite';
3
3
  import {connect} from 'react-redux';
4
4
  import {Loader} from '@gravity-ui/uikit';
5
- import DataTable from '@yandex-cloud/react-data-table';
5
+ import DataTable from '@gravity-ui/react-data-table';
6
6
 
7
7
  import Icon from '../../../../components/Icon/Icon';
8
8
 
@@ -1,15 +1,15 @@
1
1
  import {ReactNode, useCallback, useMemo} from 'react';
2
2
  import {shallowEqual, useDispatch, useSelector} from 'react-redux';
3
- import cn from 'bem-cn-lite';
4
3
 
5
- import {Loader} from '@gravity-ui/uikit';
4
+ import {Loader} from '../../../../components/Loader';
6
5
 
7
6
  //@ts-ignore
8
- import SchemaInfoViewer from '../../Schema/SchemaInfoViewer/SchemaInfoViewer';
9
7
  import {TableIndexInfo} from '../../../../components/InfoViewer/schemaInfo';
8
+ import {ResponseError} from '../../../../components/Errors/ResponseError';
10
9
 
11
10
  import {TopicInfo} from './TopicInfo';
12
11
  import {ChangefeedInfo} from './ChangefeedInfo';
12
+ import {TableInfo} from './TableInfo';
13
13
 
14
14
  import {EPathType, TEvDescribeSchemeResult} from '../../../../types/api/schema';
15
15
  import {
@@ -33,8 +33,6 @@ import {
33
33
  } from '../../../../store/reducers/olapStats';
34
34
  import {useAutofetcher, useTypedSelector} from '../../../../utils/hooks';
35
35
 
36
- import './Overview.scss';
37
-
38
36
  function prepareOlapTableGeneral(item?: TEvDescribeSchemeResult, olapStats?: any[]) {
39
37
  const tableData = item?.PathDescription?.ColumnTableDescription;
40
38
 
@@ -70,8 +68,6 @@ interface OverviewProps {
70
68
  tenantName?: string;
71
69
  }
72
70
 
73
- const b = cn('kv-tenant-overview');
74
-
75
71
  function Overview({type, tenantName, className}: OverviewProps) {
76
72
  const dispatch = useDispatch();
77
73
 
@@ -81,6 +77,7 @@ function Overview({type, tenantName, className}: OverviewProps) {
81
77
  wasLoaded,
82
78
  autorefresh,
83
79
  currentSchemaPath,
80
+ error,
84
81
  } = useSelector((state: any) => state.schema);
85
82
 
86
83
  const {data: {result: olapStats} = {result: undefined}, loading: olapStatsLoading} =
@@ -144,14 +141,6 @@ function Overview({type, tenantName, className}: OverviewProps) {
144
141
  : currentItem;
145
142
  }, [type, olapStats, currentItem]);
146
143
 
147
- const renderLoader = () => {
148
- return (
149
- <div className={b('loader')}>
150
- <Loader size="m" />
151
- </div>
152
- );
153
- };
154
-
155
144
  const renderContent = () => {
156
145
  // verbose mapping to guarantee a correct render for new path types
157
146
  // TS will error when a new type is added but not mapped here
@@ -170,15 +159,15 @@ function Overview({type, tenantName, className}: OverviewProps) {
170
159
  [EPathType.EPathTypePersQueueGroup]: () => <TopicInfo data={schemaData} />,
171
160
  };
172
161
 
173
- return (
174
- (type && pathTypeToComponent[type]?.()) || (
175
- <SchemaInfoViewer fullPath={currentItem.Path} data={schemaData} />
176
- )
177
- );
162
+ return (type && pathTypeToComponent[type]?.()) || <TableInfo data={schemaData} />;
178
163
  };
179
164
 
180
165
  if ((loading && !wasLoaded) || (isEntityWithMergedImpl && !mergedChildrenPaths)) {
181
- return renderLoader();
166
+ return <Loader size="m" />;
167
+ }
168
+
169
+ if (error) {
170
+ return <ResponseError error={error} />;
182
171
  }
183
172
 
184
173
  return <div className={className}>{renderContent()}</div>;
@@ -1,6 +1,12 @@
1
- .schema-info-viewer {
1
+ @import '../../../../../styles/mixins.scss';
2
+
3
+ .ydb-diagnostics-table-info {
2
4
  overflow: auto;
3
5
 
6
+ &__title {
7
+ @include info-viewer-title();
8
+ }
9
+
4
10
  &__row {
5
11
  display: flex;
6
12
  flex-wrap: wrap;
@@ -19,19 +25,11 @@
19
25
  }
20
26
  }
21
27
 
22
- &__item {
28
+ &__info-block {
23
29
  margin-bottom: 20px;
24
30
 
25
31
  .info-viewer__items {
26
32
  grid-template-columns: minmax(max-content, 280px);
27
33
  }
28
34
  }
29
-
30
- &__title {
31
- margin: 15px 0 10px;
32
-
33
- font-size: var(--yc-text-body-2-font-size);
34
- font-weight: 600;
35
- line-height: var(--yc-text-body-2-line-height);
36
- }
37
35
  }
@@ -0,0 +1,71 @@
1
+ import {useMemo} from 'react';
2
+ import cn from 'bem-cn-lite';
3
+
4
+ import type {TEvDescribeSchemeResult} from '../../../../../types/api/schema';
5
+
6
+ import {InfoViewer} from '../../../../../components/InfoViewer';
7
+
8
+ import {getEntityName} from '../../../utils';
9
+
10
+ import {prepareTableInfo} from './prepareTableInfo';
11
+
12
+ import i18n from './i18n';
13
+
14
+ import './TableInfo.scss';
15
+
16
+ const b = cn('ydb-diagnostics-table-info');
17
+
18
+ interface TableInfoProps {
19
+ data?: TEvDescribeSchemeResult;
20
+ }
21
+
22
+ export const TableInfo = ({data}: TableInfoProps) => {
23
+ const entityName = getEntityName(data?.PathDescription);
24
+
25
+ const {
26
+ generalTableInfo = [],
27
+ tableStatsInfo = [],
28
+ tabletMetricsInfo = [],
29
+ partitionConfigInfo = [],
30
+ } = useMemo(() => prepareTableInfo(data), [data]);
31
+
32
+ return (
33
+ <div className={b()}>
34
+ <InfoViewer
35
+ info={generalTableInfo}
36
+ title={entityName}
37
+ className={b('info-block')}
38
+ renderEmptyState={() => <div className={b('title')}>{entityName}</div>}
39
+ />
40
+ <div className={b('row')}>
41
+ {tabletMetricsInfo.length > 0 || partitionConfigInfo.length > 0 ? (
42
+ <div className={b('col')}>
43
+ <InfoViewer
44
+ info={tabletMetricsInfo}
45
+ title={i18n('tabletMetrics')}
46
+ className={b('info-block')}
47
+ renderEmptyState={() => null}
48
+ />
49
+ <InfoViewer
50
+ info={partitionConfigInfo}
51
+ title={i18n('partitionConfig')}
52
+ className={b('info-block')}
53
+ renderEmptyState={() => null}
54
+ />
55
+ </div>
56
+ ) : null}
57
+ <div className={b('col')}>
58
+ {tableStatsInfo.map((info, index) => (
59
+ <InfoViewer
60
+ key={index}
61
+ info={info}
62
+ title={index === 0 ? i18n('tableStats') : undefined}
63
+ className={b('info-block')}
64
+ renderEmptyState={() => null}
65
+ />
66
+ ))}
67
+ </div>
68
+ </div>
69
+ </div>
70
+ );
71
+ };
@@ -0,0 +1,5 @@
1
+ {
2
+ "tableStats": "Table Stats",
3
+ "tabletMetrics": "Tablet Metrics",
4
+ "partitionConfig": "Partition Config"
5
+ }
@@ -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-diagnostics-overview-table-info';
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,5 @@
1
+ {
2
+ "tableStats": "Статистика таблицы",
3
+ "tabletMetrics": "Метрики таблетки",
4
+ "partitionConfig": "Конфигурация партиции"
5
+ }
@@ -0,0 +1 @@
1
+ export * from './TableInfo';
@@ -0,0 +1,96 @@
1
+ import type {TEvDescribeSchemeResult} from '../../../../../types/api/schema';
2
+
3
+ import {formatObject} from '../../../../../components/InfoViewer';
4
+ import {
5
+ formatFollowerGroupItem,
6
+ formatPartitionConfigItem,
7
+ formatTableStatsItem,
8
+ formatTabletMetricsItem,
9
+ } from '../../../../../components/InfoViewer/formatters';
10
+
11
+ export const prepareTableInfo = (data?: TEvDescribeSchemeResult) => {
12
+ if (!data) {
13
+ return {};
14
+ }
15
+
16
+ const {PathDescription = {}} = data;
17
+
18
+ const {
19
+ TableStats = {},
20
+ TabletMetrics = {},
21
+ Table: {PartitionConfig = {}} = {},
22
+ } = PathDescription;
23
+
24
+ const {
25
+ PartCount,
26
+ RowCount,
27
+ DataSize,
28
+ IndexSize,
29
+
30
+ LastAccessTime,
31
+ LastUpdateTime,
32
+
33
+ ImmediateTxCompleted,
34
+ PlannedTxCompleted,
35
+ TxRejectedByOverload,
36
+ TxRejectedBySpace,
37
+ TxCompleteLagMsec,
38
+ InFlightTxCount,
39
+
40
+ RowUpdates,
41
+ RowDeletes,
42
+ RowReads,
43
+ RangeReads,
44
+ RangeReadRows,
45
+
46
+ ...restTableStats
47
+ } = TableStats;
48
+
49
+ const {FollowerGroups, FollowerCount, CrossDataCenterFollowerCount} = PartitionConfig;
50
+
51
+ const generalTableInfo = formatObject(formatTableStatsItem, {
52
+ PartCount,
53
+ RowCount,
54
+ DataSize,
55
+ IndexSize,
56
+ ...restTableStats,
57
+ });
58
+
59
+ const tableStatsInfo = [
60
+ formatObject(formatTableStatsItem, {
61
+ LastAccessTime,
62
+ LastUpdateTime,
63
+ }),
64
+ formatObject(formatTableStatsItem, {
65
+ ImmediateTxCompleted,
66
+ PlannedTxCompleted,
67
+ TxRejectedByOverload,
68
+ TxRejectedBySpace,
69
+ TxCompleteLagMsec,
70
+ InFlightTxCount,
71
+ }),
72
+ formatObject(formatTableStatsItem, {
73
+ RowUpdates,
74
+ RowDeletes,
75
+ RowReads,
76
+ RangeReads,
77
+ RangeReadRows,
78
+ }),
79
+ ];
80
+
81
+ const tabletMetricsInfo = formatObject(formatTabletMetricsItem, TabletMetrics);
82
+
83
+ let partitionConfigInfo = [];
84
+
85
+ if (Array.isArray(FollowerGroups) && FollowerGroups.length > 0) {
86
+ partitionConfigInfo = formatObject(formatFollowerGroupItem, FollowerGroups[0]);
87
+ } else if (FollowerCount !== undefined) {
88
+ partitionConfigInfo.push(formatPartitionConfigItem('FollowerCount', FollowerCount));
89
+ } else if (CrossDataCenterFollowerCount !== undefined) {
90
+ partitionConfigInfo.push(
91
+ formatPartitionConfigItem('CrossDataCenterFollowerCount', CrossDataCenterFollowerCount),
92
+ );
93
+ }
94
+
95
+ return {generalTableInfo, tableStatsInfo, tabletMetricsInfo, partitionConfigInfo};
96
+ };
@@ -2,7 +2,7 @@ import {useCallback, useEffect, useRef, useState} from 'react';
2
2
  import {useDispatch} from 'react-redux';
3
3
  import cn from 'bem-cn-lite';
4
4
 
5
- import DataTable, {Column, Settings} from '@yandex-cloud/react-data-table';
5
+ import DataTable, {Column, Settings} from '@gravity-ui/react-data-table';
6
6
  import {Loader} from '@gravity-ui/uikit';
7
7
 
8
8
  import {DateRange, DateRangeValues} from '../../../../components/DateRange';
@@ -21,6 +21,7 @@ import type {EPathType} from '../../../../types/api/schema';
21
21
  import type {ITopQueriesFilters} from '../../../../types/store/executeTopQueries';
22
22
  import type {IQueryResult} from '../../../../types/store/query';
23
23
 
24
+ import {formatDateTime} from '../../../../utils';
24
25
  import {DEFAULT_TABLE_SETTINGS, HOUR_IN_SECONDS} from '../../../../utils/constants';
25
26
  import {useAutofetcher, useTypedSelector} from '../../../../utils/hooks';
26
27
  import {prepareQueryError} from '../../../../utils/query';
@@ -51,6 +52,11 @@ const COLUMNS: Column<KeyValueRow>[] = [
51
52
  sortable: false,
52
53
  render: ({value}) => <TruncatedQuery value={value} maxQueryHeight={MAX_QUERY_HEIGHT} />,
53
54
  },
55
+ {
56
+ name: 'IntervalEnd',
57
+ width: 140,
58
+ render: ({value}) => formatDateTime(new Date(value as string).getTime()),
59
+ },
54
60
  ];
55
61
 
56
62
  interface TopQueriesProps {