@truedat/dd 8.12.5 → 8.12.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 (102) hide show
  1. package/package.json +3 -3
  2. package/src/api/__tests__/queries.spec.js +17 -8
  3. package/src/api/queries.js +1 -1
  4. package/src/components/CatalogCustomViewCards.js +2 -2
  5. package/src/components/CatalogViewConfigs.js +1 -1
  6. package/src/components/EditDomainForm.js +1 -1
  7. package/src/components/GrantApprovalRuleDeleteButton.js +1 -1
  8. package/src/components/GrantApprovalRuleForm.js +2 -1
  9. package/src/components/GrantFilters.js +1 -1
  10. package/src/components/GrantFiltersLoader.js +1 -1
  11. package/src/components/GrantRemovalDirectButton.js +1 -1
  12. package/src/components/GrantRequest.js +1 -1
  13. package/src/components/GrantRequestCancel.js +1 -1
  14. package/src/components/GrantsPagination.js +1 -1
  15. package/src/components/LineageForm.js +1 -1
  16. package/src/components/LineageGraph.js +95 -3801
  17. package/src/components/PendingStructureNotesPagination.js +1 -1
  18. package/src/components/ReferenceDatasetActions.js +2 -1
  19. package/src/components/ReferenceDatasetFieldSelector.js +1 -1
  20. package/src/components/ReferenceDatasetForm.js +2 -1
  21. package/src/components/ReferenceDatasetRoutes.js +1 -1
  22. package/src/components/ReferenceDatasets.js +1 -1
  23. package/src/components/ReferenceDatasetsTable.js +1 -1
  24. package/src/components/StructureDeleteButton.js +1 -1
  25. package/src/components/StructureFields.js +1 -1
  26. package/src/components/StructureFieldsDropdown.js +1 -1
  27. package/src/components/StructureGrantCart.js +1 -1
  28. package/src/components/StructureGrantCartCheckout.js +1 -1
  29. package/src/components/StructureGrantCartUserSelector.js +1 -1
  30. package/src/components/StructureGrantDropdown.js +1 -1
  31. package/src/components/StructureGrantSummaryButton.js +1 -1
  32. package/src/components/StructureNav.js +1 -1
  33. package/src/components/StructureNoteActions.js +2 -1
  34. package/src/components/StructureNotesEdit.js +1 -1
  35. package/src/components/StructureStructureForm.js +1 -1
  36. package/src/components/StructureStructureLinks.js +2 -2
  37. package/src/components/StructureTabPane.js +1 -1
  38. package/src/components/StructureTags.js +1 -1
  39. package/src/components/StructureTypeForm.js +1 -1
  40. package/src/components/StructuresBulkUpdate.js +3 -5
  41. package/src/components/StructuresDomainsUploadOption.js +1 -1
  42. package/src/components/StructuresPagination.js +1 -1
  43. package/src/components/StructuresUploadOption.js +1 -1
  44. package/src/components/SystemCards.js +1 -1
  45. package/src/components/SystemForm.js +1 -1
  46. package/src/components/Tag.js +1 -1
  47. package/src/components/TagCards.js +2 -1
  48. package/src/components/TagForm.js +2 -1
  49. package/src/components/__tests__/GrantApprovalRuleConditions.spec.js +1 -1
  50. package/src/components/__tests__/GrantRemovalDirectButton.spec.js +2 -1
  51. package/src/components/__tests__/GrantRequestApprovalForm.spec.js +2 -1
  52. package/src/components/__tests__/GrantRequestBulkActions.spec.js +2 -1
  53. package/src/components/__tests__/GrantRequestBulkApprovalForm.spec.js +2 -4
  54. package/src/components/__tests__/GrantRequestBulkApprovalPopup.spec.js +1 -1
  55. package/src/components/__tests__/GrantRequestBulkRoleSelector.spec.js +2 -2
  56. package/src/components/__tests__/GrantRequestLoader.spec.js +1 -1
  57. package/src/components/__tests__/GrantRequestRow.spec.js +1 -1
  58. package/src/components/__tests__/GrantRequestSummary.spec.js +1 -1
  59. package/src/components/__tests__/GrantRequestsDownloadButton.spec.js +2 -1
  60. package/src/components/__tests__/GrantRequestsLabelResults.spec.js +1 -1
  61. package/src/components/__tests__/GrantRequestsLoader.spec.js +1 -1
  62. package/src/components/__tests__/ImplementationStructuresLoader.spec.js +1 -1
  63. package/src/components/__tests__/LineageDownloadButton.spec.js +2 -1
  64. package/src/components/__tests__/LineageGraph.spec.js +1 -1
  65. package/src/components/__tests__/LineageGraphLayout.spec.js +1 -1
  66. package/src/components/__tests__/LineageGroup.spec.js +2 -1
  67. package/src/components/__tests__/LineageHierarchy.spec.js +1 -1
  68. package/src/components/__tests__/LineageResource.spec.js +2 -1
  69. package/src/components/__tests__/LineageResourceNode.spec.js +1 -1
  70. package/src/components/__tests__/PendingStructureNotesLabelResults.spec.js +2 -1
  71. package/src/components/__tests__/PendingStructureNotesPagination.spec.js +1 -1
  72. package/src/components/__tests__/ProfileExecution.spec.js +2 -1
  73. package/src/components/__tests__/ProfileExecutionLoader.spec.js +2 -1
  74. package/src/components/__tests__/ProfileGroupLoader.spec.js +2 -1
  75. package/src/components/__tests__/ProfileGroupMessage.spec.js +2 -1
  76. package/src/components/__tests__/ReferenceDatasetTable.spec.js +1 -1
  77. package/src/components/__tests__/StructureDeleteButton.spec.js +1 -1
  78. package/src/components/__tests__/StructureEvents.spec.js +11 -1
  79. package/src/components/__tests__/StructureGrantRequestButton.spec.js +3 -3
  80. package/src/components/__tests__/StructureItemRoot.spec.js +2 -1
  81. package/src/components/__tests__/StructureLoader.spec.js +1 -1
  82. package/src/components/__tests__/StructureProfileButton.spec.js +3 -2
  83. package/src/components/__tests__/StructureRelationRow.spec.js +3 -3
  84. package/src/components/__tests__/StructureRelations.spec.js +2 -1
  85. package/src/components/__tests__/StructureRoles.spec.js +2 -1
  86. package/src/components/__tests__/StructureRow.spec.js +2 -1
  87. package/src/components/__tests__/StructureSelectorInputField.spec.js +2 -1
  88. package/src/components/__tests__/StructureTagForm.spec.js +2 -1
  89. package/src/components/__tests__/StructureTypeLoader.spec.js +2 -1
  90. package/src/components/__tests__/StructureTypesLoader.spec.js +2 -1
  91. package/src/components/__tests__/StructuresDownloadOption.spec.js +1 -1
  92. package/src/components/__tests__/StructuresEditableDownloadOption.spec.js +1 -1
  93. package/src/components/__tests__/StructuresLabelResults.spec.js +1 -1
  94. package/src/components/__tests__/StructuresLoader.spec.js +2 -1
  95. package/src/components/__tests__/StructuresUploadOption.spec.js +1 -1
  96. package/src/components/__tests__/SystemEditButton.spec.js +2 -1
  97. package/src/components/__tests__/SystemLoader.spec.js +2 -1
  98. package/src/components/__tests__/UserSearchFiltersLoader.spec.js +2 -1
  99. package/src/selectors/__tests__/getGrantRequestsSearchColumns.spec.js +1 -1
  100. package/src/styles/LineageGraph.less +3 -0
  101. package/src/utils/__tests__/lineageTopology.spec.js +1 -1
  102. package/src/utils/lineageGraph.js +3827 -0
@@ -1,14 +1,11 @@
1
- /* eslint-disable fp/no-let, fp/no-mutation -- Geometry packing and route planning use bounded local mutation. */
2
-
3
1
  import _ from "lodash/fp";
4
2
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
5
3
  import PropTypes from "prop-types";
6
4
  import { connect } from "react-redux";
7
5
  import { FormattedMessage, useIntl } from "react-intl";
8
- import { Graph } from "@truedat/core/components";
6
+ import Graph from "@truedat/core/components/Graph";
9
7
  import LineageGroupNode from "@truedat/core/components/graph/LineageGroupNode";
10
8
  import { EdgeHoverContext } from "@truedat/core/components/graph/ColoredEdge";
11
- import lineageNodeIcon from "@truedat/core/components/graph/lineageNodeIcon";
12
9
  import { cancelPoll } from "../routines";
13
10
  import { layoutVisibleLineageGraph } from "../utils/lineageLayout";
