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.
- package/README.md +41 -0
- package/dist/components/AutoRefreshControl/i18n/index.d.ts +1 -1
- package/dist/components/FullNodeViewer/i18n/index.d.ts +1 -1
- package/dist/components/Graph/BlockComponents/ConnectionBlockComponent.d.ts +7 -0
- package/dist/components/Graph/BlockComponents/ConnectionBlockComponent.js +30 -0
- package/dist/components/Graph/BlockComponents/ConnectionBlockComponent.js.map +1 -0
- package/dist/components/Graph/BlockComponents/QueryBlockComponent.d.ts +5 -0
- package/dist/components/Graph/BlockComponents/QueryBlockComponent.js +5 -0
- package/dist/components/Graph/BlockComponents/QueryBlockComponent.js.map +1 -0
- package/dist/components/Graph/BlockComponents/ResultBlockComponent.d.ts +7 -0
- package/dist/components/Graph/BlockComponents/ResultBlockComponent.js +5 -0
- package/dist/components/Graph/BlockComponents/ResultBlockComponent.js.map +1 -0
- package/dist/components/Graph/BlockComponents/StageBlockComponent.d.ts +7 -0
- package/dist/components/Graph/BlockComponents/StageBlockComponent.js +17 -0
- package/dist/components/Graph/BlockComponents/StageBlockComponent.js.map +1 -0
- package/dist/components/Graph/FullViewportBackground.d.ts +2 -0
- package/dist/components/Graph/FullViewportBackground.js +67 -0
- package/dist/components/Graph/FullViewportBackground.js.map +1 -0
- package/dist/components/Graph/GraphControls.d.ts +6 -0
- package/dist/components/Graph/GraphControls.js +24 -0
- package/dist/components/Graph/GraphControls.js.map +1 -0
- package/dist/components/Graph/GravityGraph.d.ts +9 -0
- package/dist/components/Graph/GravityGraph.js +122 -0
- package/dist/components/Graph/GravityGraph.js.map +1 -0
- package/dist/components/Graph/GravityGraph.scss +131 -0
- package/dist/components/Graph/NonSelectableConnection.d.ts +9 -0
- package/dist/components/Graph/NonSelectableConnection.js +16 -0
- package/dist/components/Graph/NonSelectableConnection.js.map +1 -0
- package/dist/components/Graph/TooltipComponent.d.ts +8 -0
- package/dist/components/Graph/TooltipComponent.js +63 -0
- package/dist/components/Graph/TooltipComponent.js.map +1 -0
- package/dist/components/Graph/colorsConfig.d.ts +19 -0
- package/dist/components/Graph/colorsConfig.js +20 -0
- package/dist/components/Graph/colorsConfig.js.map +1 -0
- package/dist/components/Graph/i18n/en.json +5 -0
- package/dist/components/Graph/i18n/index.d.ts +2 -0
- package/dist/components/Graph/i18n/index.js +5 -0
- package/dist/components/Graph/i18n/index.js.map +1 -0
- package/dist/components/Graph/runTreeLayout.d.ts +12 -0
- package/dist/components/Graph/runTreeLayout.js +75 -0
- package/dist/components/Graph/runTreeLayout.js.map +1 -0
- package/dist/components/Graph/treeLayout.d.ts +2 -0
- package/dist/components/Graph/treeLayout.js +284 -0
- package/dist/components/Graph/treeLayout.js.map +1 -0
- package/dist/components/Graph/types.d.ts +52 -0
- package/dist/components/Graph/types.js +2 -0
- package/dist/components/Graph/types.js.map +1 -0
- package/dist/components/Graph/utils.d.ts +6 -0
- package/dist/components/Graph/utils.js +88 -0
- package/dist/components/Graph/utils.js.map +1 -0
- package/dist/components/InfoViewer/formatters/pqGroup.d.ts +5 -1
- package/dist/components/InfoViewer/formatters/pqGroup.js +24 -2
- package/dist/components/InfoViewer/formatters/pqGroup.js.map +1 -1
- package/dist/components/InfoViewer/i18n/en.json +8 -1
- package/dist/components/InfoViewer/i18n/index.d.ts +1 -1
- package/dist/components/PDiskInfo/i18n/index.d.ts +1 -1
- package/dist/components/VDiskInfo/i18n/index.d.ts +1 -1
- package/dist/containers/App/Content.js +9 -2
- package/dist/containers/App/Content.js.map +1 -1
- package/dist/containers/App/clusterRouteGuard.d.ts +5 -0
- package/dist/containers/App/clusterRouteGuard.js +13 -0
- package/dist/containers/App/clusterRouteGuard.js.map +1 -0
- package/dist/containers/Clusters/constants.d.ts +1 -1
- package/dist/containers/Nodes/i18n/index.d.ts +1 -1
- package/dist/containers/Operations/constants.d.ts +1 -1
- package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/i18n/index.d.ts +1 -1
- package/dist/containers/Tenant/Diagnostics/Overview/TransferInfo/Credentials.d.ts +1 -1
- package/dist/containers/Tenant/Diagnostics/Overview/utils/prepareTopicSchemaInfo.js +17 -3
- package/dist/containers/Tenant/Diagnostics/Overview/utils/prepareTopicSchemaInfo.js.map +1 -1
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/useTenantStorageNewData.d.ts +2 -2
- package/dist/containers/Tenant/Query/QueryResult/components/Graph/Graph.js +9 -4
- package/dist/containers/Tenant/Query/QueryResult/components/Graph/Graph.js.map +1 -1
- package/dist/containers/Tenant/Query/QueryResult/components/Graph/Graph.scss +2 -0
- package/dist/containers/Tenant/TenantNavigation/useTenantNavigation.d.ts +1 -1
- package/dist/graphLayout.worker.d.ts +1 -0
- package/dist/graphLayout.worker.js +17 -0
- package/dist/graphLayout.worker.js.map +1 -0
- package/dist/lib.d.ts +1 -1
- package/dist/lib.js.map +1 -1
- package/dist/store/reducers/query/parsers/preparePlanData.js.map +1 -1
- package/dist/store/reducers/query/types.d.ts +38 -1
- package/dist/store/reducers/settings/utils.d.ts +2 -2
- package/dist/types/api/schema/persQueueGroup.d.ts +16 -0
- package/dist/types/api/schema/persQueueGroup.js +7 -0
- package/dist/types/api/schema/persQueueGroup.js.map +1 -1
- package/dist/uiFactory/types.d.ts +3 -0
- package/dist/utils/disks/helpers.d.ts +2 -2
- package/dist/utils/hooks/useChangedQuerySettings.d.ts +2 -2
- package/dist/utils/hooks/useLastQueryExecutionSettings.d.ts +2 -2
- package/dist/utils/hooks/useQueryExecutionSettings.d.ts +2 -2
- package/dist/utils/prepareQueryExplain.d.ts +1 -2
- package/dist/utils/prepareQueryExplain.js +10 -2
- package/dist/utils/prepareQueryExplain.js.map +1 -1
- package/dist/utils/query.d.ts +1 -1
- package/package.json +3 -3
- package/src/components/Graph/BlockComponents/ConnectionBlockComponent.tsx +49 -0
- package/src/components/Graph/BlockComponents/QueryBlockComponent.tsx +7 -0
- package/src/components/Graph/BlockComponents/ResultBlockComponent.tsx +10 -0
- package/src/components/Graph/BlockComponents/StageBlockComponent.tsx +35 -0
- package/src/components/Graph/FullViewportBackground.ts +122 -0
- package/src/components/Graph/GraphControls.tsx +49 -0
- package/src/components/Graph/GravityGraph.scss +131 -0
- package/src/components/Graph/GravityGraph.tsx +174 -0
- package/src/components/Graph/NonSelectableConnection.tsx +14 -0
- package/src/components/Graph/TooltipComponent.tsx +114 -0
- package/src/components/Graph/colorsConfig.ts +21 -0
- package/src/components/Graph/i18n/en.json +5 -0
- package/src/components/Graph/i18n/index.ts +7 -0
- package/src/components/Graph/runTreeLayout.ts +105 -0
- package/src/components/Graph/treeLayout.ts +378 -0
- package/src/components/Graph/types.ts +56 -0
- package/src/components/Graph/utils.ts +112 -0
- package/src/components/InfoViewer/formatters/pqGroup.ts +30 -2
- package/src/components/InfoViewer/i18n/en.json +8 -1
- package/src/containers/App/Content.tsx +9 -1
- package/src/containers/App/clusterRouteGuard.ts +24 -0
- package/src/containers/Tenant/Diagnostics/Overview/utils/prepareTopicSchemaInfo.ts +23 -2
- package/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.scss +2 -0
- package/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.tsx +13 -7
- package/src/graphLayout.worker.ts +21 -0
- package/src/lib.ts +1 -0
- package/src/store/reducers/query/parsers/preparePlanData.ts +1 -3
- package/src/store/reducers/query/types.ts +42 -2
- package/src/types/api/schema/persQueueGroup.ts +19 -0
- package/src/uiFactory/types.ts +4 -0
- package/src/utils/prepareQueryExplain.ts +13 -5
- package/dist/components/Graph/Graph.d.ts +0 -12
- package/dist/components/Graph/Graph.js +0 -44
- package/dist/components/Graph/Graph.js.map +0 -1
- package/dist/components/Graph/colors.d.ts +0 -10
- package/dist/components/Graph/colors.js +0 -53
- package/dist/components/Graph/colors.js.map +0 -1
- package/src/components/Graph/Graph.tsx +0 -70
- 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
|
+
}
|