ydb-embedded-ui 20.5.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 (112) 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/PDiskInfo/i18n/index.d.ts +1 -1
  52. package/dist/components/VDiskInfo/i18n/index.d.ts +1 -1
  53. package/dist/containers/Clusters/constants.d.ts +1 -1
  54. package/dist/containers/Nodes/i18n/index.d.ts +1 -1
  55. package/dist/containers/Operations/constants.d.ts +1 -1
  56. package/dist/containers/Tenant/Diagnostics/Overview/TransferInfo/Credentials.d.ts +1 -1
  57. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/useTenantStorageNewData.d.ts +2 -2
  58. package/dist/containers/Tenant/Query/QueryResult/components/Graph/Graph.js +9 -4
  59. package/dist/containers/Tenant/Query/QueryResult/components/Graph/Graph.js.map +1 -1
  60. package/dist/containers/Tenant/Query/QueryResult/components/Graph/Graph.scss +2 -0
  61. package/dist/containers/Tenant/TenantNavigation/useTenantNavigation.d.ts +1 -1
  62. package/dist/graphLayout.worker.d.ts +1 -0
  63. package/dist/graphLayout.worker.js +17 -0
  64. package/dist/graphLayout.worker.js.map +1 -0
  65. package/dist/lib.d.ts +1 -1
  66. package/dist/lib.js.map +1 -1
  67. package/dist/store/reducers/query/parsers/preparePlanData.js.map +1 -1
  68. package/dist/store/reducers/query/types.d.ts +38 -1
  69. package/dist/store/reducers/settings/utils.d.ts +2 -2
  70. package/dist/uiFactory/types.d.ts +3 -0
  71. package/dist/utils/disks/helpers.d.ts +2 -2
  72. package/dist/utils/hooks/useChangedQuerySettings.d.ts +2 -2
  73. package/dist/utils/hooks/useLastQueryExecutionSettings.d.ts +2 -2
  74. package/dist/utils/hooks/useQueryExecutionSettings.d.ts +2 -2
  75. package/dist/utils/prepareQueryExplain.d.ts +1 -2
  76. package/dist/utils/prepareQueryExplain.js +10 -2
  77. package/dist/utils/prepareQueryExplain.js.map +1 -1
  78. package/dist/utils/query.d.ts +1 -1
  79. package/package.json +2 -2
  80. package/src/components/Graph/BlockComponents/ConnectionBlockComponent.tsx +49 -0
  81. package/src/components/Graph/BlockComponents/QueryBlockComponent.tsx +7 -0
  82. package/src/components/Graph/BlockComponents/ResultBlockComponent.tsx +10 -0
  83. package/src/components/Graph/BlockComponents/StageBlockComponent.tsx +35 -0
  84. package/src/components/Graph/FullViewportBackground.ts +122 -0
  85. package/src/components/Graph/GraphControls.tsx +49 -0
  86. package/src/components/Graph/GravityGraph.scss +131 -0
  87. package/src/components/Graph/GravityGraph.tsx +174 -0
  88. package/src/components/Graph/NonSelectableConnection.tsx +14 -0
  89. package/src/components/Graph/TooltipComponent.tsx +114 -0
  90. package/src/components/Graph/colorsConfig.ts +21 -0
  91. package/src/components/Graph/i18n/en.json +5 -0
  92. package/src/components/Graph/i18n/index.ts +7 -0
  93. package/src/components/Graph/runTreeLayout.ts +105 -0
  94. package/src/components/Graph/treeLayout.ts +378 -0
  95. package/src/components/Graph/types.ts +56 -0
  96. package/src/components/Graph/utils.ts +112 -0
  97. package/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.scss +2 -0
  98. package/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.tsx +13 -7
  99. package/src/graphLayout.worker.ts +21 -0
  100. package/src/lib.ts +1 -0
  101. package/src/store/reducers/query/parsers/preparePlanData.ts +1 -3
  102. package/src/store/reducers/query/types.ts +42 -2
  103. package/src/uiFactory/types.ts +4 -0
  104. package/src/utils/prepareQueryExplain.ts +13 -5
  105. package/dist/components/Graph/Graph.d.ts +0 -12
  106. package/dist/components/Graph/Graph.js +0 -44
  107. package/dist/components/Graph/Graph.js.map +0 -1
  108. package/dist/components/Graph/colors.d.ts +0 -10
  109. package/dist/components/Graph/colors.js +0 -53
  110. package/dist/components/Graph/colors.js.map +0 -1
  111. package/src/components/Graph/Graph.tsx +0 -70
  112. package/src/components/Graph/colors.ts +0 -75
