ydb-embedded-ui 4.18.0 → 4.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/components/FullNodeViewer/FullNodeViewer.scss +1 -1
  3. package/dist/components/FullNodeViewer/FullNodeViewer.tsx +13 -4
  4. package/dist/components/ProgressViewer/ProgressViewer.tsx +11 -5
  5. package/dist/containers/Nodes/getNodesColumns.tsx +1 -0
  6. package/dist/containers/Storage/PDiskPopup/PDiskPopup.tsx +1 -1
  7. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +3 -232
  8. package/dist/containers/Storage/StorageGroups/getStorageGroupsColumns.tsx +278 -0
  9. package/dist/containers/Tenant/Diagnostics/TenantOverview/MetricsCards/MetricsCards.tsx +2 -3
  10. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +22 -6
  11. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.scss +41 -0
  12. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx +68 -0
  13. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopGroups.tsx +76 -0
  14. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopTables.tsx +105 -0
  15. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json +2 -0
  16. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/ru.json +2 -0
  17. package/dist/containers/Versions/NodesTable/NodesTable.tsx +1 -0
  18. package/dist/store/reducers/index.ts +4 -0
  19. package/dist/store/reducers/storage/utils.ts +20 -11
  20. package/dist/store/reducers/tenantOverview/executeTopTables/executeTopTables.ts +93 -0
  21. package/dist/store/reducers/tenantOverview/executeTopTables/types.ts +14 -0
  22. package/dist/store/reducers/tenantOverview/topStorageGroups/topStorageGroups.ts +98 -0
  23. package/dist/store/reducers/tenantOverview/topStorageGroups/types.ts +29 -0
  24. package/dist/store/reducers/tenantOverview/topStorageGroups/utils.ts +20 -0
  25. package/dist/store/reducers/tenants/utils.ts +28 -18
  26. package/dist/styles/constants.scss +4 -0
  27. package/dist/types/additionalProps.ts +1 -1
  28. package/dist/types/api/storage.ts +1 -1
  29. package/dist/types/api/tenant.ts +21 -8
  30. package/dist/utils/bytesParsers/__test__/formatBytes.test.ts +10 -1
  31. package/dist/utils/bytesParsers/formatBytes.ts +3 -3
  32. package/dist/utils/constants.ts +8 -0
  33. package/dist/utils/dataFormatters/__test__/roundToSignificant.test.ts +22 -0
  34. package/dist/utils/dataFormatters/dataFormatters.ts +42 -20
  35. package/package.json +1 -1
@@ -0,0 +1,20 @@
1
+ import {TENANT_OVERVIEW_TABLES_LIMIT} from '../../../../utils/constants';
2
+ import type {TStorageInfo} from '../../../../types/api/storage';
3
+ import {prepareStorageGroups} from '../../storage/utils';
4
+ import type {PreparedTopStorageGroupsResponse} from './types';
5
+
6
+ export const prepareTopStorageGroupsResponse = (
7
+ data: TStorageInfo,
8
+ ): PreparedTopStorageGroupsResponse => {
9
+ const {StoragePools, StorageGroups} = data;
10
+
11
+ const preparedGroups = prepareStorageGroups(StorageGroups, StoragePools);
12
+
13
+ if (StoragePools) {
14
+ preparedGroups.sort((a, b) => b.Usage - a.Usage);
15
+ }
16
+
17
+ return {
18
+ groups: preparedGroups.slice(0, TENANT_OVERVIEW_TABLES_LIMIT),
19
+ };
20
+ };
@@ -26,34 +26,44 @@ export const calculateTenantMetrics = (tenant?: TTenant) => {
26
26
  CoresUsed,
27
27
  MemoryUsed,
28
28
  StorageAllocatedSize,
29
- CoresLimit,
30
29
  MemoryLimit,
31
- StorageLimit,
30
+ StorageAllocatedLimit,
31
+ PoolStats,
32
32
  Metrics = {},
33
+ DatabaseQuotas = {},
33
34
  } = tenant || {};
34
35
 
35
- const cpuFromCores = isNumeric(CoresUsed) ? Number(CoresUsed) * 1_000_000 : undefined;
36
- const cpuFromMetrics = isNumeric(Metrics.CPU) ? Number(Metrics.CPU) : undefined;
36
+ const systemPoolUsage = PoolStats?.find(({Name}) => Name === 'System')?.Usage;
37
+ const userPoolUsage = PoolStats?.find(({Name}) => Name === 'User')?.Usage;
37
38
 