14
11
  import {
@@ -17,3812 +14,109 @@ import {
17
14
  focusLineageFromSeeds,
18
15
  projectLineageNodeIds,
19
16
  } from "../utils/lineageGraphIndex";
17
+ import {
18
+ normalizeId,
19
+ LINEAGE_FIT_VIEW_PADDING,
20
+ LINEAGE_MIN_ZOOM,
21
+ LINEAGE_MAX_ZOOM,
22
+ LINEAGE_WHEEL_ZOOM_SENSITIVITY,
23
+ LINEAGE_DEFAULT_EXPANSION_DEPTH,
24
+ LINEAGE_UNKNOWN_SYSTEM_TYPE_LABEL,
25
+ LINEAGE_UNKNOWN_SYSTEM_TYPE_COLOR,
26
+ hasPositionedLineageLayout,
27
+ nodeId,
28
+ positionedDuplicateNodeIds,
29
+ nodeAliasValues,
30
+ nodeAliasMap,
31
+ resolveNodeId,
32
+ hasResolvedLineageEndpoints,
33
+ lineageNodeContentWidth,
34
+ lineageNode,
35
+ lineageTopologyNodes,
36
+ preparePositionedLineageHierarchy,
37
+ uniqueNodes,
38
+ normalizedExpansionDepth,
39
+ lineageMaximumExpansionDepth,
40
+ lineageCollapsedGroupIdsForDepth,
41
+ expandLineageDescendantsUntilOversized,
42
+ lineageDefaultCollapsedGroupIds,
43
+ lineageShouldLoadExpanded,
44
+ lineageOversizedGroupIds,
45
+ collapseLineageNodes,
46
+ aggregateCollapsedEdges,
47
+ routeOpenGroupEdgesToSingleVisibleChild,
48
+ mergeRenderedNode,
49
+ lineagePositionedLayoutNodes,
50
+ applyFieldHandlesToEdges,
51
+ redesignedLineageEdges,
52
+ planLineageEdgeRoutes,
53
+ focusLineageNodes,
54
+ focusLineageEdges,
55
+ focusedLineageGraphForNodeSet,
56
+ lineageNodeMatchesToolbarFilters,
57
+ selectedNodeFromGraphNode,
58
+ lineageToolbarSearchResults,
59
+ lineageAncestorNodeIds,
60
+ lineageEdgeContext,
61
+ lineageEdge,
62
+ pathNodeCandidates,
63
+ spreadLegacyFieldIndexes,
64
+ legacyPathEdges,
65
+ } from "../utils/lineageGraph";
20
66
  import LineageResourceNode from "./LineageResourceNode";
21
67
  import LineageGraphToolbar, { visibleLegendKeys } from "./LineageGraphToolbar";
22
68
 
23
- const normalizeId = (value) =>
24
- value === undefined || value === null ? "" : `${value}`;
69
+ export {
70
+ DEFAULT_DEPTH,
71
+ hasPositionedLineageLayout,
72
+ isRestrictiveLineageEdge,
73
+ normalizeLineageEntityType,
74
+ lineageCatalogState,
75
+ lineageSystemTypeLabel,
76
+ lineageSystemTypeClassName,
77
+ lineageSystemTypeColor,
78
+ lineageSystemType,
79
+ lineageNodeContentWidth,
80
+ redesignedLineageNodeHeight,
81
+ lineageTopologyNodes,
82
+ preparePositionedLineageHierarchy,
83
+ lineageMaximumExpansionDepth,
84
+ lineageCollapsedGroupIdsForDepth,
85
+ expandLineageDescendants,
86
+ LINEAGE_OVERSIZED_CHILD_LIMIT,
87
+ lineageDirectChildCounts,
88
+ expandLineageDescendantsUntilOversized,
89
+ lineageDefaultCollapsedGroupIds,
90
+ LINEAGE_DEFAULT_EXPAND_MAX_GROUPS,
91
+ LINEAGE_DEFAULT_EXPAND_MAX_LEAF_NODES,
92
+ lineageShouldLoadExpanded,
93
+ lineageOversizedGroupIds,
94
+ collapseLineageNodes,
95
+ aggregateCollapsedEdges,
96
+ routeOpenGroupEdgesToSingleVisibleChild,
97
+ lineagePositionedLayoutNodes,
98
+ applyFieldHandlesToEdges,
99
+ bundleLineageEdges,
100
+ redesignedLineageEdges,
101
+ planLineageEdgeRoutes,
102
+ focusedLineageGraph,
103
+ focusLineageNodes,
104
+ focusLineageEdges,
105
+ focusedLineageGraphForNodeSet,
106
+ lineageNodeMatchesToolbarFilters,
107
+ selectedNodeFromGraphNode,
108
+ lineageToolbarSearchResults,
109
+ lineageEdge,
110
+ spreadLegacyFieldIndexes,
111
+ legacyPathEdge,
112
+ legacyPathEdges,
113
+ } from "../utils/lineageGraph";
25
114
 
26
- const NODE_FIELD_LIMIT = 5;
27
- const NODE_FIELD_LIMIT_REDESIGNED = 6;
28
- const NODE_FIELD_EXPANDED_LIMIT_REDESIGNED = 10;
29
- const NODE_WIDTH = 240;
30
- const NODE_WIDTH_REDESIGNED = 288;
31
- const LINEAGE_NODE_WIDTH_MIN = 240;
32
- const LINEAGE_NODE_WIDTH_MAX = 360;
33
- const LINEAGE_NODE_WIDTH_PADDING = 88;
34
- const LINEAGE_TEXT_WIDTH_FACTOR = 6.5;
35
- const NODE_BASE_HEIGHT = 68;
36
- const NODE_BASE_HEIGHT_REDESIGNED = 42;
37
- const NODE_CONTEXT_HEIGHT = 30;
38
- const NODE_FIELD_HEIGHT = 24;
39
- const NODE_FIELD_HEIGHT_REDESIGNED = 36;
40
- const NODE_MORE_HEIGHT = 18;
41
- const NODE_MORE_HEIGHT_REDESIGNED = 32;
42
- const LINEAGE_COMPACT_MIN_WIDTH = 200;
43
- const LINEAGE_COMPACT_MIN_HEIGHT = 34;
44
- const LINEAGE_EDGE_COLOR = "var(--td-graph-edge-stroke, #7b8798)";
45
- const LINEAGE_EDGE_COLOR_BY_TYPE = {
46
- impact: "#2563eb",
47
- lineage: "#ed5c17",
48
- };
49
- const lineageEdgeColor = (graphType) =>
50
- LINEAGE_EDGE_COLOR_BY_TYPE[graphType] || LINEAGE_EDGE_COLOR;
51
- const LINEAGE_FIT_VIEW_PADDING = 0.32;
52
- const LINEAGE_MIN_ZOOM = 0.05;
53
- const LINEAGE_MAX_ZOOM = 1.25;
54
- const LINEAGE_WHEEL_ZOOM_SENSITIVITY = 0.0015;
55
- const LINEAGE_SEARCH_RESULT_LIMIT = 50;
56
- const LINEAGE_DEFAULT_EXPANSION_DEPTH = 4;
57
- export const DEFAULT_DEPTH = LINEAGE_DEFAULT_EXPANSION_DEPTH;
58
- const LINEAGE_UNKNOWN_SYSTEM_TYPE_LABEL = "Without System Group";
59
- const LINEAGE_UNKNOWN_SYSTEM_TYPE_COLOR = {
60
- accent: "#334155",
61
- accentStrong: "#1f2937",
62
- accentSoft: "rgba(51, 65, 85, 0.16)",
63
- border: "#64748b",
64
- bg: "rgba(241, 245, 249, 0.78)",
65
- text: "#1f2937",
66
- shadow: "rgba(15, 23, 42, 0.18)",
67
- };
68
- const LINEAGE_SYSTEM_TYPE_PALETTE = [
69
- ["#1f6feb", "#1d4ed8", "#16263a", "rgba(31, 111, 235, 0.14)"],
70
- ["#168657", "#15803d", "#bbf7d0", "rgba(22, 134, 87, 0.14)"],
71
- ["#b45309", "#9a3412", "#fed7aa", "rgba(180, 83, 9, 0.14)"],
72
- ["#6d5bd0", "#5b46be", "#ddd6fe", "rgba(109, 91, 208, 0.14)"],
73
- ["#0f8f8c", "#0f766e", "#99f6e4", "rgba(15, 143, 140, 0.14)"],
74
- ["#be123c", "#9f1239", "#fecdd3", "rgba(190, 18, 60, 0.14)"],
75
- ["#7c3aed", "#6d28d9", "#ddd6fe", "rgba(124, 58, 237, 0.14)"],
76
- ["#0369a1", "#075985", "#bae6fd", "rgba(3, 105, 161, 0.14)"],
77
- ["#047857", "#065f46", "#a7f3d0", "rgba(4, 120, 87, 0.14)"],
78
- ["#c2410c", "#9a3412", "#fed7aa", "rgba(194, 65, 12, 0.14)"],
79
- ["#4f46e5", "#4338ca", "#c7d2fe", "rgba(79, 70, 229, 0.14)"],
80
- ["#0e7490", "#155e75", "#a5f3fc", "rgba(14, 116, 144, 0.14)"],
81
- ];
82
- const LINEAGE_COLUMN_HEADER_HEIGHT = 42;
83
- const POSITIONED_GROUP_PADDING_X = 16;
84
- const POSITIONED_GROUP_PADDING_BOTTOM = 12;
85
- const POSITIONED_GROUP_LABEL_GUTTER = 58;
86
- const POSITIONED_SIBLING_GAP = 14;
87
- const POSITIONED_LEAF_SIBLING_GAP = 6;
88
- const POSITIONED_BRANCH_SIBLING_GAP = 40;
89
- const LINEAGE_GROUP_Z_INDEX = 10;
90
- const LINEAGE_EDGE_Z_INDEX = 100;
91
- const LINEAGE_RESOURCE_Z_INDEX = 200;
92
- const LINEAGE_DIRECT_NODE_Z_INDEX = 250;
93
- const LINEAGE_SELECTED_NODE_Z_INDEX = 300;
94
- const LINEAGE_FOCUSED_EDGE_Z_INDEX = 350;
95
- const LINEAGE_ROUTE_CLEARANCE = 24;
96
- const LINEAGE_ROUTE_OBSTACLE_PADDING = 10;
97
- const LINEAGE_ROUTE_LANE_GAP = 8;
98
- const LINEAGE_ROUTE_MAX_LANE_ATTEMPTS = 12;
99
- const COLLAPSED_GROUP_WIDTH = 240;
100
- const COLLAPSED_GROUP_HEIGHT = 82;
101
- const PATH_NUMBER_PATTERN = /-?\d+(?:\.\d+)?/g;
102
- const PATH_SEGMENT_PATTERN = /[Mm][^Mm]*/g;
103
- const ROOT_GROUP_ID = "@@ROOT";
104
115
  const LINEAGE_NODE_TYPES = {
105
116
  lineageGroup: LineageGroupNode,
106
117
  lineageResource: LineageResourceNode,
107
118
  };
108
119
 
109
- const toNumber = (value) => {
110
- const number = Number(value);
111
-
112
- return Number.isFinite(number) ? number : undefined;
113
- };
114
-
115
- const hasNodeGeometry = (node = {}) =>
116
- [node.x, node.y, node.w, node.h].every(
117
- (value) => toNumber(value) !== undefined,
118
- );
119
-
120
- const nodeGeometry = (node = {}) => ({
121
- x: toNumber(node.x) || 0,
122
- y: toNumber(node.y) || 0,
123
- w: toNumber(node.w) || NODE_WIDTH,
124
- h: toNumber(node.h) || NODE_BASE_HEIGHT,
125
- });
126
-
127
- const absoluteGeometry = ({ x, y, w, h }) => ({
128
- x,
129
- y,
130
- w,
131
- h,
132
- });
133
-
134
- const containsGeometry = (parent, child) => {
135
- const parentGeometry = nodeGeometry(parent);
136
- const childGeometry = nodeGeometry(child);
137
-
138
- return (
139
- childGeometry.x >= parentGeometry.x &&
140
- childGeometry.y >= parentGeometry.y &&
141
- childGeometry.x + childGeometry.w <= parentGeometry.x + parentGeometry.w &&
142
- childGeometry.y + childGeometry.h <= parentGeometry.y + parentGeometry.h
143
- );
144
- };
145
-
146
- const geometryArea = (node) => {
147
- const { w, h } = nodeGeometry(node);
148
-
149
- return w * h;
150
- };
151
-
152
- const nodeDepth = (node = {}) =>
153
- toNumber(node.z) || toNumber(node.data?.z) || 0;
154
-
155
- const canBePositionedParent = (group, node) =>
156
- group?.id !== node?.id &&
157
- hasNodeGeometry(group) &&
158
- (node.z === undefined || nodeDepth(group) < nodeDepth(node));
159
-
160
- const anchoredParentGeometry = (parent, child) => {
161
- const parentGeometry = nodeGeometry(parent);
162
- const childGeometry = nodeGeometry(child);
163
-
164
- return (
165
- childGeometry.x >= parentGeometry.x &&
166
- childGeometry.y >= parentGeometry.y &&
167
- childGeometry.x <= parentGeometry.x + parentGeometry.w &&
168
- childGeometry.y <= parentGeometry.y + parentGeometry.h &&
169
- geometryArea(parent) > geometryArea(child)
170
- );
171
- };
172
-
173
- const positionedParentSort = (groupA, groupB) => {
174
- const zDiff = nodeDepth(groupB) - nodeDepth(groupA);
175
-
176
- if (zDiff !== 0) return zDiff;
177
-
178
- return geometryArea(groupA) - geometryArea(groupB);
179
- };
180
-
181
- const parentGroupFor = (groups = [], node) =>
182
- groups.reduce((parent, group) => {
183
- if (
184
- !canBePositionedParent(group, node) ||
185
- !(containsGeometry(group, node) || anchoredParentGeometry(group, node))
186
- ) {
187
- return parent;
188
- }
189
-
190
- return !parent || positionedParentSort(group, parent) < 0 ? group : parent;
191
- }, undefined);
192
-
193
- export const hasPositionedLineageLayout = (groups = [], resources = []) => {
194
- const nodes = [...groups, ...resources];
195
-
196
- if (_.isEmpty(nodes)) return false;
197
- return nodes.some(hasNodeGeometry);
198
- };
199
-
200
- const nodeId = ({ id, external_id, name }) =>
201
- normalizeId(external_id || id || name);
202
-
203
- const positionedNodeInstanceKey = (node = {}) => {
204
- const { x, y, w, h } = nodeGeometry(node);
205
-
206
- return [x, y, w, h, nodeDepth(node)].join(":");
207
- };
208
-
209
- const positionedDuplicateNodeIds = (groups = [], resources = []) => {
210
- const counts = [...groups, ...resources].reduce((nodeCounts, node) => {
211
- if (!hasNodeGeometry(node)) return nodeCounts;
212
-
213
- const id = nodeId(node);
214
-
215
- if (!id) return nodeCounts;
216
-
217
- nodeCounts.set(id, (nodeCounts.get(id) || 0) + 1);
218
-
219
- return nodeCounts;
220
- }, new Map());
221
-
222
- return new Set(
223
- Array.from(counts.entries())
224
- .filter(([, count]) => count > 1)
225
- .map(([id]) => id),
226
- );
227
- };
228
-
229
- const positionedNodeInstanceId = (node, duplicateNodeIds = new Set()) => {
230
- const id = nodeId(node);
231
-
232
- return duplicateNodeIds.has(id)
233
- ? `${id}@@${positionedNodeInstanceKey(node)}`
234
- : id;
235
- };
236
-
237
- const centeredGeometry = ({ x, y, w, h }, width, height) => ({
238
- x: x + (w - width) / 2,
239
- y: y + (h - height) / 2,
240
- w: width,
241
- h: height,
242
- });
243
-
244
- const resourceRenderedGeometry = (node) => {
245
- const geometry = nodeGeometry(node);
246
- const width = Math.max(geometry.w, LINEAGE_COMPACT_MIN_WIDTH);
247
- const height = Math.max(geometry.h, LINEAGE_COMPACT_MIN_HEIGHT);
248
-
249
- return centeredGeometry(geometry, width, height);
250
- };
251
-
252
- const geometriesOverlap = (geometryA, geometryB, gap = 0) =>
253
- geometryA.x < geometryB.x + geometryB.w + gap &&
254
- geometryA.x + geometryA.w + gap > geometryB.x &&
255
- geometryA.y < geometryB.y + geometryB.h + gap &&
256
- geometryA.y + geometryA.h + gap > geometryB.y;
257
-
258
- const sortGeometriesByPosition = (nodeA, nodeB) => {
259
- const yDiff = nodeA.geometry.y - nodeB.geometry.y;
260
-
261
- return yDiff || nodeA.geometry.x - nodeB.geometry.x;
262
- };
263
-
264
- const packSiblingGeometries = (
265
- childIds = [],
266
- geometryById,
267
- shiftGeometryTree,
268
- gap = POSITIONED_SIBLING_GAP,
269
- ) => {
270
- const placedGeometries = [];
271
- const siblings = childIds
272
- .map((id) => ({
273
- id,
274
- geometry: geometryById.get(id),
275
- }))
276
- .filter(({ geometry }) => geometry)
277
- .sort(sortGeometriesByPosition);
278
-
279
- siblings.forEach(({ id, geometry }) => {
280
- let nextY = geometry.y;
281
- let nextGeometry = geometry;
282
- let shifted = false;
283
-
284
- do {
285
- shifted = false;
286
-
287
- placedGeometries.forEach((placedGeometry) => {
288
- if (!geometriesOverlap(nextGeometry, placedGeometry, gap)) return;
289
-
290
- nextY = Math.max(nextY, placedGeometry.y + placedGeometry.h + gap);
291
- nextGeometry = { ...geometry, y: nextY };
292
- shifted = true;
293
- });
294
- } while (shifted);
295
-
296
- if (nextY !== geometry.y) {
297
- shiftGeometryTree(id, 0, nextY - geometry.y);
298
- }
299
-
300
- placedGeometries.push(geometryById.get(id) || nextGeometry);
301
- });
302
- };
303
-
304
- const stackSiblingGeometries = (
305
- childIds = [],
306
- geometryById,
307
- shiftGeometryTree,
308
- gap = POSITIONED_SIBLING_GAP,
309
- preserveOrder = false,
310
- ) => {
311
- const siblings = childIds
312
- .map((id) => ({
313
- id,
314
- geometry: geometryById.get(id),
315
- }))
316
- .filter(({ geometry }) => geometry);
317
-
318
- if (_.isEmpty(siblings)) return;
319
-
320
- if (!preserveOrder) siblings.sort(sortGeometriesByPosition);
321
-
322
- const columnX = Math.min(...siblings.map(({ geometry }) => geometry.x));
323
- let nextY = Math.min(...siblings.map(({ geometry }) => geometry.y));
324
-
325
- siblings.forEach(({ id }) => {
326
- const geometry = geometryById.get(id);
327
-
328
- if (!geometry) return;
329
-
330
- if (columnX !== geometry.x || nextY !== geometry.y) {
331
- shiftGeometryTree(id, columnX - geometry.x, nextY - geometry.y);
332
- }
333
-
334
- nextY += geometry.h + gap;
335
- });
336
- };
337
-
338
- const rowSiblingGeometries = (
339
- childIds = [],
340
- geometryById,
341
- shiftGeometryTree,
342
- preserveOrder = false,
343
- ) => {
344
- const siblings = childIds
345
- .map((id) => ({ id, geometry: geometryById.get(id) }))
346
- .filter(({ geometry }) => geometry);
347
-
348
- if (_.isEmpty(siblings)) return;
349
-
350
- if (!preserveOrder) siblings.sort(sortGeometriesByPosition);
351
-
352
- let nextX = Math.min(...siblings.map(({ geometry }) => geometry.x));
353
- const rowY = Math.min(...siblings.map(({ geometry }) => geometry.y));
354
-
355
- siblings.forEach(({ id }) => {
356
- const geometry = geometryById.get(id);
357
-
358
- if (!geometry) return;
359
-
360
- if (nextX !== geometry.x || rowY !== geometry.y) {
361
- shiftGeometryTree(id, nextX - geometry.x, rowY - geometry.y);
362
- }
363
-
364
- nextX += geometry.w + POSITIONED_BRANCH_SIBLING_GAP;
365
- });
366
- };
367
-
368
- const shouldRowSiblingGroups = (childNodes = []) =>
369
- childNodes.length > 1 &&
370
- childNodes.every((childNode) => childNode?.type === "lineageGroup");
371
-
372
- const shouldCompactVisibleGroupChildren = (node, childNodes = []) =>
373
- node.type === "lineageGroup" &&
374
- node.data?.variant === "absolute" &&
375
- (childNodes.some((childNode) => childNode?.data?.collapsed) ||
376
- childNodes.every((childNode) => childNode?.type !== "lineageGroup"));
377
-
378
- const buildPositionedGeometryMap = (
379
- groups = [],
380
- resources = [],
381
- duplicateNodeIds = positionedDuplicateNodeIds(groups, resources),
382
- resourceGeometry = resourceRenderedGeometry,
383
- parentByNode,
384
- ) => {
385
- const geometryById = new Map();
386
- const groupById = new Map();
387
- const childrenByGroupId = new Map();
388
- const rootResourceIds = [];
389
- const rootGroupIds = [];
390
-
391
- groups.forEach((group) => {
392
- if (!hasNodeGeometry(group)) return;
393
-
394
- const id = positionedNodeInstanceId(group, duplicateNodeIds);
395
-
396
- groupById.set(id, group);
397
- childrenByGroupId.set(id, []);
398
- });
399
-
400
- const addChild = (node, childId) => {
401
- const parentGroup = parentByNode.get(node);
402
-
403
- if (!parentGroup) return false;
404
-
405
- const parentId = positionedNodeInstanceId(parentGroup, duplicateNodeIds);
406
- const children = childrenByGroupId.get(parentId);
407
-
408
- if (children) children.push(childId);
409
- return Boolean(children);
410
- };
411
-
412
- const shiftGeometryTree = (id, deltaX, deltaY) => {
413
- const geometry = geometryById.get(id);
414
-
415
- if (geometry) {
416
- geometryById.set(id, {
417
- ...geometry,
418
- x: geometry.x + deltaX,
419
- y: geometry.y + deltaY,
420
- });
421
- }
422
-
423
- (childrenByGroupId.get(id) || [])
424
- .filter((childId) => childId !== id)
425
- .forEach((childId) => shiftGeometryTree(childId, deltaX, deltaY));
426
- };
427
-
428
- resources.forEach((resource) => {
429
- if (!hasNodeGeometry(resource)) return;
430
-
431
- const id = positionedNodeInstanceId(resource, duplicateNodeIds);
432
-
433
- geometryById.set(id, resourceGeometry(resource));
434
- if (!addChild(resource, id)) rootResourceIds.push(id);
435
- });
436
-
437
- groups.forEach((group) => {
438
- const id = positionedNodeInstanceId(group, duplicateNodeIds);
439
-
440
- if (!addChild(group, id)) rootGroupIds.push(id);
441
- });
442
-
443
- const groupGeometry = (id) => {
444
- if (geometryById.has(id)) return geometryById.get(id);
445
-
446
- const group = groupById.get(id);
447
- const rawGeometry = nodeGeometry(group);
448
- const childIds = (childrenByGroupId.get(id) || []).filter(
449
- (childId) => childId !== id,
450
- );
451
-
452
- childIds.forEach((childId) => {
453
- if (groupById.has(childId)) groupGeometry(childId);
454
- });
455
- if (
456
- childIds.length > 1 &&
457
- childIds.every((childId) => groupById.has(childId))
458
- ) {
459
- rowSiblingGeometries(childIds, geometryById, shiftGeometryTree);
460
- } else {
461
- stackSiblingGeometries(childIds, geometryById, shiftGeometryTree);
462
- }
463
-
464
- const children = childIds
465
- .map((childId) => geometryById.get(childId))
466
- .filter(Boolean);
467
-
468
- if (_.isEmpty(children)) {
469
- geometryById.set(id, rawGeometry);
470
- return rawGeometry;
471
- }
472
-
473
- const minX = Math.min(
474
- rawGeometry.x,
475
- ...children.map((child) => child.x - POSITIONED_GROUP_PADDING_X),
476
- );
477
- const minY = Math.min(
478
- rawGeometry.y,
479
- ...children.map((child) => child.y - POSITIONED_GROUP_LABEL_GUTTER),
480
- );
481
- const maxX = Math.max(
482
- ...children.map(
483
- (child) => child.x + child.w + POSITIONED_GROUP_PADDING_X,
484
- ),
485
- );
486
- const maxY = Math.max(
487
- ...children.map(
488
- (child) => child.y + child.h + POSITIONED_GROUP_PADDING_BOTTOM,
489
- ),
490
- );
491
- const effectiveGeometry = {
492
- x: minX,
493
- y: minY,
494
- w: Math.max(COLLAPSED_GROUP_WIDTH, maxX - minX),
495
- h: Math.max(COLLAPSED_GROUP_HEIGHT, maxY - minY),
496
- };
497
-
498
- geometryById.set(id, effectiveGeometry);
499
- return effectiveGeometry;
500
- };
501
-
502
- groups.forEach((group) => {
503
- groupGeometry(positionedNodeInstanceId(group, duplicateNodeIds));
504
- });
505
- packSiblingGeometries(
506
- [...rootGroupIds, ...rootResourceIds],
507
- geometryById,
508
- shiftGeometryTree,
509
- );
510
-
511
- return geometryById;
512
- };
513
-
514
- const nodeAliasValues = (node = {}) =>
515
- [
516
- node.external_id,
517
- node.id,
518
- node.name,
519
- node.structure_id,
520
- node.data_structure_id,
521
- ]
522
- .map(normalizeId)
523
- .filter((value) => !_.isEmpty(value));
524
-
525
- const nodeAliasMap = (nodes = []) =>
526
- nodes.reduce((aliases, node) => {
527
- const resolvedId = nodeId(node);
528
-
529
- nodeAliasValues(node).forEach((alias) => {
530
- if (!aliases.has(alias)) aliases.set(alias, resolvedId);
531
- });
532
-
533
- return aliases;
534
- }, new Map());
535
-
536
- const resolveNodeId = (aliases) => (value) => {
537
- const id = normalizeId(value);
538
-
539
- return aliases.get(id) || id;
540
- };
541
-
542
- const endpointId = (value) => {
543
- if (value === undefined || value === null) return "";
544
- if (typeof value === "object") return nodeId(value);
545
-
546
- return normalizeId(value);
547
- };
548
-
549
- const firstEndpoint = (...values) =>
550
- values.find((value) => !_.isEmpty(endpointId(value)));
551
-
552
- const lineageEndpoint = (edge = {}, endpoint) => {
553
- if (endpoint === "start") {
554
- return firstEndpoint(
555
- edge.inicio_nodo,
556
- edge.inicioNodo,
557
- edge.inicio_nodo_id,
558
- edge.inicioNodoId,
559
- edge.start_node,
560
- edge.startNode,
561
- edge.start_node_id,
562
- edge.startNodeId,
563
- edge.source_inicio_nodo,
564
- edge.sourceInicioNodo,
565
- edge.source_start_node,
566
- edge.sourceStartNode,
567
- edge.v1,
568
- edge.source?.inicio_nodo,
569
- edge.source?.inicioNodo,
570
- edge.source?.inicio_nodo_id,
571
- edge.source?.inicioNodoId,
572
- edge.source?.start_node,
573
- edge.source?.startNode,
574
- edge.source?.start_node_id,
575
- edge.source?.startNodeId,
576
- edge.source,
577
- edge.source_id,
578
- edge.sourceId,
579
- edge.from,
580
- );
581
- }
582
-
583
- return firstEndpoint(
584
- edge.fin_nodo,
585
- edge.finNodo,
586
- edge.fin_nodo_id,
587
- edge.finNodoId,
588
- edge.end_node,
589
- edge.endNode,
590
- edge.end_node_id,
591
- edge.endNodeId,
592
- edge.target_fin_nodo,
593
- edge.targetFinNodo,
594
- edge.target_end_node,
595
- edge.targetEndNode,
596
- edge.v2,
597
- edge.target?.fin_nodo,
598
- edge.target?.finNodo,
599
- edge.target?.fin_nodo_id,
600
- edge.target?.finNodoId,
601
- edge.target?.end_node,
602
- edge.target?.endNode,
603
- edge.target?.end_node_id,
604
- edge.target?.endNodeId,
605
- edge.target,
606
- edge.target_id,
607
- edge.targetId,
608
- edge.to,
609
- );
610
- };
611
-
612
- const resolveLineageEndpoint = (edge, endpoint, resolveId) => {
613
- const id = endpointId(lineageEndpoint(edge, endpoint));
614
-
615
- return id ? resolveId(id) : "";
616
- };
617
-
618
- const hasResolvedLineageEndpoints = (edge, resolveId, nodeById) => {
619
- const source = resolveLineageEndpoint(edge, "start", resolveId);
620
- const target = resolveLineageEndpoint(edge, "end", resolveId);
621
-
622
- return Boolean(
623
- source && target && nodeById.has(source) && nodeById.has(target),
624
- );
625
- };
626
-
627
- const truthyRelationValue = (value) => {
628
- if (value === true) return true;
629
- if (typeof value === "number") return value > 0;
630
- if (typeof value === "string") {
631
- const normalized = value.trim().toLowerCase();
632
-
633
- if (
634
- [
635
- "false",
636
- "0",
637
- "no",
638
- "non_restrictive",
639
- "non-restrictive",
640
- "non restrictive",
641
- "no restrictiva",
642
- "no restrictivo",
643
- "unrestricted",
644
- ].includes(normalized)
645
- ) {
646
- return false;
647
- }
648
-
649
- return (
650
- [
651
- "true",
652
- "1",
653
- "yes",
654
- "si",
655
- "sí",
656
- "restrictive",
657
- "restricted",
658
- "restrictiva",
659
- "restrictivo",
660
- ].includes(normalized) || normalized.includes("restrict")
661
- );
662
- }
663
-
664
- if (value && typeof value === "object") {
665
- return [
666
- value.restrictive,
667
- value.is_restrictive,
668
- value.isRestrictive,
669
- value.restrictive_relation,
670
- value.restrictiveRelation,
671
- value.name,
672
- value.label,
673
- value.type,
674
- ].some(truthyRelationValue);
675
- }
676
-
677
- return false;
678
- };
679
-
680
- export const isRestrictiveLineageEdge = (edge = {}) =>
681
- [
682
- edge.restrictive,
683
- edge.is_restrictive,
684
- edge.isRestrictive,
685
- edge.restrictive_relation,
686
- edge.restrictiveRelation,
687
- edge.metadata?.restrictive,
688
- edge.metadata?.is_restrictive,
689
- edge.metadata?.isRestrictive,
690
- edge.metadata?.restrictive_relation,
691
- edge.metadata?.restrictiveRelation,
692
- edge.relation?.restrictive,
693
- edge.relation?.is_restrictive,
694
- edge.relation_type,
695
- edge.relationType,
696
- edge.metadata?.relation_type,
697
- edge.metadata?.relationType,
698
- ].some(truthyRelationValue);
699
-
700
- const nodeKind = (node, kind) =>
701
- kind === "group" && _.toLower(node.type) === "session" ? "process" : kind;
702
-
703
- const LINEAGE_ENTITY_ICON = {
704
- column: "grip vertical",
705
- database: "database",
706
- field: "copy",
707
- process: "sync alternate",
708
- report: "tasks",
709
- schema: "th",
710
- system: "sitemap",
711
- table: "table",
712
- view: "eye",
713
- };
714
-
715
- export const normalizeLineageEntityType = (value, fallback = "table") => {
716
- const normalized = compactText(value)
717
- .trim()
718
- .toLowerCase()
719
- .replace(/^lineage[\s_-]*/, "")
720
- .replace(/[\s_]+/g, "-");
721
- const aliases = {
722
- db: "database",
723
- "data-base": "database",
724
- dataset: "table",
725
- dashboard: "report",
726
- query: "view",
727
- session: "process",
728
- };
729
- const entityType = aliases[normalized] || normalized;
730
-
731
- return LINEAGE_ENTITY_ICON[entityType] ? entityType : fallback;
732
- };
733
-
734
- const lineageEntityPresentation = (node, kind) => {
735
- const fallback =
736
- kind === "process" ? "process" : kind === "group" ? "system" : "table";
737
- const rawType =
738
- node.entity_type || node.entityType || node.type || node.metadata?.type;
739
- const entityType = node.topology
740
- ? normalizeLineageEntityType(rawType, rawType || "")
741
- : normalizeLineageEntityType(rawType, fallback);
742
-
743
- return {
744
- entityType,
745
- iconName: LINEAGE_ENTITY_ICON[entityType],
746
- isSystem: normalizeLineageEntityType(rawType) === "system",
747
- };
748
- };
749
-
750
- export const lineageCatalogState = (node = {}) => {
751
- const records = [node, ...(node.originals || [])];
752
- const isDeleted = (record) =>
753
- [record, record.metadata || {}].some(
754
- (value) =>
755
- Boolean(value.deleted_at || value.deletion_date) ||
756
- [value.deleted, value.is_deleted, value.isDeleted].some(
757
- (flag) => flag === true || flag === "true",
758
- ) ||
759
- [value.status, value.state].some(
760
- (status) =>
761
- typeof status === "string" && status.toLowerCase() === "deleted",
762
- ),
763
- );
764
- const hasStructure = (record) =>
765
- [record.structure_id, record.data_structure_id].some(
766
- (id) => id !== undefined && id !== null && id !== "",
767
- );
768
-
769
- if (records.some((record) => hasStructure(record) && !isDeleted(record)))
770
- return "present";
771
- if (records.some(isDeleted)) return "deleted";
772
- if (
773
- records.some(
774
- (record) =>
775
- record.structure_id === null || record.data_structure_id === null,
776
- )
777
- )
778
- return "absent";
779
- return "unknown";
780
- };
781
-
782
- const compactText = (value) => {
783
- if (value === undefined || value === null) return "";
784
- if (typeof value === "object") {
785
- return compactText(
786
- value.name || value.label || value.value || value.external_id || value.id,
787
- );
788
- }
789
-
790
- return `${value}`;
791
- };
792
-
793
- const firstText = (...values) =>
794
- values.map(compactText).find((value) => !_.isEmpty(value));
795
-
796
- const normalizeFieldText = (value) => compactText(value).trim().toLowerCase();
797
-
798
- const lineageFieldName = (field) =>
799
- normalizeFieldText(
800
- typeof field === "string"
801
- ? field
802
- : field?.name ||
803
- field?.label ||
804
- field?.field ||
805
- field?.external_id ||
806
- field?.id,
807
- );
808
-
809
- const fieldHandleId = (type, index) => `field-${type}-${index}`;
810
-
811
- const normalizeSystemTypeText = (value) => {
812
- const text = compactText(value).trim();
813
- const normalized = text
814
- .toLowerCase()
815
- .replace(/[\s_]+/g, " ")
816
- .trim();
817
- if (
818
- !normalized ||
819
- normalized === "without system group" ||
820
- normalized === "without system" ||
821
- normalized === "without group" ||
822
- normalized === "sin sistema" ||
823
- normalized === "sin grupo" ||
824
- normalized === "sin grupo de sistema" ||
825
- normalized === "sin grupo sistema" ||
826
- normalized.startsWith("without system") ||
827
- normalized.startsWith("sin grupo") ||
828
- normalized === "unknown" ||
829
- normalized === "desconocido" ||
830
- normalized === "n/a" ||
831
- normalized === "na" ||
832
- normalized === "none" ||
833
- normalized === "null" ||
834
- normalized === "-"
835
- )
836
- return "";
837
- return text;
838
- };
839
-
840
- const nestedSystemGroupValue = (value) => {
841
- if (!value || typeof value !== "object") return "";
842
-
843
- return firstText(
844
- value.system_group,
845
- value.systemGroup,
846
- value.system_group_name,
847
- value.systemGroupName,
848
- value.system_group_id,
849
- value.systemGroupId,
850
- value.group?.name,
851
- value.group?.label,
852
- value.group?.value,
853
- value.group,
854
- value.value,
855
- value.name,
856
- value.label,
857
- value.id,
858
- );
859
- };
860
-
861
- export const lineageSystemTypeLabel = (node = {}) =>
862
- normalizeSystemTypeText(
863
- firstText(
864
- node.system_group,
865
- node.systemGroup,
866
- node.system_group_name,
867
- node.systemGroupName,
868
- node.system_group_id,
869
- node.systemGroupId,
870
- node.dynamic_content?.system_group?.value,
871
- node.dynamic_content?.systemGroup?.value,
872
- node.dynamicContent?.system_group?.value,
873
- node.dynamicContent?.systemGroup?.value,
874
- node.metadata?.system_group,
875
- node.metadata?.systemGroup,
876
- node.metadata?.system_group_name,
877
- node.metadata?.systemGroupName,
878
- node.metadata?.dynamic_content?.system_group?.value,
879
- node.metadata?.dynamic_content?.systemGroup?.value,
880
- node.metadata?.dynamicContent?.system_group?.value,
881
- node.metadata?.dynamicContent?.systemGroup?.value,
882
- nestedSystemGroupValue(node.system_group),
883
- nestedSystemGroupValue(node.systemGroup),
884
- nestedSystemGroupValue(node.metadata?.system_group),
885
- nestedSystemGroupValue(node.metadata?.systemGroup),
886
- nestedSystemGroupValue(node.system),
887
- nestedSystemGroupValue(node.metadata?.system),
888
- nestedSystemGroupValue(node.source_system),
889
- nestedSystemGroupValue(node.sourceSystem),
890
- ),
891
- );
892
-
893
- const systemTypeKey = (label) =>
894
- normalizeSystemTypeText(label).toLowerCase().replace(/\s+/g, " ");
895
-
896
- export const lineageSystemTypeClassName = (label) =>
897
- systemTypeKey(label)
898
- .replace(/[^a-z0-9]+/g, "-")
899
- .replace(/^-|-$/g, "") || "without-system-type";
900
-
901
- const systemTypeHash = (label) =>
902
- systemTypeKey(label)
903
- .split("")
904
- .reduce((hash, char) => (hash * 31 + char.charCodeAt(0)) >>> 0, 0);
905
-
906
- export const lineageSystemTypeColor = (label) => {
907
- if (_.isEmpty(systemTypeKey(label))) return LINEAGE_UNKNOWN_SYSTEM_TYPE_COLOR;
908
-
909
- const [accent, accentStrong, border, accentSoft] =
910
- LINEAGE_SYSTEM_TYPE_PALETTE[
911
- systemTypeHash(label) % LINEAGE_SYSTEM_TYPE_PALETTE.length
912
- ];
913
-
914
- return {
915
- accent,
916
- accentStrong,
917
- accentSoft,
918
- border,
919
- bg: accentSoft.replace(/0\.14\)$/, "0.7)"),
920
- text: accentStrong,
921
- shadow: accentSoft.replace(/0\.14\)$/, "0.18)"),
922
- };
923
- };
924
-
925
- export const lineageSystemType = (node = {}) => {
926
- const label = lineageSystemTypeLabel(node);
927
- const color = lineageSystemTypeColor(label);
928
-
929
- return {
930
- color,
931
- className: lineageSystemTypeClassName(label),
932
- key: systemTypeKey(label) || "__without_system_type__",
933
- label,
934
- missing: _.isEmpty(systemTypeKey(label)),
935
- };
936
- };
937
-
938
- const lineageSystemTypeWithParentFallback = (parentByNode, node = {}) => {
939
- const ownSystemType = lineageSystemType(node);
940
-
941
- if (!ownSystemType.missing) return ownSystemType;
942
-
943
- const visitedIds = new Set([nodeId(node)]);
944
- let parentGroup = parentByNode.get(node);
945
-
946
- while (parentGroup) {
947
- const parentId = nodeId(parentGroup);
948
-
949
- if (visitedIds.has(parentId)) return ownSystemType;
950
- visitedIds.add(parentId);
951
-
952
- const parentSystemType = lineageSystemType(parentGroup);
953
-
954
- if (!parentSystemType.missing) return parentSystemType;
955
-
956
- parentGroup = parentByNode.get(parentGroup);
957
- }
958
-
959
- return ownSystemType;
960
- };
961
-
962
- const lineageSystemColorClass = (systemType) =>
963
- `td-lineage-system-color--${
964
- systemType.missing
965
- ? "unknown"
966
- : systemTypeHash(systemType.label) % LINEAGE_SYSTEM_TYPE_PALETTE.length
967
- }`;
968
-
969
- const lineageEntityColorClass = (entityType) =>
970
- `td-lineage-entity-color--${LINEAGE_ENTITY_ICON[entityType] ? entityType : "table"}`;
971
-
972
- const lineageFieldCollection = (node) =>
973
- [
974
- node.fields,
975
- node.columns,
976
- node.metadata?.fields,
977
- node.metadata?.columns,
978
- node.data_fields,
979
- ].find(Array.isArray);
980
-
981
- const lineageFields = (node) =>
982
- (lineageFieldCollection(node) || []).filter(Boolean);
983
-
984
- export const lineageNodeContentWidth = (resources = []) => {
985
- const longestLine = resources.reduce((maxLength, node) => {
986
- const labelLength = compactText(node.name || node.label || node.id).length;
987
- const fieldLines = lineageFields(node).map((field) => {
988
- const fieldNameLength = compactText(
989
- typeof field === "string"
990
- ? field
991
- : firstText(
992
- field?.name,
993
- field?.label,
994
- field?.field,
995
- field?.external_id,
996
- field?.id,
997
- ),
998
- ).length;
999
- const fieldTypeLength =
1000
- typeof field === "string"
1001
- ? 0
1002
- : compactText(
1003
- firstText(
1004
- field?.type,
1005
- field?.data_type,
1006
- field?.dataType,
1007
- field?.metadata?.type,
1008
- ),
1009
- ).length;
1010
-
1011
- return fieldNameLength + (fieldTypeLength ? fieldTypeLength + 3 : 0);
1012
- });
1013
-
1014
- return Math.max(maxLength, labelLength, ...fieldLines);
1015
- }, 0);
1016
- const contentWidth = Math.ceil(
1017
- longestLine * LINEAGE_TEXT_WIDTH_FACTOR + LINEAGE_NODE_WIDTH_PADDING,
1018
- );
1019
-
1020
- return Math.min(
1021
- LINEAGE_NODE_WIDTH_MAX,
1022
- Math.max(LINEAGE_NODE_WIDTH_MIN, contentWidth),
1023
- );
1024
- };
1025
-
1026
- const lineagePath = (node, system, schema) => {
1027
- const path = node.path || node.ancestry || node.parents || node.parent_path;
1028
- const pathItems = Array.isArray(path)
1029
- ? path.map((item) => firstText(item?.name, item?.label, item?.value, item))
1030
- : typeof path === "string"
1031
- ? path.split(/\s*(?:>|\/)\s*/)
1032
- : [];
1033
-
1034
- return [...pathItems, system, schema].filter(Boolean);
1035
- };
1036
-
1037
- const lineageStatus = (node) =>
1038
- firstText(
1039
- node.status,
1040
- node.health,
1041
- node.metadata?.status,
1042
- node.metadata?.health,
1043
- );
1044
-
1045
- const lineageQuality = (node) =>
1046
- firstText(
1047
- node.quality,
1048
- node.quality_score,
1049
- node.metadata?.quality,
1050
- node.metadata?.quality_score,
1051
- node.metadata?.score,
1052
- );
1053
-
1054
- const nodeHeight = ({ fields, hiddenFieldsCount, system, schema }) =>
1055
- NODE_BASE_HEIGHT +
1056
- (system || schema ? NODE_CONTEXT_HEIGHT : 0) +
1057
- fields.length * NODE_FIELD_HEIGHT +
1058
- (hiddenFieldsCount ? NODE_MORE_HEIGHT : 0);
1059
-
1060
- export const redesignedLineageNodeHeight = (
1061
- fieldCount = 0,
1062
- fieldsExpanded = false,
1063
- ) => {
1064
- const visibleRowCount = Math.min(
1065
- fieldCount,
1066
- fieldsExpanded
1067
- ? NODE_FIELD_EXPANDED_LIMIT_REDESIGNED
1068
- : NODE_FIELD_LIMIT_REDESIGNED,
1069
- );
1070
- const hasDisclosure = fieldCount > NODE_FIELD_LIMIT_REDESIGNED;
1071
-
1072
- return (
1073
- NODE_BASE_HEIGHT_REDESIGNED +
1074
- visibleRowCount * NODE_FIELD_HEIGHT_REDESIGNED +
1075
- (hasDisclosure ? NODE_MORE_HEIGHT_REDESIGNED : 0)
1076
- );
1077
- };
1078
-
1079
- const lineageNode =
1080
- (
1081
- kind,
1082
- selectedNodeId,
1083
- nodeDesign = "redesigned",
1084
- {
1085
- expandedFieldNodeIds = new Set(),
1086
- instanceId,
1087
- onToggleFields,
1088
- nodeWidth,
1089
- } = {},
1090
- ) =>
1091
- (node) => {
1092
- const id = instanceId || nodeId(node);
1093
- const resolvedKind = nodeKind(node, kind);
1094
- const allFields = lineageFields(node);
1095
- const fieldsKnown = Boolean(lineageFieldCollection(node));
1096
- const redesigned = nodeDesign === "redesigned";
1097
- const fieldsExpanded = redesigned && expandedFieldNodeIds.has(id);
1098
- const fieldLimit = redesigned
1099
- ? fieldsExpanded
1100
- ? allFields.length
1101
- : NODE_FIELD_LIMIT_REDESIGNED
1102
- : NODE_FIELD_LIMIT;
1103
- const fields = allFields.slice(0, fieldLimit);
1104
- const hiddenFieldsCount = Math.max(
1105
- allFields.length -
1106
- (redesigned ? NODE_FIELD_LIMIT_REDESIGNED : fieldLimit),
1107
- 0,
1108
- );
1109
- const system = firstText(node.system, node.system_name);
1110
- const schema = firstText(node.schema, node.schema_name, node.database);
1111
- const systemType = lineageSystemType(node);
1112
- const entity = lineageEntityPresentation(node, resolvedKind);
1113
- const path = redesigned ? lineagePath(node, system, schema) : [];
1114
- const width = redesigned ? nodeWidth || NODE_WIDTH_REDESIGNED : NODE_WIDTH;
1115
-
1116
- return {
1117
- id,
1118
- type: redesigned ? "lineageResource" : "concept",
1119
- width,
1120
- height: redesigned
1121
- ? redesignedLineageNodeHeight(allFields.length, fieldsExpanded)
1122
- : nodeHeight({ fields, hiddenFieldsCount, system, schema }),
1123
- draggable: false,
1124
- className: [
1125
- lineageSystemColorClass(systemType),
1126
- lineageEntityColorClass(entity.entityType),
1127
- `td-lineage-catalog--${lineageCatalogState(node)}`,
1128
- entity.isSystem ? "td-lineage-resource-node--entity-system" : "",
1129
- ]
1130
- .filter(Boolean)
1131
- .join(" "),
1132
- style: redesigned
1133
- ? { "--td-lineage-node-width": `${width}px` }
1134
- : undefined,
1135
- data: {
1136
- label: node.name || id,
1137
- meta: node.type,
1138
- kind: resolvedKind,
1139
- entityType: entity.entityType,
1140
- iconName: entity.iconName,
1141
- variant: redesigned ? "lineage-redesigned" : "lineage",
1142
- systemType: systemType.key,
1143
- systemTypeClassName: systemType.className,
1144
- systemTypeColor: systemType.color.accent,
1145
- systemTypeLabel: systemType.label,
1146
- systemTypeMissing: systemType.missing,
1147
- system,
1148
- schema,
1149
- path,
1150
- status: redesigned ? lineageStatus(node) : undefined,
1151
- quality: redesigned ? lineageQuality(node) : undefined,
1152
- fields,
1153
- allFields,
1154
- fieldsKnown,
1155
- fieldHandleLimit: redesigned
1156
- ? Math.min(
1157
- allFields.length,
1158
- fieldsExpanded
1159
- ? NODE_FIELD_EXPANDED_LIMIT_REDESIGNED
1160
- : NODE_FIELD_LIMIT_REDESIGNED,
1161
- )
1162
- : fields.length,
1163
- fieldsExpanded,
1164
- hiddenFieldsCount,
1165
- onToggleFields:
1166
- redesigned && hiddenFieldsCount > 0 && onToggleFields
1167
- ? () => onToggleFields(id)
1168
- : undefined,
1169
- totalFields: allFields.length,
1170
- isActive: selectedNodeId === id,
1171
- lineageNode: { ...node, id, lineageKind: resolvedKind },
1172
- },
1173
- };
1174
- };
1175
-
1176
- const positionedNodeGeometryForDesign = (node, nodeDesign, kind, nodeWidth) => {
1177
- if (nodeDesign !== "redesigned") return resourceRenderedGeometry(node);
1178
-
1179
- const geometry = nodeGeometry(node);
1180
- const baseNode = lineageNode(kind, undefined, nodeDesign, { nodeWidth })(
1181
- node,
1182
- );
1183
-
1184
- return {
1185
- x: geometry.x,
1186
- y: geometry.y,
1187
- w: baseNode.width,
1188
- h: baseNode.height,
1189
- };
1190
- };
1191
-
1192
- const lineagePositionedGroupNode =
1193
- (
1194
- { parentByNode, childCountsByCanonicalId },
1195
- selectedNodeId,
1196
- duplicateNodeIds,
1197
- geometryById,
1198
- nodeDesign,
1199
- resolveSystemType = lineageSystemType,
1200
- ) =>
1201
- (node) => {
1202
- const canonicalId = nodeId(node);
1203
- const id = positionedNodeInstanceId(node, duplicateNodeIds);
1204
- const parentGroup = parentByNode.get(node);
1205
- const parentId = parentGroup
1206
- ? positionedNodeInstanceId(parentGroup, duplicateNodeIds)
1207
- : undefined;
1208
- const { x, y, w, h } = geometryById.get(id) || nodeGeometry(node);
1209
- const parentGeometry = parentId ? geometryById.get(parentId) : undefined;
1210
- const position = {
1211
- x: parentGeometry ? x - parentGeometry.x : x,
1212
- y: parentGeometry ? y - parentGeometry.y : y,
1213
- };
1214
- const systemType = resolveSystemType(node);
1215
- const entity = lineageEntityPresentation(node, "group");
1216
- const rootGroup = canonicalId === ROOT_GROUP_ID;
1217
- const z = nodeDepth(node);
1218
- const childCounts = childCountsByCanonicalId.get(canonicalId) || {
1219
- nodeCount: 0,
1220
- groupCount: 0,
1221
- };
1222
-
1223
- return {
1224
- id,
1225
- type: "lineageGroup",
1226
- width: w,
1227
- height: h,
1228
- parentId,
1229
- extent: parentId ? "parent" : undefined,
1230
- position,
1231
- draggable: !rootGroup && (!parentId || parentId === ROOT_GROUP_ID),
1232
- selectable: !rootGroup,
1233
- data: {
1234
- canonicalId,
1235
- designVariant: nodeDesign,
1236
- isActive: selectedNodeId === canonicalId,
1237
- label: node.name || "",
1238
- absoluteGeometry: absoluteGeometry({ x, y, w, h }),
1239
- variant: "absolute",
1240
- z,
1241
- entityType: entity.entityType,
1242
- iconName: entity.iconName,
1243
- systemType: systemType.key,
1244
- systemTypeClassName: systemType.className,
1245
- systemTypeColor: systemType.color.accent,
1246
- systemTypeLabel: systemType.label,
1247
- systemTypeMissing: systemType.missing,
1248
- nodeCount: childCounts.nodeCount,
1249
- groupCount: childCounts.groupCount,
1250
- lineageNode: { ...node, id: canonicalId, lineageKind: "group" },
1251
- },
1252
- className: [
1253
- lineageSystemColorClass(systemType),
1254
- lineageEntityColorClass(entity.entityType),
1255
- `td-lineage-catalog--${lineageCatalogState(node)}`,
1256
- entity.isSystem ? "td-lineage-group-node--entity-system" : "",
1257
- ]
1258
- .filter(Boolean)
1259
- .join(" "),
1260
- style: { width: w, height: h },
1261
- zIndex: LINEAGE_GROUP_Z_INDEX + nodeDepth(node),
1262
- };
1263
- };
1264
-
1265
- const lineagePositionedResourceNode =
1266
- (
1267
- parentByNode,
1268
- selectedNodeId,
1269
- duplicateNodeIds,
1270
- geometryById,
1271
- nodeDesign,
1272
- kind = "resource",
1273
- resolveSystemType = lineageSystemType,
1274
- expandedFieldNodeIds = new Set(),
1275
- onToggleFields,
1276
- nodeWidth,
1277
- ) =>
1278
- (node) => {
1279
- const redesigned = nodeDesign === "redesigned";
1280
- const canonicalId = nodeId(node);
1281
- const id = positionedNodeInstanceId(node, duplicateNodeIds);
1282
- const parentGroup = parentByNode.get(node);
1283
- const parentId = parentGroup
1284
- ? positionedNodeInstanceId(parentGroup, duplicateNodeIds)
1285
- : undefined;
1286
- const baseNode = lineageNode(kind, selectedNodeId, nodeDesign, {
1287
- expandedFieldNodeIds,
1288
- instanceId: id,
1289
- onToggleFields,
1290
- nodeWidth,
1291
- })(node);
1292
- const systemType = resolveSystemType(node);
1293
- const rawGeometry =
1294
- geometryById.get(id) ||
1295
- positionedNodeGeometryForDesign(node, nodeDesign, kind, nodeWidth);
1296
- const w = redesigned ? baseNode.width : rawGeometry.w;
1297
- const h = redesigned ? baseNode.height : rawGeometry.h;
1298
- const x = rawGeometry.x;
1299
- const y = rawGeometry.y;
1300
- const parentGeometry = parentId ? geometryById.get(parentId) : undefined;
1301
- const position = {
1302
- x: parentGeometry ? x - parentGeometry.x : x,
1303
- y: parentGeometry ? y - parentGeometry.y : y,
1304
- };
1305
-
1306
- return {
1307
- ...baseNode,
1308
- id,
1309
- className: [
1310
- lineageSystemColorClass(systemType),
1311
- `td-lineage-catalog--${lineageCatalogState(node)}`,
1312
- baseNode.className.includes("td-lineage-resource-node--entity-system")
1313
- ? "td-lineage-resource-node--entity-system"
1314
- : "",
1315
- ]
1316
- .filter(Boolean)
1317
- .join(" "),
1318
- type: "lineageResource",
1319
- width: w,
1320
- height: h,
1321
- parentId,
1322
- extent: parentId ? "parent" : undefined,
1323
- position,
1324
- draggable: false,
1325
- data: {
1326
- ...baseNode.data,
1327
- systemType: systemType.key,
1328
- systemTypeClassName: systemType.className,
1329
- systemTypeColor: systemType.color.accent,
1330
- systemTypeLabel: systemType.label,
1331
- systemTypeMissing: systemType.missing,
1332
- absoluteGeometry: absoluteGeometry({
1333
- x,
1334
- y,
1335
- w,
1336
- h,
1337
- }),
1338
- canonicalId,
1339
- isActive: selectedNodeId === canonicalId,
1340
- lineageNode: baseNode.data.lineageNode
1341
- ? { ...baseNode.data.lineageNode, id: canonicalId }
1342
- : undefined,
1343
- meta: redesigned ? baseNode.data.meta : undefined,
1344
- variant: redesigned ? baseNode.data.variant : "lineage-compact",
1345
- },
1346
- style: {
1347
- ...(redesigned ? { "--td-lineage-node-width": `${w}px` } : {}),
1348
- width: w,
1349
- height: h,
1350
- },
1351
- zIndex: LINEAGE_RESOURCE_Z_INDEX + nodeDepth(parentGroup),
1352
- };
1353
- };
1354
-
1355
- export const lineageTopologyNodes = (
1356
- topology,
1357
- nodeDesign = "redesigned",
1358
- nodeWidth = NODE_WIDTH_REDESIGNED,
1359
- ) => {
1360
- const parentByNode = new Map();
1361
- const childCountsByCanonicalId = new Map();
1362
- const nodeById = new Map(topology.nodes.map((node) => [node.id, node]));
1363
- const geometryById = new Map();
1364
- topology.nodes.forEach((node) => {
1365
- parentByNode.set(node, nodeById.get(node.parentId));
1366
- geometryById.set(node.id, {
1367
- x: 0,
1368
- y: 0,
1369
- w: nodeWidth,
1370
- h: COLLAPSED_GROUP_HEIGHT,
1371
- });
1372
- if (!node.parentId) return;
1373
- const counts = childCountsByCanonicalId.get(node.parentId) || {
1374
- nodeCount: 0,
1375
- groupCount: 0,
1376
- };
1377
- counts[node.container ? "groupCount" : "nodeCount"] += 1;
1378
- childCountsByCanonicalId.set(node.parentId, counts);
1379
- });
1380
- const resolveSystemType = (node) =>
1381
- lineageSystemTypeWithParentFallback(parentByNode, node);
1382
- const groupNode = lineagePositionedGroupNode(
1383
- { parentByNode, childCountsByCanonicalId },
1384
- undefined,
1385
- new Set(),
1386
- geometryById,
1387
- nodeDesign,
1388
- resolveSystemType,
1389
- );
1390
- const resourceNode = lineagePositionedResourceNode(
1391
- parentByNode,
1392
- undefined,
1393
- new Set(),
1394
- geometryById,
1395
- nodeDesign,
1396
- "resource",
1397
- resolveSystemType,
1398
- new Set(),
1399
- undefined,
1400
- nodeWidth,
1401
- );
1402
- return [...topology.nodes]
1403
- .sort((a, b) => a.z - b.z || a.id.localeCompare(b.id))
1404
- .map((node) => {
1405
- const rendered = node.container ? groupNode(node) : resourceNode(node);
1406
- const height = node.field
1407
- ? NODE_FIELD_HEIGHT_REDESIGNED
1408
- : rendered.height;
1409
- return {
1410
- ...rendered,
1411
- height,
1412
- style: { ...rendered.style, height },
1413
- data: {
1414
- ...rendered.data,
1415
- topology: true,
1416
- resourceContainer: node.resourceContainer,
1417
- variant: node.field ? "lineage-field" : rendered.data.variant,
1418
- absoluteGeometry: { ...rendered.data.absoluteGeometry, h: height },
1419
- },
1420
- };
1421
- });
1422
- };
1423
-
1424
- const isRootPositionedGroup = (node) => nodeId(node) === ROOT_GROUP_ID;
1425
-
1426
- const hasPositionedChildren = (group, groups = [], resources = []) =>
1427
- [...groups, ...resources].some(
1428
- (node) =>
1429
- canBePositionedParent(group, node) &&
1430
- (containsGeometry(group, node) || anchoredParentGeometry(group, node)),
1431
- );
1432
-
1433
- const splitPositionedGroups = (groups = [], resources = []) =>
1434
- groups.reduce(
1435
- (result, group) => {
1436
- const container =
1437
- isRootPositionedGroup(group) ||
1438
- hasPositionedChildren(group, groups, resources);
1439
-
1440
- result[container ? "containerGroups" : "leafGroups"].push(group);
1441
-
1442
- return result;
1443
- },
1444
- { containerGroups: [], leafGroups: [] },
1445
- );
1446
-
1447
- export const preparePositionedLineageHierarchy = (
1448
- groups = [],
1449
- resources = [],
1450
- ) => {
1451
- const { containerGroups, leafGroups } = splitPositionedGroups(
1452
- groups,
1453
- resources,
1454
- );
1455
- const parentByNode = new Map();
1456
- const childrenByParent = new Map();
1457
- const childCountsByCanonicalId = new Map();
1458
- const groupSet = new Set(containerGroups);
1459
-
1460
- [...containerGroups, ...resources, ...leafGroups].forEach((node) => {
1461
- if (!parentByNode.has(node)) {
1462
- parentByNode.set(node, parentGroupFor(containerGroups, node));
1463
- }
1464
- const parent = parentByNode.get(node);
1465
-
1466
- if (!parent) return;
1467
-
1468
- const children = childrenByParent.get(parent) || [];
1469
- children.push(node);
1470
- childrenByParent.set(parent, children);
1471
- });
1472
-
1473
- childrenByParent.forEach((children, parent) => {
1474
- const canonicalId = nodeId(parent);
1475
- const counts = childCountsByCanonicalId.get(canonicalId) || {
1476
- nodeCount: 0,
1477
- groupCount: 0,
1478
- };
1479
- children.forEach((child) => {
1480
- counts[groupSet.has(child) ? "groupCount" : "nodeCount"] += 1;
1481
- });
1482
- childCountsByCanonicalId.set(canonicalId, counts);
1483
- });
1484
-
1485
- return {
1486
- containerGroups,
1487
- leafGroups,
1488
- parentByNode,
1489
- childrenByParent,
1490
- childCountsByCanonicalId,
1491
- };
1492
- };
1493
-
1494
- const sortPositionedGroups = (groups = []) =>
1495
- [...groups].sort((groupA, groupB) => {
1496
- const zDiff = nodeDepth(groupA) - nodeDepth(groupB);
1497
-
1498
- if (zDiff !== 0) return zDiff;
1499
-
1500
- return geometryArea(groupB) - geometryArea(groupA);
1501
- });
1502
-
1503
- const uniqueNodes = (nodes = []) => {
1504
- const seen = new Set();
1505
-
1506
- return nodes.filter((node) => {
1507
- if (!node?.id || seen.has(node.id)) return false;
1508
-
1509
- seen.add(node.id);
1510
- return true;
1511
- });
1512
- };
1513
-
1514
- const removeRenderedRootContainer = (nodes = []) =>
1515
- nodes
1516
- .filter((node) => !isRootLineageGroupNode(node))
1517
- .map((node) => {
1518
- if (node.parentId !== ROOT_GROUP_ID) return node;
1519
-
1520
- const geometry = lineageNodeAbsoluteGeometry(node);
1521
-
1522
- return {
1523
- ...node,
1524
- parentId: undefined,
1525
- extent: undefined,
1526
- position: { x: geometry.x, y: geometry.y },
1527
- };
1528
- });
1529
-
1530
- const presentTerminalResourcesAsFieldRows = (nodes = []) => {
1531
- const childrenByParentId = nodes.reduce((children, node) => {
1532
- if (!node.parentId) return children;
1533
-
1534
- const siblings = children.get(node.parentId) || [];
1535
-
1536
- siblings.push(node);
1537
- children.set(node.parentId, siblings);
1538
- return children;
1539
- }, new Map());
1540
- const resourceContainerIds = new Set(
1541
- nodes
1542
- .filter((node) => {
1543
- if (node.type !== "lineageGroup") return false;
1544
-
1545
- const children = childrenByParentId.get(node.id) || [];
1546
-
1547
- return (
1548
- children.length > 0 &&
1549
- children.every(
1550
- (child) =>
1551
- child.type === "lineageResource" &&
1552
- Number(child.data?.totalFields || 0) === 0,
1553
- )
1554
- );
1555
- })
1556
- .map((node) => node.id),
1557
- );
1558
-
1559
- if (!resourceContainerIds.size) return nodes;
1560
-
1561
- return nodes.map((node) => {
1562
- if (resourceContainerIds.has(node.id)) {
1563
- return {
1564
- ...node,
1565
- data: {
1566
- ...node.data,
1567
- resourceContainer: true,
1568
- iconName: lineageNodeIcon({
1569
- kind: "group",
1570
- resourceContainer: true,
1571
- z: node.data?.z,
1572
- }),
1573
- },
1574
- };
1575
- }
1576
-
1577
- if (!resourceContainerIds.has(node.parentId)) return node;
1578
-
1579
- const height = NODE_FIELD_HEIGHT_REDESIGNED;
1580
-
1581
- return {
1582
- ...node,
1583
- height,
1584
- data: {
1585
- ...node.data,
1586
- absoluteGeometry: node.data?.absoluteGeometry
1587
- ? { ...node.data.absoluteGeometry, h: height }
1588
- : node.data?.absoluteGeometry,
1589
- variant: "lineage-field",
1590
- },
1591
- style: {
1592
- ...node.style,
1593
- height,
1594
- },
1595
- };
1596
- });
1597
- };
1598
-
1599
- const lineageNodeParentMap = (nodes = []) =>
1600
- nodes.reduce((parents, node) => {
1601
- if (node?.id && node.parentId) parents.set(node.id, node.parentId);
1602
-
1603
- return parents;
1604
- }, new Map());
1605
-
1606
- const normalizedExpansionDepth = (value, fallback, maximum) => {
1607
- const depth = Number(value);
1608
- const fallbackDepth = Number(fallback);
1609
- const safeFallback = Math.min(
1610
- maximum,
1611
- Math.max(
1612
- 0,
1613
- Number.isInteger(fallbackDepth)
1614
- ? fallbackDepth
1615
- : LINEAGE_DEFAULT_EXPANSION_DEPTH,
1616
- ),
1617
- );
1618
-
1619
- return Number.isInteger(depth) && depth >= 0 && depth <= maximum
1620
- ? depth
1621
- : safeFallback;
1622
- };
1623
-
1624
- const lineageGroupDepths = (nodes = []) => {
1625
- const nodeById = new Map(nodes.map((node) => [node.id, node]));
1626
- const parentById = lineageNodeParentMap(nodes);
1627
- const depthById = new Map();
1628
- const groupDepth = (nodeId, visiting = new Set()) => {
1629
- if (depthById.has(nodeId)) return depthById.get(nodeId);
1630
- if (visiting.has(nodeId)) return 1;
1631
-
1632
- const parentId = parentById.get(nodeId);
1633
- const parent = nodeById.get(parentId);
1634
- const nextVisiting = new Set(visiting).add(nodeId);
1635
- const value =
1636
- parent?.type === "lineageGroup" && !isRootLineageGroupNode(parent)
1637
- ? groupDepth(parentId, nextVisiting) + 1
1638
- : 1;
1639
-
1640
- depthById.set(nodeId, value);
1641
- return value;
1642
- };
1643
-
1644
- nodes
1645
- .filter(
1646
- (node) => node.type === "lineageGroup" && !isRootLineageGroupNode(node),
1647
- )
1648
- .forEach((node) => groupDepth(node.id));
1649
-
1650
- return depthById;
1651
- };
1652
-
1653
- const lineageDepthCollapsibleGroups = (nodes = []) => {
1654
- const descendantCounts = lineageDescendantCounts(nodes);
1655
-
1656
- return nodes.filter(
1657
- (node) =>
1658
- node.type === "lineageGroup" &&
1659
- node.data?.variant === "absolute" &&
1660
- !isRootLineageGroupNode(node) &&
1661
- (descendantCounts.get(node.id) || 0) > 0,
1662
- );
1663
- };
1664
-
1665
- export const lineageMaximumExpansionDepth = (nodes = []) => {
1666
- const depthById = lineageGroupDepths(nodes);
1667
-
1668
- return lineageDepthCollapsibleGroups(nodes).reduce(
1669
- (maximumDepth, node) => Math.max(maximumDepth, depthById.get(node.id) || 0),
1670
- 0,
1671
- );
1672
- };
1673
-
1674
- export const lineageCollapsedGroupIdsForDepth = (nodes = [], depth = 3) => {
1675
- const depthById = lineageGroupDepths(nodes);
1676
-
1677
- return new Set(
1678
- lineageDepthCollapsibleGroups(nodes)
1679
- .filter((node) => (depthById.get(node.id) || 0) > depth)
1680
- .map((node) => node.id),
1681
- );
1682
- };
1683
-
1684
- const lineageDescendantCounts = (nodes = []) => {
1685
- const parentById = lineageNodeParentMap(nodes);
1686
- const counts = new Map();
1687
-
1688
- nodes.forEach((node) => {
1689
- let parentId = parentById.get(node.id);
1690
-
1691
- while (parentId) {
1692
- counts.set(parentId, (counts.get(parentId) || 0) + 1);
1693
- parentId = parentById.get(parentId);
1694
- }
1695
- });
1696
-
1697
- return counts;
1698
- };
1699
-
1700
- const hiddenByCollapsedAncestor = (node, parentById, collapsedIds) => {
1701
- let parentId = parentById.get(node.id);
1702
-
1703
- while (parentId) {
1704
- if (collapsedIds.has(parentId)) return true;
1705
-
1706
- parentId = parentById.get(parentId);
1707
- }
1708
-
1709
- return false;
1710
- };
1711
-
1712
- export const expandLineageDescendants = (collapsedIds, nodeId, nodes = []) => {
1713
- const parentById = lineageNodeParentMap(nodes);
1714
- const expandedIds = new Set([nodeId]);
1715
-
1716
- nodes.forEach((node) => {
1717
- let parentId = parentById.get(node.id);
1718
-
1719
- while (parentId) {
1720
- if (parentId === nodeId) {
1721
- expandedIds.add(node.id);
1722
- break;
1723
- }
1724
-
1725
- parentId = parentById.get(parentId);
1726
- }
1727
- });
1728
-
1729
- return new Set(Array.from(collapsedIds).filter((id) => !expandedIds.has(id)));
1730
- };
1731
-
1732
- export const LINEAGE_OVERSIZED_CHILD_LIMIT = 20;
1733
-
1734
- export const lineageDirectChildCounts = (nodes = []) => {
1735
- const counts = new Map();
1736
-
1737
- nodes.forEach((node) => {
1738
- if (!node?.parentId) return;
1739
-
1740
- counts.set(node.parentId, (counts.get(node.parentId) || 0) + 1);
1741
- });
1742
-
1743
- return counts;
1744
- };
1745
-
1746
- export const expandLineageDescendantsUntilOversized = (
1747
- collapsedIds,
1748
- nodeId,
1749
- nodes = [],
1750
- oversizedIds,
1751
- warnedIds = new Set(),
1752
- ) => {
1753
- const parentById = lineageNodeParentMap(nodes);
1754
- const next = expandLineageDescendants(collapsedIds, nodeId, nodes);
1755
-
1756
- oversizedIds.forEach((id) => {
1757
- if (id === nodeId || warnedIds.has(id)) return;
1758
-
1759
- let parentId = parentById.get(id);
1760
-
1761
- while (parentId) {
1762
- if (parentId === nodeId) {
1763
- next.add(id);
1764
- return;
1765
- }
1766
- parentId = parentById.get(parentId);
1767
- }
1768
- });
1769
-
1770
- return next;
1771
- };
1772
-
1773
- const visibleEndpointId = (nodeId, parentById, visibleNodeIds) => {
1774
- let currentId = nodeId;
1775
-
1776
- while (currentId) {
1777
- if (visibleNodeIds.has(currentId)) return currentId;
1778
-
1779
- currentId = parentById.get(currentId);
1780
- }
1781
-
1782
- return "";
1783
- };
1784
-
1785
- const isRootLineageGroupNode = (node = {}) =>
1786
- node.id === ROOT_GROUP_ID &&
1787
- node.type === "lineageGroup" &&
1788
- node.data?.variant === "absolute";
1789
-
1790
- const lineageNodeAbsoluteGeometry = (node = {}) =>
1791
- node.data?.absoluteGeometry || {
1792
- x: node.position?.x || node.x || 0,
1793
- y: node.position?.y || node.y || 0,
1794
- w: node.width || node.measured?.width || NODE_WIDTH,
1795
- h: node.height || node.measured?.height || NODE_BASE_HEIGHT,
1796
- };
1797
-
1798
- const resizeVisibleLineageGroups = (nodes = [], options = {}) => {
1799
- const { stackChildren = false } = options;
1800
- const nodeById = new Map(nodes.map((node) => [node.id, node]));
1801
- const rootNodeIds = [];
1802
- const childIdsByParentId = nodes.reduce((childrenByParentId, node) => {
1803
- if (!node.parentId || !nodeById.has(node.parentId)) {
1804
- if (node.id) rootNodeIds.push(node.id);
1805
- return childrenByParentId;
1806
- }
1807
-
1808
- const childIds = childrenByParentId.get(node.parentId) || [];
1809
-
1810
- childIds.push(node.id);
1811
- childrenByParentId.set(node.parentId, childIds);
1812
-
1813
- return childrenByParentId;
1814
- }, new Map());
1815
- const geometryById = new Map();
1816
-
1817
- const shiftGeometryTree = (id, deltaX, deltaY) => {
1818
- const geometry = geometryById.get(id);
1819
-
1820
- if (geometry) {
1821
- geometryById.set(id, {
1822
- ...geometry,
1823
- x: geometry.x + deltaX,
1824
- y: geometry.y + deltaY,
1825
- });
1826
- }
1827
-
1828
- (childIdsByParentId.get(id) || []).forEach((childId) =>
1829
- shiftGeometryTree(childId, deltaX, deltaY),
1830
- );
1831
- };
1832
-
1833
- const resolveGeometry = (node) => {
1834
- if (geometryById.has(node.id)) return geometryById.get(node.id);
1835
-
1836
- const baseGeometry = lineageNodeAbsoluteGeometry(node);
1837
- const childIds = childIdsByParentId.get(node.id) || [];
1838
-
1839
- const childNodes = childIds
1840
- .map((childId) => nodeById.get(childId))
1841
- .filter(Boolean);
1842
-
1843
- childNodes.forEach(resolveGeometry);
1844
-
1845
- if (stackChildren) {
1846
- stackSiblingGeometries(
1847
- childIds,
1848
- geometryById,
1849
- shiftGeometryTree,
1850
- shouldCompactVisibleGroupChildren(node, childNodes)
1851
- ? POSITIONED_LEAF_SIBLING_GAP
1852
- : POSITIONED_SIBLING_GAP,
1853
- );
1854
- } else if (shouldRowSiblingGroups(childNodes)) {
1855
- rowSiblingGeometries(childIds, geometryById, shiftGeometryTree);
1856
- } else {
1857
- stackSiblingGeometries(
1858
- childIds,
1859
- geometryById,
1860
- shiftGeometryTree,
1861
- shouldCompactVisibleGroupChildren(node, childNodes)
1862
- ? POSITIONED_LEAF_SIBLING_GAP
1863
- : POSITIONED_SIBLING_GAP,
1864
- );
1865
- }
1866
-
1867
- const childGeometries = childIds
1868
- .map((childId) => geometryById.get(childId))
1869
- .filter(Boolean);
1870
-
1871
- if (
1872
- node.type !== "lineageGroup" ||
1873
- node.data?.variant !== "absolute" ||
1874
- _.isEmpty(childGeometries)
1875
- ) {
1876
- geometryById.set(node.id, baseGeometry);
1877
- return baseGeometry;
1878
- }
1879
-
1880
- const maxChildX = Math.max(
1881
- ...childGeometries.map(
1882
- (geometry) => geometry.x + geometry.w + POSITIONED_GROUP_PADDING_X,
1883
- ),
1884
- );
1885
- const maxChildY = Math.max(
1886
- ...childGeometries.map(
1887
- (geometry) => geometry.y + geometry.h + POSITIONED_GROUP_PADDING_BOTTOM,
1888
- ),
1889
- );
1890
- const minChildX = Math.min(
1891
- ...childGeometries.map(
1892
- (geometry) => geometry.x - POSITIONED_GROUP_PADDING_X,
1893
- ),
1894
- );
1895
- const minChildY = Math.min(
1896
- ...childGeometries.map(
1897
- (geometry) => geometry.y - POSITIONED_GROUP_LABEL_GUTTER,
1898
- ),
1899
- );
1900
- const x = minChildX;
1901
- const y = minChildY;
1902
- const nextGeometry = {
1903
- x,
1904
- y,
1905
- w: Math.max(COLLAPSED_GROUP_WIDTH, maxChildX - x),
1906
- h: Math.max(COLLAPSED_GROUP_HEIGHT, maxChildY - y),
1907
- };
1908
-
1909
- geometryById.set(node.id, nextGeometry);
1910
- return nextGeometry;
1911
- };
1912
-
1913
- nodes.forEach(resolveGeometry);
1914
- packSiblingGeometries(rootNodeIds, geometryById, shiftGeometryTree);
1915
-
1916
- return nodes.map((node) => {
1917
- const geometry =
1918
- geometryById.get(node.id) || lineageNodeAbsoluteGeometry(node);
1919
- const parentGeometry = node.parentId
1920
- ? geometryById.get(node.parentId)
1921
- : null;
1922
- const position = parentGeometry
1923
- ? { x: geometry.x - parentGeometry.x, y: geometry.y - parentGeometry.y }
1924
- : { x: geometry.x, y: geometry.y };
1925
-
1926
- return {
1927
- ...node,
1928
- width: geometry.w,
1929
- height: geometry.h,
1930
- position,
1931
- data: {
1932
- ...node.data,
1933
- absoluteGeometry: geometry,
1934
- },
1935
- style: {
1936
- ...node.style,
1937
- width: geometry.w,
1938
- height: geometry.h,
1939
- },
1940
- };
1941
- });
1942
- };
1943
-
1944
- export const lineageDefaultCollapsedGroupIds = (nodes = []) => {
1945
- return lineageDepthCollapsibleGroups(nodes).map((node) => node.id);
1946
- };
1947
-
1948
- export const LINEAGE_DEFAULT_EXPAND_MAX_GROUPS = 5;
1949
- export const LINEAGE_DEFAULT_EXPAND_MAX_LEAF_NODES = 20;
1950
-
1951
- /*
1952
- * A small graph (at most MAX_GROUPS collapsible boxes and at most
1953
- * MAX_LEAF_NODES leaf nodes per box) should load fully expanded, so the
1954
- * user sees every box open instead of collapsed by default.
1955
- */
1956
- export const lineageShouldLoadExpanded = (nodes = []) => {
1957
- const descendantCounts = lineageDescendantCounts(nodes);
1958
- const collapsibleGroups = nodes.filter(
1959
- (node) =>
1960
- node.type === "lineageGroup" &&
1961
- node.data?.variant === "absolute" &&
1962
- !isRootLineageGroupNode(node) &&
1963
- (descendantCounts.get(node.id) || 0) > 0,
1964
- );
1965
-
1966
- if (collapsibleGroups.length > LINEAGE_DEFAULT_EXPAND_MAX_GROUPS) {
1967
- return false;
1968
- }
1969
-
1970
- const parentById = lineageNodeParentMap(nodes);
1971
- const parentIds = new Set(Array.from(parentById.values()));
1972
- const isLeaf = (nodeId) => !parentIds.has(nodeId);
1973
- const leafDescendantCount = (groupId) =>
1974
- nodes.reduce((count, node) => {
1975
- let parentId = parentById.get(node.id);
1976
-
1977
- while (parentId) {
1978
- if (parentId === groupId) {
1979
- return isLeaf(node.id) ? count + 1 : count;
1980
- }
1981
- parentId = parentById.get(parentId);
1982
- }
1983
-
1984
- return count;
1985
- }, 0);
1986
-
1987
- return collapsibleGroups.every(
1988
- (group) =>
1989
- leafDescendantCount(group.id) <= LINEAGE_DEFAULT_EXPAND_MAX_LEAF_NODES,
1990
- );
1991
- };
1992
-
1993
- export const lineageOversizedGroupIds = (nodes = []) => {
1994
- const directChildCounts = lineageDirectChildCounts(nodes);
1995
-
1996
- return new Set(
1997
- nodes
1998
- .filter(
1999
- (node) =>
2000
- node.type === "lineageGroup" &&
2001
- node.data?.variant === "absolute" &&
2002
- !isRootLineageGroupNode(node) &&
2003
- (directChildCounts.get(node.id) || 0) > LINEAGE_OVERSIZED_CHILD_LIMIT,
2004
- )
2005
- .map((node) => node.id),
2006
- );
2007
- };
2008
-
2009
- export const collapseLineageNodes = (
2010
- nodes = [],
2011
- collapsedIds,
2012
- onToggleCollapse,
2013
- onExpandDescendants,
2014
- oversizedIds = new Set(),
2015
- pendingExpand = null,
2016
- onConfirmOversizedExpand,
2017
- onDismissOversizedWarning,
2018
- ) => {
2019
- if (_.isEmpty(nodes)) return nodes;
2020
-
2021
- const parentById = lineageNodeParentMap(nodes);
2022
- const graphIndex = createLineageGraphIndex(nodes);
2023
- const descendantCounts = lineageDescendantCounts(nodes);
2024
- const directChildCounts = lineageDirectChildCounts(nodes);
2025
- const collapsibleIds = new Set(lineageDefaultCollapsedGroupIds(nodes));
2026
- const effectiveCollapsedIds = new Set(
2027
- Array.from(collapsedIds).filter((id) => collapsibleIds.has(id)),
2028
- );
2029
-
2030
- const visibleNodes = nodes
2031
- .filter(
2032
- (node) =>
2033
- !hiddenByCollapsedAncestor(node, parentById, effectiveCollapsedIds),
2034
- )
2035
- .map((node) => {
2036
- const hiddenChildrenCount = descendantCounts.get(node.id) || 0;
2037
- const canCollapse =
2038
- node.type === "lineageGroup" &&
2039
- node.data?.variant === "absolute" &&
2040
- !isRootLineageGroupNode(node) &&
2041
- hiddenChildrenCount > 0;
2042
-
2043
- if (!canCollapse) {
2044
- if (
2045
- node.type !== "lineageGroup" ||
2046
- node.data?.variant !== "absolute" ||
2047
- isRootLineageGroupNode(node) ||
2048
- hiddenChildrenCount > 0 ||
2049
- node.data?.collapsed
2050
- )
2051
- return node;
2052
-
2053
- const height = 44;
2054
- return {
2055
- ...node,
2056
- height,
2057
- style: { ...node.style, height },
2058
- data: {
2059
- ...node.data,
2060
- absoluteGeometry: node.data?.absoluteGeometry
2061
- ? { ...node.data.absoluteGeometry, h: height }
2062
- : node.data?.absoluteGeometry,
2063
- },
2064
- };
2065
- }
2066
-
2067
- const collapsed = effectiveCollapsedIds.has(node.id);
2068
- const hasCollapsedDescendantGroups = Array.from(
2069
- graphIndex.descendantIds(node.id),
2070
- ).some((id) => effectiveCollapsedIds.has(id));
2071
- const width = collapsed ? COLLAPSED_GROUP_WIDTH : node.width;
2072
- const height = collapsed ? COLLAPSED_GROUP_HEIGHT : node.height;
2073
-
2074
- return {
2075
- ...node,
2076
- width,
2077
- height,
2078
- data: {
2079
- ...node.data,
2080
- absoluteGeometry: node.data?.absoluteGeometry
2081
- ? {
2082
- ...node.data.absoluteGeometry,
2083
- w: width,
2084
- h: height,
2085
- }
2086
- : node.data?.absoluteGeometry,
2087
- canCollapse,
2088
- collapsed,
2089
- hiddenChildrenCount,
2090
- hasCollapsedDescendantGroups,
2091
- oversized: oversizedIds.has(node.id),
2092
- oversizedChildCount: directChildCounts.get(node.id) || 0,
2093
- oversizedWarningOpen: pendingExpand?.groupId === node.id,
2094
- onConfirmOversizedExpand:
2095
- pendingExpand?.groupId === node.id && onConfirmOversizedExpand
2096
- ? () => onConfirmOversizedExpand(node.id, pendingExpand.action)
2097
- : undefined,
2098
- onDismissOversizedWarning:
2099
- pendingExpand?.groupId === node.id && onDismissOversizedWarning
2100
- ? () => onDismissOversizedWarning()
2101
- : undefined,
2102
- onToggleCollapse: () => onToggleCollapse(node.id),
2103
- onExpandDescendants:
2104
- onExpandDescendants && hasCollapsedDescendantGroups
2105
- ? () => onExpandDescendants(node.id)
2106
- : undefined,
2107
- },
2108
- style: {
2109
- ...node.style,
2110
- width,
2111
- height,
2112
- },
2113
- };
2114
- });
2115
-
2116
- return resizeVisibleLineageGroups(visibleNodes);
2117
- };
2118
-
2119
- export const aggregateCollapsedEdges = (
2120
- edges = [],
2121
- nodes = [],
2122
- visibleNodes = [],
2123
- ) => {
2124
- if (_.isEmpty(edges)) return edges;
2125
-
2126
- const parentById = lineageNodeParentMap(nodes);
2127
- const visibleNodeIds = new Set(visibleNodes.map((node) => node.id));
2128
- const hasHiddenEndpoint = edges.some(
2129
- (edge) =>
2130
- !visibleNodeIds.has(edge.source) || !visibleNodeIds.has(edge.target),
2131
- );
2132
-
2133
- if (!hasHiddenEndpoint) {
2134
- return edges.filter(
2135
- (edge) =>
2136
- visibleNodeIds.has(edge.source) &&
2137
- visibleNodeIds.has(edge.target) &&
2138
- edge.source !== edge.target,
2139
- );
2140
- }
2141
-
2142
- const aggregateByKey = new Map();
2143
- const visibleEdges = [];
2144
-
2145
- edges.forEach((edge) => {
2146
- const sourceVisible = visibleNodeIds.has(edge.source);
2147
- const targetVisible = visibleNodeIds.has(edge.target);
2148
-
2149
- if (sourceVisible && targetVisible) {
2150
- if (edge.source !== edge.target) visibleEdges.push(edge);
2151
- return;
2152
- }
2153
-
2154
- const source = visibleEndpointId(edge.source, parentById, visibleNodeIds);
2155
- const target = visibleEndpointId(edge.target, parentById, visibleNodeIds);
2156
-
2157
- if (!source || !target || source === target) return;
2158
-
2159
- const primaryType = edge.data?.primaryType || "";
2160
- const key = `${source}::${target}::${primaryType}`;
2161
- const current = aggregateByKey.get(key);
2162
-
2163
- if (!current) {
2164
- aggregateByKey.set(key, {
2165
- count: 1,
2166
- edge: {
2167
- ...edge,
2168
- source,
2169
- sourceHandle: "end-node",
2170
- target,
2171
- targetHandle: "start-node",
2172
- data: {
2173
- ...edge.data,
2174
- originalEdgeIds: edge.data?.originalEdgeIds || [edge.id],
2175
- sourceFieldIndex: undefined,
2176
- targetFieldIndex: undefined,
2177
- },
2178
- },
2179
- });
2180
- return;
2181
- }
2182
-
2183
- aggregateByKey.set(key, {
2184
- count: current.count + 1,
2185
- edge: {
2186
- ...current.edge,
2187
- id: `lineage-aggregated-${key}`,
2188
- data: {
2189
- ...current.edge.data,
2190
- metadata: current.edge.data?.metadata || edge.data?.metadata,
2191
- onClick: current.edge.data?.onClick || edge.data?.onClick,
2192
- originalEdgeIds: [
2193
- ...(current.edge.data?.originalEdgeIds || [current.edge.id]),
2194
- ...(edge.data?.originalEdgeIds || [edge.id]),
2195
- ],
2196
- },
2197
- style: {
2198
- ...current.edge.style,
2199
- strokeWidth: Math.min(
2200
- Math.max(
2201
- current.edge.style?.strokeWidth || 1.8,
2202
- edge.style?.strokeWidth || 1.8,
2203
- ) + 0.15,
2204
- 3.2,
2205
- ),
2206
- },
2207
- },
2208
- });
2209
- });
2210
-
2211
- const collapsedEdges = Array.from(aggregateByKey.values()).map(
2212
- ({ count, edge }) => ({
2213
- ...edge,
2214
- data: {
2215
- ...edge.data,
2216
- aggregatedCount: count > 1 ? count : undefined,
2217
- label: count > 1 ? `${count}` : edge.data?.label,
2218
- },
2219
- }),
2220
- );
2221
-
2222
- return [...visibleEdges, ...collapsedEdges];
2223
- };
2224
-
2225
- const singleVisibleDescendantEndpoint = (
2226
- nodeId,
2227
- nodeById,
2228
- childIdsByParentId,
2229
- ) => {
2230
- const node = nodeById.get(nodeId);
2231
-
2232
- if (
2233
- !node ||
2234
- node.type !== "lineageGroup" ||
2235
- node.data?.variant !== "absolute" ||
2236
- node.data?.collapsed
2237
- ) {
2238
- return nodeId;
2239
- }
2240
-
2241
- const childIds = childIdsByParentId.get(nodeId) || [];
2242
-
2243
- if (childIds.length !== 1) return nodeId;
2244
-
2245
- return singleVisibleDescendantEndpoint(
2246
- childIds[0],
2247
- nodeById,
2248
- childIdsByParentId,
2249
- );
2250
- };
2251
-
2252
- const visibleDescendantIds = (nodeId, childIdsByParentId) => {
2253
- const childIds = childIdsByParentId.get(nodeId) || [];
2254
-
2255
- return childIds.flatMap((childId) => [
2256
- childId,
2257
- ...visibleDescendantIds(childId, childIdsByParentId),
2258
- ]);
2259
- };
2260
-
2261
- const endpointFieldNameForEdge = (edge, endpoint) =>
2262
- normalizeFieldText(
2263
- endpoint === "source"
2264
- ? edge.data?.sourceFieldName
2265
- : edge.data?.targetFieldName,
2266
- );
2267
-
2268
- const nodeVisibleLabel = (node) =>
2269
- normalizeFieldText(
2270
- firstText(
2271
- node?.data?.label,
2272
- node?.data?.lineageNode?.name,
2273
- node?.data?.canonicalId,
2274
- node?.id,
2275
- ),
2276
- );
2277
-
2278
- const sortVisibleChildEndpointIds = (nodeById) => (idA, idB) => {
2279
- const geometryA = lineageNodeAbsoluteGeometry(nodeById.get(idA));
2280
- const geometryB = lineageNodeAbsoluteGeometry(nodeById.get(idB));
2281
- const yDiff = geometryA.y - geometryB.y;
2282
- const xDiff = geometryA.x - geometryB.x;
2283
-
2284
- if (yDiff !== 0) return yDiff;
2285
- if (xDiff !== 0) return xDiff;
2286
-
2287
- return idA.localeCompare(idB);
2288
- };
2289
-
2290
- const visibleChildEndpointsByField = (nodeId, nodeById, childIdsByParentId) => {
2291
- const descendants = visibleDescendantIds(nodeId, childIdsByParentId)
2292
- .filter((id) => {
2293
- const node = nodeById.get(id);
2294
-
2295
- return node && node.type !== "lineageGroup";
2296
- })
2297
- .sort(sortVisibleChildEndpointIds(nodeById));
2298
-
2299
- return descendants.reduce((byField, id) => {
2300
- const label = nodeVisibleLabel(nodeById.get(id));
2301
-
2302
- if (_.isEmpty(label)) return byField;
2303
-
2304
- const ids = byField.get(label) || [];
2305
-
2306
- ids.push(id);
2307
- byField.set(label, ids);
2308
-
2309
- return byField;
2310
- }, new Map());
2311
- };
2312
-
2313
- const visibleChildEndpointIds = (nodeId, nodeById, childIdsByParentId) =>
2314
- visibleDescendantIds(nodeId, childIdsByParentId)
2315
- .filter((id) => {
2316
- const node = nodeById.get(id);
2317
-
2318
- return (
2319
- node && (node.type !== "lineageGroup" || Boolean(node.data?.collapsed))
2320
- );
2321
- })
2322
- .sort(sortVisibleChildEndpointIds(nodeById));
2323
-
2324
- const endpointLegacyYForEdge = (edge, endpoint) =>
2325
- endpoint === "source" ? edge.data?.legacySourceY : edge.data?.legacyTargetY;
2326
-
2327
- const endpointFieldIndexForEdge = (edge, endpoint) =>
2328
- endpoint === "source"
2329
- ? edge.data?.sourceFieldIndex
2330
- : edge.data?.targetFieldIndex;
2331
-
2332
- const visibleEndpointClosestToY = (ids = [], nodeById, y) => {
2333
- if (y === undefined || _.isEmpty(ids)) return undefined;
2334
-
2335
- return [...ids].sort((idA, idB) => {
2336
- const geometryA = lineageNodeAbsoluteGeometry(nodeById.get(idA));
2337
- const geometryB = lineageNodeAbsoluteGeometry(nodeById.get(idB));
2338
- const distanceA = Math.abs(geometryA.y + geometryA.h / 2 - y);
2339
- const distanceB = Math.abs(geometryB.y + geometryB.h / 2 - y);
2340
-
2341
- if (distanceA !== distanceB) return distanceA - distanceB;
2342
-
2343
- return sortVisibleChildEndpointIds(nodeById)(idA, idB);
2344
- })[0];
2345
- };
2346
-
2347
- const distributedVisibleEndpoint = ({ ids = [], routeIndexes, routeKey }) => {
2348
- if (_.isEmpty(ids)) return undefined;
2349
-
2350
- const routeIndex = routeIndexes.get(routeKey) || 0;
2351
-
2352
- routeIndexes.set(routeKey, routeIndex + 1);
2353
-
2354
- return ids[routeIndex % ids.length];
2355
- };
2356
-
2357
- const visibleEndpointForOpenGroup = ({
2358
- edge,
2359
- endpoint,
2360
- nodeById,
2361
- childIdsByParentId,
2362
- routeIndexes,
2363
- }) => {
2364
- const nodeId = endpoint === "source" ? edge.source : edge.target;
2365
- const node = nodeById.get(nodeId);
2366
-
2367
- if (
2368
- !node ||
2369
- node.type !== "lineageGroup" ||
2370
- node.data?.variant !== "absolute" ||
2371
- node.data?.collapsed
2372
- ) {
2373
- return nodeId;
2374
- }
2375
-
2376
- const fieldName = endpointFieldNameForEdge(edge, endpoint);
2377
- const endpointsByField = visibleChildEndpointsByField(
2378
- nodeId,
2379
- nodeById,
2380
- childIdsByParentId,
2381
- );
2382
- const fieldEndpoints = endpointsByField.get(fieldName) || [];
2383
-
2384
- if (!_.isEmpty(fieldName) && fieldEndpoints.length) {
2385
- const routeKey = `${endpoint}:${nodeId}:${fieldName}`;
2386
-
2387
- return distributedVisibleEndpoint({
2388
- ids: fieldEndpoints,
2389
- routeIndexes,
2390
- routeKey,
2391
- });
2392
- }
2393
-
2394
- const childEndpointIds = visibleChildEndpointIds(
2395
- nodeId,
2396
- nodeById,
2397
- childIdsByParentId,
2398
- );
2399
- const endpointClosestToLegacyY = visibleEndpointClosestToY(
2400
- childEndpointIds,
2401
- nodeById,
2402
- endpointLegacyYForEdge(edge, endpoint),
2403
- );
2404
-
2405
- if (endpointClosestToLegacyY) return endpointClosestToLegacyY;
2406
-
2407
- const fieldIndex = endpointFieldIndexForEdge(edge, endpoint);
2408
-
2409
- if (fieldIndex !== undefined && childEndpointIds[fieldIndex]) {
2410
- return childEndpointIds[fieldIndex];
2411
- }
2412
-
2413
- const singleEndpoint = singleVisibleDescendantEndpoint(
2414
- nodeId,
2415
- nodeById,
2416
- childIdsByParentId,
2417
- );
2418
-
2419
- if (singleEndpoint !== nodeId) return singleEndpoint;
2420
-
2421
- return (
2422
- distributedVisibleEndpoint({
2423
- ids: childEndpointIds,
2424
- routeIndexes,
2425
- routeKey: `${endpoint}:${nodeId}:visible-descendants`,
2426
- }) || nodeId
2427
- );
2428
- };
2429
-
2430
- export const routeOpenGroupEdgesToSingleVisibleChild = (
2431
- edges = [],
2432
- visibleNodes = [],
2433
- ) => {
2434
- if (_.isEmpty(edges) || _.isEmpty(visibleNodes)) return edges;
2435
-
2436
- const nodeById = new Map(visibleNodes.map((node) => [node.id, node]));
2437
- const childIdsByParentId = visibleNodes.reduce((children, node) => {
2438
- if (!node.parentId || !nodeById.has(node.parentId)) return children;
2439
-
2440
- const childIds = children.get(node.parentId) || [];
2441
-
2442
- childIds.push(node.id);
2443
- children.set(node.parentId, childIds);
2444
-
2445
- return children;
2446
- }, new Map());
2447
- const routeIndexes = new Map();
2448
-
2449
- return edges.map((edge) => {
2450
- if (edge.data?.containment || edge.data?.aggregatedCount !== undefined) {
2451
- return edge;
2452
- }
2453
-
2454
- const source = visibleEndpointForOpenGroup({
2455
- edge,
2456
- endpoint: "source",
2457
- nodeById,
2458
- childIdsByParentId,
2459
- routeIndexes,
2460
- });
2461
- const target = visibleEndpointForOpenGroup({
2462
- edge,
2463
- endpoint: "target",
2464
- nodeById,
2465
- childIdsByParentId,
2466
- routeIndexes,
2467
- });
2468
-
2469
- if (source === edge.source && target === edge.target) return edge;
2470
-
2471
- return {
2472
- ...edge,
2473
- source,
2474
- sourceHandle: source === edge.source ? edge.sourceHandle : "end-node",
2475
- target,
2476
- targetHandle: target === edge.target ? edge.targetHandle : "start-node",
2477
- data: {
2478
- ...edge.data,
2479
- sourceFieldIndex:
2480
- source === edge.source ? edge.data?.sourceFieldIndex : undefined,
2481
- targetFieldIndex:
2482
- target === edge.target ? edge.data?.targetFieldIndex : undefined,
2483
- },
2484
- };
2485
- });
2486
- };
2487
-
2488
- const mergeRenderedNode = (baseNode, renderedNode) => ({
2489
- ...baseNode,
2490
- ...renderedNode,
2491
- width: renderedNode.width || baseNode?.width,
2492
- height: renderedNode.height || baseNode?.height,
2493
- data: {
2494
- ...baseNode?.data,
2495
- ...renderedNode.data,
2496
- },
2497
- });
2498
-
2499
- export const lineagePositionedLayoutNodes = ({
2500
- groups = [],
2501
- resources = [],
2502
- selectedNodeId,
2503
- duplicateNodeIds = positionedDuplicateNodeIds(groups, resources),
2504
- nodeDesign = "redesigned",
2505
- expandedFieldNodeIds = new Set(),
2506
- onToggleFields,
2507
- nodeWidth,
2508
- preparedHierarchy = preparePositionedLineageHierarchy(groups, resources),
2509
- }) => {
2510
- const { containerGroups, leafGroups, parentByNode } = preparedHierarchy;
2511
- const leafGroupIds = new Set(leafGroups.map(nodeId));
2512
- const positionedResourceGeometry = (node) =>
2513
- positionedNodeGeometryForDesign(
2514
- node,
2515
- nodeDesign,
2516
- leafGroupIds.has(nodeId(node)) ? "group" : "resource",
2517
- nodeWidth,
2518
- );
2519
- const geometryById = buildPositionedGeometryMap(
2520
- containerGroups,
2521
- [...resources, ...leafGroups],
2522
- duplicateNodeIds,
2523
- positionedResourceGeometry,
2524
- parentByNode,
2525
- );
2526
- const resolveSystemType = (node) =>
2527
- lineageSystemTypeWithParentFallback(parentByNode, node);
2528
-
2529
- return presentTerminalResourcesAsFieldRows(
2530
- removeRenderedRootContainer(
2531
- uniqueNodes([
2532
- ...sortPositionedGroups(containerGroups).map(
2533
- lineagePositionedGroupNode(
2534
- preparedHierarchy,
2535
- selectedNodeId,
2536
- duplicateNodeIds,
2537
- geometryById,
2538
- nodeDesign,
2539
- resolveSystemType,
2540
- ),
2541
- ),
2542
- ...leafGroups.map(
2543
- lineagePositionedResourceNode(
2544
- parentByNode,
2545
- selectedNodeId,
2546
- duplicateNodeIds,
2547
- geometryById,
2548
- nodeDesign,
2549
- "group",
2550
- resolveSystemType,
2551
- expandedFieldNodeIds,
2552
- onToggleFields,
2553
- nodeWidth,
2554
- ),
2555
- ),
2556
- ...resources.map(
2557
- lineagePositionedResourceNode(
2558
- parentByNode,
2559
- selectedNodeId,
2560
- duplicateNodeIds,
2561
- geometryById,
2562
- nodeDesign,
2563
- "resource",
2564
- resolveSystemType,
2565
- expandedFieldNodeIds,
2566
- onToggleFields,
2567
- nodeWidth,
2568
- ),
2569
- ),
2570
- ]),
2571
- ),
2572
- );
2573
- };
2574
-
2575
- const edgeFieldName = (edge = {}, endpoint) => {
2576
- const metadata = edge.metadata || {};
2577
-
2578
- if (endpoint === "source") {
2579
- return normalizeFieldText(
2580
- firstText(
2581
- edge.source_field,
2582
- edge.sourceField,
2583
- edge.source_field_name,
2584
- edge.sourceFieldName,
2585
- edge.start_field,
2586
- edge.startField,
2587
- edge.start_field_name,
2588
- edge.startFieldName,
2589
- edge.from_field,
2590
- edge.fromField,
2591
- edge.inicio_campo,
2592
- edge.inicioCampo,
2593
- edge.source?.field,
2594
- edge.source?.column,
2595
- edge.source?.field_name,
2596
- metadata.source_field,
2597
- metadata.sourceField,
2598
- metadata.source_field_name,
2599
- metadata.sourceFieldName,
2600
- metadata.start_field,
2601
- metadata.startField,
2602
- metadata.from_field,
2603
- metadata.fromField,
2604
- ),
2605
- );
2606
- }
2607
-
2608
- return normalizeFieldText(
2609
- firstText(
2610
- edge.target_field,
2611
- edge.targetField,
2612
- edge.target_field_name,
2613
- edge.targetFieldName,
2614
- edge.end_field,
2615
- edge.endField,
2616
- edge.end_field_name,
2617
- edge.endFieldName,
2618
- edge.to_field,
2619
- edge.toField,
2620
- edge.fin_campo,
2621
- edge.finCampo,
2622
- edge.target?.field,
2623
- edge.target?.column,
2624
- edge.target?.field_name,
2625
- metadata.target_field,
2626
- metadata.targetField,
2627
- metadata.target_field_name,
2628
- metadata.targetFieldName,
2629
- metadata.end_field,
2630
- metadata.endField,
2631
- metadata.to_field,
2632
- metadata.toField,
2633
- ),
2634
- );
2635
- };
2636
-
2637
- const fieldIndexFromName = (fields = [], name) => {
2638
- if (_.isEmpty(name)) return undefined;
2639
-
2640
- const index = fields.findIndex((field) => lineageFieldName(field) === name);
2641
-
2642
- return index === -1 ? undefined : index;
2643
- };
2644
-
2645
- const fieldIndexForEdge = ({ edge, node, endpoint }) => {
2646
- const fields = node?.data?.fields || [];
2647
- const allFields = node?.data?.allFields || fields;
2648
- const fieldHandleLimit = Math.min(
2649
- node?.data?.fieldHandleLimit ?? fields.length,
2650
- fields.length,
2651
- );
2652
-
2653
- if (_.isEmpty(fields) || fieldHandleLimit === 0) return undefined;
2654
-
2655
- const dataIndex =
2656
- endpoint === "source"
2657
- ? edge.data?.sourceFieldIndex
2658
- : edge.data?.targetFieldIndex;
2659
-
2660
- if (dataIndex !== undefined) {
2661
- return dataIndex < fieldHandleLimit ? dataIndex : undefined;
2662
- }
2663
-
2664
- const fieldName =
2665
- endpoint === "source"
2666
- ? edge.data?.sourceFieldName
2667
- : edge.data?.targetFieldName;
2668
- const explicitIndex = fieldIndexFromName(allFields, fieldName);
2669
-
2670
- if (explicitIndex !== undefined) {
2671
- return explicitIndex < fieldHandleLimit ? explicitIndex : undefined;
2672
- }
2673
-
2674
- return undefined;
2675
- };
2676
-
2677
- export const applyFieldHandlesToEdges = (edges = [], nodeById) => {
2678
- return edges.map((edge) => {
2679
- const sourceNode = nodeById.get(edge.source);
2680
- const targetNode = nodeById.get(edge.target);
2681
- const sourceGeometry = sourceNode?.data?.absoluteGeometry;
2682
- const targetGeometry = targetNode?.data?.absoluteGeometry;
2683
- const sourceFieldIndex = fieldIndexForEdge({
2684
- edge,
2685
- endpoint: "source",
2686
- node: sourceNode,
2687
- });
2688
- const targetFieldIndex = fieldIndexForEdge({
2689
- edge,
2690
- endpoint: "target",
2691
- node: targetNode,
2692
- });
2693
- const sourceUsesFieldHandle =
2694
- sourceFieldIndex !== undefined &&
2695
- sourceNode?.data?.variant === "lineage-redesigned";
2696
- const targetUsesFieldHandle =
2697
- targetFieldIndex !== undefined &&
2698
- targetNode?.data?.variant === "lineage-redesigned";
2699
- const sourcePortPosition = "right";
2700
- const targetPortPosition = "left";
2701
-
2702
- return {
2703
- ...edge,
2704
- sourceHandle: edge.data?.containment
2705
- ? edge.sourceHandle
2706
- : sourceUsesFieldHandle
2707
- ? fieldHandleId("source", sourceFieldIndex)
2708
- : "end-node",
2709
- targetHandle: targetUsesFieldHandle
2710
- ? fieldHandleId("target", targetFieldIndex)
2711
- : "start-node",
2712
- data: {
2713
- ...edge.data,
2714
- forceHorizontalPorts: !edge.data?.containment,
2715
- lockSourcePort: !edge.data?.containment && !sourceUsesFieldHandle,
2716
- sourceNodeX: sourceGeometry?.x,
2717
- sourceNodeY: sourceGeometry?.y,
2718
- sourceNodeWidth: sourceNode?.width,
2719
- sourceNodeHeight: sourceNode?.height,
2720
- sourcePortY: edge.data?.legacySourceY,
2721
- sourceFieldIndex,
2722
- sourcePortPosition,
2723
- targetNodeX: targetGeometry?.x,
2724
- targetNodeY: targetGeometry?.y,
2725
- targetNodeWidth: targetNode?.width,
2726
- targetNodeHeight: targetNode?.height,
2727
- targetPortY: edge.data?.legacyTargetY,
2728
- targetFieldIndex,
2729
- targetPortPosition,
2730
- },
2731
- };
2732
- });
2733
- };
2734
-
2735
- export const bundleLineageEdges = (edges = []) => {
2736
- const sourceCounts = new Map();
2737
- const targetCounts = new Map();
2738
- const endpointKey = (nodeId, handleId) => `${nodeId}::${handleId || ""}`;
2739
-
2740
- edges.forEach((edge) => {
2741
- if (edge.data?.containment) return;
2742
-
2743
- const sourceKey = endpointKey(edge.source, edge.sourceHandle);
2744
- const targetKey = endpointKey(edge.target, edge.targetHandle);
2745
-
2746
- sourceCounts.set(sourceKey, (sourceCounts.get(sourceKey) || 0) + 1);
2747
- targetCounts.set(targetKey, (targetCounts.get(targetKey) || 0) + 1);
2748
- });
2749
-
2750
- return edges.map((edge) => {
2751
- if (edge.data?.containment) return edge;
2752
-
2753
- const sourceCount =
2754
- sourceCounts.get(endpointKey(edge.source, edge.sourceHandle)) || 0;
2755
- const targetCount =
2756
- targetCounts.get(endpointKey(edge.target, edge.targetHandle)) || 0;
2757
-
2758
- return {
2759
- ...edge,
2760
- data: {
2761
- ...edge.data,
2762
- bundleOffset: 40,
2763
- bundleSource: sourceCount > 1,
2764
- bundleTarget: targetCount > 1,
2765
- },
2766
- };
2767
- });
2768
- };
2769
-
2770
- export const redesignedLineageEdges = (edges = []) =>
2771
- bundleLineageEdges(
2772
- edges.map((edge) => ({
2773
- ...edge,
2774
- style: _.omit(["stroke", "strokeOpacity", "strokeWidth"])(edge.style),
2775
- data: {
2776
- ...edge.data,
2777
- edgeCurvature: 0.32,
2778
- edgePath: "bezier",
2779
- routeGuide: edge.data?.topology ? edge.data.routeGuide : undefined,
2780
- },
2781
- })),
2782
- );
2783
-
2784
- const inflatedRouteObstacle = (node) => {
2785
- const geometry = lineageNodeAbsoluteGeometry(node);
2786
- const openGroup =
2787
- node.type === "lineageGroup" &&
2788
- node.data?.variant === "absolute" &&
2789
- !node.data?.collapsed;
2790
- const height = openGroup
2791
- ? Math.min(geometry.h, LINEAGE_COLUMN_HEADER_HEIGHT)
2792
- : geometry.h;
2793
-
2794
- return {
2795
- id: node.id,
2796
- x: geometry.x - LINEAGE_ROUTE_OBSTACLE_PADDING,
2797
- y: geometry.y - LINEAGE_ROUTE_OBSTACLE_PADDING,
2798
- w: geometry.w + LINEAGE_ROUTE_OBSTACLE_PADDING * 2,
2799
- h: height + LINEAGE_ROUTE_OBSTACLE_PADDING * 2,
2800
- };
2801
- };
2802
-
2803
- const segmentIntersectsRouteObstacle = (start, end, obstacle) => {
2804
- const minX = Math.min(start.x, end.x);
2805
- const maxX = Math.max(start.x, end.x);
2806
- const minY = Math.min(start.y, end.y);
2807
- const maxY = Math.max(start.y, end.y);
2808
-
2809
- if (start.y === end.y) {
2810
- return (
2811
- start.y > obstacle.y &&
2812
- start.y < obstacle.y + obstacle.h &&
2813
- maxX > obstacle.x &&
2814
- minX < obstacle.x + obstacle.w
2815
- );
2816
- }
2817
-
2818
- if (start.x === end.x) {
2819
- return (
2820
- start.x > obstacle.x &&
2821
- start.x < obstacle.x + obstacle.w &&
2822
- maxY > obstacle.y &&
2823
- minY < obstacle.y + obstacle.h
2824
- );
2825
- }
2826
-
2827
- return true;
2828
- };
2829
-
2830
- const routePointsAreClear = (points, obstacles) =>
2831
- points
2832
- .slice(1)
2833
- .every((point, index) =>
2834
- obstacles.every(
2835
- (obstacle) =>
2836
- !segmentIntersectsRouteObstacle(points[index], point, obstacle),
2837
- ),
2838
- );
2839
-
2840
- const alternatingLaneOffset = (index) => {
2841
- if (!index) return 0;
2842
-
2843
- const direction = index % 2 === 1 ? 1 : -1;
2844
-
2845
- return Math.ceil(index / 2) * LINEAGE_ROUTE_LANE_GAP * direction;
2846
- };
2847
-
2848
- const routePortGeometry = (node, position) => {
2849
- const geometry = lineageNodeAbsoluteGeometry(node);
2850
-
2851
- return {
2852
- x: position === "right" ? geometry.x + geometry.w : geometry.x,
2853
- y: geometry.y + geometry.h / 2,
2854
- };
2855
- };
2856
-
2857
- const verticalStackGuideForEdge = ({
2858
- sourceGeometry,
2859
- targetGeometry,
2860
- sourcePort,
2861
- targetPort,
2862
- obstacles,
2863
- }) => {
2864
- const overlapsHorizontally =
2865
- sourceGeometry.x < targetGeometry.x + targetGeometry.w &&
2866
- sourceGeometry.x + sourceGeometry.w > targetGeometry.x;
2867
- const sourceAboveTarget =
2868
- sourceGeometry.y + sourceGeometry.h <= targetGeometry.y;
2869
- const targetAboveSource =
2870
- targetGeometry.y + targetGeometry.h <= sourceGeometry.y;
2871
-
2872
- if (!overlapsHorizontally || (!sourceAboveTarget && !targetAboveSource)) {
2873
- return undefined;
2874
- }
2875
-
2876
- const verticalGapStart = sourceAboveTarget
2877
- ? sourceGeometry.y + sourceGeometry.h
2878
- : targetGeometry.y + targetGeometry.h;
2879
- const verticalGapEnd = sourceAboveTarget
2880
- ? targetGeometry.y
2881
- : sourceGeometry.y;
2882
- const laneY = (verticalGapStart + verticalGapEnd) / 2;
2883
- const edgeOffset = Math.max(8, POSITIONED_GROUP_PADDING_X - 4);
2884
- const sourceLaneX = sourcePort.x + edgeOffset;
2885
- const targetLaneX = targetPort.x - edgeOffset;
2886
- const points = [
2887
- sourcePort,
2888
- { x: sourceLaneX, y: sourcePort.y },
2889
- { x: sourceLaneX, y: laneY },
2890
- { x: targetLaneX, y: laneY },
2891
- { x: targetLaneX, y: targetPort.y },
2892
- targetPort,
2893
- ];
2894
-
2895
- if (!routePointsAreClear(points, obstacles)) return undefined;
2896
-
2897
- return {
2898
- kind: "bypass",
2899
- edgeOffset,
2900
- laneY,
2901
- sourceLaneX,
2902
- targetLaneX,
2903
- labelPoint: {
2904
- x: (sourceLaneX + targetLaneX) / 2,
2905
- y: laneY,
2906
- },
2907
- };
2908
- };
2909
-
2910
- const corridorGuideForEdge = ({
2911
- sourcePort,
2912
- targetPort,
2913
- obstacles,
2914
- laneIndex,
2915
- }) => {
2916
- const minLaneX =
2917
- Math.min(sourcePort.x, targetPort.x) + LINEAGE_ROUTE_CLEARANCE;
2918
- const maxLaneX =
2919
- Math.max(sourcePort.x, targetPort.x) - LINEAGE_ROUTE_CLEARANCE;
2920
-
2921
- if (maxLaneX <= minLaneX) return undefined;
2922
-
2923
- const centerX = (minLaneX + maxLaneX) / 2;
2924
- const preferredOffset = alternatingLaneOffset(laneIndex);
2925
- const candidateLaneXs = new Set([centerX + preferredOffset, centerX]);
2926
-
2927
- obstacles.forEach((obstacle) => {
2928
- candidateLaneXs.add(obstacle.x - LINEAGE_ROUTE_CLEARANCE + preferredOffset);
2929
- candidateLaneXs.add(
2930
- obstacle.x + obstacle.w + LINEAGE_ROUTE_CLEARANCE + preferredOffset,
2931
- );
2932
- });
2933
-
2934
- return Array.from(candidateLaneXs)
2935
- .filter((laneX) => laneX >= minLaneX && laneX <= maxLaneX)
2936
- .sort(
2937
- (laneXA, laneXB) =>
2938
- Math.abs(laneXA - (centerX + preferredOffset)) -
2939
- Math.abs(laneXB - (centerX + preferredOffset)),
2940
- )
2941
- .find((laneX) =>
2942
- routePointsAreClear(
2943
- [
2944
- sourcePort,
2945
- { x: laneX, y: sourcePort.y },
2946
- { x: laneX, y: targetPort.y },
2947
- targetPort,
2948
- ],
2949
- obstacles,
2950
- ),
2951
- );
2952
- };
2953
-
2954
- const bypassGuideForEdge = ({
2955
- sourcePort,
2956
- targetPort,
2957
- obstacles,
2958
- laneIndex,
2959
- }) => {
2960
- const routeMinX = Math.min(sourcePort.x, targetPort.x);
2961
- const routeMaxX = Math.max(sourcePort.x, targetPort.x);
2962
- const relevantObstacles = obstacles.filter(
2963
- (obstacle) => obstacle.x < routeMaxX && obstacle.x + obstacle.w > routeMinX,
2964
- );
2965
- const obstaclePool = relevantObstacles.length ? relevantObstacles : obstacles;
2966
- const top =
2967
- Math.min(sourcePort.y, targetPort.y, ...obstaclePool.map(({ y }) => y)) -
2968
- LINEAGE_ROUTE_CLEARANCE;
2969
- const bottom =
2970
- Math.max(
2971
- sourcePort.y,
2972
- targetPort.y,
2973
- ...obstaclePool.map(({ y, h }) => y + h),
2974
- ) + LINEAGE_ROUTE_CLEARANCE;
2975
- const laneOffset = Math.abs(alternatingLaneOffset(laneIndex));
2976
- const laneYs = [top - laneOffset, bottom + laneOffset].sort(
2977
- (laneYA, laneYB) =>
2978
- Math.abs(sourcePort.y - laneYA) +
2979
- Math.abs(targetPort.y - laneYA) -
2980
- (Math.abs(sourcePort.y - laneYB) + Math.abs(targetPort.y - laneYB)),
2981
- );
2982
-
2983
- for (
2984
- let attempt = 0;
2985
- attempt < LINEAGE_ROUTE_MAX_LANE_ATTEMPTS;
2986
- attempt += 1
2987
- ) {
2988
- const horizontalOffset = attempt * LINEAGE_ROUTE_LANE_GAP;
2989
- const sourceLaneX =
2990
- sourcePort.x + LINEAGE_ROUTE_CLEARANCE + horizontalOffset;
2991
- const targetLaneX =
2992
- targetPort.x - LINEAGE_ROUTE_CLEARANCE - horizontalOffset;
2993
-
2994
- for (const laneY of laneYs) {
2995
- const points = [
2996
- sourcePort,
2997
- { x: sourceLaneX, y: sourcePort.y },
2998
- { x: sourceLaneX, y: laneY },
2999
- { x: targetLaneX, y: laneY },
3000
- { x: targetLaneX, y: targetPort.y },
3001
- targetPort,
3002
- ];
3003
-
3004
- if (routePointsAreClear(points, obstacles)) {
3005
- return { laneY, sourceLaneX, targetLaneX };
3006
- }
3007
- }
3008
- }
3009
-
3010
- return undefined;
3011
- };
3012
-
3013
- export const planLineageEdgeRoutes = (edges = [], nodes = []) => {
3014
- if (_.isEmpty(edges) || _.isEmpty(nodes)) return edges;
3015
-
3016
- const nodeById = new Map(nodes.map((node) => [node.id, node]));
3017
- const parentById = lineageNodeParentMap(nodes);
3018
- const obstacles = nodes.map(inflatedRouteObstacle);
3019
- const bundleLaneIndexes = new Map();
3020
- const routeGuides = new Array(edges.length);
3021
- const routePortPositions = new Array(edges.length);
3022
- const orderedEdges = edges
3023
- .map((edge, index) => ({ edge, index }))
3024
- .sort(({ edge: edgeA }, { edge: edgeB }) => {
3025
- const sourceA = nodeById.get(edgeA.source);
3026
- const sourceB = nodeById.get(edgeB.source);
3027
- const targetA = nodeById.get(edgeA.target);
3028
- const targetB = nodeById.get(edgeB.target);
3029
- const sourceYDiff =
3030
- (lineageNodeAbsoluteGeometry(sourceA).y || 0) -
3031
- (lineageNodeAbsoluteGeometry(sourceB).y || 0);
3032
- const targetYDiff =
3033
- (lineageNodeAbsoluteGeometry(targetA).y || 0) -
3034
- (lineageNodeAbsoluteGeometry(targetB).y || 0);
3035
-
3036
- return (
3037
- sourceYDiff || targetYDiff || `${edgeA.id}`.localeCompare(`${edgeB.id}`)
3038
- );
3039
- });
3040
-
3041
- orderedEdges.forEach(({ edge, index }) => {
3042
- if (edge.data?.containment) return;
3043
-
3044
- const sourceNode = nodeById.get(edge.source);
3045
- const targetNode = nodeById.get(edge.target);
3046
-
3047
- if (!sourceNode || !targetNode) return;
3048
-
3049
- const sourceGeometry = lineageNodeAbsoluteGeometry(sourceNode);
3050
- const targetGeometry = lineageNodeAbsoluteGeometry(targetNode);
3051
- const targetToRight =
3052
- targetGeometry.x + targetGeometry.w / 2 >=
3053
- sourceGeometry.x + sourceGeometry.w / 2;
3054
- const sourcePortPosition = "right";
3055
- const targetPortPosition = "left";
3056
- const sourcePort = routePortGeometry(sourceNode, sourcePortPosition);
3057
- const targetPort = routePortGeometry(targetNode, targetPortPosition);
3058
- const forward = targetToRight;
3059
- routePortPositions[index] = {
3060
- sourcePortPosition,
3061
- targetPortPosition,
3062
- };
3063
- const bundleKey = [
3064
- parentById.get(edge.source) || edge.source,
3065
- parentById.get(edge.target) || edge.target,
3066
- forward ? "forward" : "reverse",
3067
- ].join("::");
3068
- const laneIndex = bundleLaneIndexes.get(bundleKey) || 0;
3069
- const edgeObstacles = obstacles.filter(
3070
- ({ id }) => id !== edge.source && id !== edge.target,
3071
- );
3072
- const sameParent =
3073
- parentById.get(edge.source) !== undefined &&
3074
- parentById.get(edge.source) === parentById.get(edge.target);
3075
- const verticalStackGuide = sameParent
3076
- ? verticalStackGuideForEdge({
3077
- sourceGeometry,
3078
- targetGeometry,
3079
- sourcePort,
3080
- targetPort,
3081
- obstacles: edgeObstacles,
3082
- })
3083
- : undefined;
3084
- const corridorLaneX = !verticalStackGuide
3085
- ? corridorGuideForEdge({
3086
- sourcePort,
3087
- targetPort,
3088
- obstacles: edgeObstacles,
3089
- laneIndex,
3090
- })
3091
- : undefined;
3092
- const bypass =
3093
- verticalStackGuide || corridorLaneX
3094
- ? undefined
3095
- : bypassGuideForEdge({
3096
- sourcePort,
3097
- targetPort,
3098
- obstacles: edgeObstacles,
3099
- laneIndex,
3100
- });
3101
-
3102
- bundleLaneIndexes.set(bundleKey, laneIndex + 1);
3103
-
3104
- if (verticalStackGuide) {
3105
- routeGuides[index] = verticalStackGuide;
3106
- } else if (corridorLaneX !== undefined) {
3107
- routeGuides[index] = {
3108
- kind: "corridor",
3109
- laneX: corridorLaneX,
3110
- labelPoint: {
3111
- x: corridorLaneX,
3112
- y: (sourcePort.y + targetPort.y) / 2,
3113
- },
3114
- };
3115
- } else if (bypass) {
3116
- routeGuides[index] = {
3117
- kind: "bypass",
3118
- ...bypass,
3119
- labelPoint: {
3120
- x: (bypass.sourceLaneX + bypass.targetLaneX) / 2,
3121
- y: bypass.laneY,
3122
- },
3123
- };
3124
- }
3125
- });
3126
-
3127
- return edges.map((edge, index) => ({
3128
- ...edge,
3129
- data: {
3130
- ...edge.data,
3131
- ...routePortPositions[index],
3132
- edgeOffset: routeGuides[index]?.edgeOffset ?? edge.data?.edgeOffset,
3133
- routeGuide: routeGuides[index] || edge.data?.routeGuide,
3134
- },
3135
- }));
3136
- };
3137
-
3138
- export const focusedLineageGraph = (selectedNodeId, edges = []) => {
3139
- const focusedNodeIds = new Set();
3140
- const focusedEdgeIds = new Set();
3141
-
3142
- if (!selectedNodeId) return { focusedEdgeIds, focusedNodeIds };
3143
-
3144
- focusedNodeIds.add(selectedNodeId);
3145
-
3146
- edges.forEach((edge) => {
3147
- if (edge.source !== selectedNodeId && edge.target !== selectedNodeId)
3148
- return;
3149
-
3150
- focusedEdgeIds.add(edge.id);
3151
- focusedNodeIds.add(edge.source);
3152
- focusedNodeIds.add(edge.target);
3153
- });
3154
-
3155
- return { focusedEdgeIds, focusedNodeIds };
3156
- };
3157
-
3158
- export const focusLineageNodes = (
3159
- nodes = [],
3160
- focusedNodeIds,
3161
- focusActive,
3162
- selectedNodeId,
3163
- ) =>
3164
- focusActive
3165
- ? nodes.map((node) => {
3166
- const groupNode =
3167
- node.type === "lineageGroup" ||
3168
- node.data?.lineageNode?.lineageKind === "group";
3169
- const selected = node.id === selectedNodeId;
3170
- const direct = !selected && focusedNodeIds.has(node.id);
3171
- const dimmed = !selected && !focusedNodeIds.has(node.id);
3172
-
3173
- return {
3174
- ...node,
3175
- className: [
3176
- node.className,
3177
- dimmed ? "lineage-node--muted" : "lineage-node--emphasized",
3178
- ]
3179
- .filter(Boolean)
3180
- .join(" "),
3181
- data: {
3182
- ...node.data,
3183
- focusDirect: direct,
3184
- focusDimmed: dimmed,
3185
- focusSelected: selected,
3186
- isActive: selected,
3187
- },
3188
- zIndex: groupNode
3189
- ? node.zIndex
3190
- : selected
3191
- ? LINEAGE_SELECTED_NODE_Z_INDEX
3192
- : direct
3193
- ? LINEAGE_DIRECT_NODE_Z_INDEX
3194
- : node.zIndex,
3195
- style: {
3196
- ...node.style,
3197
- opacity: 1,
3198
- },
3199
- };
3200
- })
3201
- : nodes;
3202
-
3203
- export const focusLineageEdges = (
3204
- edges = [],
3205
- focusedEdgeIds,
3206
- focusActive,
3207
- animateFocusedEdges = false,
3208
- selectedNodeId,
3209
- selectedEdgeId,
3210
- ) =>
3211
- focusActive
3212
- ? edges.map((edge) => {
3213
- const dimmed = !focusedEdgeIds.has(edge.id);
3214
-
3215
- return {
3216
- ...edge,
3217
- zIndex:
3218
- (selectedNodeId || edge.id === selectedEdgeId) &&
3219
- !dimmed &&
3220
- !edge.data?.containment
3221
- ? LINEAGE_FOCUSED_EDGE_Z_INDEX
3222
- : edge.zIndex,
3223
- data: {
3224
- ...edge.data,
3225
- animated: Boolean(
3226
- animateFocusedEdges && !dimmed && !edge.data?.containment,
3227
- ),
3228
- selectedEndpoint:
3229
- selectedNodeId === edge.source
3230
- ? "source"
3231
- : selectedNodeId === edge.target
3232
- ? "target"
3233
- : undefined,
3234
- focusDimmed: dimmed,
3235
- focusFocused: !dimmed,
3236
- },
3237
- style: {
3238
- ...edge.style,
3239
- opacity: 1,
3240
- },
3241
- };
3242
- })
3243
- : edges;
3244
-
3245
- export const focusedLineageGraphForNodeSet = (nodeIds, edges = []) => {
3246
- const focusedNodeIds = nodeIds instanceof Set ? nodeIds : new Set();
3247
-
3248
- if (focusedNodeIds.size === 0) {
3249
- return { focusedNodeIds: new Set(), focusedEdgeIds: new Set() };
3250
- }
3251
-
3252
- const focusedEdgeIds = new Set();
3253
-
3254
- edges.forEach((edge) => {
3255
- if (focusedNodeIds.has(edge.source) && focusedNodeIds.has(edge.target)) {
3256
- focusedEdgeIds.add(edge.id);
3257
- }
3258
- });
3259
-
3260
- return { focusedNodeIds, focusedEdgeIds };
3261
- };
3262
-
3263
- export const lineageNodeMatchesToolbarFilters = (
3264
- node,
3265
- normalizedSearchTerm,
3266
- systemTypeFilter,
3267
- ) => {
3268
- if (!node?.data?.label || !node.data.lineageNode) return false;
3269
-
3270
- const lineageNodeData = node.data.lineageNode || {};
3271
- const searchableText = [
3272
- node.data.label,
3273
- lineageNodeData.name,
3274
- lineageNodeData.label,
3275
- lineageNodeData.id,
3276
- lineageNodeData.external_id,
3277
- lineageNodeData.structure_id,
3278
- ...(lineageNodeData.originals || []).flatMap((original) => [
3279
- original.id,
3280
- original.structure_id,
3281
- ]),
3282
- ]
3283
- .filter((value) => value !== undefined && value !== null)
3284
- .join(" ")
3285
- .toLowerCase();
3286
-
3287
- const matchesSearch =
3288
- !normalizedSearchTerm ||
3289
- searchableText.includes(normalizedSearchTerm.toLowerCase());
3290
- const matchesSystem =
3291
- !systemTypeFilter || node.data.systemType === systemTypeFilter;
3292
-
3293
- return matchesSearch && matchesSystem;
3294
- };
3295
-
3296
- const lineageParentPath = (
3297
- node,
3298
- nodeById,
3299
- visitedIds = new Set([node?.id]),
3300
- ) => {
3301
- const parent = node?.parentId ? nodeById?.get(node.parentId) : null;
3302
-
3303
- if (!parent || visitedIds.has(parent.id)) return [];
3304
-
3305
- return [
3306
- ...lineageParentPath(parent, nodeById, new Set([...visitedIds, parent.id])),
3307
- firstText(parent.data?.label, parent.data?.name, parent.id),
3308
- ];
3309
- };
3310
-
3311
- const selectedNodePath = (node, nodeById, lineageNode) => {
3312
- const ancestors = lineageParentPath(node, nodeById);
3313
-
3314
- const system = firstText(node?.data?.system, lineageNode.system);
3315
- const schema = firstText(node?.data?.schema, lineageNode.schema);
3316
- const fallback = lineagePath(lineageNode, system, schema);
3317
- const path = ancestors.length >= fallback.length ? ancestors : fallback;
3318
- const name = firstText(
3319
- lineageNode.name,
3320
- lineageNode.label,
3321
- node?.data?.label,
3322
- node?.id,
3323
- );
3324
-
3325
- return [...path.filter((part) => part !== name), name]
3326
- .filter(Boolean)
3327
- .filter((part, index, parts) => index === 0 || part !== parts[index - 1]);
3328
- };
3329
-
3330
- export const selectedNodeFromGraphNode = (node, nodeById) => {
3331
- const lineageNode = node?.data?.lineageNode;
3332
-
3333
- if (!lineageNode) {
3334
- return {
3335
- graphNodeId: node.id,
3336
- iconName: lineageNodeIcon(node.data),
3337
- name: firstText(node.data?.label, node.data?.name, node.id),
3338
- lineageKind: "group",
3339
- path: selectedNodePath(node, nodeById, {}),
3340
- };
3341
- }
3342
-
3343
- const name = firstText(
3344
- lineageNode.name,
3345
- lineageNode.label,
3346
- node.data?.label,
3347
- node.id,
3348
- );
3349
-
3350
- return {
3351
- ...lineageNode,
3352
- fieldsKnown: node.data?.fieldsKnown,
3353
- graphNodeId: node.id,
3354
- iconName: node.data?.iconName || lineageNodeIcon(node.data),
3355
- entityType: node.data?.entityType,
3356
- name,
3357
- path: selectedNodePath(node, nodeById, { ...lineageNode, name }),
3358
- quality: firstText(node.data.quality, lineageNode.quality),
3359
- schema: firstText(node.data.schema, lineageNode.schema),
3360
- status: firstText(node.data.status, lineageNode.status),
3361
- system: firstText(node.data.system, lineageNode.system),
3362
- systemTypeLabel: firstText(
3363
- node.data.systemTypeLabel,
3364
- lineageNode.systemTypeLabel,
3365
- ),
3366
- totalFields: node.data?.totalFields,
3367
- };
3368
- };
3369
-
3370
- export const lineageToolbarSearchResults = (
3371
- nodes = [],
3372
- normalizedSearchTerm = "",
3373
- systemTypeFilter = "",
3374
- limit = LINEAGE_SEARCH_RESULT_LIMIT,
3375
- ) => {
3376
- const matchedNodes = nodes.filter((node) =>
3377
- lineageNodeMatchesToolbarFilters(
3378
- node,
3379
- normalizedSearchTerm,
3380
- systemTypeFilter,
3381
- ),
3382
- );
3383
- const results = matchedNodes.slice(0, limit).map((node) => {
3384
- const selectedNode = selectedNodeFromGraphNode(node);
3385
- const path = Array.isArray(node.data?.path)
3386
- ? node.data.path.filter(Boolean).join(" / ")
3387
- : "";
3388
-
3389
- return {
3390
- contextLabel:
3391
- path || node.data?.systemTypeLabel || node.data?.system || "—",
3392
- graphNodeId: node.id,
3393
- label: firstText(node.data?.label, selectedNode?.name, node.id),
3394
- iconName: selectedNode?.iconName,
3395
- lineageKind: selectedNode?.lineageKind || "group",
3396
- };
3397
- });
3398
-
3399
- return { results, total: matchedNodes.length };
3400
- };
3401
-
3402
- const lineageAncestorNodeIds = (node, nodeById) => {
3403
- const parentNode = node?.parentId ? nodeById.get(node.parentId) : null;
3404
-
3405
- return parentNode
3406
- ? [parentNode.id, ...lineageAncestorNodeIds(parentNode, nodeById)]
3407
- : [];
3408
- };
3409
-
3410
- const lineageRelationType = (edge = {}) =>
3411
- firstText(
3412
- edge.relation_type,
3413
- edge.relationType,
3414
- edge.metadata?.relation_type,
3415
- edge.metadata?.relationType,
3416
- );
3417
-
3418
- const lineageEdgeContext = ({ edge, id, nodeById, source, target }) => ({
3419
- type: "edge",
3420
- edge: {
3421
- id,
3422
- label: edge.label,
3423
- metadata: edge.metadata,
3424
- relationType: lineageRelationType(edge),
3425
- originalSource: edge.originalSource,
3426
- originalTarget: edge.originalTarget,
3427
- source,
3428
- sourceName: firstText(nodeById.get(source)?.data?.label, source),
3429
- target,
3430
- targetName: firstText(nodeById.get(target)?.data?.label, target),
3431
- },
3432
- });
3433
-
3434
- export const lineageEdge =
3435
- (onEdgeSelect, resolveId, nodeById, graphType = "impact") =>
3436
- (edge, index) => {
3437
- const start = resolveLineageEndpoint(edge, "start", resolveId);
3438
- const end = resolveLineageEndpoint(edge, "end", resolveId);
3439
- const reverse = graphType === "lineage" && !edge.normalizedDirection;
3440
- const source = reverse ? end : start;
3441
- const target = reverse ? start : end;
3442
-
3443
- if (
3444
- !source ||
3445
- !target ||
3446
- source === target ||
3447
- !nodeById.has(source) ||
3448
- !nodeById.has(target)
3449
- ) {
3450
- return null;
3451
- }
3452
-
3453
- const hasMetadata = !_.isEmpty(edge.metadata);
3454
- const restrictive = isRestrictiveLineageEdge(edge);
3455
- const markerPosition = "target"; // Flechas estáticas siempre en target; flowDirection controla animación
3456
- const sourceSystemType = nodeById.get(source)?.data?.systemType;
3457
- const targetSystemType = nodeById.get(target)?.data?.systemType;
3458
- const primarySystemType =
3459
- sourceSystemType || targetSystemType || "__without_system_type__";
3460
-
3461
- const id = normalizeId(edge.id || `${source}-${target}-${index}`);
3462
-
3463
- return {
3464
- id,
3465
- source,
3466
- sourceHandle: "end-node",
3467
- target,
3468
- targetHandle: "start-node",
3469
- markerEnd: { type: "arrow", width: 10, height: 10 },
3470
- type: "colored",
3471
- zIndex: LINEAGE_EDGE_Z_INDEX,
3472
- data: {
3473
- animated: false,
3474
- flowDirection: "forward",
3475
- label: edge.label,
3476
- edgePath: "orthogonal",
3477
- explicitEndpoints: true,
3478
- markerPosition,
3479
- metadata: edge.metadata,
3480
- relationType: lineageRelationType(edge),
3481
- originalEdgeIds: [id],
3482
- topology: Boolean(edge.normalizedDirection),
3483
- originalSource: edge.originalSource,
3484
- originalTarget: edge.originalTarget,
3485
- onClick:
3486
- hasMetadata || edge.normalizedDirection
3487
- ? () =>
3488
- onEdgeSelect(
3489
- lineageEdgeContext({ edge, id, nodeById, source, target }),
3490
- )
3491
- : undefined,
3492
- primaryType: primarySystemType,
3493
- restrictive,
3494
- showSourceDot: false,
3495
- showTargetArrow: true,
3496
- sourceFieldName: edgeFieldName(edge, reverse ? "target" : "source"),
3497
- targetFieldName: edgeFieldName(edge, reverse ? "source" : "target"),
3498
- },
3499
- style: {
3500
- stroke: lineageEdgeColor(graphType),
3501
- strokeOpacity: 0.95,
3502
- strokeWidth: restrictive ? 2.4 : hasMetadata ? 2 : 1.8,
3503
- },
3504
- };
3505
- };
3506
-
3507
- const pathCoordinates = (path) =>
3508
- `${path || ""}`.match(PATH_NUMBER_PATTERN)?.map((value) => Number(value)) ||
3509
- [];
3510
-
3511
- const legacyPathEndpoints = (path) => {
3512
- const coordinates = pathCoordinates(path);
3513
-
3514
- if (coordinates.length < 4) return {};
3515
-
3516
- const startPoint = { x: coordinates[0], y: coordinates[1] };
3517
- const endPoint = {
3518
- x: coordinates[coordinates.length - 2],
3519
- y: coordinates[coordinates.length - 1],
3520
- };
3521
- const [sourcePoint, targetPoint] =
3522
- startPoint.x <= endPoint.x
3523
- ? [startPoint, endPoint]
3524
- : [endPoint, startPoint];
3525
-
3526
- return {
3527
- startPoint,
3528
- endPoint,
3529
- sourcePoint,
3530
- targetPoint,
3531
- };
3532
- };
3533
-
3534
- const legacyPathSegments = (path) =>
3535
- `${path || ""}`.match(PATH_SEGMENT_PATTERN) || [];
3536
-
3537
- const pathNodeCandidates = (
3538
- groups = [],
3539
- resources = [],
3540
- duplicateNodeIds = positionedDuplicateNodeIds(groups, resources),
3541
- ) =>
3542
- [...resources, ...groups]
3543
- .filter(
3544
- (node) =>
3545
- node?.id && hasNodeGeometry(node) && !isRootPositionedGroup(node),
3546
- )
3547
- .map((node) => ({
3548
- ...node,
3549
- id: positionedNodeInstanceId(node, duplicateNodeIds),
3550
- canonicalId: nodeId(node),
3551
- }));
3552
-
3553
- const distanceToNodePort = (node, point, endpoint) => {
3554
- const { x, y, w, h } = nodeGeometry(node);
3555
- const portX = endpoint === "source" ? x + w : x;
3556
- const portY = Math.max(y, Math.min(point.y, y + h));
3557
- const areaPenalty = geometryArea(node) / 1000000;
3558
-
3559
- return Math.hypot(point.x - portX, point.y - portY) + areaPenalty;
3560
- };
3561
-
3562
- const closestNodeToPathPoint = (nodes, point, endpoint) =>
3563
- nodes
3564
- .map((node) => ({
3565
- node,
3566
- score: distanceToNodePort(node, point, endpoint),
3567
- }))
3568
- .sort((nodeA, nodeB) => nodeA.score - nodeB.score)[0]?.node;
3569
-
3570
- const findNodeCandidate = (nodes = [], id) =>
3571
- nodes.find((node) => node.id === id || node.canonicalId === id);
3572
-
3573
- const explicitPathPoints = ({
3574
- sourceNode,
3575
- targetNode,
3576
- startPoint,
3577
- endPoint,
3578
- }) => {
3579
- if (!sourceNode || !targetNode || !startPoint || !endPoint) return {};
3580
-
3581
- const startAsSourceScore =
3582
- distanceToNodePort(sourceNode, startPoint, "source") +
3583
- distanceToNodePort(targetNode, endPoint, "target");
3584
- const endAsSourceScore =
3585
- distanceToNodePort(sourceNode, endPoint, "source") +
3586
- distanceToNodePort(targetNode, startPoint, "target");
3587
-
3588
- return startAsSourceScore <= endAsSourceScore
3589
- ? { sourcePoint: startPoint, targetPoint: endPoint }
3590
- : { sourcePoint: endPoint, targetPoint: startPoint };
3591
- };
3592
-
3593
- const fieldIndexFromPathPoint = (node, point) => {
3594
- const fields = lineageFields(node).slice(0, NODE_FIELD_LIMIT);
3595
-
3596
- if (_.isEmpty(fields)) return undefined;
3597
-
3598
- const { y, h } = nodeGeometry(node);
3599
- const fieldAreaHeight = fields.length * NODE_FIELD_HEIGHT;
3600
- const fieldAreaTop = y + Math.max(0, h - fieldAreaHeight - 4);
3601
- const relativeY = point.y - fieldAreaTop;
3602
-
3603
- if (relativeY < 0) return 0;
3604
-
3605
- return Math.min(Math.floor(relativeY / NODE_FIELD_HEIGHT), fields.length - 1);
3606
- };
3607
-
3608
- const legacyEdgeY = (edge, endpoint) =>
3609
- endpoint === "source" ? edge.data?.legacySourceY : edge.data?.legacyTargetY;
3610
-
3611
- const legacyFieldIndexByEdge = (edges = [], nodeById, endpoint) => {
3612
- const edgeIndexes = new Map();
3613
- const edgesByNodeId = edges.reduce((groups, edge) => {
3614
- const nodeId = endpoint === "source" ? edge.source : edge.target;
3615
- const y = legacyEdgeY(edge, endpoint);
3616
-
3617
- if (y === undefined) return groups;
3618
-
3619
- const nodeEdges = groups.get(nodeId) || [];
3620
-
3621
- nodeEdges.push(edge);
3622
- groups.set(nodeId, nodeEdges);
3623
-
3624
- return groups;
3625
- }, new Map());
3626
-
3627
- edgesByNodeId.forEach((nodeEdges, nodeId) => {
3628
- const fields = nodeById.get(nodeId)?.data?.fields || [];
3629
-
3630
- if (_.isEmpty(fields)) return;
3631
-
3632
- [...nodeEdges]
3633
- .sort((edgeA, edgeB) => {
3634
- const yDiff =
3635
- legacyEdgeY(edgeA, endpoint) - legacyEdgeY(edgeB, endpoint);
3636
-
3637
- return yDiff || edgeA.id.localeCompare(edgeB.id);
3638
- })
3639
- .forEach((edge, index) => {
3640
- edgeIndexes.set(edge.id, Math.min(index, fields.length - 1));
3641
- });
3642
- });
3643
-
3644
- return edgeIndexes;
3645
- };
3646
-
3647
- export const spreadLegacyFieldIndexes = (edges = [], nodeById) => {
3648
- const sourceIndexByEdgeId = legacyFieldIndexByEdge(edges, nodeById, "source");
3649
- const targetIndexByEdgeId = legacyFieldIndexByEdge(edges, nodeById, "target");
3650
-
3651
- return edges.map((edge) => ({
3652
- ...edge,
3653
- data: {
3654
- ...edge.data,
3655
- sourceFieldIndex:
3656
- edge.data?.sourceFieldIndex ?? sourceIndexByEdgeId.get(edge.id),
3657
- targetFieldIndex:
3658
- edge.data?.targetFieldIndex ?? targetIndexByEdgeId.get(edge.id),
3659
- },
3660
- }));
3661
- };
3662
-
3663
- export const legacyPathEdge =
3664
- (
3665
- nodeCandidates,
3666
- nodeById,
3667
- onEdgeSelect,
3668
- resolveId = (id) => id,
3669
- graphType = "impact",
3670
- ) =>
3671
- (edge, index) => {
3672
- if (!edge?.path) return null;
3673
-
3674
- const {
3675
- endPoint,
3676
- sourcePoint: fallbackSourcePoint,
3677
- startPoint,
3678
- targetPoint: fallbackTargetPoint,
3679
- } = legacyPathEndpoints(edge.path);
3680
- const explicitSource = resolveLineageEndpoint(edge, "start", resolveId);
3681
- const explicitTarget = resolveLineageEndpoint(edge, "end", resolveId);
3682
- const hasExplicitEndpoints =
3683
- explicitSource &&
3684
- explicitTarget &&
3685
- nodeById.has(explicitSource) &&
3686
- nodeById.has(explicitTarget);
3687
- const explicitSourceNode = hasExplicitEndpoints
3688
- ? findNodeCandidate(nodeCandidates, explicitSource)
3689
- : undefined;
3690
- const explicitTargetNode = hasExplicitEndpoints
3691
- ? findNodeCandidate(nodeCandidates, explicitTarget)
3692
- : undefined;
3693
- const explicitPoints = explicitPathPoints({
3694
- sourceNode: explicitSourceNode,
3695
- targetNode: explicitTargetNode,
3696
- startPoint,
3697
- endPoint,
3698
- });
3699
- const sourcePoint = hasExplicitEndpoints
3700
- ? explicitPoints.sourcePoint || fallbackSourcePoint
3701
- : fallbackSourcePoint;
3702
- const targetPoint = hasExplicitEndpoints
3703
- ? explicitPoints.targetPoint || fallbackTargetPoint
3704
- : fallbackTargetPoint;
3705
-
3706
- if (!sourcePoint || !targetPoint) return null;
3707
-
3708
- const resolvedSourceNode =
3709
- explicitSourceNode ||
3710
- closestNodeToPathPoint(nodeCandidates, sourcePoint, "source");
3711
- const resolvedTargetNode =
3712
- explicitTargetNode ||
3713
- closestNodeToPathPoint(nodeCandidates, targetPoint, "target");
3714
- const reverseExplicitLineage =
3715
- graphType === "lineage" && hasExplicitEndpoints;
3716
- const sourceNode = reverseExplicitLineage
3717
- ? resolvedTargetNode
3718
- : resolvedSourceNode;
3719
- const targetNode = reverseExplicitLineage
3720
- ? resolvedSourceNode
3721
- : resolvedTargetNode;
3722
- const sourceFieldPoint = reverseExplicitLineage ? targetPoint : sourcePoint;
3723
- const targetFieldPoint = reverseExplicitLineage ? sourcePoint : targetPoint;
3724
-
3725
- if (
3726
- !sourceNode ||
3727
- !targetNode ||
3728
- sourceNode.id === targetNode.id ||
3729
- !nodeById.has(sourceNode.id) ||
3730
- !nodeById.has(targetNode.id)
3731
- ) {
3732
- return null;
3733
- }
3734
-
3735
- const hasMetadata = !_.isEmpty(edge.metadata);
3736
-
3737
- const id = normalizeId(
3738
- edge.id || `legacy-path-${sourceNode.id}-${targetNode.id}-${index}`,
3739
- );
3740
-
3741
- return {
3742
- id,
3743
- source: sourceNode.id,
3744
- sourceHandle: "end-node",
3745
- target: targetNode.id,
3746
- targetHandle: "start-node",
3747
- type: "colored",
3748
- zIndex: LINEAGE_EDGE_Z_INDEX,
3749
- data: {
3750
- animated: false,
3751
- flowDirection: "forward",
3752
- label: edge.label,
3753
- edgePath: "orthogonal",
3754
- explicitEndpoints: Boolean(hasExplicitEndpoints),
3755
- legacyPath: true,
3756
- legacySourceY: sourceFieldPoint.y,
3757
- legacyTargetY: targetFieldPoint.y,
3758
- markerPosition: "target",
3759
- metadata: edge.metadata,
3760
- relationType: lineageRelationType(edge),
3761
- originalEdgeIds: [id],
3762
- onClick: hasMetadata
3763
- ? () =>
3764
- onEdgeSelect(
3765
- lineageEdgeContext({
3766
- edge,
3767
- id,
3768
- nodeById,
3769
- source: sourceNode.id,
3770
- target: targetNode.id,
3771
- }),
3772
- )
3773
- : undefined,
3774
- primaryType: "__without_system_type__",
3775
- restrictive: isRestrictiveLineageEdge(edge),
3776
- showSourceDot: false,
3777
- showTargetArrow: true,
3778
- sourceFieldIndex: fieldIndexFromPathPoint(sourceNode, sourceFieldPoint),
3779
- targetFieldIndex: fieldIndexFromPathPoint(targetNode, targetFieldPoint),
3780
- },
3781
- style: {
3782
- stroke: lineageEdgeColor(graphType),
3783
- strokeOpacity: 0.95,
3784
- strokeWidth: hasMetadata ? 2 : 1.8,
3785
- },
3786
- };
3787
- };
3788
-
3789
- export const legacyPathEdges =
3790
- (
3791
- nodeCandidates,
3792
- nodeById,
3793
- onEdgeSelect,
3794
- resolveId = (id) => id,
3795
- graphType = "impact",
3796
- ) =>
3797
- (edge, index) => {
3798
- const segments = legacyPathSegments(edge?.path);
3799
-
3800
- if (_.isEmpty(segments)) return [];
3801
-
3802
- return segments
3803
- .map((segment, segmentIndex) =>
3804
- legacyPathEdge(
3805
- nodeCandidates,
3806
- nodeById,
3807
- onEdgeSelect,
3808
- resolveId,
3809
- graphType,
3810
- )(
3811
- {
3812
- ...edge,
3813
- id: normalizeId(
3814
- edge.id
3815
- ? `${edge.id}-${segmentIndex}`
3816
- : `legacy-path-${index}-${segmentIndex}`,
3817
- ),
3818
- path: segment,
3819
- },
3820
- segmentIndex,
3821
- ),
3822
- )
3823
- .filter(Boolean);
3824
- };
3825
-
3826
120
  export const LineageGraph = ({
3827
121
  defaultExpansionDepth = LINEAGE_DEFAULT_EXPANSION_DEPTH,
3828
122
  height = "80vh",