@truedat/dd 8.11.6 → 8.11.7

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 (43) hide show
  1. package/package.json +4 -3
  2. package/src/components/Graphs.js +30 -4
  3. package/src/components/LineageContextMenu.js +185 -58
  4. package/src/components/LineageDownloadButton.js +16 -6
  5. package/src/components/LineageEventsPanel.js +80 -27
  6. package/src/components/LineageEventsTable.js +7 -3
  7. package/src/components/LineageGraph.js +5144 -306
  8. package/src/components/LineageGraphToolbar.js +896 -0
  9. package/src/components/LineageInspectorPanel.js +748 -0
  10. package/src/components/LineageResourceNode.js +501 -0
  11. package/src/components/LineageRoutes.js +1 -2
  12. package/src/components/LineageSelectionPanel.js +28 -12
  13. package/src/components/StructureGraph.js +8 -1
  14. package/src/components/StructureGraphView.js +14 -0
  15. package/src/components/StructureImpact.js +2 -22
  16. package/src/components/StructureLineage.js +2 -19
  17. package/src/components/StructuresUploadEventsTable.js +0 -3
  18. package/src/components/__tests__/LineageContextMenu.spec.js +42 -0
  19. package/src/components/__tests__/LineageGraph.spec.js +896 -0
  20. package/src/components/__tests__/LineageGraphInteractions.spec.js +660 -0
  21. package/src/components/__tests__/LineageGraphLayout.spec.js +620 -0
  22. package/src/components/__tests__/LineageGraphToolbar.spec.js +366 -0
  23. package/src/components/__tests__/LineageHierarchy.spec.js +116 -0
  24. package/src/components/__tests__/LineageInspectorPanel.spec.js +357 -0
  25. package/src/components/__tests__/LineageResourceNode.spec.js +338 -0
  26. package/src/components/__tests__/LineageRoutes.spec.js +27 -9
  27. package/src/components/__tests__/__snapshots__/LineageDownloadButton.spec.js.snap +23 -3
  28. package/src/components/__tests__/__snapshots__/LineageForm.spec.js.snap +23 -3
  29. package/src/components/__tests__/lineageEventHelpers.spec.js +60 -0
  30. package/src/components/lineageEventHelpers.js +53 -17
  31. package/src/reducers/__tests__/lineageEvents.spec.js +164 -0
  32. package/src/reducers/__tests__/lineageQuery.spec.js +11 -0
  33. package/src/reducers/lineageEvents.js +68 -2
  34. package/src/reducers/lineageQuery.js +2 -6
  35. package/src/styles/LineageGraphColors.less +105 -0
  36. package/src/styles/LineageGraphCore.less +20 -0
  37. package/src/styles/LineageResourceNode.less +711 -0
  38. package/src/styles/lineageGraph.less +2973 -84
  39. package/src/utils/__tests__/lineageGraphIndex.spec.js +52 -0
  40. package/src/utils/__tests__/lineageLayout.spec.js +234 -0
  41. package/src/utils/__tests__/lineageTopology.spec.js +256 -0
  42. package/src/utils/lineageGraphIndex.js +296 -0
  43. package/src/utils/lineageLayout.js +1047 -0