38
- const cpu = cpuFromCores ?? cpuFromMetrics ?? undefined;
39
+ const cpu = isNumeric(CoresUsed) ? Number(CoresUsed) * 1_000_000 : undefined;
40
+ const memory = isNumeric(MemoryUsed) ? Number(MemoryUsed) : undefined;
41
+ const blobStorage = isNumeric(StorageAllocatedSize) ? Number(StorageAllocatedSize) : undefined;
42
+ const tableStorage = isNumeric(Metrics.Storage) ? Number(Metrics.Storage) : undefined;
39
43
 
40
- const rawMemory = MemoryUsed ?? Metrics.Memory;
41
-
42
- const memory = isNumeric(rawMemory) ? Number(rawMemory) : undefined;
43
-
44
- // Blob storage - actual database size
45
- const storage = isNumeric(StorageAllocatedSize) ? Number(StorageAllocatedSize) : undefined;
46
- const cpuLimit = isNumeric(CoresLimit) ? Number(CoresLimit) : undefined;
44
+ // We use system pool usage and user pool usage to calculate cpu usage because
45
+ // only these pools directly indicate resources available to perform user queries
46
+ const cpuUsage =
47
+ isNumeric(systemPoolUsage) || isNumeric(userPoolUsage)
48
+ ? Math.max(Number(systemPoolUsage), Number(userPoolUsage)) * 100
49
+ : undefined;
47
50
  const memoryLimit = isNumeric(MemoryLimit) ? Number(MemoryLimit) : undefined;
48
- const storageLimit = isNumeric(StorageLimit) ? Number(StorageLimit) : undefined;
51
+ const blobStorageLimit = isNumeric(StorageAllocatedLimit)
52
+ ? Number(StorageAllocatedLimit)
53
+ : undefined;
54
+ const tableStorageLimit = isNumeric(DatabaseQuotas.data_size_hard_quota)
55
+ ? Number(DatabaseQuotas.data_size_hard_quota)
56
+ : undefined;
49
57
 
50
58
  return {
51
59
  cpu,
52
60
  memory,
53
- storage,
54
- cpuLimit,
61
+ blobStorage,
62
+ tableStorage,
63
+ cpuUsage,
55
64
  memoryLimit,
56
- storageLimit,
65
+ blobStorageLimit,
66
+ tableStorageLimit,
57
67
  };
58
68
  };
59
69
 
@@ -71,7 +81,7 @@ export const prepareTenants = (tenants: TTenant[], useNodeAsBackend: boolean) =>
71
81
  const backend = useNodeAsBackend ? getTenantBackend(tenant) : undefined;
72
82
  const sharedTenantName = tenants.find((item) => item.Id === tenant.ResourceId)?.Name;
73
83
  const controlPlaneName = getControlPlaneValue(tenant);
74
- const {cpu, memory, storage} = calculateTenantMetrics(tenant);
84
+ const {cpu, memory, blobStorage} = calculateTenantMetrics(tenant);
75
85
  const {nodesCount, groupsCount} = calculateTenantEntities(tenant);
76
86
 
77
87
  return {
@@ -82,7 +92,7 @@ export const prepareTenants = (tenants: TTenant[], useNodeAsBackend: boolean) =>
82
92
  controlPlaneName,
83
93
  cpu,
84
94
  memory,
85
- storage,
95
+ storage: blobStorage,
86
96
  nodesCount,
87
97
  groupsCount,
88
98
  };
@@ -2,4 +2,8 @@
2
2
  --tenant-object-info-max-value-width: 300px;
3
3
 
4
4
  --diagnostics-section-title-margin: 20px;
5
+
6
+ --diagnostics-section-margin: 16px;
7
+
8
+ --diagnostics-section-table-width: 872px;
5
9
  }
@@ -24,7 +24,7 @@ export interface AdditionalTenantsProps {
24
24
  getMonitoringLink?: (name?: string, type?: ETenantType) => ReactNode;
25
25
  }
26
26
 
27
- export type NodeAddress = Pick<TSystemStateInfo, 'Host' | 'Endpoints'>;
27
+ export type NodeAddress = Pick<TSystemStateInfo, 'Host' | 'Endpoints' | 'NodeId'>;
28
28
 
