@truedat/dd 8.12.10 → 8.12.11
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/components/LineageGraph.js +104 -79
- package/src/components/LineageGraphNode.js +53 -0
- package/src/components/LineageGraphToolbar.js +45 -33
- package/src/components/__tests__/LineageGraph.spec.js +16 -0
- package/src/components/__tests__/LineageGraphInteraction.spec.js +1 -1
- package/src/components/__tests__/LineageGraphLayout.spec.js +21 -0
- package/src/components/__tests__/LineageGraphNode.spec.js +56 -0
- package/src/components/__tests__/LineageGraphPin.spec.js +338 -0
- package/src/components/__tests__/LineageGraphToolbar.spec.js +54 -2
- package/src/components/__tests__/LineageInspectorPanel.spec.js +1 -1
- package/src/styles/LineageGraph.less +81 -24
- package/src/utils/__tests__/lineageLayout.spec.js +68 -0
- package/src/utils/lineageGraph.js +5 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/dd",
|
|
3
|
-
"version": "8.12.
|
|
3
|
+
"version": "8.12.11",
|
|
4
4
|
"description": "Truedat Web Data Dictionary",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"@testing-library/jest-dom": "^6.6.3",
|
|
55
55
|
"@testing-library/react": "^16.3.0",
|
|
56
56
|
"@testing-library/user-event": "^14.6.1",
|
|
57
|
-
"@truedat/test": "8.12.
|
|
57
|
+
"@truedat/test": "8.12.11",
|
|
58
58
|
"identity-obj-proxy": "^3.0.0",
|
|
59
59
|
"jest": "^29.7.0",
|
|
60
60
|
"redux-saga-test-plan": "^4.0.6"
|
|
@@ -90,5 +90,5 @@
|
|
|
90
90
|
"svg-pan-zoom": "^3.6.2",
|
|
91
91
|
"swr": "^2.3.3"
|
|
92
92
|
},
|
|
93
|
-
"gitHead": "
|
|
93
|
+
"gitHead": "48671e0f1467f7713019ab8d729818c741fa9bc7"
|
|
94
94
|
}
|
|
@@ -4,7 +4,6 @@ import PropTypes from "prop-types";
|
|
|
4
4
|
import { connect } from "react-redux";
|
|
5
5
|
import { FormattedMessage, useIntl } from "react-intl";
|
|
6
6
|
import Graph from "@truedat/core/components/Graph";
|
|
7
|
-
import LineageGroupNode from "@truedat/core/components/graph/LineageGroupNode";
|
|
8
7
|
import { EdgeHoverContext } from "@truedat/core/components/graph/ColoredEdge";
|
|
9
8
|
import { cancelPoll } from "../routines";
|
|
10
9
|
import { layoutVisibleLineageGraph } from "../utils/lineageLayout";
|
|
@@ -63,7 +62,7 @@ import {
|
|
|
63
62
|
spreadLegacyFieldIndexes,
|
|
64
63
|
legacyPathEdges,
|
|
65
64
|
} from "../utils/lineageGraph";
|
|
66
|
-
import
|
|
65
|
+
import LineageGraphNode from "./LineageGraphNode";
|
|
67
66
|
import LineageModuleButton from "./LineageModuleButton";
|
|
68
67
|
import LineageGraphToolbar, { visibleLegendKeys } from "./LineageGraphToolbar";
|
|
69
68
|
|
|
@@ -114,8 +113,8 @@ export {
|
|
|
114
113
|
} from "../utils/lineageGraph";
|
|
115
114
|
|
|
116
115
|
const LINEAGE_NODE_TYPES = {
|
|
117
|
-
lineageGroup:
|
|
118
|
-
lineageResource:
|
|
116
|
+
lineageGroup: LineageGraphNode,
|
|
117
|
+
lineageResource: LineageGraphNode,
|
|
119
118
|
};
|
|
120
119
|
|
|
121
120
|
export const LineageGraph = ({
|
|
@@ -286,16 +285,11 @@ export const LineageGraph = ({
|
|
|
286
285
|
setFitViewRequestKey((requestKey) => (requestKey || 0) + 1);
|
|
287
286
|
}, []);
|
|
288
287
|
|
|
289
|
-
const handleEdgeSelect = useCallback(
|
|
290
|
-
(edgeContext
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
setFocusNodeId(null);
|
|
295
|
-
setActivePanel("inspector");
|
|
296
|
-
},
|
|
297
|
-
[pinnedGraphNodeId],
|
|
298
|
-
);
|
|
288
|
+
const handleEdgeSelect = useCallback((edgeContext) => {
|
|
289
|
+
setSelectedContext({ ...edgeContext, iconName: "exchange" });
|
|
290
|
+
setFocusNodeId(null);
|
|
291
|
+
setActivePanel("inspector");
|
|
292
|
+
}, []);
|
|
299
293
|
|
|
300
294
|
const resolvedNodeWidth = useMemo(
|
|
301
295
|
() =>
|
|
@@ -878,38 +872,41 @@ export const LineageGraph = ({
|
|
|
878
872
|
);
|
|
879
873
|
}, [layoutedGraphNodes, requestedGraphNodeIds, searchableGraphNodes]);
|
|
880
874
|
const selectedGraphNodeId =
|
|
881
|
-
pinnedGraphNodeId ||
|
|
882
875
|
(selectedContext?.type === "node" && selectedContext.node?.graphNodeId) ||
|
|
883
876
|
null;
|
|
877
|
+
const visiblePinnedGraphNodeId =
|
|
878
|
+
preparedTopology?.originalToVisibleId.get(pinnedGraphNodeId) ||
|
|
879
|
+
pinnedGraphNodeId;
|
|
880
|
+
const focusGraphNodeId = visiblePinnedGraphNodeId || selectedGraphNodeId;
|
|
884
881
|
const selectedGraphEdgeId =
|
|
885
882
|
selectedContext?.type === "edge" ? selectedContext.edge?.id : null;
|
|
886
883
|
const focusActive =
|
|
887
|
-
Boolean(
|
|
884
|
+
Boolean(focusGraphNodeId || selectedGraphEdgeId) || toolbarFilterActive;
|
|
888
885
|
const focusedSelectedNodeId = selectedGraphNodeId;
|
|
889
886
|
const { focusedEdgeIds, focusedNodeIds } = useMemo(() => {
|
|
890
|
-
if (selectedGraphEdgeId) {
|
|
887
|
+
if (!pinnedGraphNodeId && selectedGraphEdgeId) {
|
|
891
888
|
const edge = graphEdges.find(({ id }) => id === selectedGraphEdgeId);
|
|
892
889
|
return {
|
|
893
890
|
focusedEdgeIds: new Set(edge ? [edge.id] : []),
|
|
894
891
|
focusedNodeIds: new Set(edge ? [edge.source, edge.target] : []),
|
|
895
892
|
};
|
|
896
893
|
}
|
|
897
|
-
if (!
|
|
894
|
+
if (!focusGraphNodeId) {
|
|
898
895
|
return toolbarFilterActive
|
|
899
896
|
? focusedLineageGraphForNodeSet(toolbarMatchedNodeIds, graphEdges)
|
|
900
897
|
: { focusedEdgeIds: new Set(), focusedNodeIds: new Set() };
|
|
901
898
|
}
|
|
902
899
|
|
|
903
|
-
const selectedNode = lineageGraphIndex.nodeById.get(
|
|
900
|
+
const selectedNode = lineageGraphIndex.nodeById.get(focusGraphNodeId);
|
|
904
901
|
const seedIds = new Set(
|
|
905
902
|
preparedTopology?.originalsByVisibleId
|
|
906
|
-
.get(
|
|
907
|
-
?.map(({ id }) => id) || [
|
|
903
|
+
.get(focusGraphNodeId)
|
|
904
|
+
?.map(({ id }) => id) || [focusGraphNodeId],
|
|
908
905
|
);
|
|
909
906
|
|
|
910
907
|
if (selectedNode?.type === "lineageGroup") {
|
|
911
908
|
lineageGraphIndex
|
|
912
|
-
.descendantIds(
|
|
909
|
+
.descendantIds(focusGraphNodeId)
|
|
913
910
|
.forEach((id) => seedIds.add(id));
|
|
914
911
|
}
|
|
915
912
|
|
|
@@ -936,15 +933,22 @@ export const LineageGraph = ({
|
|
|
936
933
|
focusedNodeIds: projectedNodeIds,
|
|
937
934
|
};
|
|
938
935
|
}, [
|
|
936
|
+
pinnedGraphNodeId,
|
|
939
937
|
graphEdges,
|
|
940
938
|
layoutedGraphNodes,
|
|
941
939
|
lineageGraphIndex,
|
|
942
940
|
preparedTopology,
|
|
943
|
-
|
|
941
|
+
focusGraphNodeId,
|
|
944
942
|
selectedGraphEdgeId,
|
|
945
943
|
toolbarFilterActive,
|
|
946
944
|
toolbarMatchedNodeIds,
|
|
947
945
|
]);
|
|
946
|
+
const unpinFocus = useCallback(() => {
|
|
947
|
+
setPinnedGraphNodeId(null);
|
|
948
|
+
setSelectedContext(null);
|
|
949
|
+
setActivePanel(null);
|
|
950
|
+
setFocusNodeId(null);
|
|
951
|
+
}, []);
|
|
948
952
|
const renderedGraphNodes = useMemo(
|
|
949
953
|
() =>
|
|
950
954
|
focusLineageNodes(
|
|
@@ -952,12 +956,14 @@ export const LineageGraph = ({
|
|
|
952
956
|
focusedNodeIds,
|
|
953
957
|
focusActive,
|
|
954
958
|
focusedSelectedNodeId,
|
|
959
|
+
visiblePinnedGraphNodeId,
|
|
955
960
|
).map((node) => ({
|
|
956
961
|
...node,
|
|
957
962
|
className: [
|
|
958
963
|
node.className,
|
|
959
964
|
queryOriginStates.has(node.id) ? "lineage-node--origin" : "",
|
|
960
965
|
node.id === focusedSelectedNodeId ? "lineage-node--selected" : "",
|
|
966
|
+
node.id === visiblePinnedGraphNodeId ? "lineage-node--pinned" : "",
|
|
961
967
|
]
|
|
962
968
|
.filter(Boolean)
|
|
963
969
|
.join(" "),
|
|
@@ -965,6 +971,8 @@ export const LineageGraph = ({
|
|
|
965
971
|
data: {
|
|
966
972
|
...node.data,
|
|
967
973
|
queryOrigin: queryOriginStates.get(node.id),
|
|
974
|
+
onUnpin:
|
|
975
|
+
node.id === visiblePinnedGraphNodeId ? unpinFocus : undefined,
|
|
968
976
|
connectedHandleIds: Array.from(
|
|
969
977
|
connectedHandleIdsByNode.get(node.id) || [],
|
|
970
978
|
),
|
|
@@ -975,6 +983,8 @@ export const LineageGraph = ({
|
|
|
975
983
|
focusActive,
|
|
976
984
|
focusedNodeIds,
|
|
977
985
|
focusedSelectedNodeId,
|
|
986
|
+
visiblePinnedGraphNodeId,
|
|
987
|
+
unpinFocus,
|
|
978
988
|
layoutedGraphNodes,
|
|
979
989
|
queryOriginStates,
|
|
980
990
|
],
|
|
@@ -985,8 +995,8 @@ export const LineageGraph = ({
|
|
|
985
995
|
graphEdges,
|
|
986
996
|
focusedEdgeIds,
|
|
987
997
|
focusActive,
|
|
988
|
-
Boolean(
|
|
989
|
-
|
|
998
|
+
Boolean(focusGraphNodeId),
|
|
999
|
+
focusGraphNodeId,
|
|
990
1000
|
selectedGraphEdgeId,
|
|
991
1001
|
).map((edge) => ({
|
|
992
1002
|
...edge,
|
|
@@ -1009,7 +1019,7 @@ export const LineageGraph = ({
|
|
|
1009
1019
|
focusActive,
|
|
1010
1020
|
focusedEdgeIds,
|
|
1011
1021
|
graphEdges,
|
|
1012
|
-
|
|
1022
|
+
focusGraphNodeId,
|
|
1013
1023
|
handleEdgeSelect,
|
|
1014
1024
|
visibleNodeById,
|
|
1015
1025
|
selectedGraphEdgeId,
|
|
@@ -1073,6 +1083,12 @@ export const LineageGraph = ({
|
|
|
1073
1083
|
() => new Map(searchableGraphNodes.map((node) => [node.id, node])),
|
|
1074
1084
|
[searchableGraphNodes],
|
|
1075
1085
|
);
|
|
1086
|
+
useEffect(() => {
|
|
1087
|
+
setPinnedGraphNodeId(null);
|
|
1088
|
+
setSelectedContext(null);
|
|
1089
|
+
setActivePanel(null);
|
|
1090
|
+
setFocusNodeId(null);
|
|
1091
|
+
}, [graphId, graphType]);
|
|
1076
1092
|
useEffect(() => {
|
|
1077
1093
|
if (
|
|
1078
1094
|
!initialViewState ||
|
|
@@ -1143,19 +1159,37 @@ export const LineageGraph = ({
|
|
|
1143
1159
|
searchableNodeById,
|
|
1144
1160
|
renderedGraphEdges,
|
|
1145
1161
|
]);
|
|
1162
|
+
const pinnedNode = searchableNodeById.get(visiblePinnedGraphNodeId);
|
|
1163
|
+
const pinnedContextNode = pinnedNode
|
|
1164
|
+
? selectedNodeFromGraphNode(pinnedNode, searchableNodeById)
|
|
1165
|
+
: null;
|
|
1166
|
+
const pinnedContext = pinnedContextNode
|
|
1167
|
+
? { type: "node", node: pinnedContextNode }
|
|
1168
|
+
: null;
|
|
1146
1169
|
useEffect(() => {
|
|
1147
|
-
if (
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1170
|
+
if (loading) return;
|
|
1171
|
+
if (
|
|
1172
|
+
pinnedGraphNodeId &&
|
|
1173
|
+
!searchableNodeById.has(visiblePinnedGraphNodeId) &&
|
|
1174
|
+
!lineageGraphIndex.nodeById.has(pinnedGraphNodeId)
|
|
1175
|
+
) {
|
|
1176
|
+
setPinnedGraphNodeId(null);
|
|
1177
|
+
}
|
|
1178
|
+
if (selectedGraphNodeId && !searchableNodeById.has(selectedGraphNodeId)) {
|
|
1179
|
+
setActivePanel(null);
|
|
1180
|
+
setSelectedContext(null);
|
|
1181
|
+
setFocusNodeId(null);
|
|
1182
|
+
}
|
|
1183
|
+
}, [
|
|
1184
|
+
loading,
|
|
1185
|
+
pinnedGraphNodeId,
|
|
1186
|
+
visiblePinnedGraphNodeId,
|
|
1187
|
+
lineageGraphIndex,
|
|
1188
|
+
searchableNodeById,
|
|
1189
|
+
selectedGraphNodeId,
|
|
1190
|
+
]);
|
|
1155
1191
|
const selectGraphNode = useCallback(
|
|
1156
1192
|
(node, { center = false } = {}) => {
|
|
1157
|
-
if (pinnedGraphNodeId) return;
|
|
1158
|
-
|
|
1159
1193
|
const selectedNode = selectedNodeFromGraphNode(node, searchableNodeById);
|
|
1160
1194
|
|
|
1161
1195
|
if (!selectedNode) return;
|
|
@@ -1176,7 +1210,7 @@ export const LineageGraph = ({
|
|
|
1176
1210
|
setFocusNodeId(center ? node.id : null);
|
|
1177
1211
|
if (center) setFocusNodeRequestKey((requestKey) => requestKey + 1);
|
|
1178
1212
|
},
|
|
1179
|
-
[
|
|
1213
|
+
[positionedLayout, searchableNodeById],
|
|
1180
1214
|
);
|
|
1181
1215
|
const handleNodeClick = useCallback(
|
|
1182
1216
|
(event, node) => {
|
|
@@ -1220,15 +1254,21 @@ export const LineageGraph = ({
|
|
|
1220
1254
|
|
|
1221
1255
|
if (!graphNodeId) return;
|
|
1222
1256
|
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1257
|
+
if (visiblePinnedGraphNodeId === graphNodeId) {
|
|
1258
|
+
unpinFocus();
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
setPinnedGraphNodeId(graphNodeId);
|
|
1262
|
+
}, [selectedContext, unpinFocus, visiblePinnedGraphNodeId]);
|
|
1263
|
+
const handleCenterNode = useCallback(
|
|
1264
|
+
(nodeId) => {
|
|
1265
|
+
const node = searchableNodeById.get(nodeId);
|
|
1266
|
+
|
|
1267
|
+
if (!node) return;
|
|
1268
|
+
selectGraphNode(node, { center: true });
|
|
1269
|
+
},
|
|
1270
|
+
[searchableNodeById, selectGraphNode],
|
|
1271
|
+
);
|
|
1232
1272
|
const handleNodeDoubleClick = useCallback(
|
|
1233
1273
|
(event, node) => {
|
|
1234
1274
|
event?.preventDefault();
|
|
@@ -1245,19 +1285,11 @@ export const LineageGraph = ({
|
|
|
1245
1285
|
return;
|
|
1246
1286
|
}
|
|
1247
1287
|
|
|
1248
|
-
if (pinnedGraphNodeId) {
|
|
1249
|
-
if (node?.id === pinnedGraphNodeId) {
|
|
1250
|
-
setActivePanel("inspector");
|
|
1251
|
-
}
|
|
1252
|
-
return;
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
1288
|
selectGraphNode(node);
|
|
1256
1289
|
},
|
|
1257
1290
|
[
|
|
1258
1291
|
collapsibleGroupIds,
|
|
1259
1292
|
doubleClickExpandsNodes,
|
|
1260
|
-
pinnedGraphNodeId,
|
|
1261
1293
|
selectGraphNode,
|
|
1262
1294
|
toggleGroupCollapse,
|
|
1263
1295
|
],
|
|
@@ -1270,7 +1302,9 @@ export const LineageGraph = ({
|
|
|
1270
1302
|
? activeSurfaceRef.current
|
|
1271
1303
|
: stageRef.current
|
|
1272
1304
|
)
|
|
1273
|
-
?.querySelector(
|
|
1305
|
+
?.querySelector(
|
|
1306
|
+
"button.lineage-graph__dock-context-reopen, .lineage-graph__dock-context-clear",
|
|
1307
|
+
)
|
|
1274
1308
|
?.focus();
|
|
1275
1309
|
});
|
|
1276
1310
|
}, []);
|
|
@@ -1280,28 +1314,21 @@ export const LineageGraph = ({
|
|
|
1280
1314
|
}, [focusContextButton]);
|
|
1281
1315
|
const clearSelectedContext = useCallback(() => {
|
|
1282
1316
|
setActivePanel(null);
|
|
1283
|
-
setPinnedGraphNodeId(null);
|
|
1284
1317
|
setSelectedContext(null);
|
|
1285
1318
|
setFocusNodeId(null);
|
|
1286
1319
|
}, []);
|
|
1287
|
-
const handlePanelChange = useCallback(
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
);
|
|
1320
|
+
const handlePanelChange = useCallback((panel) => setActivePanel(panel), []);
|
|
1321
|
+
const selectPinnedContext = () => {
|
|
1322
|
+
if (!pinnedContext) return;
|
|
1323
|
+
setSelectedContext(pinnedContext);
|
|
1324
|
+
setActivePanel("inspector");
|
|
1325
|
+
};
|
|
1294
1326
|
const handlePaneSelectionClear = useCallback(
|
|
1295
1327
|
(event) => {
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
setActivePanel(null);
|
|
1299
|
-
setPinnedGraphNodeId(null);
|
|
1300
|
-
setSelectedContext(null);
|
|
1301
|
-
setFocusNodeId(null);
|
|
1328
|
+
clearSelectedContext();
|
|
1302
1329
|
onPaneClick?.(event);
|
|
1303
1330
|
},
|
|
1304
|
-
[
|
|
1331
|
+
[clearSelectedContext, onPaneClick],
|
|
1305
1332
|
);
|
|
1306
1333
|
|
|
1307
1334
|
useEffect(() => {
|
|
@@ -1317,21 +1344,17 @@ export const LineageGraph = ({
|
|
|
1317
1344
|
) {
|
|
1318
1345
|
return;
|
|
1319
1346
|
}
|
|
1320
|
-
if (
|
|
1321
|
-
setActivePanel(null);
|
|
1347
|
+
if (pinnedGraphNodeId) clearSelectedContext();
|
|
1348
|
+
else setActivePanel(null);
|
|
1322
1349
|
};
|
|
1323
1350
|
const closeOnEscape = (event) => {
|
|
1324
1351
|
if (event.key !== "Escape") return;
|
|
1325
1352
|
|
|
1326
|
-
if (pinnedGraphNodeId && !activePanel) return;
|
|
1327
1353
|
event.preventDefault();
|
|
1328
1354
|
event.stopPropagation();
|
|
1329
|
-
if (pinnedGraphNodeId) {
|
|
1330
|
-
setActivePanel(null);
|
|
1331
|
-
return;
|
|
1332
|
-
}
|
|
1333
1355
|
if (selectedContext) {
|
|
1334
1356
|
clearSelectedContext();
|
|
1357
|
+
focusContextButton();
|
|
1335
1358
|
return;
|
|
1336
1359
|
}
|
|
1337
1360
|
setActivePanel(null);
|
|
@@ -1348,9 +1371,9 @@ export const LineageGraph = ({
|
|
|
1348
1371
|
}, [
|
|
1349
1372
|
activePanel,
|
|
1350
1373
|
selectedContext,
|
|
1374
|
+
pinnedGraphNodeId,
|
|
1351
1375
|
clearSelectedContext,
|
|
1352
1376
|
focusContextButton,
|
|
1353
|
-
pinnedGraphNodeId,
|
|
1354
1377
|
]);
|
|
1355
1378
|
|
|
1356
1379
|
const shouldRenderGraph = !loading && hasGraph;
|
|
@@ -1376,6 +1399,8 @@ export const LineageGraph = ({
|
|
|
1376
1399
|
canExpandAll={canExpandAll}
|
|
1377
1400
|
collapsible={positionedLayout && Boolean(collapsibleGroupIds.length)}
|
|
1378
1401
|
context={selectedContext}
|
|
1402
|
+
pinnedContext={pinnedContext}
|
|
1403
|
+
onPinnedContextSelect={selectPinnedContext}
|
|
1379
1404
|
doubleClickExpandsNodes={doubleClickExpandsNodes}
|
|
1380
1405
|
visibleLegend={visibleLegend}
|
|
1381
1406
|
legendItems={visibleLegendItems}
|
|
@@ -1397,7 +1422,7 @@ export const LineageGraph = ({
|
|
|
1397
1422
|
onCenterNode={handleCenterNode}
|
|
1398
1423
|
pinned={
|
|
1399
1424
|
selectedContext?.type === "node" &&
|
|
1400
|
-
|
|
1425
|
+
visiblePinnedGraphNodeId === selectedContext.node?.graphNodeId
|
|
1401
1426
|
}
|
|
1402
1427
|
expansionDepth={expansionDepth}
|
|
1403
1428
|
maxExpansionDepth={effectiveMaxDepth}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { memo } from "react";
|
|
2
|
+
import PropTypes from "prop-types";
|
|
3
|
+
import { useIntl } from "react-intl";
|
|
4
|
+
import { Pin } from "lucide-react";
|
|
5
|
+
import LineageGroupNode from "@truedat/core/components/graph/LineageGroupNode";
|
|
6
|
+
import LineageResourceNode from "./LineageResourceNode";
|
|
7
|
+
|
|
8
|
+
const stopPropagation = (event) => event.stopPropagation();
|
|
9
|
+
|
|
10
|
+
const LineageGraphNode = ({ type, data, ...props }) => {
|
|
11
|
+
const { formatMessage } = useIntl();
|
|
12
|
+
const Node = type === "lineageGroup" ? LineageGroupNode : LineageResourceNode;
|
|
13
|
+
const unpinLabel = formatMessage({
|
|
14
|
+
id: "lineage.context_menu.unpin_focus",
|
|
15
|
+
defaultMessage: "Unpin focus",
|
|
16
|
+
});
|
|
17
|
+
const unpin = (event) => {
|
|
18
|
+
event.preventDefault();
|
|
19
|
+
event.stopPropagation();
|
|
20
|
+
event.currentTarget.closest(".react-flow__node")?.focus();
|
|
21
|
+
data.onUnpin();
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<>
|
|
26
|
+
<Node {...props} data={data} />
|
|
27
|
+
{data.onUnpin ? (
|
|
28
|
+
<button
|
|
29
|
+
aria-label={unpinLabel}
|
|
30
|
+
aria-pressed="true"
|
|
31
|
+
className="lineage-graph__node-unpin nodrag nopan"
|
|
32
|
+
onClick={unpin}
|
|
33
|
+
onDoubleClick={stopPropagation}
|
|
34
|
+
onPointerDown={stopPropagation}
|
|
35
|
+
onMouseDown={stopPropagation}
|
|
36
|
+
onKeyDown={stopPropagation}
|
|
37
|
+
onKeyUp={stopPropagation}
|
|
38
|
+
title={unpinLabel}
|
|
39
|
+
type="button"
|
|
40
|
+
>
|
|
41
|
+
<Pin size={14} aria-hidden="true" />
|
|
42
|
+
</button>
|
|
43
|
+
) : null}
|
|
44
|
+
</>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
LineageGraphNode.propTypes = {
|
|
49
|
+
type: PropTypes.string,
|
|
50
|
+
data: PropTypes.object.isRequired,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export default memo(LineageGraphNode);
|
|
@@ -539,6 +539,8 @@ export const LineageGraphToolbar = ({
|
|
|
539
539
|
onUnpin,
|
|
540
540
|
onCenterNode,
|
|
541
541
|
pinned,
|
|
542
|
+
pinnedContext,
|
|
543
|
+
onPinnedContextSelect,
|
|
542
544
|
expansionDepth = 4,
|
|
543
545
|
maxExpansionDepth = expansionDepth,
|
|
544
546
|
searchInput = "",
|
|
@@ -551,14 +553,22 @@ export const LineageGraphToolbar = ({
|
|
|
551
553
|
const { formatMessage } = useIntl();
|
|
552
554
|
const [depthPopoverOpen, setDepthPopoverOpen] = useState(false);
|
|
553
555
|
const [settingsPopoverOpen, setSettingsPopoverOpen] = useState(false);
|
|
554
|
-
const
|
|
555
|
-
const
|
|
556
|
-
const
|
|
556
|
+
const dockContext = context || pinnedContext;
|
|
557
|
+
const pinnedOnly = !context && Boolean(pinnedContext);
|
|
558
|
+
const ContextSummary = pinnedOnly ? "button" : "div";
|
|
559
|
+
const dockPinned = pinned || pinnedOnly;
|
|
560
|
+
const hasContext = Boolean(dockContext);
|
|
561
|
+
const inspectorOpen = Boolean(context) && activePanel === "inspector";
|
|
562
|
+
const selectedLabel = contextLabel(dockContext);
|
|
557
563
|
const selectedTypeLabel = hasContext
|
|
558
|
-
? contextTypeLabel(
|
|
564
|
+
? contextTypeLabel(dockContext, formatMessage)
|
|
559
565
|
: "";
|
|
560
566
|
const togglePanel = (panel) =>
|
|
561
567
|
onPanelChange(activePanel === panel ? null : panel);
|
|
568
|
+
const unpinLabel = formatMessage({
|
|
569
|
+
id: "lineage.context_menu.unpin_focus",
|
|
570
|
+
defaultMessage: "Unpin focus",
|
|
571
|
+
});
|
|
562
572
|
const clearContextLabel = formatMessage(
|
|
563
573
|
{
|
|
564
574
|
id: "lineage.graph.toolbar.context.clear",
|
|
@@ -566,6 +576,17 @@ export const LineageGraphToolbar = ({
|
|
|
566
576
|
},
|
|
567
577
|
{ name: selectedLabel },
|
|
568
578
|
);
|
|
579
|
+
const unpinButton = (
|
|
580
|
+
<button
|
|
581
|
+
aria-label={unpinLabel}
|
|
582
|
+
aria-pressed="true"
|
|
583
|
+
className="lineage-graph__dock-context-unpin"
|
|
584
|
+
onClick={onUnpin}
|
|
585
|
+
type="button"
|
|
586
|
+
>
|
|
587
|
+
<DockIcon name="pin" />
|
|
588
|
+
</button>
|
|
589
|
+
);
|
|
569
590
|
const visibleLevelsLabel = formatMessage({
|
|
570
591
|
id: "lineage.graph.controls.visible_levels",
|
|
571
592
|
defaultMessage: "Visible levels",
|
|
@@ -592,7 +613,7 @@ export const LineageGraphToolbar = ({
|
|
|
592
613
|
hasContext ? " lineage-graph__dock-context--selected" : ""
|
|
593
614
|
}${
|
|
594
615
|
inspectorOpen ? " lineage-graph__dock-context--inspector-open" : ""
|
|
595
|
-
}${
|
|
616
|
+
}${dockPinned ? " lineage-graph__dock-context--pinned" : ""}`}
|
|
596
617
|
>
|
|
597
618
|
{inspectorOpen ? (
|
|
598
619
|
<LineageInspectorPanel
|
|
@@ -610,28 +631,16 @@ export const LineageGraphToolbar = ({
|
|
|
610
631
|
className={`lineage-graph__dock-context-bar${
|
|
611
632
|
inspectorOpen ? " lineage-graph__dock-context-bar--expanded" : ""
|
|
612
633
|
}${
|
|
613
|
-
|
|
634
|
+
dockContext?.type === "edge"
|
|
614
635
|
? " lineage-graph__dock-context-bar--edge"
|
|
615
636
|
: ""
|
|
616
637
|
}`}
|
|
617
638
|
>
|
|
618
|
-
{
|
|
619
|
-
|
|
620
|
-
aria-label={formatMessage({
|
|
621
|
-
id: "lineage.context_menu.unpin_focus",
|
|
622
|
-
defaultMessage: "Unpin focus",
|
|
623
|
-
})}
|
|
624
|
-
aria-pressed="true"
|
|
625
|
-
className="lineage-graph__dock-context-unpin"
|
|
626
|
-
onClick={onUnpin}
|
|
627
|
-
type="button"
|
|
628
|
-
>
|
|
629
|
-
<DockIcon name="pin" />
|
|
630
|
-
</button>
|
|
631
|
-
) : null}
|
|
632
|
-
<div
|
|
633
|
-
aria-live="polite"
|
|
639
|
+
{pinnedOnly ? unpinButton : null}
|
|
640
|
+
<ContextSummary
|
|
634
641
|
className="lineage-graph__dock-context-reopen lineage-graph__dock-context-button"
|
|
642
|
+
onClick={pinnedOnly ? onPinnedContextSelect : undefined}
|
|
643
|
+
type={pinnedOnly ? "button" : undefined}
|
|
635
644
|
>
|
|
636
645
|
<span
|
|
637
646
|
aria-live="polite"
|
|
@@ -641,7 +650,7 @@ export const LineageGraphToolbar = ({
|
|
|
641
650
|
aria-hidden="true"
|
|
642
651
|
className="lineage-graph__dock-context-summary-icon"
|
|
643
652
|
>
|
|
644
|
-
<DockIcon name={contextIcon(
|
|
653
|
+
<DockIcon name={contextIcon(dockContext)} />
|
|
645
654
|
</span>
|
|
646
655
|
<span className="lineage-graph__dock-context-copy">
|
|
647
656
|
<span className="lineage-graph__dock-context-label">
|
|
@@ -652,15 +661,17 @@ export const LineageGraphToolbar = ({
|
|
|
652
661
|
</span>
|
|
653
662
|
</span>
|
|
654
663
|
</span>
|
|
655
|
-
</
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
+
</ContextSummary>
|
|
665
|
+
{context ? (
|
|
666
|
+
<button
|
|
667
|
+
aria-label={clearContextLabel}
|
|
668
|
+
className="lineage-graph__dock-context-clear"
|
|
669
|
+
onClick={onClearContext}
|
|
670
|
+
type="button"
|
|
671
|
+
>
|
|
672
|
+
<DockIcon name="close" />
|
|
673
|
+
</button>
|
|
674
|
+
) : null}
|
|
664
675
|
</div>
|
|
665
676
|
) : (
|
|
666
677
|
<LineageToolButton
|
|
@@ -680,7 +691,6 @@ export const LineageGraphToolbar = ({
|
|
|
680
691
|
<LineageToolButton
|
|
681
692
|
active={activePanel === "search"}
|
|
682
693
|
aria-expanded={activePanel === "search"}
|
|
683
|
-
disabled={pinned}
|
|
684
694
|
icon="search"
|
|
685
695
|
label={formatMessage({
|
|
686
696
|
id: "lineage.graph.toolbar.search.header",
|
|
@@ -884,6 +894,8 @@ LineageGraphToolbar.propTypes = {
|
|
|
884
894
|
onUnpin: PropTypes.func,
|
|
885
895
|
onCenterNode: PropTypes.func,
|
|
886
896
|
pinned: PropTypes.bool,
|
|
897
|
+
pinnedContext: PropTypes.object,
|
|
898
|
+
onPinnedContextSelect: PropTypes.func,
|
|
887
899
|
expansionDepth: PropTypes.number,
|
|
888
900
|
maxExpansionDepth: PropTypes.number,
|
|
889
901
|
searchInput: PropTypes.string,
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
collapseLineageNodes,
|
|
3
3
|
expandLineageDescendantsUntilOversized,
|
|
4
4
|
focusLineageEdges,
|
|
5
|
+
focusLineageNodes,
|
|
5
6
|
focusedLineageGraphForNodeSet,
|
|
6
7
|
lineageDirectChildCounts,
|
|
7
8
|
lineageEdge,
|
|
@@ -894,3 +895,18 @@ describe("lineageCatalogState", () => {
|
|
|
894
895
|
).toBe("deleted");
|
|
895
896
|
});
|
|
896
897
|
});
|
|
898
|
+
|
|
899
|
+
describe("focusLineageNodes with a pinned node", () => {
|
|
900
|
+
it("keeps an unrelated selection visible without losing the pinned focus", () => {
|
|
901
|
+
const nodes = ["A", "B", "C"].map((id) => ({
|
|
902
|
+
id,
|
|
903
|
+
type: "lineageResource",
|
|
904
|
+
data: {},
|
|
905
|
+
}));
|
|
906
|
+
const result = focusLineageNodes(nodes, new Set(), true, "B", "A");
|
|
907
|
+
expect(result[0].data.focusDimmed).toBe(false);
|
|
908
|
+
expect(result[1].data.focusSelected).toBe(true);
|
|
909
|
+
expect(result[1].data.focusDimmed).toBe(false);
|
|
910
|
+
expect(result[2].data.focusDimmed).toBe(true);
|
|
911
|
+
});
|
|
912
|
+
});
|
|
@@ -175,7 +175,7 @@ it("restores groups, fields, depth and a pinned selection without reapplying def
|
|
|
175
175
|
},
|
|
176
176
|
});
|
|
177
177
|
expect(screen.getAllByRole("button", { name: "Unpin focus" })).toHaveLength(
|
|
178
|
-
|
|
178
|
+
1,
|
|
179
179
|
);
|
|
180
180
|
fireEvent.click(
|
|
181
181
|
screen.getByRole("button", { name: "View in Lineage module" }),
|
|
@@ -455,6 +455,27 @@ describe("layoutVisibleLineageGraph", () => {
|
|
|
455
455
|
});
|
|
456
456
|
|
|
457
457
|
describe("positioned lineage layout", () => {
|
|
458
|
+
it("drops a route from a previous layout when its endpoint is unavailable", () => {
|
|
459
|
+
const source = groupNode("source", 0, 0, 288, 120);
|
|
460
|
+
const [edge] = planLineageEdgeRoutes(
|
|
461
|
+
[
|
|
462
|
+
{
|
|
463
|
+
id: "e1",
|
|
464
|
+
source: "source",
|
|
465
|
+
target: "missing",
|
|
466
|
+
data: {
|
|
467
|
+
edgeOffset: 12,
|
|
468
|
+
routeGuide: { kind: "corridor", laneX: 400 },
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
],
|
|
472
|
+
[source],
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
expect(edge.data.routeGuide).toBeUndefined();
|
|
476
|
+
expect(edge.data.edgeOffset).toBeUndefined();
|
|
477
|
+
});
|
|
478
|
+
|
|
458
479
|
it("routes stacked siblings through the gap between their boxes", () => {
|
|
459
480
|
const group = groupNode("group", 0, 0, 360, 280);
|
|
460
481
|
const source = resourceNode("source", group, 24, 52, 288, 80);
|