@truedat/dd 8.13.8 → 8.14.1
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 +177 -30
- package/src/components/LineageGraphToolbar.js +174 -25
- package/src/components/__tests__/LineageGraph.spec.js +98 -0
- package/src/components/__tests__/LineageGraphInteraction.spec.js +278 -13
- package/src/components/__tests__/LineageGraphToolbar.spec.js +86 -0
- package/src/styles/LineageGraph.less +35 -0
- package/src/utils/__tests__/fixtures/lineageInternalDependencies.json +375 -0
- package/src/utils/__tests__/lineageInternalRoutes.spec.js +202 -0
- package/src/utils/__tests__/lineageLayout.spec.js +6 -6
- package/src/utils/__tests__/lineageViewPreferences.spec.js +27 -0
- package/src/utils/lineageGraph.js +56 -11
- package/src/utils/lineageInternalRoutes.js +389 -0
- package/src/utils/lineageLayout.js +32 -12
- package/src/utils/lineageViewPreferences.js +21 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import _ from "lodash/fp";
|
|
2
2
|
import PropTypes from "prop-types";
|
|
3
|
-
import { useState } from "react";
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
4
|
import { useIntl } from "react-intl";
|
|
5
5
|
import { Dropdown, Input, Popup } from "semantic-ui-react";
|
|
6
6
|
import {
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
ChevronLeft,
|
|
11
11
|
ChevronRight,
|
|
12
12
|
ClipboardList,
|
|
13
|
+
Columns2,
|
|
14
|
+
Columns3,
|
|
13
15
|
Copy,
|
|
14
16
|
Database,
|
|
15
17
|
Eye,
|
|
@@ -61,6 +63,8 @@ export const DOCK_ICONS = {
|
|
|
61
63
|
linkify: Link2,
|
|
62
64
|
certificate: Award,
|
|
63
65
|
"mouse pointer": MousePointer2,
|
|
66
|
+
"columns-2": Columns2,
|
|
67
|
+
"columns-3": Columns3,
|
|
64
68
|
compress: Minimize2,
|
|
65
69
|
"expand arrows alternate": Maximize2,
|
|
66
70
|
"chevron right": ChevronRight,
|
|
@@ -522,6 +526,8 @@ export const LineageGraphToolbar = ({
|
|
|
522
526
|
collapsible,
|
|
523
527
|
context,
|
|
524
528
|
doubleClickExpandsNodes = false,
|
|
529
|
+
dependencyLayout = "compact",
|
|
530
|
+
onDependencyLayoutChange,
|
|
525
531
|
legendItems = [],
|
|
526
532
|
visibleLegend = [],
|
|
527
533
|
onClosePanel,
|
|
@@ -541,6 +547,7 @@ export const LineageGraphToolbar = ({
|
|
|
541
547
|
pinned,
|
|
542
548
|
pinnedContext,
|
|
543
549
|
onPinnedContextSelect,
|
|
550
|
+
expansionDepthCustom = false,
|
|
544
551
|
expansionDepth = 4,
|
|
545
552
|
maxExpansionDepth = expansionDepth,
|
|
546
553
|
searchInput = "",
|
|
@@ -553,6 +560,47 @@ export const LineageGraphToolbar = ({
|
|
|
553
560
|
const { formatMessage } = useIntl();
|
|
554
561
|
const [depthPopoverOpen, setDepthPopoverOpen] = useState(false);
|
|
555
562
|
const [settingsPopoverOpen, setSettingsPopoverOpen] = useState(false);
|
|
563
|
+
|
|
564
|
+
const dockRef = useRef(null);
|
|
565
|
+
const depthRef = useRef(null);
|
|
566
|
+
const settingsRef = useRef(null);
|
|
567
|
+
const handleDepthEscape = useCallback(
|
|
568
|
+
(event) => {
|
|
569
|
+
if (event.key !== "Escape" || !depthPopoverOpen) return;
|
|
570
|
+
event.preventDefault();
|
|
571
|
+
event.stopPropagation();
|
|
572
|
+
setDepthPopoverOpen(false);
|
|
573
|
+
dockRef.current
|
|
574
|
+
?.querySelector(".lineage-graph__dock-depth-button")
|
|
575
|
+
?.focus();
|
|
576
|
+
},
|
|
577
|
+
[depthPopoverOpen],
|
|
578
|
+
);
|
|
579
|
+
useEffect(() => {
|
|
580
|
+
if (!depthPopoverOpen) return undefined;
|
|
581
|
+
depthRef.current?.querySelector("input")?.focus();
|
|
582
|
+
window.addEventListener("keydown", handleDepthEscape, true);
|
|
583
|
+
return () => window.removeEventListener("keydown", handleDepthEscape, true);
|
|
584
|
+
}, [depthPopoverOpen, handleDepthEscape]);
|
|
585
|
+
const handleSettingsEscape = useCallback(
|
|
586
|
+
(event) => {
|
|
587
|
+
if (event.key !== "Escape" || !settingsPopoverOpen) return;
|
|
588
|
+
event.preventDefault();
|
|
589
|
+
event.stopPropagation();
|
|
590
|
+
setSettingsPopoverOpen(false);
|
|
591
|
+
dockRef.current
|
|
592
|
+
?.querySelector(".lineage-graph__dock-settings-button")
|
|
593
|
+
?.focus();
|
|
594
|
+
},
|
|
595
|
+
[settingsPopoverOpen],
|
|
596
|
+
);
|
|
597
|
+
useEffect(() => {
|
|
598
|
+
if (!settingsPopoverOpen) return undefined;
|
|
599
|
+
settingsRef.current?.querySelector("input:checked, input")?.focus();
|
|
600
|
+
window.addEventListener("keydown", handleSettingsEscape, true);
|
|
601
|
+
return () =>
|
|
602
|
+
window.removeEventListener("keydown", handleSettingsEscape, true);
|
|
603
|
+
}, [handleSettingsEscape, settingsPopoverOpen]);
|
|
556
604
|
const dockContext = context || pinnedContext;
|
|
557
605
|
const pinnedOnly = !context && Boolean(pinnedContext);
|
|
558
606
|
const ContextSummary = pinnedOnly ? "button" : "div";
|
|
@@ -595,6 +643,16 @@ export const LineageGraphToolbar = ({
|
|
|
595
643
|
id: "lineage.graph.settings.header",
|
|
596
644
|
defaultMessage: "Graph settings",
|
|
597
645
|
});
|
|
646
|
+
const layoutToggleLabel = formatMessage({
|
|
647
|
+
id:
|
|
648
|
+
dependencyLayout === "compact"
|
|
649
|
+
? "lineage.graph.layout.switch_expanded"
|
|
650
|
+
: "lineage.graph.layout.switch_compact",
|
|
651
|
+
defaultMessage:
|
|
652
|
+
dependencyLayout === "compact"
|
|
653
|
+
? "Switch to expanded view"
|
|
654
|
+
: "Switch to compact view",
|
|
655
|
+
});
|
|
598
656
|
const doubleClickInfoLabel = formatMessage({
|
|
599
657
|
id: "lineage.graph.settings.double_click_info",
|
|
600
658
|
defaultMessage: "Double-click expands or collapses nodes",
|
|
@@ -603,6 +661,8 @@ export const LineageGraphToolbar = ({
|
|
|
603
661
|
return (
|
|
604
662
|
<div
|
|
605
663
|
className="lineage-graph__dock nodrag nopan nowheel"
|
|
664
|
+
ref={dockRef}
|
|
665
|
+
onKeyDown={handleSettingsEscape}
|
|
606
666
|
onClick={stopGraphInteraction}
|
|
607
667
|
onMouseDown={stopGraphInteraction}
|
|
608
668
|
onPointerDown={stopGraphInteraction}
|
|
@@ -706,7 +766,7 @@ export const LineageGraphToolbar = ({
|
|
|
706
766
|
size="mini"
|
|
707
767
|
tooltipPosition="left center"
|
|
708
768
|
/>
|
|
709
|
-
{collapsible ? (
|
|
769
|
+
{collapsible || onDependencyLayoutChange ? (
|
|
710
770
|
<>
|
|
711
771
|
<span className="lineage-graph__dock-divider" aria-hidden="true" />
|
|
712
772
|
{!simplified ? (
|
|
@@ -737,25 +797,78 @@ export const LineageGraphToolbar = ({
|
|
|
737
797
|
aria-label={settingsLabel}
|
|
738
798
|
className="lineage-graph__settings-popover"
|
|
739
799
|
role="dialog"
|
|
800
|
+
ref={settingsRef}
|
|
801
|
+
onKeyDown={handleSettingsEscape}
|
|
740
802
|
>
|
|
741
803
|
<span className="lineage-graph__settings-popover-header">
|
|
742
804
|
{settingsLabel}
|
|
743
805
|
</span>
|
|
744
|
-
|
|
745
|
-
<
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
806
|
+
{!simplified ? (
|
|
807
|
+
<label className="lineage-graph__settings-toggle">
|
|
808
|
+
<input
|
|
809
|
+
checked={doubleClickExpandsNodes}
|
|
810
|
+
onChange={(event) =>
|
|
811
|
+
onDoubleClickExpandsNodesChange?.(
|
|
812
|
+
event.target.checked,
|
|
813
|
+
)
|
|
814
|
+
}
|
|
815
|
+
type="checkbox"
|
|
816
|
+
/>
|
|
817
|
+
<span>{doubleClickInfoLabel}</span>
|
|
818
|
+
</label>
|
|
819
|
+
) : null}
|
|
754
820
|
</div>
|
|
755
821
|
}
|
|
756
822
|
className="lineage-graph__settings-popup"
|
|
757
823
|
/>
|
|
758
824
|
) : null}
|
|
825
|
+
</>
|
|
826
|
+
) : null}
|
|
827
|
+
{onDependencyLayoutChange ? (
|
|
828
|
+
<Popup
|
|
829
|
+
on={["hover", "focus"]}
|
|
830
|
+
mouseEnterDelay={TOOLTIP_DELAY}
|
|
831
|
+
mouseLeaveDelay={0}
|
|
832
|
+
position="left center"
|
|
833
|
+
header={formatMessage({
|
|
834
|
+
id: `lineage.graph.layout.${dependencyLayout === "compact" ? "expanded" : "compact"}`,
|
|
835
|
+
defaultMessage:
|
|
836
|
+
dependencyLayout === "compact" ? "Expanded view" : "Compact view",
|
|
837
|
+
})}
|
|
838
|
+
content={
|
|
839
|
+
<>
|
|
840
|
+
<p>
|
|
841
|
+
{formatMessage({
|
|
842
|
+
id:
|
|
843
|
+
dependencyLayout === "compact"
|
|
844
|
+
? "lineage.graph.layout.expanded_hint"
|
|
845
|
+
: "lineage.graph.layout.compact_hint",
|
|
846
|
+
defaultMessage:
|
|
847
|
+
dependencyLayout === "compact"
|
|
848
|
+
? "Shows internal dependencies between nodes in each group."
|
|
849
|
+
: "Reduces visual clutter. Select a node to explore its internal dependencies.",
|
|
850
|
+
})}
|
|
851
|
+
</p>
|
|
852
|
+
<strong>{layoutToggleLabel}</strong>
|
|
853
|
+
</>
|
|
854
|
+
}
|
|
855
|
+
trigger={
|
|
856
|
+
<LineageToolButton
|
|
857
|
+
className="lineage-graph__dock-layout-button"
|
|
858
|
+
icon={dependencyLayout === "compact" ? "columns-3" : "columns-2"}
|
|
859
|
+
label={layoutToggleLabel}
|
|
860
|
+
showTooltip={false}
|
|
861
|
+
onClick={() =>
|
|
862
|
+
onDependencyLayoutChange(
|
|
863
|
+
dependencyLayout === "compact" ? "expanded" : "compact",
|
|
864
|
+
)
|
|
865
|
+
}
|
|
866
|
+
/>
|
|
867
|
+
}
|
|
868
|
+
/>
|
|
869
|
+
) : null}
|
|
870
|
+
{collapsible ? (
|
|
871
|
+
<>
|
|
759
872
|
<Popup
|
|
760
873
|
on="click"
|
|
761
874
|
onClose={() => setDepthPopoverOpen(false)}
|
|
@@ -777,7 +890,9 @@ export const LineageGraphToolbar = ({
|
|
|
777
890
|
label={visibleLevelsLabel}
|
|
778
891
|
showTooltip={false}
|
|
779
892
|
>
|
|
780
|
-
<span
|
|
893
|
+
<span
|
|
894
|
+
className={`lineage-graph__dock-depth-value${expansionDepthCustom ? " is-custom" : ""}`}
|
|
895
|
+
>
|
|
781
896
|
{expansionDepth}
|
|
782
897
|
</span>
|
|
783
898
|
</LineageToolButton>
|
|
@@ -785,23 +900,54 @@ export const LineageGraphToolbar = ({
|
|
|
785
900
|
content={
|
|
786
901
|
<div
|
|
787
902
|
aria-label={visibleLevelsLabel}
|
|
788
|
-
className=
|
|
903
|
+
className={`lineage-graph__depth-popover${expansionDepthCustom ? " is-custom" : ""}`}
|
|
904
|
+
onClick={stopGraphInteraction}
|
|
905
|
+
onKeyDown={handleDepthEscape}
|
|
906
|
+
onMouseDown={stopGraphInteraction}
|
|
907
|
+
onPointerDown={stopGraphInteraction}
|
|
908
|
+
onWheel={stopGraphInteraction}
|
|
909
|
+
ref={depthRef}
|
|
789
910
|
role="dialog"
|
|
790
911
|
>
|
|
791
912
|
<div className="lineage-graph__depth-popover-header">
|
|
792
913
|
<span>{visibleLevelsLabel}</span>
|
|
793
914
|
</div>
|
|
794
|
-
<
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
915
|
+
<div className="lineage-graph__depth-slider-wrap">
|
|
916
|
+
<input
|
|
917
|
+
aria-label={visibleLevelsLabel}
|
|
918
|
+
aria-valuetext={
|
|
919
|
+
expansionDepthCustom
|
|
920
|
+
? formatMessage(
|
|
921
|
+
{
|
|
922
|
+
id: "lineage.graph.controls.custom_depth",
|
|
923
|
+
defaultMessage:
|
|
924
|
+
"Last selected level: {depth}. Nodes have been expanded or collapsed individually.",
|
|
925
|
+
},
|
|
926
|
+
{ depth: expansionDepth },
|
|
927
|
+
)
|
|
928
|
+
: undefined
|
|
929
|
+
}
|
|
930
|
+
className="lineage-graph__depth-slider"
|
|
931
|
+
type="range"
|
|
932
|
+
min={0}
|
|
933
|
+
max={maxExpansionDepth}
|
|
934
|
+
value={expansionDepth}
|
|
935
|
+
onChange={(event) =>
|
|
936
|
+
onExpansionDepthChange(event.target.value)
|
|
937
|
+
}
|
|
938
|
+
/>
|
|
939
|
+
{expansionDepthCustom ? (
|
|
940
|
+
<span
|
|
941
|
+
aria-hidden="true"
|
|
942
|
+
className="lineage-graph__depth-custom-marker"
|
|
943
|
+
style={{
|
|
944
|
+
left: `calc(8px + (100% - 16px) * ${maxExpansionDepth ? expansionDepth / maxExpansionDepth : 0})`,
|
|
945
|
+
}}
|
|
946
|
+
>
|
|
947
|
+
?
|
|
948
|
+
</span>
|
|
949
|
+
) : null}
|
|
950
|
+
</div>
|
|
805
951
|
<div
|
|
806
952
|
aria-hidden="true"
|
|
807
953
|
className="lineage-graph__depth-popover-scale"
|
|
@@ -877,6 +1023,8 @@ LineageGraphToolbar.propTypes = {
|
|
|
877
1023
|
collapsible: PropTypes.bool,
|
|
878
1024
|
context: PropTypes.object,
|
|
879
1025
|
doubleClickExpandsNodes: PropTypes.bool,
|
|
1026
|
+
dependencyLayout: PropTypes.oneOf(["compact", "expanded"]),
|
|
1027
|
+
onDependencyLayoutChange: PropTypes.func,
|
|
880
1028
|
legendItems: PropTypes.array,
|
|
881
1029
|
visibleLegend: PropTypes.array,
|
|
882
1030
|
onClosePanel: PropTypes.func.isRequired,
|
|
@@ -896,6 +1044,7 @@ LineageGraphToolbar.propTypes = {
|
|
|
896
1044
|
pinned: PropTypes.bool,
|
|
897
1045
|
pinnedContext: PropTypes.object,
|
|
898
1046
|
onPinnedContextSelect: PropTypes.func,
|
|
1047
|
+
expansionDepthCustom: PropTypes.bool,
|
|
899
1048
|
expansionDepth: PropTypes.number,
|
|
900
1049
|
maxExpansionDepth: PropTypes.number,
|
|
901
1050
|
searchInput: PropTypes.string,
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
lineageEdge,
|
|
9
9
|
lineageCatalogState,
|
|
10
10
|
lineageNodeContentWidth,
|
|
11
|
+
lineageTopologyNodes,
|
|
11
12
|
lineageNodeMatchesToolbarFilters,
|
|
12
13
|
lineageOversizedGroupIds,
|
|
13
14
|
lineagePositionedLayoutNodes,
|
|
@@ -310,6 +311,103 @@ describe("focusedLineageGraphForNodeSet", () => {
|
|
|
310
311
|
});
|
|
311
312
|
});
|
|
312
313
|
|
|
314
|
+
describe("sibling content widths", () => {
|
|
315
|
+
const shortGroup = { id: "short", container: true, z: 0 };
|
|
316
|
+
const longGroup = { id: "long", container: true, z: 0 };
|
|
317
|
+
const nested = {
|
|
318
|
+
id: "nested",
|
|
319
|
+
parentId: "short",
|
|
320
|
+
container: true,
|
|
321
|
+
z: 1,
|
|
322
|
+
name: "Container title ".repeat(10),
|
|
323
|
+
};
|
|
324
|
+
const leaves = [
|
|
325
|
+
{ id: "a", name: "a", parentId: "short", z: 1 },
|
|
326
|
+
{ id: "b", name: "b", parentId: "short", z: 1 },
|
|
327
|
+
{ id: "c", name: "c".repeat(50), parentId: "long", z: 1 },
|
|
328
|
+
{ id: "d", name: "d", parentId: "long", z: 1 },
|
|
329
|
+
{ id: "e", name: "e".repeat(30), parentId: "nested", z: 2 },
|
|
330
|
+
{ id: "f", name: "f", parentId: "nested", z: 2 },
|
|
331
|
+
{ id: "root-a", name: "root".repeat(8), z: 0 },
|
|
332
|
+
{ id: "root-b", name: "root", z: 0 },
|
|
333
|
+
];
|
|
334
|
+
const nodes = [shortGroup, longGroup, nested, ...leaves];
|
|
335
|
+
const widths = (rendered) =>
|
|
336
|
+
Object.fromEntries(rendered.map((node) => [node.id, node.width]));
|
|
337
|
+
|
|
338
|
+
it("sizes each immediate sibling group independently, including roots", () => {
|
|
339
|
+
const result = widths(lineageTopologyNodes({ nodes }));
|
|
340
|
+
expect(result.a).toBe(240);
|
|
341
|
+
expect(result.b).toBe(result.a);
|
|
342
|
+
expect(result.c).toBe(360);
|
|
343
|
+
expect(result.d).toBe(result.c);
|
|
344
|
+
expect(result.e).toBe(283);
|
|
345
|
+
expect(result.f).toBe(result.e);
|
|
346
|
+
expect(result["root-a"]).toBe(296);
|
|
347
|
+
expect(result["root-b"]).toBe(result["root-a"]);
|
|
348
|
+
expect(
|
|
349
|
+
widths(lineageTopologyNodes({ nodes: [...nodes].reverse() })),
|
|
350
|
+
).toEqual(result);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("preserves classic widths and explicit width overrides", () => {
|
|
354
|
+
expect(widths(lineageTopologyNodes({ nodes }, "legacy")).a).toBe(288);
|
|
355
|
+
expect(widths(lineageTopologyNodes({ nodes }, "redesigned", 320)).a).toBe(
|
|
356
|
+
320,
|
|
357
|
+
);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it("uses all field content before expansion in positioned groups", () => {
|
|
361
|
+
const resources = leaves.slice(0, 4).map((node, index) => ({
|
|
362
|
+
...node,
|
|
363
|
+
x: index * 400,
|
|
364
|
+
y: 60,
|
|
365
|
+
w: 120,
|
|
366
|
+
h: 60,
|
|
367
|
+
fields:
|
|
368
|
+
node.id === "c"
|
|
369
|
+
? Array.from({ length: 12 }, (_, i) => ({
|
|
370
|
+
name: i === 11 ? "hidden_long_field_name" : "id",
|
|
371
|
+
type: i === 11 ? "VARCHAR(512)" : "INT",
|
|
372
|
+
}))
|
|
373
|
+
: [],
|
|
374
|
+
name: node.id,
|
|
375
|
+
}));
|
|
376
|
+
const groups = [shortGroup, longGroup].map((group) => ({
|
|
377
|
+
...group,
|
|
378
|
+
x: 0,
|
|
379
|
+
y: 0,
|
|
380
|
+
w: 1000,
|
|
381
|
+
h: 600,
|
|
382
|
+
}));
|
|
383
|
+
const preparedHierarchy = {
|
|
384
|
+
containerGroups: groups,
|
|
385
|
+
leafGroups: [],
|
|
386
|
+
parentByNode: new Map(
|
|
387
|
+
resources.map((node) => [
|
|
388
|
+
node,
|
|
389
|
+
groups.find((group) => group.id === node.parentId),
|
|
390
|
+
]),
|
|
391
|
+
),
|
|
392
|
+
childCountsByCanonicalId: new Map(),
|
|
393
|
+
};
|
|
394
|
+
const options = { resources, groups, preparedHierarchy };
|
|
395
|
+
const collapsed = widths(lineagePositionedLayoutNodes(options));
|
|
396
|
+
const expanded = widths(
|
|
397
|
+
lineagePositionedLayoutNodes({
|
|
398
|
+
...options,
|
|
399
|
+
expandedFieldNodeIds: new Set(["c"]),
|
|
400
|
+
}),
|
|
401
|
+
);
|
|
402
|
+
expect(collapsed.a).toBe(240);
|
|
403
|
+
expect(collapsed.b).toBe(240);
|
|
404
|
+
expect(collapsed.c).toBe(lineageNodeContentWidth([resources[2]]));
|
|
405
|
+
expect(collapsed.c).toBeGreaterThan(collapsed.a);
|
|
406
|
+
expect(collapsed.d).toBe(collapsed.c);
|
|
407
|
+
expect(expanded).toEqual(collapsed);
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
|
|
313
411
|
describe("lineageNodeContentWidth", () => {
|
|
314
412
|
it("returns the minimum width when no content is provided", () => {
|
|
315
413
|
expect(lineageNodeContentWidth()).toBe(240);
|