ydb-embedded-ui 20.4.0 → 21.0.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 (134) hide show
  1. package/README.md +41 -0
  2. package/dist/components/AutoRefreshControl/i18n/index.d.ts +1 -1
  3. package/dist/components/FullNodeViewer/i18n/index.d.ts +1 -1
  4. package/dist/components/Graph/BlockComponents/ConnectionBlockComponent.d.ts +7 -0
  5. package/dist/components/Graph/BlockComponents/ConnectionBlockComponent.js +30 -0
  6. package/dist/components/Graph/BlockComponents/ConnectionBlockComponent.js.map +1 -0
  7. package/dist/components/Graph/BlockComponents/QueryBlockComponent.d.ts +5 -0
  8. package/dist/components/Graph/BlockComponents/QueryBlockComponent.js +5 -0
  9. package/dist/components/Graph/BlockComponents/QueryBlockComponent.js.map +1 -0
  10. package/dist/components/Graph/BlockComponents/ResultBlockComponent.d.ts +7 -0
  11. package/dist/components/Graph/BlockComponents/ResultBlockComponent.js +5 -0
  12. package/dist/components/Graph/BlockComponents/ResultBlockComponent.js.map +1 -0
  13. package/dist/components/Graph/BlockComponents/StageBlockComponent.d.ts +7 -0
  14. package/dist/components/Graph/BlockComponents/StageBlockComponent.js +17 -0
  15. package/dist/components/Graph/BlockComponents/StageBlockComponent.js.map +1 -0
  16. package/dist/components/Graph/FullViewportBackground.d.ts +2 -0
  17. package/dist/components/Graph/FullViewportBackground.js +67 -0
  18. package/dist/components/Graph/FullViewportBackground.js.map +1 -0
  19. package/dist/components/Graph/GraphControls.d.ts +6 -0
  20. package/dist/components/Graph/GraphControls.js +24 -0
  21. package/dist/components/Graph/GraphControls.js.map +1 -0
  22. package/dist/components/Graph/GravityGraph.d.ts +9 -0
  23. package/dist/components/Graph/GravityGraph.js +122 -0
  24. package/dist/components/Graph/GravityGraph.js.map +1 -0
  25. package/dist/components/Graph/GravityGraph.scss +131 -0
  26. package/dist/components/Graph/NonSelectableConnection.d.ts +9 -0
  27. package/dist/components/Graph/NonSelectableConnection.js +16 -0
  28. package/dist/components/Graph/NonSelectableConnection.js.map +1 -0
  29. package/dist/components/Graph/TooltipComponent.d.ts +8 -0
  30. package/dist/components/Graph/TooltipComponent.js +63 -0
  31. package/dist/components/Graph/TooltipComponent.js.map +1 -0
  32. package/dist/components/Graph/colorsConfig.d.ts +19 -0
  33. package/dist/components/Graph/colorsConfig.js +20 -0
  34. package/dist/components/Graph/colorsConfig.js.map +1 -0
  35. package/dist/components/Graph/i18n/en.json +5 -0
  36. package/dist/components/Graph/i18n/index.d.ts +2 -0
  37. package/dist/components/Graph/i18n/index.js +5 -0
  38. package/dist/components/Graph/i18n/index.js.map +1 -0
  39. package/dist/components/Graph/runTreeLayout.d.ts +12 -0
  40. package/dist/components/Graph/runTreeLayout.js +75 -0
  41. package/dist/components/Graph/runTreeLayout.js.map +1 -0
  42. package/dist/components/Graph/treeLayout.d.ts +2 -0
  43. package/dist/components/Graph/treeLayout.js +284 -0
  44. package/dist/components/Graph/treeLayout.js.map +1 -0
  45. package/dist/components/Graph/types.d.ts +52 -0
  46. package/dist/components/Graph/types.js +2 -0
  47. package/dist/components/Graph/types.js.map +1 -0
  48. package/dist/components/Graph/utils.d.ts +6 -0
  49. package/dist/components/Graph/utils.js +88 -0
  50. package/dist/components/Graph/utils.js.map +1 -0
  51. package/dist/components/InfoViewer/formatters/pqGroup.d.ts +5 -1
  52. package/dist/components/InfoViewer/formatters/pqGroup.js +24 -2
  53. package/dist/components/InfoViewer/formatters/pqGroup.js.map +1 -1
  54. package/dist/components/InfoViewer/i18n/en.json +8 -1
  55. package/dist/components/InfoViewer/i18n/index.d.ts +1 -1
  56. package/dist/components/PDiskInfo/i18n/index.d.ts +1 -1
  57. package/dist/components/VDiskInfo/i18n/index.d.ts +1 -1
  58. package/dist/containers/App/Content.js +9 -2
  59. package/dist/containers/App/Content.js.map +1 -1
  60. package/dist/containers/App/clusterRouteGuard.d.ts +5 -0
  61. package/dist/containers/App/clusterRouteGuard.js +13 -0
  62. package/dist/containers/App/clusterRouteGuard.js.map +1 -0
  63. package/dist/containers/Clusters/constants.d.ts +1 -1
  64. package/dist/containers/Nodes/i18n/index.d.ts +1 -1
  65. package/dist/containers/Operations/constants.d.ts +1 -1
  66. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/i18n/index.d.ts +1 -1
  67. package/dist/containers/Tenant/Diagnostics/Overview/TransferInfo/Credentials.d.ts +1 -1
  68. package/dist/containers/Tenant/Diagnostics/Overview/utils/prepareTopicSchemaInfo.js +17 -3
  69. package/dist/containers/Tenant/Diagnostics/Overview/utils/prepareTopicSchemaInfo.js.map +1 -1
  70. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/useTenantStorageNewData.d.ts +2 -2
  71. package/dist/containers/Tenant/Query/QueryResult/components/Graph/Graph.js +9 -4
  72. package/dist/containers/Tenant/Query/QueryResult/components/Graph/Graph.js.map +1 -1
  73. package/dist/containers/Tenant/Query/QueryResult/components/Graph/Graph.scss +2 -0
  74. package/dist/containers/Tenant/TenantNavigation/useTenantNavigation.d.ts +1 -1
  75. package/dist/graphLayout.worker.d.ts +1 -0
  76. package/dist/graphLayout.worker.js +17 -0
  77. package/dist/graphLayout.worker.js.map +1 -0
  78. package/dist/lib.d.ts +1 -1
  79. package/dist/lib.js.map +1 -1
  80. package/dist/store/reducers/query/parsers/preparePlanData.js.map +1 -1
  81. package/dist/store/reducers/query/types.d.ts +38 -1
  82. package/dist/store/reducers/settings/utils.d.ts +2 -2
  83. package/dist/types/api/schema/persQueueGroup.d.ts +16 -0
  84. package/dist/types/api/schema/persQueueGroup.js +7 -0
  85. package/dist/types/api/schema/persQueueGroup.js.map +1 -1
  86. package/dist/uiFactory/types.d.ts +3 -0
  87. package/dist/utils/disks/helpers.d.ts +2 -2
  88. package/dist/utils/hooks/useChangedQuerySettings.d.ts +2 -2
  89. package/dist/utils/hooks/useLastQueryExecutionSettings.d.ts +2 -2
  90. package/dist/utils/hooks/useQueryExecutionSettings.d.ts +2 -2
  91. package/dist/utils/prepareQueryExplain.d.ts +1 -2
  92. package/dist/utils/prepareQueryExplain.js +10 -2
  93. package/dist/utils/prepareQueryExplain.js.map +1 -1
  94. package/dist/utils/query.d.ts +1 -1
  95. package/package.json +3 -3
  96. package/src/components/Graph/BlockComponents/ConnectionBlockComponent.tsx +49 -0
  97. package/src/components/Graph/BlockComponents/QueryBlockComponent.tsx +7 -0
  98. package/src/components/Graph/BlockComponents/ResultBlockComponent.tsx +10 -0
  99. package/src/components/Graph/BlockComponents/StageBlockComponent.tsx +35 -0
  100. package/src/components/Graph/FullViewportBackground.ts +122 -0
  101. package/src/components/Graph/GraphControls.tsx +49 -0
  102. package/src/components/Graph/GravityGraph.scss +131 -0
  103. package/src/components/Graph/GravityGraph.tsx +174 -0
  104. package/src/components/Graph/NonSelectableConnection.tsx +14 -0
  105. package/src/components/Graph/TooltipComponent.tsx +114 -0
  106. package/src/components/Graph/colorsConfig.ts +21 -0
  107. package/src/components/Graph/i18n/en.json +5 -0
  108. package/src/components/Graph/i18n/index.ts +7 -0
  109. package/src/components/Graph/runTreeLayout.ts +105 -0
  110. package/src/components/Graph/treeLayout.ts +378 -0
  111. package/src/components/Graph/types.ts +56 -0
  112. package/src/components/Graph/utils.ts +112 -0
  113. package/src/components/InfoViewer/formatters/pqGroup.ts +30 -2
  114. package/src/components/InfoViewer/i18n/en.json +8 -1
  115. package/src/containers/App/Content.tsx +9 -1
  116. package/src/containers/App/clusterRouteGuard.ts +24 -0
  117. package/src/containers/Tenant/Diagnostics/Overview/utils/prepareTopicSchemaInfo.ts +23 -2
  118. package/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.scss +2 -0
  119. package/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.tsx +13 -7
  120. package/src/graphLayout.worker.ts +21 -0
  121. package/src/lib.ts +1 -0
  122. package/src/store/reducers/query/parsers/preparePlanData.ts +1 -3
  123. package/src/store/reducers/query/types.ts +42 -2
  124. package/src/types/api/schema/persQueueGroup.ts +19 -0
  125. package/src/uiFactory/types.ts +4 -0
  126. package/src/utils/prepareQueryExplain.ts +13 -5
  127. package/dist/components/Graph/Graph.d.ts +0 -12
  128. package/dist/components/Graph/Graph.js +0 -44
  129. package/dist/components/Graph/Graph.js.map +0 -1
  130. package/dist/components/Graph/colors.d.ts +0 -10
  131. package/dist/components/Graph/colors.js +0 -53
  132. package/dist/components/Graph/colors.js.map +0 -1
  133. package/src/components/Graph/Graph.tsx +0 -70
  134. package/src/components/Graph/colors.ts +0 -75