@@ -0,0 +1,105 @@
1
+ import {prepareTreeLayout} from './treeLayout';
2
+ import type {TreeLayoutRequest, TreeLayoutResult, TreeLayoutWorkerResponse} from './types';
3
+
4
+ type Schedule = (callback: () => void) => () => void;
5
+
6
+ interface RunTreeLayoutOptions {
7
+ request: TreeLayoutRequest;
8
+ createWorker: () => Worker;
9
+ calculateLayout?: () => TreeLayoutResult;
10
+ onResult: (result: TreeLayoutResult) => void;
11
+ onError: (error: Error) => void;
12
+ schedule?: Schedule;
13
+ }
14
+
15
+ const scheduleOnMainThread: Schedule = (callback) => {
16
+ const timeoutId = globalThis.setTimeout(callback, 0);
17
+
18
+ return () => globalThis.clearTimeout(timeoutId);
19
+ };
20
+
21
+ const normalizeError = (error: unknown) => {
22
+ return error instanceof Error ? error : new Error(String(error));
23
+ };
24
+
25
+ export function runTreeLayout({
26
+ request,
27
+ createWorker,
28
+ calculateLayout = () => prepareTreeLayout(request),
29
+ onResult,
30
+ onError,
31
+ schedule = scheduleOnMainThread,
32
+ }: RunTreeLayoutOptions) {
33
+ let worker: Worker | undefined;
34
+ let cancelFallback: (() => void) | undefined;
35
+ let cancelled = false;
36
+ let settled = false;
37
+
38
+ const terminateWorker = () => {
39
+ worker?.terminate();
40
+ worker = undefined;
41
+ };
42
+
43
+ const reportError = (error: unknown) => {
44
+ if (cancelled || settled) {
45
+ return;
46
+ }
47
+
48
+ settled = true;
49
+ terminateWorker();
50
+ onError(normalizeError(error));
51
+ };
52
+
53
+ const complete = (result: TreeLayoutResult) => {
54
+ if (cancelled || settled) {
55
+ return;
56
+ }
57
+
58
+ settled = true;
59
+ terminateWorker();
60
+ onResult(result);
61
+ };
62
+
63
+ const scheduleFallback = () => {
64
+ if (cancelled || settled || cancelFallback) {
65
+ return;
66
+ }
67
+
68
+ terminateWorker();
69
+ cancelFallback = schedule(() => {
70
+ cancelFallback = undefined;
71
+
72
+ if (cancelled || settled) {
73
+ return;
74
+ }
75
+
76
+ try {
77
+ complete(calculateLayout());
78
+ } catch (error) {
79
+ reportError(error);
80
+ }
81
+ });
82
+ };
83
+
84
+ try {
85
+ worker = createWorker();
86
+ worker.onmessage = ({data}: MessageEvent<TreeLayoutWorkerResponse>) => {
87
+ if (data.type === 'success') {
88
+ complete(data.result);
89
+ } else {
90
+ reportError(new Error(data.error));
91
+ }
92
+ };
93
+ worker.onerror = scheduleFallback;
94
+ worker.onmessageerror = scheduleFallback;
95
+ worker.postMessage(request);
96
+ } catch {
97
+ scheduleFallback();
98
+ }
99
+
100
+ return () => {
101
+ cancelled = true;
102
+ cancelFallback?.();
103
+ terminateWorker();
104
+ };
105
+ }
@@ -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,5 +1,7 @@
1
1
  .ydb-query-explain-graph {
2
2
  &__canvas-container {
3
+ position: relative;
4
+
3
5
  overflow-y: auto;
4
6
 
5
7
  width: 100%;
@@ -1,8 +1,6 @@
1
1
  import React from 'react';
2
2
 
3
- import type {Data} from '@gravity-ui/paranoid';
4
-
5
- import {YDBGraph} from '../../../../../../components/Graph/Graph';
3
+ import {GravityGraph} from '../../../../../../components/Graph/GravityGraph';
6
4
  import type {PreparedPlan} from '../../../../../../store/reducers/query/types';
7
5
  import {cn} from '../../../../../../utils/cn';
8
6
  import i18n from '../../i18n';
@@ -17,22 +15,30 @@ interface GraphProps {
17
15
  theme?: string;
18
16
  }
19
17
 
20
- function isValidGraphData(data: Partial<Data>): data is Data {
18
+ function isValidGraphData(data: Partial<PreparedPlan>) {
21
19
  return Boolean(data.links && data.nodes && data.nodes.length);
22
20
  }
23
21
 
24
22
  export function Graph({explain = {}, theme}: GraphProps) {
25
23
  const {links, nodes} = explain;
26
24
 
27
- const data = React.useMemo(() => ({links, nodes}), [links, nodes]);
25
+ const data = React.useMemo(() => ({links: links || [], nodes: nodes || []}), [links, nodes]);
26
+ const [failedData, setFailedData] = React.useState<typeof data>();
27
+ const handleLayoutError = React.useCallback(
28
+ (error: Error) => {
29
+ console.error(error);
30
+ setFailedData(data);
31
+ },
32
+ [data],
33
+ );
28
34
 
29
- if (!isValidGraphData(data)) {
35
+ if (!isValidGraphData(data) || failedData === data) {
30
36
  return <StubMessage message={i18n('description.graph-is-not-supported')} />;
31
37
  }
32
38
 
33
39
  return (
34
40
  <div className={b('canvas-container')}>
35
- <YDBGraph key={theme} data={data} />
41
+ <GravityGraph data={data} onError={handleLayoutError} theme={theme} />
36
42
  </div>
37
43
  );
38
44
  }