29
29
  export interface AdditionalNodesProps extends Record<string, unknown> {
30
30
  getNodeRef?: (node?: NodeAddress) => string | null;
@@ -16,7 +16,7 @@ export interface TStorageInfo {
16
16
  FoundGroups?: string;
17
17
  }
18
18
 
19
- interface TStoragePoolInfo {
19
+ export interface TStoragePoolInfo {
20
20
  Overall?: EFlag;
21
21
  Name?: string;
22
22
  Kind?: string;
@@ -42,24 +42,24 @@ export interface TTenant {
42
42
  ResourceId?: string;
43
43
  Tablets?: TTabletStateInfo[];
44
44
  /** uint64 */
45
- StorageAllocatedSize?: string;
45
+ StorageAllocatedSize?: string; // Actual database size
46
46
  /** uint64 */
47
47
  StorageMinAvailableSize?: string;
48
48
  Nodes?: TSystemStateInfo[];
49
49
  /** uint64 */
50
- MemoryUsed?: string;
50
+ MemoryUsed?: string; // Actual memory consumption
51
51
  /** uint64 */
52
52
  MemoryLimit?: string;
53
53
  /** double */
54
- CoresUsed?: number;
54
+ CoresUsed?: number; // Actual cpu consumption
55
55
  /** uint64 */
56
56
  StorageGroups?: string;
57
57
 
58
58
  MonitoringEndpoint?: string; // additional
59
59
  ControlPlane?: ControlPlane; // additional
60
60
 
61
- CoresLimit?: string; // TODO: check correctness in backend protos when fully supported
62
- StorageLimit?: string; // TODO: check correctness in backend protos when fully supported
61
+ StorageAllocatedLimit?: string;
62
+ DatabaseQuotas?: DatabaseQuotas;
63
63
  }
64
64
 
65
65
  interface THiveDomainStatsStateCount {
@@ -69,15 +69,15 @@ interface THiveDomainStatsStateCount {
69
69
 
70
70
  export interface TMetrics {
71
71
  /** uint64 */
72
- CPU?: string;
72
+ CPU?: string; // Logical cpu consumption
73
73
  /** uint64 */
74
- Memory?: string;
74
+ Memory?: string; // Logical memory consumption
75
75
  /** uint64 */
76
76
  Network?: string;
77
77
  /** uint64 */
78
78
  Counter?: string;
79
79
  /** uint64 */
80
- Storage?: string;
80
+ Storage?: string; // Logical database size
81
81
  /** uint64 */
82
82
  ReadThroughput?: string;
83
83
  /** uint64 */
@@ -149,3 +149,16 @@ export enum ETabletVolatileState {
149
149
  'TABLET_VOLATILE_STATE_STARTING' = 'TABLET_VOLATILE_STATE_STARTING',
150
150
  'TABLET_VOLATILE_STATE_RUNNING' = 'TABLET_VOLATILE_STATE_RUNNING',
151
151
  }
152
+
153
+ interface DatabaseQuotas {
154
+ /** uint64 */
155
+ data_size_hard_quota?: string;
156
+ /** uint64 */
157
+ data_size_soft_quota?: string;
158
+ /** uint64 */
159
+ data_stream_shards_quota?: string;
160
+ /** uint64 */
161
+ data_stream_reserved_storage_quota?: string;
162
+ /** uint32 */
163
+ ttl_min_run_internal_seconds?: string;
164
+ }
@@ -28,11 +28,20 @@ describe('formatBytes', () => {
28
28
  expect(formatBytes({value: 99_000_000_000_000, significantDigits: 2})).toEqual('99,000 GB');
29
29
  expect(formatBytes({value: 100_000_000_000_000, significantDigits: 2})).toEqual('100 TB');
30
30
  });
31
- it('shoudl return empty string on invalid data', () => {
31
+ it('should return empty string on invalid data', () => {
32
32
  expect(formatBytes({value: undefined})).toEqual('');
33
33
  expect(formatBytes({value: null})).toEqual('');
34
34
  expect(formatBytes({value: ''})).toEqual('');
35
35
  expect(formatBytes({value: 'false'})).toEqual('');
36
36
  expect(formatBytes({value: '123qwe'})).toEqual('');
37
37
  });
38
+ it('should work with precision', () => {
39
+ expect(formatBytes({value: 123.123, precision: 2})).toBe('123 B');
40
+ expect(formatBytes({value: 12.123, precision: 2})).toBe('12 B');
41
+ expect(formatBytes({value: 1.123, precision: 2})).toBe('1.1 B');
42
+ expect(formatBytes({value: 0.123, precision: 2})).toBe('0.12 B');
43
+ expect(formatBytes({value: 0.012, precision: 2})).toBe('0.01 B');
44
+ expect(formatBytes({value: 0.001, precision: 2})).toBe('0 B');
45
+ expect(formatBytes({value: 0, precision: 2})).toBe('0 B');
46
+ });
38
47
  });
@@ -1,4 +1,4 @@
1
- import {formatNumber} from '../dataFormatters/dataFormatters';
1
+ import {formatNumber, roundToPrecision} from '../dataFormatters/dataFormatters';
2
2
  import {GIGABYTE, KILOBYTE, MEGABYTE, TERABYTE} from '../constants';
3
3
  import {isNumeric} from '../utils';
4
4
 
@@ -46,7 +46,7 @@ export type BytesSizes = keyof typeof sizes;
46
46
  *
47
47
  * significantDigits = 3 - 900 000 mb and 1000 gb
48
48
  */
49
- const getSizeWithSignificantDigits = (value: number, significantDigits: number) => {
49
+ export const getSizeWithSignificantDigits = (value: number, significantDigits: number) => {
50
50
  const multiplier = 10 ** significantDigits;
51
51
 
52
52
  const tbLevel = sizes.tb.value * multiplier;
@@ -79,7 +79,7 @@ interface FormatToSizeArgs {
79
79
  }
80
80
 
81
81
  const formatToSize = ({value, size = 'mb', precision = 0}: FormatToSizeArgs) => {
82
- const result = (Number(value) / sizes[size].value).toFixed(precision);
82
+ const result = roundToPrecision(Number(value) / sizes[size].value, precision);
83
83
 
84
84
  return formatNumber(result);
85
85
  };
@@ -77,6 +77,8 @@ export const COLORS_PRIORITY = {
77
77
  grey: 1,
78
78
  };
79
79
 
80
+ export const TENANT_OVERVIEW_TABLES_LIMIT = 5;
81
+
80
82
  // ==== Titles ====
81
83
  export const DEVELOPER_UI_TITLE = 'Developer UI';
82
84
  export const CLUSTER_DEFAULT_TITLE = 'Cluster';
@@ -113,6 +115,12 @@ export const DEFAULT_TABLE_SETTINGS = {
113
115
  highlightRows: true,
114
116
  } as const;
115
117
 
118
+ export const TENANT_OVERVIEW_TABLES_SETTINGS = {
119
+ ...DEFAULT_TABLE_SETTINGS,
120
+ stickyHead: 'fixed',
121
+ dynamicRender: false,
122
+ } as const;
123
+
116
124
  export const QUERY_INITIAL_MODE_KEY = 'query_initial_mode';
117
125
  export const LAST_USED_QUERY_ACTION_KEY = 'last_used_query_action';
118
126
 
@@ -0,0 +1,22 @@
1
+ import {roundToPrecision} from '../dataFormatters';
2
+
3
+ describe('roundToSignificant', () => {
4
+ it('should work with only value', () => {
5
+ expect(roundToPrecision(123)).toBe(123);
6
+ expect(roundToPrecision(123.123)).toBe(123);
7
+ expect(roundToPrecision(12.123)).toBe(12);
8
+ expect(roundToPrecision(1.123)).toBe(1);
9
+ expect(roundToPrecision(0.123)).toBe(0);
10
+ expect(roundToPrecision(0)).toBe(0);
11
+ });
12
+ it('should work with precision', () => {
13
+ expect(roundToPrecision(123, 2)).toBe(123);
14
+ expect(roundToPrecision(123.123, 2)).toBe(123);
15
+ expect(roundToPrecision(12.123, 2)).toBe(12);
16
+ expect(roundToPrecision(1.123, 2)).toBe(1.1);
17
+ expect(roundToPrecision(0.123, 2)).toBe(0.12);
18
+ expect(roundToPrecision(0.012, 2)).toBe(0.01);
19
+ expect(roundToPrecision(0.001, 2)).toBe(0);
20
+ expect(roundToPrecision(0, 2)).toBe(0);
21
+ });
22
+ });
@@ -1,9 +1,14 @@
1
1
  import {dateTimeParse} from '@gravity-ui/date-utils';
2
2
 
3
3
  import type {TVDiskID, TVSlotId} from '../../types/api/vdisk';
4
- import {DAY_IN_SECONDS, GIGABYTE, TERABYTE} from '../constants';
4
+ import {DAY_IN_SECONDS, GIGABYTE} from '../constants';
5
5
  import {configuredNumeral} from '../numeral';
6
6
  import {isNumeric} from '../utils';
7
+ import {
8
+ type BytesSizes,
9
+ formatBytes as formatBytesCustom,
10
+ getSizeWithSignificantDigits,
11
+ } from '../bytesParsers/formatBytes';
7
12
 
8
13
  import i18n from './i18n';
9
14
 
@@ -55,17 +60,30 @@ export const formatMsToUptime = (ms?: number) => {
55
60
  return ms && formatUptime(ms / 1000);
56
61
  };
57
62
 
58
- export const formatStorageValues = (value?: number, total?: number) => {
59
- return [
60
- value ? String(Math.floor(value / TERABYTE)) : undefined,
61
- total ? `${Math.floor(total / TERABYTE)} TB` : undefined,
62
- ];
63
+ export const formatStorageValues = (value?: number, total?: number, size?: BytesSizes) => {
64
+ let calculatedSize = getSizeWithSignificantDigits(Number(value), 0);
65
+ let valueWithSizeLabel = true;
66
+ let valuePrecision = 0;
67
+
68
+ if (isNumeric(total)) {
69
+ calculatedSize = getSizeWithSignificantDigits(Number(total), 0);
70
+ valueWithSizeLabel = false;
71
+ valuePrecision = 1;
72
+ }
73
+
74
+ const formattedValue = formatBytesCustom({
75
+ value,
76
+ withSizeLabel: valueWithSizeLabel,
77
+ size: size || calculatedSize,
78
+ precision: valuePrecision,
79
+ });
80
+ const formattedTotal = formatBytesCustom({value: total, size: size || calculatedSize});
81
+
82
+ return [formattedValue, formattedTotal];
63
83
  };
64
- export const formatStorageValuesToGb = (value?: number, total?: number): (string | undefined)[] => {
65
- return [
66
- value ? String(Math.floor(value / 1000000000)) : undefined,
67
- total ? `${Math.floor(total / 1000000000)} GB` : undefined,
68
- ];
84
+
85
+ export const formatStorageValuesToGb = (value?: number, total?: number) => {
86
+ return formatStorageValues(value, total, 'gb');
69
87
  };
70
88
 
71
89
  export const formatNumber = (number?: unknown) => {
@@ -73,20 +91,24 @@ export const formatNumber = (number?: unknown) => {
73
91
  return '';
74
92
  }
75
93
 
76
- return configuredNumeral(number).format();
94
+ return configuredNumeral(number).format('0,0.[00000]');
77
95
  };
78
96
 
79
- const normalizeCPU = (value: number | string) => {
80
- const rawCores = Number(value) / 1000000;
81
- let cores = rawCores.toPrecision(3);
82
- if (rawCores >= 1000) {
83
- cores = rawCores.toFixed();
97
+ export const roundToPrecision = (value: number | string, precision = 0) => {
98
+ let [digits] = String(value).split('.');
99
+ if (Number(value) < 1) {
100
+ digits = '';
84
101
  }
85
- if (rawCores < 0.001) {
86
- cores = '0';
102
+ if (digits.length >= precision) {
103
+ return Math.round(Number(value));
87
104
  }
105
+ return Number(Number(value).toFixed(precision - digits.length));
106
+ };
107
+
108
+ const normalizeCPU = (value: number | string) => {
109
+ const rawCores = Number(value) / 1000000;
88
110
 
89
- return Number(cores);
111
+ return roundToPrecision(rawCores, 3);
90
112
  };
91
113
 
92
114
  export const formatCPU = (value?: number | string) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ydb-embedded-ui",
3
- "version": "4.18.0",
3
+ "version": "4.19.0",
4
4
  "files": [
5
5
  "dist"
6
6
  ],