@@ -0,0 +1,378 @@
1
+ import type {TBlock} from '@gravity-ui/graph';
2
+
3
+ import type {
4
+ EdgeResult,
5
+ ExtendedTBlock,
6
+ LayoutOptions,
7
+ TreeConnection,
8
+ TreeLayoutRequest,
9
+ TreeLayoutResult,
10
+ TreeNode,
11
+ } from './types';
12
+ import {prepareBlocks, prepareConnections} from './utils';
13
+
14
+ class TreeLayoutEngine {
15
+ private blocks: Map<string, any>;
16
+ private connections: TreeConnection[];
17
+ private options: Required<LayoutOptions>;
18
+ private tree: TreeNode | null;
19
+ private levels: TreeNode[][];
20
+
21
+ constructor(blocks: any[], connections: TreeConnection[], options: LayoutOptions = {}) {
22
+ this.blocks = new Map(blocks.map((block: any) => [block.id, {...block}]));
23
+ this.connections = connections;
24
+
25
+ // Layout settings
26
+ this.options = {
27
+ horizontalSpacing: options.horizontalSpacing || 40, // horizontal distance between blocks
28
+ padding: 12,
29
+ verticalSpacing: options.verticalSpacing || 20, // vertical distance between levels
30
+ ...options,
31
+ };
32
+
33
+ this.tree = null;
34
+ this.levels = [];
35
+ }
36
+
37
+ // Building a tree structure
38
+ buildTree() {
39
+ const childrenMap = new Map();
40
+ const parentMap = new Map();
41
+
42
+ for (const block of this.blocks.values()) {
43
+ childrenMap.set(block.id, []);
44
+ }
45
+
46
+ // Setting connections
47
+ for (const connection of this.connections) {
48
+ const parent = connection.sourceBlockId;
49
+ const child = connection.targetBlockId;
50
+
51
+ childrenMap.get(parent).push(child);
52
+ parentMap.set(child, parent);
53
+ }
54
+
55
+ // Find root (a node without a parent)
56
+ const rootId = Array.from(this.blocks.keys()).find((id) => !parentMap.has(id));
57
+
58
+ if (!rootId) {
59
+ throw new Error('Root node not found');
60
+ }
61
+
62
+ // Recursively building a tree
63
+ const buildNode = (nodeId: string, level = 0): TreeNode => {
64
+ const block = this.blocks.get(nodeId);
65
+ const children = childrenMap
66
+ .get(nodeId)
67
+ .map((childId: string) => buildNode(childId, level + 1));
68
+
69
+ return {
70
+ id: nodeId,
71
+ block: block,
72
+ children: children,
73
+ level: level,
74
+ x: 0,
75
+ y: 0,
76
+ subtreeWidth: 0,
77
+ };
78
+ };
79
+
80
+ this.tree = buildNode(rootId);
81
+ return this.tree;
82
+ }
83
+
84
+ // Grouping nodes by levels
85
+ groupByLevels(node: TreeNode | null = this.tree, levels: TreeNode[][] = []): TreeNode[][] {
86
+ if (!node) {
87
+ return levels;
88
+ }
89
+
90
+ if (!levels[node.level]) {
91
+ levels[node.level] = [];
92
+ }
93
+ levels[node.level].push(node);
94
+
95
+ for (const child of node.children) {
96
+ this.groupByLevels(child, levels);
97
+ }
98
+
99
+ this.levels = levels;
100
+ return levels;
101
+ }
102
+
103
+ // Calculating the width of the subtree for each node
104
+ calculateSubtreeWidths(node: TreeNode = this.tree!): number {
105
+ if (node.children.length === 0) {
106
+ // Leaf node - width is equal to the width of the block
107
+ node.subtreeWidth = node.block.width;
108
+ } else {
109
+ // Recursively calculating the width for children
110
+ for (const child of node.children) {
111
+ this.calculateSubtreeWidths(child);
112
+ }
113
+
114
+ // Subtree width = sum of the widths of the children's subtrees + the margins between them
115
+ const childrenWidth = node.children.reduce(
116
+ (sum: number, child: TreeNode) => sum + child.subtreeWidth,
117
+ 0,
118
+ );
119
+ const spacingWidth = (node.children.length - 1) * this.options.horizontalSpacing;
120
+ const totalChildrenWidth = childrenWidth + spacingWidth;
121
+
122
+ // Subtree width = the maximum of the width of the node itself and the total width of the children
123
+ node.subtreeWidth = Math.max(node.block.width, totalChildrenWidth);
124
+ }
125
+
126
+ return node.subtreeWidth;
127
+ }
128
+
129
+ // Placement of nodes by position
130
+ positionNodes() {
131
+ if (!this.tree) {
132
+ return;
133
+ }
134
+
135
+ // Calculating the Y coordinates for each level
136
+ let currentY = 0;
137
+ const levelY: number[] = [];
138
+
139
+ for (let level = 0; level < this.levels.length; level++) {
140
+ levelY[level] = currentY;
141
+
142
+ // We find the maximum height of the blocks at this level
143
+ const maxHeight = Math.max(
144
+ ...this.levels[level].map((node: TreeNode) => node.block.height),
145
+ );
146
+ currentY += maxHeight + this.options.verticalSpacing;
147
+ }
148
+
149
+ // Recursively placing nodes
150
+ const positionNode = (node: TreeNode, leftX: number): void => {
151
+ // Setting the Y coordinate
152
+ node.y = levelY[node.level];
153
+
154
+ if (node.children.length === 0) {
155
+ // The leaf node is placed in the current position.
156
+ node.x = leftX;
157
+ } else {
158
+ // Place children
159
+ let childX = leftX;
160
+
161
+ // If the node width is greater than the total width of the children, add an indentation.
162
+ const childrenWidth = node.children.reduce(
163
+ (sum: number, child: TreeNode) => sum + child.subtreeWidth,
164
+ 0,
165
+ );
166
+ const spacingWidth = (node.children.length - 1) * this.options.horizontalSpacing;
167
+ const totalChildrenWidth = childrenWidth + spacingWidth;
168
+
169
+ if (node.block.width > totalChildrenWidth) {
170
+ const extraSpace = (node.block.width - totalChildrenWidth) / 2;
171
+ childX += extraSpace;
172
+ }
173
+
174
+ // Place each child
175
+ for (const child of node.children) {
176
+ positionNode(child, childX);
177
+ childX += child.subtreeWidth + this.options.horizontalSpacing;
178
+ }
179
+
180
+ // Center the parent node relative to the children
181
+ const firstChild = node.children[0];
182
+ const lastChild = node.children[node.children.length - 1];
183
+ const childrenCenter = (firstChild.x + lastChild.x + lastChild.block.width) / 2;
184
+ node.x = childrenCenter - node.block.width / 2;
185
+ }
186
+ };
187
+
188
+ positionNode(this.tree, 0);
189
+ }
190
+
191
+ // Normalization of coordinates with padding from the canvas edges
192
+ normalizeCoordinates() {
193
+ if (!this.tree) {
194
+ return;
195
+ }
196
+
197
+ const allNodes: TreeNode[] = [];
198
+
199
+ const collectNodes = (node: TreeNode) => {
200
+ allNodes.push(node);
201
+ for (const child of node.children) {
202
+ collectNodes(child);
203
+ }
204
+ };
205
+
206
+ collectNodes(this.tree);
207
+
208
+ const minX = Math.min(...allNodes.map((node) => node.x));
209
+ const minY = Math.min(...allNodes.map((node) => node.y));
210
+
211
+ const offsetX = this.options.padding - minX;
212
+ const offsetY = this.options.padding - minY;
213
+
214
+ for (const node of allNodes) {
215
+ node.x += offsetX;
216
+ node.y += offsetY;
217
+ }
218
+ }
219
+
220
+ // Main method of composition
221
+ layout() {
222
+ this.buildTree();
223
+ this.groupByLevels();
224
+ this.calculateSubtreeWidths();
225
+ this.positionNodes();
226
+ this.normalizeCoordinates();
227
+
228
+ return this.getLayoutResult();
229
+ }
230
+
231
+ // Getting the result of composition
232
+ getLayoutResult(): ExtendedTBlock[] {
233
+ if (!this.tree) {
234
+ return [];
235
+ }
236
+
237
+ const result: ExtendedTBlock[] = [];
238
+
239
+ const collectResults = (node: TreeNode) => {
240
+ result.push({
241
+ id: node.id,
242
+ x: node.x,
243
+ y: node.y,
244
+ width: node.block.width,
245
+ height: node.block.height,
246
+ level: node.level,
247
+ ...node.block,
248
+ });
249
+
250
+ for (const child of node.children) {
251
+ collectResults(child);
252
+ }
253
+ };
254
+
255
+ collectResults(this.tree);
256
+
257
+ return result;
258
+ }
259
+ }
260
+
261
+ // Function for using the algorithm
262
+ function calculateTreeLayout(blocks: TBlock[], connections: TreeConnection[], options = {}) {
263
+ const engine = new TreeLayoutEngine(blocks, connections, options);
264
+ return engine.layout();
265
+ }
266
+
267
+ function calculateTreeEdges(
268
+ layoutResult: ExtendedTBlock[],
269
+ connections: TreeConnection[],
270
+ ): EdgeResult[] {
271
+ // Create a position map for convenience of search
272
+ const positionMap = new Map(layoutResult.map((item) => [item.id, item]));
273
+
274
+ // Group connections by parent block
275
+ const connectionsByParent = new Map();
276
+
277
+ for (const connection of connections) {
278
+ const parentId = connection.sourceBlockId;
279
+ if (!connectionsByParent.has(parentId)) {
280
+ connectionsByParent.set(parentId, []);
281
+ }
282
+ connectionsByParent.get(parentId).push(connection);
283
+ }
284
+
285
+ const connectionPaths: EdgeResult[] = [];
286
+
287
+ // For each parent block, we calculate the paths to the children
288
+ for (const [parentId, parentConnections] of connectionsByParent) {
289
+ const parent = positionMap.get(parentId);
290
+ if (!parent) {
291
+ continue;
292
+ }
293
+
294
+ // Coordinates of the initial point (center of the lower part of the parent)
295
+ const startX = parent.x + parent.width / 2;
296
+ const startY = parent.y + parent.height;
297
+
298
+ if (parentConnections.length === 1) {
299
+ // One child block - simple straight line
300
+ const connection = parentConnections[0];
301
+ const child = positionMap.get(connection.targetBlockId);
302
+
303
+ if (child) {
304
+ const endX = child.x + child.width / 2;
305
+ const endY = child.y;
306
+
307
+ connectionPaths.push({
308
+ connectionId: connection.id,
309
+ sourceBlockId: connection.sourceBlockId,
310
+ targetBlockId: connection.targetBlockId,
311
+ points: [
312
+ {x: startX, y: startY},
313
+ {x: endX, y: endY},
314
+ ],
315
+ });
316
+ }
317
+ } else {
318
+ // Several child blocks - broken lines
319
+
320
+ // We find the vertical distance between the parent and the nearest child
321
+ const children = parentConnections
322
+ .map((conn: TreeConnection) => positionMap.get(conn.targetBlockId))
323
+ .filter(
324
+ (child: ExtendedTBlock | undefined): child is ExtendedTBlock =>
325
+ child !== undefined,
326
+ );
327
+
328
+ if (children.length === 0) {
329
+ continue;
330
+ }
331
+
332
+ // We find the minimum distance to the children by Y
333
+ const minChildY = Math.min(...children.map((child: ExtendedTBlock) => child.y));
334
+
335
+ // The point of branching - in the middle between the parent and the children
336
+ const branchY = startY + (minChildY - startY) / 2;
337
+
338
+ // For each child block, we create a broken line
339
+ for (const connection of parentConnections) {
340
+ const child = positionMap.get(connection.targetBlockId);
341
+ if (!child) {
342
+ continue;
343
+ }
344
+
345
+ const endX = child.x + child.width / 2;
346
+ const endY = child.y;
347
+
348
+ const points = [
349
+ {x: startX, y: startY}, // Start - center of the lower part of the parent
350
+ {x: startX, y: branchY}, // Vertically down to the point of branching
351
+ {x: endX, y: branchY}, // Horizontally to the center of the child block
352
+ {x: endX, y: endY}, // Vertically down to the center of the upper part of the child block
353
+ ];
354
+
355
+ connectionPaths.push({
356
+ connectionId: connection.id,
357
+ sourceBlockId: connection.sourceBlockId,
358
+ targetBlockId: connection.targetBlockId,
359
+ points: points,
360
+ });
361
+ }
362
+ }
363
+ }
364
+
365
+ return connectionPaths;
366
+ }
367
+
368
+ export function prepareTreeLayout({nodes, links}: TreeLayoutRequest): TreeLayoutResult {
369
+ const blocks = prepareBlocks(nodes);
370
+ const connections = prepareConnections(links);
371
+ const layout = calculateTreeLayout(blocks, connections);
372
+ const edges = calculateTreeEdges(layout, connections);
373
+
374
+ return {
375
+ layout,
376
+ edges,
377
+ };
378
+ }
@@ -0,0 +1,56 @@
1
+ import type {TBlock, TBlockId, TConnection, TMultipointConnection} from '@gravity-ui/graph';
2
+
3
+ import type {GraphNode, Link, TopologyNodeDataStats} from '../../store/reducers/query/types';
4
+
5
+ // Extended block interface with additional properties
6
+ export interface ExtendedTBlock extends TBlock {
7
+ planNodeId?: number;
8
+ stats?: TopologyNodeDataStats[];
9
+ operators?: string[];
10
+ tables?: string[];
11
+ }
12
+ export type LinkType = 'arrow' | 'line';
13
+ export interface Data<TData = any> {
14
+ links: Link[];
15
+ nodes: GraphNode<TData>[];
16
+ }
17
+
18
+ // TreeLayout related types
19
+ export interface LayoutOptions {
20
+ horizontalSpacing?: number;
21
+ padding?: number;
22
+ verticalSpacing?: number;
23
+ }
24
+
25
+ export interface TreeNode {
26
+ id: string;
27
+ level: number;
28
+ block: any;
29
+ children: TreeNode[];
30
+ subtreeWidth: number;
31
+ x: number;
32
+ y: number;
33
+ }
34
+
35
+ export type EdgeResult = TMultipointConnection & {
36
+ connectionId: string | undefined;
37
+ points: Array<{x: number; y: number}>;
38
+ sourceBlockId: TBlockId;
39
+ targetBlockId: TBlockId;
40
+ };
41
+
42
+ export type TreeLayoutRequest = Pick<Data, 'links' | 'nodes'>;
43
+
44
+ export interface TreeLayoutResult {
45
+ layout: ExtendedTBlock[];
46
+ edges: EdgeResult[];
47
+ }
48
+
49
+ export type TreeLayoutWorkerResponse =
50
+ | {type: 'success'; result: TreeLayoutResult}
51
+ | {type: 'error'; error: string};
52
+
53
+ export type TreeConnection = TConnection & {
54
+ sourceBlockId: TBlockId;
55
+ targetBlockId: TBlockId;
56
+ };
@@ -0,0 +1,112 @@
1
+ import type {TBlock} from '@gravity-ui/graph';
2
+
3
+ import type {ExplainPlanNodeData} from '../../store/reducers/query/types';
4
+
5
+ import type {AbstractGraphColorsConfig} from './colorsConfig';
6
+ import type {Data, TreeConnection} from './types';
7
+
8
+ const BLOCK_TOP_PADDING = 8;
9
+ const BLOCK_LINE_HEIGHT = 16;
10
+ const BORDER_HEIGHT = 2;
11
+
12
+ const getBlockSize = (block: ExplainPlanNodeData) => {
13
+ const ONE_LINE_HEIGHT = BLOCK_TOP_PADDING * 2 + BLOCK_LINE_HEIGHT + BORDER_HEIGHT;
14
+ const operatorsLength = Math.max(block.operators?.length ?? 0, 1);
15
+ const tablesLength = block.tables?.length ?? 0;
16
+
17
+ switch (block.type) {
18
+ case 'query':
19
+ return {
20
+ width: 40,
21
+ height: 40,
22
+ };
23
+ case 'result':
24
+ return {
25
+ width: 112,
26
+ height: ONE_LINE_HEIGHT,
27
+ };
28
+ case 'stage':
29
+ return {
30
+ width: 248,
31
+ height:
32
+ BORDER_HEIGHT +
33
+ BLOCK_TOP_PADDING * 2 +
34
+ (operatorsLength + tablesLength) * BLOCK_LINE_HEIGHT,
35
+ };
36
+ case 'connection':
37
+ return {
38
+ width: 122,
39
+ height: ONE_LINE_HEIGHT,
40
+ };
41
+ case 'materialize':
42
+ return {
43
+ width: 190,
44
+ height: ONE_LINE_HEIGHT,
45
+ };
46
+ default:
47
+ return {
48
+ width: 100,
49
+ height: ONE_LINE_HEIGHT,
50
+ };
51
+ }
52
+ };
53
+
54
+ export const prepareBlocks = (nodes: Data['nodes']): TBlock[] => {
55
+ return nodes.map(({name: id, data: {id: planNodeId, name, type, ...rest}, data}) => ({
56
+ id,
57
+ is: type,
58
+ name,
59
+ planNodeId,
60
+ ...getBlockSize(data),
61
+ ...rest,
62
+ }));
63
+ };
64
+
65
+ export const prepareConnections = (links: Data['links']): TreeConnection[] => {
66
+ return links.map(({from, to}) => ({
67
+ id: `${from}:${to}`,
68
+ sourceBlockId: from,
69
+ targetBlockId: to,
70
+ }));
71
+ };
72
+
73
+ function calculateCurrentCustomPropertyValue<T extends string>(
74
+ colorSettings: Partial<Record<T, string>>,
75
+ computedStyle: CSSStyleDeclaration,
76
+ ): Partial<Record<T, string>> {
77
+ const result: Partial<Record<T, string>> = {};
78
+
79
+ for (const nestedKey in colorSettings) {
80
+ if (Object.prototype.hasOwnProperty.call(colorSettings, nestedKey)) {
81
+ const value = colorSettings[nestedKey];
82
+
83
+ if (value !== undefined) {
84
+ result[nestedKey] = value?.startsWith('var(')
85
+ ? computedStyle.getPropertyValue(value.substring(4, value.length - 1)).trim()
86
+ : value;
87
+ }
88
+ }
89
+ }
90
+
91
+ return result;
92
+ }
93
+
94
+ export function parseCustomPropertyValue<T extends AbstractGraphColorsConfig>(
95
+ colors: T,
96
+ block: HTMLElement = globalThis.document.body,
97
+ ): T {
98
+ const parsed: AbstractGraphColorsConfig = {};
99
+ const computedStyle = window.getComputedStyle(block);
100
+
101
+ for (const topKey in colors) {
102
+ if (Object.prototype.hasOwnProperty.call(colors, topKey)) {
103
+ const nestedObj = colors[topKey];
104
+
105
+ if (nestedObj) {
106
+ parsed[topKey] = calculateCurrentCustomPropertyValue(nestedObj, computedStyle);
107
+ }
108
+ }
109
+ }
110
+
111
+ return parsed as T;
112
+ }
@@ -1,11 +1,13 @@
1
1
  import type {
2
2
  TPQPartitionConfig,
3
+ TPQPartitionStrategy,
3
4
  TPQTabletConfig,
4
5
  TPersQueueGroupDescription,
5
6
  } from '../../../types/api/schema';