@@ -0,0 +1,296 @@
1
+ const appendToMap = (map, key, value) => {
2
+ if (!key) return;
3
+
4
+ const values = map.get(key) || [];
5
+ values.push(value);
6
+ map.set(key, values);
7
+ };
8
+
9
+ const pathParts = (id) => {
10
+ try {
11
+ return decodeURIComponent(id).split(/[\/#]/).filter(Boolean);
12
+ } catch {
13
+ return id.split(/[\/#]/).filter(Boolean);
14
+ }
15
+ };
16
+
17
+ const glueScheme = (id) => {
18
+ if (/^https:\/\/glue\.[^/]+\.amazonaws\.com\/#\//.test(id)) return "https";
19
+ if (id.startsWith("glue:/Glue/")) return "glue";
20
+ return undefined;
21
+ };
22
+
23
+ export const prepareLineageTopology = (topology) => {
24
+ if (
25
+ !Array.isArray(topology?.nodes) ||
26
+ !Array.isArray(topology?.contains) ||
27
+ !Array.isArray(topology?.depends)
28
+ )
29
+ return undefined;
30
+
31
+ const nodeById = new Map(
32
+ topology.nodes.map((node) => [
33
+ String(node.id),
34
+ { ...node, id: String(node.id) },
35
+ ]),
36
+ );
37
+ const parentById = new Map();
38
+ const childrenById = new Map();
39
+ const ordered = [...nodeById.values()].sort((a, b) =>
40
+ a.id.localeCompare(b.id),
41
+ );
42
+ const contains = [...topology.contains].sort(
43
+ (a, b) =>
44
+ a.source.localeCompare(b.source) || a.target.localeCompare(b.target),
45
+ );
46
+ for (const { source, target } of contains) {
47
+ if (!nodeById.has(source) || !nodeById.has(target) || source === target)
48
+ continue;
49
+ if (parentById.has(target) && parentById.get(target) !== source)
50
+ return undefined;
51
+ const followsAncestors = (id) =>
52
+ !id || id === target
53
+ ? id === target
54
+ : followsAncestors(parentById.get(id));
55
+ if (followsAncestors(source)) return undefined;
56
+ parentById.set(target, source);
57
+ }
58
+ parentById.forEach((parent, child) =>
59
+ appendToMap(childrenById, parent, child),
60
+ );
61
+ const isGroup = (node) => String(node.class).toLowerCase() === "group";
62
+ const fieldIds = new Set(
63
+ ordered
64
+ .filter((node) => {
65
+ const parent = nodeById.get(parentById.get(node.id));
66
+ if (!parent || isGroup(node) || childrenById.has(node.id)) return false;
67
+ const type = String(node.entity_type || node.type || "").toLowerCase();
68
+ if (["column", "field", "attribute"].includes(type)) return true;
69
+ if (
70
+ [
71
+ "table",
72
+ "view",
73
+ "report",
74
+ "dashboard",
75
+ "database",
76
+ "schema",
77
+ "system",
78
+ "process",
79
+ ].includes(type)
80
+ )
81
+ return false;
82
+ const parts = pathParts(node.id);
83
+ const parentParts = pathParts(parent.id);
84
+ if (parts.length < 2) return false;
85
+ if (parts.at(-2) === parentParts.at(-1)) return true;
86
+ const siblings = childrenById.get(parent.id) || [];
87
+ const qualifier = parts.slice(0, -1).join("/");
88
+ return (
89
+ siblings.length > 1 &&
90
+ siblings.every((id) => {
91
+ const sibling = nodeById.get(id);
92
+ const siblingParts = pathParts(id);
93
+ return (
94
+ !isGroup(sibling) &&
95
+ !childrenById.has(id) &&
96
+ siblingParts.length > 1 &&
97
+ siblingParts.slice(0, -1).join("/") === qualifier
98
+ );
99
+ })
100
+ );
101
+ })
102
+ .map(({ id }) => id),
103
+ );
104
+
105
+ const aliasCandidates = new Map();
106
+ ordered.forEach((node) => {
107
+ if (!fieldIds.has(node.id) || !glueScheme(node.id)) return;
108
+ const key = JSON.stringify([
109
+ parentById.get(node.id),
110
+ (node.name || pathParts(node.id).at(-1)).trim(),
111
+ ]);
112
+ appendToMap(aliasCandidates, key, node);
113
+ });
114
+ const originalsByVisibleId = new Map(
115
+ ordered.map((node) => [node.id, [node]]),
116
+ );
117
+ const originalToVisibleId = new Map(ordered.map(({ id }) => [id, id]));
118
+ aliasCandidates.forEach((members) => {
119
+ if (
120
+ members.length !== 2 ||
121
+ new Set(members.map(({ id }) => glueScheme(id))).size !== 2
122
+ )
123
+ return;
124
+ const visibleId = members[0].id;
125
+ originalsByVisibleId.set(visibleId, members);
126
+ members.forEach(({ id }) => {
127
+ originalToVisibleId.set(id, visibleId);
128
+ if (id !== visibleId) originalsByVisibleId.delete(id);
129
+ });
130
+ });
131
+ const nodes = ordered
132
+ .filter(({ id }) => originalsByVisibleId.has(id))
133
+ .map((node) => {
134
+ const parentId = parentById.get(node.id);
135
+ const depthOf = (id) => (id ? 1 + depthOf(parentById.get(id)) : 0);
136
+ const z = depthOf(parentId);
137
+ return {
138
+ ...node,
139
+ name: node.name || pathParts(node.id).at(-1) || node.id,
140
+ parentId,
141
+ z,
142
+ topology: true,
143
+ field: fieldIds.has(node.id),
144
+ container: isGroup(node) || childrenById.has(node.id),
145
+ resourceContainer: (childrenById.get(node.id) || []).some((id) =>
146
+ fieldIds.has(id),
147
+ ),
148
+ originals: originalsByVisibleId.get(node.id),
149
+ };
150
+ });
151
+ const edges = topology.depends.map((edge, index) => ({
152
+ ...edge,
153
+ id: edge.id || `topology-depends-${index}`,
154
+ originalSource: edge.source,
155
+ originalTarget: edge.target,
156
+ source: originalToVisibleId.get(edge.source),
157
+ target: originalToVisibleId.get(edge.target),
158
+ normalizedDirection: true,
159
+ }));
160
+ if (edges.some(({ source, target }) => !source || !target)) return undefined;
161
+ return {
162
+ nodes,
163
+ edges,
164
+ nodeById,
165
+ parentById,
166
+ originalToVisibleId,
167
+ originalsByVisibleId,
168
+ };
169
+ };
170
+
171
+ export const createLineageGraphIndex = (nodes = [], edges = []) => {
172
+ const nodeById = new Map();
173
+ const parentById = new Map();
174
+ const childrenByParentId = new Map();
175
+ const incomingByNode = new Map();
176
+ const outgoingByNode = new Map();
177
+
178
+ nodes.forEach((node) => {
179
+ if (!node?.id) return;
180
+ nodeById.set(node.id, node);
181
+ if (node.parentId) {
182
+ parentById.set(node.id, node.parentId);
183
+ appendToMap(childrenByParentId, node.parentId, node.id);
184
+ }
185
+ });
186
+
187
+ edges.forEach((edge) => {
188
+ if (!edge?.id) return;
189
+ appendToMap(outgoingByNode, edge.source, edge);
190
+ appendToMap(incomingByNode, edge.target, edge);
191
+ });
192
+
193
+ const descendantCache = new Map();
194
+ const descendantIds = (rootId) => {
195
+ if (descendantCache.has(rootId)) return descendantCache.get(rootId);
196
+
197
+ const descendants = new Set();
198
+ const queue = [...(childrenByParentId.get(rootId) || [])];
199
+
200
+ const bfsCollect = ([head, ...tail]) => {
201
+ if (head === undefined) return;
202
+ if (descendants.has(head)) {
203
+ bfsCollect(tail);
204
+ return;
205
+ }
206
+ descendants.add(head);
207
+ bfsCollect([...tail, ...(childrenByParentId.get(head) || [])]);
208
+ };
209
+ bfsCollect(queue);
210
+
211
+ descendantCache.set(rootId, descendants);
212
+ return descendants;
213
+ };
214
+
215
+ return {
216
+ descendantIds,
217
+ incomingByNode,
218
+ nodeById,
219
+ outgoingByNode,
220
+ parentById,
221
+ };
222
+ };
223
+
224
+ const walkEdges = (
225
+ seedIds,
226
+ edgesByNode,
227
+ endpoint,
228
+ focusedNodeIds,
229
+ focusedEdgeIds,
230
+ ) => {
231
+ const visited = new Set(seedIds);
232
+ const queue = [...seedIds];
233
+
234
+ const bfsWalk = ([nodeId, ...remaining]) => {
235
+ if (nodeId === undefined) return;
236
+ (edgesByNode.get(nodeId) || []).forEach((edge) => {
237
+ focusedEdgeIds.add(edge.id);
238
+ focusedNodeIds.add(edge.source);
239
+ focusedNodeIds.add(edge.target);
240
+ const nextId = edge[endpoint];
241
+ if (!visited.has(nextId)) {
242
+ visited.add(nextId);
243
+ remaining.push(nextId);
244
+ }
245
+ });
246
+ bfsWalk(remaining);
247
+ };
248
+ bfsWalk(queue);
249
+ };
250
+
251
+ export const focusLineageFromSeeds = (seedIds, index) => {
252
+ const seeds = seedIds instanceof Set ? seedIds : new Set(seedIds || []);
253
+ const focusedNodeIds = new Set(seeds);
254
+ const focusedEdgeIds = new Set();
255
+
256
+ walkEdges(
257
+ seeds,
258
+ index.incomingByNode,
259
+ "source",
260
+ focusedNodeIds,
261
+ focusedEdgeIds,
262
+ );
263
+ walkEdges(
264
+ seeds,
265
+ index.outgoingByNode,
266
+ "target",
267
+ focusedNodeIds,
268
+ focusedEdgeIds,
269
+ );
270
+
271
+ return { focusedEdgeIds, focusedNodeIds };
272
+ };
273
+
274
+ export const projectLineageNodeIds = (
275
+ nodeIds,
276
+ parentById,
277
+ visibleNodeIds,
278
+ aliases = new Map(),
279
+ ) => {
280
+ const projected = new Set();
281
+
282
+ nodeIds.forEach((nodeId) => {
283
+ const findVisible = (id, seen) => {
284
+ if (!id || seen.has(id)) return;
285
+ const visibleId = aliases.get(id) || id;
286
+ if (visibleNodeIds.has(visibleId)) {
287
+ projected.add(visibleId);
288
+ return;
289
+ }
290
+ findVisible(parentById.get(id), new Set([...seen, id]));
291
+ };
292
+ findVisible(nodeId, new Set());
293
+ });
294
+
295
+ return projected;
296
+ };