@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.
- package/package.json +3 -3
- package/src/api/__tests__/queries.spec.js +17 -8
- package/src/api/queries.js +1 -1
- package/src/components/CatalogCustomViewCards.js +2 -2
- package/src/components/CatalogViewConfigs.js +1 -1
- package/src/components/EditDomainForm.js +1 -1
- package/src/components/GrantApprovalRuleDeleteButton.js +1 -1
- package/src/components/GrantApprovalRuleForm.js +2 -1
- package/src/components/GrantFilters.js +1 -1
- package/src/components/GrantFiltersLoader.js +1 -1
- package/src/components/GrantRemovalDirectButton.js +1 -1
- package/src/components/GrantRequest.js +1 -1
- package/src/components/GrantRequestCancel.js +1 -1
- package/src/components/GrantsPagination.js +1 -1
- package/src/components/LineageForm.js +1 -1
- package/src/components/LineageGraph.js +95 -3801
- package/src/components/PendingStructureNotesPagination.js +1 -1
- package/src/components/ReferenceDatasetActions.js +2 -1
- package/src/components/ReferenceDatasetFieldSelector.js +1 -1
- package/src/components/ReferenceDatasetForm.js +2 -1
- package/src/components/ReferenceDatasetRoutes.js +1 -1
- package/src/components/ReferenceDatasets.js +1 -1
- package/src/components/ReferenceDatasetsTable.js +1 -1
- package/src/components/StructureDeleteButton.js +1 -1
- package/src/components/StructureFields.js +1 -1
- package/src/components/StructureFieldsDropdown.js +1 -1
- package/src/components/StructureGrantCart.js +1 -1
- package/src/components/StructureGrantCartCheckout.js +1 -1
- package/src/components/StructureGrantCartUserSelector.js +1 -1
- package/src/components/StructureGrantDropdown.js +1 -1
- package/src/components/StructureGrantSummaryButton.js +1 -1
- package/src/components/StructureNav.js +1 -1
- package/src/components/StructureNoteActions.js +2 -1
- package/src/components/StructureNotesEdit.js +1 -1
- package/src/components/StructureStructureForm.js +1 -1
- package/src/components/StructureStructureLinks.js +2 -2
- package/src/components/StructureTabPane.js +1 -1
- package/src/components/StructureTags.js +1 -1
- package/src/components/StructureTypeForm.js +1 -1
- package/src/components/StructuresBulkUpdate.js +3 -5
- package/src/components/StructuresDomainsUploadOption.js +1 -1
- package/src/components/StructuresPagination.js +1 -1
- package/src/components/StructuresUploadOption.js +1 -1
- package/src/components/SystemCards.js +1 -1
- package/src/components/SystemForm.js +1 -1
- package/src/components/Tag.js +1 -1
- package/src/components/TagCards.js +2 -1
- package/src/components/TagForm.js +2 -1
- package/src/components/__tests__/GrantApprovalRuleConditions.spec.js +1 -1
- package/src/components/__tests__/GrantRemovalDirectButton.spec.js +2 -1
- package/src/components/__tests__/GrantRequestApprovalForm.spec.js +2 -1
- package/src/components/__tests__/GrantRequestBulkActions.spec.js +2 -1
- package/src/components/__tests__/GrantRequestBulkApprovalForm.spec.js +2 -4
- package/src/components/__tests__/GrantRequestBulkApprovalPopup.spec.js +1 -1
- package/src/components/__tests__/GrantRequestBulkRoleSelector.spec.js +2 -2
- package/src/components/__tests__/GrantRequestLoader.spec.js +1 -1
- package/src/components/__tests__/GrantRequestRow.spec.js +1 -1
- package/src/components/__tests__/GrantRequestSummary.spec.js +1 -1
- package/src/components/__tests__/GrantRequestsDownloadButton.spec.js +2 -1
- package/src/components/__tests__/GrantRequestsLabelResults.spec.js +1 -1
- package/src/components/__tests__/GrantRequestsLoader.spec.js +1 -1
- package/src/components/__tests__/ImplementationStructuresLoader.spec.js +1 -1
- package/src/components/__tests__/LineageDownloadButton.spec.js +2 -1
- package/src/components/__tests__/LineageGraph.spec.js +1 -1
- package/src/components/__tests__/LineageGraphLayout.spec.js +1 -1
- package/src/components/__tests__/LineageGroup.spec.js +2 -1
- package/src/components/__tests__/LineageHierarchy.spec.js +1 -1
- package/src/components/__tests__/LineageResource.spec.js +2 -1
- package/src/components/__tests__/LineageResourceNode.spec.js +1 -1
- package/src/components/__tests__/PendingStructureNotesLabelResults.spec.js +2 -1
- package/src/components/__tests__/PendingStructureNotesPagination.spec.js +1 -1
- package/src/components/__tests__/ProfileExecution.spec.js +2 -1
- package/src/components/__tests__/ProfileExecutionLoader.spec.js +2 -1
- package/src/components/__tests__/ProfileGroupLoader.spec.js +2 -1
- package/src/components/__tests__/ProfileGroupMessage.spec.js +2 -1
- package/src/components/__tests__/ReferenceDatasetTable.spec.js +1 -1
- package/src/components/__tests__/StructureDeleteButton.spec.js +1 -1
- package/src/components/__tests__/StructureEvents.spec.js +11 -1
- package/src/components/__tests__/StructureGrantRequestButton.spec.js +3 -3
- package/src/components/__tests__/StructureItemRoot.spec.js +2 -1
- package/src/components/__tests__/StructureLoader.spec.js +1 -1
- package/src/components/__tests__/StructureProfileButton.spec.js +3 -2
- package/src/components/__tests__/StructureRelationRow.spec.js +3 -3
- package/src/components/__tests__/StructureRelations.spec.js +2 -1
- package/src/components/__tests__/StructureRoles.spec.js +2 -1
- package/src/components/__tests__/StructureRow.spec.js +2 -1
- package/src/components/__tests__/StructureSelectorInputField.spec.js +2 -1
- package/src/components/__tests__/StructureTagForm.spec.js +2 -1
- package/src/components/__tests__/StructureTypeLoader.spec.js +2 -1
- package/src/components/__tests__/StructureTypesLoader.spec.js +2 -1
- package/src/components/__tests__/StructuresDownloadOption.spec.js +1 -1
- package/src/components/__tests__/StructuresEditableDownloadOption.spec.js +1 -1
- package/src/components/__tests__/StructuresLabelResults.spec.js +1 -1
- package/src/components/__tests__/StructuresLoader.spec.js +2 -1
- package/src/components/__tests__/StructuresUploadOption.spec.js +1 -1
- package/src/components/__tests__/SystemEditButton.spec.js +2 -1
- package/src/components/__tests__/SystemLoader.spec.js +2 -1
- package/src/components/__tests__/UserSearchFiltersLoader.spec.js +2 -1
- package/src/selectors/__tests__/getGrantRequestsSearchColumns.spec.js +1 -1
- package/src/styles/LineageGraph.less +3 -0
- package/src/utils/__tests__/lineageTopology.spec.js +1 -1
- 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
|
|
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
|
-
|
|
24
|
-
|
|
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",
|