6
- import {EMeteringMode} from '../../../types/api/schema';
7
- import {HOUR_IN_SECONDS} from '../../../utils/constants';
7
+ import {EMeteringMode, EPQPartitionStrategyType} from '../../../types/api/schema';
8
+ import {EMPTY_DATA_PLACEHOLDER, HOUR_IN_SECONDS} from '../../../utils/constants';
8
9
  import {formatBps, formatBytes, formatNumber} from '../../../utils/dataFormatters/dataFormatters';
10
+ import i18n from '../i18n';
9
11
  import {createInfoFormatter} from '../utils';
10
12
 
11
13
  const EMeteringModeToNames: Record<EMeteringMode, string> = {
@@ -13,6 +15,13 @@ const EMeteringModeToNames: Record<EMeteringMode, string> = {
13
15
  [EMeteringMode.METERING_MODE_RESERVED_CAPACITY]: 'reserved-capacity',
14
16
  };
15
17
 
18
+ const EPQPartitionStrategyTypeToNames: Record<EPQPartitionStrategyType, string> = {
19
+ [EPQPartitionStrategyType.DISABLED]: i18n('value_autopartitioning-disabled'),
20
+ [EPQPartitionStrategyType.CAN_SPLIT]: i18n('value_autopartitioning-up'),
21
+ [EPQPartitionStrategyType.CAN_SPLIT_AND_MERGE]: i18n('value_autopartitioning-up-and-down'),
22
+ [EPQPartitionStrategyType.PAUSED]: i18n('value_autopartitioning-paused'),
23
+ };
24
+
16
25
  export const formatPQGroupItem = createInfoFormatter<TPersQueueGroupDescription>({
17
26
  values: {
18
27
  Partitions: (value) => formatNumber(value?.length || 0),
@@ -48,3 +57,22 @@ export const formatPQPartitionConfig = createInfoFormatter<TPQPartitionConfig>({
48
57
  WriteSpeedInBytesPerSecond: 'Partitions write speed',
49
58
  },
50
59
  });
60
+
61
+ const formatPartitionCount = (value?: number) =>
62
+ value === undefined ? EMPTY_DATA_PLACEHOLDER : formatNumber(value);
63
+
64
+ export const formatPQPartitionStrategy = createInfoFormatter<TPQPartitionStrategy>({
65
+ values: {
66
+ // Fall back to the raw enum value so a strategy introduced by a newer YDB
67
+ // version still renders the Autopartitioning row instead of being dropped.
68
+ PartitionStrategyType: (value) =>
69
+ value && (EPQPartitionStrategyTypeToNames[value] ?? value),
70
+ MinPartitionCount: formatPartitionCount,
71
+ MaxPartitionCount: formatPartitionCount,
72
+ },
73
+ labels: {
74
+ PartitionStrategyType: i18n('field_autopartitioning'),
75
+ MinPartitionCount: i18n('field_min-partitions-count'),
76
+ MaxPartitionCount: i18n('field_max-partitions-count'),
77
+ },
78
+ });
@@ -1,5 +1,12 @@
1
1
  {
2
2
  "common.created": "Created",
3
3
  "common.type": "Type",
4
- "no-data": "No data"
4
+ "no-data": "No data",
5
+ "field_autopartitioning": "Autopartitioning",
6
+ "field_min-partitions-count": "Min partitions count",
7
+ "field_max-partitions-count": "Max partitions count",
8
+ "value_autopartitioning-disabled": "Disabled",
9
+ "value_autopartitioning-up": "Up",
10
+ "value_autopartitioning-up-and-down": "Up and down",
11
+ "value_autopartitioning-paused": "Paused"
5
12
  }
@@ -2,7 +2,7 @@ import React from 'react';
2
2
 
3
3
  import {connect} from 'react-redux';
4
4
  import type {RedirectProps} from 'react-router-dom';
5
- import {Redirect, Route, Switch} from 'react-router-dom';
5
+ import {Redirect, Route, Switch, useLocation} from 'react-router-dom';
6
6
 
7
7
  import {Unauthenticated} from '../../components/Errors/401';
8
8
  import {AccessDenied} from '../../components/Errors/403';
@@ -41,6 +41,7 @@ import {
41
41
  TenantSlot,
42
42
  VDiskPageSlot,
43
43
  } from './appSlots';
44
+ import {shouldRedirectClusterRouteToRoot} from './clusterRouteGuard';
44
45
  import {getLegacyClusterTenantsRedirect, getLegacyTenantRedirect} from './legacyRedirects';
45
46
 
46
47
  import './App.scss';
@@ -148,6 +149,12 @@ export function Content(props: ContentProps) {
148
149
  const {singleClusterMode} = props;
149
150
 
150
151
  const slots = useSlots(props);
152
+ const currentLocation = useLocation();
153
+ const shouldRedirectToRoot = shouldRedirectClusterRouteToRoot({
154
+ singleClusterMode,
155
+ pathname: currentLocation.pathname,
156
+ search: currentLocation.search,
157
+ });
151
158
 
152
159
  const additionalRoutes = slots.get(RoutesSlot);
153
160
 
@@ -158,6 +165,7 @@ export function Content(props: ContentProps) {
158
165
  return (
159
166
  <Switch>
160
167
  {additionalRoutes?.rendered}
168
+ {shouldRedirectToRoot ? <Redirect to="/" /> : null}
161
169
  <Route>
162
170
  <Header />
163
171
  <Switch>
@@ -0,0 +1,24 @@
1
+ import {matchPath} from 'react-router-dom';
2
+
3
+ import routes from '../../routes';
4
+
5
+ export function shouldRedirectClusterRouteToRoot({
6
+ singleClusterMode,
7
+ pathname,
8
+ search,
9
+ }: {
10
+ singleClusterMode: boolean;
11
+ pathname: string;
12
+ search: string;
13
+ }) {
14
+ const clusterRouteMatch = matchPath(pathname, {
15
+ path: routes.cluster,
16
+ exact: false,
17
+ });
18
+
19
+ if (singleClusterMode || !clusterRouteMatch) {
20
+ return false;
21
+ }
22
+
23
+ return !new URLSearchParams(search).get('clusterName');
24
+ }