@truedat/dd 8.11.4 → 8.11.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -3
- package/src/components/Graphs.js +30 -4
- package/src/components/LineageContextMenu.js +185 -58
- package/src/components/LineageDownloadButton.js +16 -6
- package/src/components/LineageEventsPanel.js +80 -27
- package/src/components/LineageEventsTable.js +7 -3
- package/src/components/LineageGraph.js +5144 -306
- package/src/components/LineageGraphToolbar.js +896 -0
- package/src/components/LineageInspectorPanel.js +748 -0
- package/src/components/LineageResourceNode.js +501 -0
- package/src/components/LineageRoutes.js +1 -2
- package/src/components/LineageSelectionPanel.js +28 -12
- package/src/components/StructureGraph.js +8 -1
- package/src/components/StructureGraphView.js +14 -0
- package/src/components/StructureImpact.js +2 -22
- package/src/components/StructureLineage.js +2 -19
- package/src/components/StructuresUploadEventsTable.js +0 -3
- package/src/components/__tests__/LineageContextMenu.spec.js +42 -0
- package/src/components/__tests__/LineageGraph.spec.js +896 -0
- package/src/components/__tests__/LineageGraphInteractions.spec.js +660 -0
- package/src/components/__tests__/LineageGraphLayout.spec.js +620 -0
- package/src/components/__tests__/LineageGraphToolbar.spec.js +366 -0
- package/src/components/__tests__/LineageHierarchy.spec.js +116 -0
- package/src/components/__tests__/LineageInspectorPanel.spec.js +357 -0
- package/src/components/__tests__/LineageResourceNode.spec.js +338 -0
- package/src/components/__tests__/LineageRoutes.spec.js +27 -9
- package/src/components/__tests__/__snapshots__/LineageDownloadButton.spec.js.snap +23 -3
- package/src/components/__tests__/__snapshots__/LineageForm.spec.js.snap +23 -3
- package/src/components/__tests__/lineageEventHelpers.spec.js +60 -0
- package/src/components/lineageEventHelpers.js +53 -17
- package/src/reducers/__tests__/lineageEvents.spec.js +164 -0
- package/src/reducers/__tests__/lineageQuery.spec.js +11 -0
- package/src/reducers/lineageEvents.js +68 -2
- package/src/reducers/lineageQuery.js +2 -6
- package/src/styles/LineageGraphColors.less +105 -0
- package/src/styles/LineageGraphCore.less +20 -0
- package/src/styles/LineageResourceNode.less +711 -0
- package/src/styles/lineageGraph.less +2973 -84
- package/src/utils/__tests__/lineageGraphIndex.spec.js +52 -0
- package/src/utils/__tests__/lineageLayout.spec.js +234 -0
- package/src/utils/__tests__/lineageTopology.spec.js +256 -0
- package/src/utils/lineageGraphIndex.js +296 -0
- package/src/utils/lineageLayout.js +1047 -0
|
@@ -63,9 +63,18 @@ describe("<LineageEventsPanel />", () => {
|
|
|
63
63
|
status: "COMPLETED",
|
|
64
64
|
inserted_at: "2021-11-25T10:09:27.168999Z",
|
|
65
65
|
},
|
|
66
|
+
{
|
|
67
|
+
id: "impact-with-record-type",
|
|
68
|
+
graph_id: 103,
|
|
69
|
+
graph_data: JSON.stringify(["node-d"]),
|
|
70
|
+
type: "lineage_event",
|
|
71
|
+
opts: { type: "impact" },
|
|
72
|
+
status: "COMPLETED",
|
|
73
|
+
inserted_at: "2021-11-29T10:09:27.168999Z",
|
|
74
|
+
},
|
|
66
75
|
];
|
|
67
76
|
|
|
68
|
-
it("renders
|
|
77
|
+
it("renders distinct graph IDs as separate cards, newest first, with status and errors", async () => {
|
|
69
78
|
const rendered = render(
|
|
70
79
|
<LineageEventsPanel
|
|
71
80
|
lineageEvents={lineageEvents}
|
|
@@ -74,18 +83,27 @@ describe("<LineageEventsPanel />", () => {
|
|
|
74
83
|
);
|
|
75
84
|
const user = userEvent.setup({ delay: null });
|
|
76
85
|
|
|
77
|
-
|
|
78
|
-
expect(
|
|
79
|
-
expect(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
const cards = [...rendered.container.querySelectorAll("article")];
|
|
87
|
+
expect(cards).toHaveLength(4);
|
|
88
|
+
expect(
|
|
89
|
+
cards.map(
|
|
90
|
+
(card) =>
|
|
91
|
+
card.querySelector(".lineage-events-panel__item-title").textContent,
|
|
92
|
+
),
|
|
93
|
+
).toEqual(["node-d", "node-a", "node-a", "node-c"]);
|
|
94
|
+
expect(rendered.getAllByText(/Finance/)).toHaveLength(1);
|
|
83
95
|
expect(rendered.getAllByText("OK")).toHaveLength(3);
|
|
84
96
|
expect(rendered.getByText("KO")).toBeInTheDocument();
|
|
85
|
-
expect(rendered.getAllByText("navigation.lineage.lineage")).toHaveLength(
|
|
86
|
-
expect(rendered.
|
|
97
|
+
expect(rendered.getAllByText("navigation.lineage.lineage")).toHaveLength(1);
|
|
98
|
+
expect(rendered.getAllByText("navigation.lineage.impact")).toHaveLength(2);
|
|
99
|
+
expect(
|
|
100
|
+
rendered.getAllByRole("button", { name: "lineage.events.panel.load" }),
|
|
101
|
+
).toHaveLength(2);
|
|
102
|
+
expect(cards[1]).not.toHaveAttribute("role", "button");
|
|
103
|
+
expect(cards[3]).not.toHaveAttribute("role", "button");
|
|
87
104
|
|
|
88
105
|
await user.click(rendered.getByLabelText("lineage.events.panel.error"));
|
|
106
|
+
expect(await rendered.findByText("#102")).toBeInTheDocument();
|
|
89
107
|
expect(
|
|
90
108
|
await rendered.findByText("Graph generation failed"),
|
|
91
109
|
).toBeInTheDocument();
|
|
@@ -4,12 +4,32 @@ exports[`<LineageDownloadButton /> matches the latest snapshot 1`] = `
|
|
|
4
4
|
<div>
|
|
5
5
|
<span>
|
|
6
6
|
<button
|
|
7
|
+
aria-label="graph.actions.download.tooltip"
|
|
7
8
|
class="ui icon secondary button"
|
|
8
9
|
>
|
|
9
|
-
<
|
|
10
|
+
<svg
|
|
10
11
|
aria-hidden="true"
|
|
11
|
-
class="download
|
|
12
|
-
|
|
12
|
+
class="lucide lucide-download"
|
|
13
|
+
fill="none"
|
|
14
|
+
height="20"
|
|
15
|
+
stroke="currentColor"
|
|
16
|
+
stroke-linecap="round"
|
|
17
|
+
stroke-linejoin="round"
|
|
18
|
+
stroke-width="2"
|
|
19
|
+
viewBox="0 0 24 24"
|
|
20
|
+
width="20"
|
|
21
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
22
|
+
>
|
|
23
|
+
<path
|
|
24
|
+
d="M12 15V3"
|
|
25
|
+
/>
|
|
26
|
+
<path
|
|
27
|
+
d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"
|
|
28
|
+
/>
|
|
29
|
+
<path
|
|
30
|
+
d="m7 10 5 5 5-5"
|
|
31
|
+
/>
|
|
32
|
+
</svg>
|
|
13
33
|
</button>
|
|
14
34
|
</span>
|
|
15
35
|
</div>
|
|
@@ -373,14 +373,34 @@ exports[`<LineageForm /> matches the latest snapshot 1`] = `
|
|
|
373
373
|
</button>
|
|
374
374
|
<span>
|
|
375
375
|
<button
|
|
376
|
+
aria-label="graph.actions.download.popup"
|
|
376
377
|
class="ui icon secondary disabled button"
|
|
377
378
|
disabled=""
|
|
378
379
|
tabindex="-1"
|
|
379
380
|
>
|
|
380
|
-
<
|
|
381
|
+
<svg
|
|
381
382
|
aria-hidden="true"
|
|
382
|
-
class="download
|
|
383
|
-
|
|
383
|
+
class="lucide lucide-download"
|
|
384
|
+
fill="none"
|
|
385
|
+
height="20"
|
|
386
|
+
stroke="currentColor"
|
|
387
|
+
stroke-linecap="round"
|
|
388
|
+
stroke-linejoin="round"
|
|
389
|
+
stroke-width="2"
|
|
390
|
+
viewBox="0 0 24 24"
|
|
391
|
+
width="20"
|
|
392
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
393
|
+
>
|
|
394
|
+
<path
|
|
395
|
+
d="M12 15V3"
|
|
396
|
+
/>
|
|
397
|
+
<path
|
|
398
|
+
d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"
|
|
399
|
+
/>
|
|
400
|
+
<path
|
|
401
|
+
d="m7 10 5 5 5-5"
|
|
402
|
+
/>
|
|
403
|
+
</svg>
|
|
384
404
|
</button>
|
|
385
405
|
</span>
|
|
386
406
|
</div>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
lineageEventType,
|
|
3
|
+
groupLineageEvents,
|
|
4
|
+
graphTypeMessageId,
|
|
5
|
+
graphTypeIcon,
|
|
6
|
+
} from "../lineageEventHelpers";
|
|
7
|
+
|
|
8
|
+
describe("lineageEventHelpers: type classification", () => {
|
|
9
|
+
it("classifies lineage and impact from any supported key", () => {
|
|
10
|
+
expect(lineageEventType({ type: "impact" })).toBe("impact");
|
|
11
|
+
expect(lineageEventType({ type: "lineage" })).toBe("lineage");
|
|
12
|
+
expect(lineageEventType({ graph_type: "impact" })).toBe("impact");
|
|
13
|
+
expect(lineageEventType({ opts: { type: "impact" } })).toBe("impact");
|
|
14
|
+
expect(
|
|
15
|
+
lineageEventType({
|
|
16
|
+
graph_data: JSON.stringify({ opts: { type: "impact" } }),
|
|
17
|
+
}),
|
|
18
|
+
).toBe("impact");
|
|
19
|
+
expect(
|
|
20
|
+
lineageEventType({ graph_data: JSON.stringify({ type: "lineage" }) }),
|
|
21
|
+
).toBe("lineage");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("returns null instead of assuming lineage when the type is missing", () => {
|
|
25
|
+
expect(lineageEventType({})).toBeNull();
|
|
26
|
+
expect(
|
|
27
|
+
lineageEventType({ graph_data: JSON.stringify(["a", "b"]) }),
|
|
28
|
+
).toBeNull();
|
|
29
|
+
expect(lineageEventType({ type: "lineage_event" })).toBeNull();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("does not merge untyped events into the lineage group", () => {
|
|
33
|
+
const events = [
|
|
34
|
+
{
|
|
35
|
+
id: "lineage-1",
|
|
36
|
+
graph_id: 1,
|
|
37
|
+
graph_data: JSON.stringify(["a", "b"]),
|
|
38
|
+
type: "lineage",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "untyped-1",
|
|
42
|
+
graph_id: 2,
|
|
43
|
+
graph_data: JSON.stringify(["a", "b"]),
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
const groups = groupLineageEvents(events);
|
|
47
|
+
const types = groups.flatMap((group) => Object.keys(group.eventsByType));
|
|
48
|
+
|
|
49
|
+
expect(types).toContain("lineage");
|
|
50
|
+
expect(types).toContain("null");
|
|
51
|
+
expect(types).not.toHaveLength(1);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("renders a neutral label and icon for unknown types", () => {
|
|
55
|
+
expect(graphTypeMessageId("impact")).toBe("navigation.lineage.impact");
|
|
56
|
+
expect(graphTypeMessageId("lineage")).toBe("navigation.lineage.lineage");
|
|
57
|
+
expect(graphTypeMessageId(null)).toBe("lineage.events.type.pending");
|
|
58
|
+
expect(graphTypeIcon(null)).toBe("question circle outline");
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -57,19 +57,37 @@ const eventTimestamp = (event = {}) => {
|
|
|
57
57
|
const newestEvent = (left, right) =>
|
|
58
58
|
eventTimestamp(right) >= eventTimestamp(left) ? right : left;
|
|
59
59
|
|
|
60
|
+
const GRAPH_EVENT_TYPES = {
|
|
61
|
+
impact: "impact",
|
|
62
|
+
impacto: "impact",
|
|
63
|
+
impacto_impact: "impact",
|
|
64
|
+
impact_impact: "impact",
|
|
65
|
+
lineage: "lineage",
|
|
66
|
+
linaje: "lineage",
|
|
67
|
+
trazabilidad: "lineage",
|
|
68
|
+
traceability: "lineage",
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const resolvedGraphEventType = (value) =>
|
|
72
|
+
GRAPH_EVENT_TYPES[`${value || ""}`.trim().toLowerCase()] || null;
|
|
73
|
+
|
|
60
74
|
const lineageEventType = (event = {}) => {
|
|
61
75
|
const parsedGraphData = parseGraphData(event.graph_data);
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
event.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
const parsedType = Array.isArray(parsedGraphData)
|
|
77
|
+
? undefined
|
|
78
|
+
: parsedGraphData?.type;
|
|
79
|
+
const candidates = [
|
|
80
|
+
event.graph_type,
|
|
81
|
+
event.graph_kind,
|
|
82
|
+
event.opts?.type,
|
|
83
|
+
parsedType,
|
|
84
|
+
parsedGraphData?.opts?.type,
|
|
85
|
+
event.graph_data_type,
|
|
86
|
+
event.type,
|
|
87
|
+
event.kind,
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
return candidates.map(resolvedGraphEventType).find(Boolean) || null;
|
|
73
91
|
};
|
|
74
92
|
|
|
75
93
|
export const lineageEventGroupKey = (event = {}) => {
|
|
@@ -111,13 +129,25 @@ export const groupLineageEvents = (events = []) => {
|
|
|
111
129
|
});
|
|
112
130
|
};
|
|
113
131
|
|
|
114
|
-
const
|
|
132
|
+
const graphIdentity = (event, index) =>
|
|
115
133
|
event.graph_id
|
|
116
134
|
? `#${event.graph_id}`
|
|
117
135
|
: event.id
|
|
118
136
|
? `#${event.id}`
|
|
119
137
|
: `#${index + 1}`;
|
|
120
138
|
|
|
139
|
+
const graphLabel = (event, index) => {
|
|
140
|
+
const firstLevel = graphFirstLevel(event);
|
|
141
|
+
if (firstLevel) return firstLevel;
|
|
142
|
+
const scope = graphScope(event.graph_data);
|
|
143
|
+
if (scope) return scope;
|
|
144
|
+
return event.graph_id
|
|
145
|
+
? `#${event.graph_id}`
|
|
146
|
+
: event.id
|
|
147
|
+
? `#${event.id}`
|
|
148
|
+
: `#${index + 1}`;
|
|
149
|
+
};
|
|
150
|
+
|
|
121
151
|
const graphScope = (graphData) => {
|
|
122
152
|
const parsedGraphData = parseGraphData(graphData);
|
|
123
153
|
|
|
@@ -157,12 +187,17 @@ const graphFirstLevel = (event) => {
|
|
|
157
187
|
);
|
|
158
188
|
};
|
|
159
189
|
|
|
160
|
-
const graphTypeMessageId = (type) =>
|
|
161
|
-
type === "impact"
|
|
162
|
-
|
|
163
|
-
|
|
190
|
+
const graphTypeMessageId = (type) => {
|
|
191
|
+
if (type === "impact") return "navigation.lineage.impact";
|
|
192
|
+
if (type === "lineage") return "navigation.lineage.lineage";
|
|
193
|
+
return "lineage.events.type.pending";
|
|
194
|
+
};
|
|
164
195
|
|
|
165
|
-
const graphTypeIcon = (type) =>
|
|
196
|
+
const graphTypeIcon = (type) => {
|
|
197
|
+
if (type === "impact") return "bullseye";
|
|
198
|
+
if (type === "lineage") return "sitemap";
|
|
199
|
+
return "question circle outline";
|
|
200
|
+
};
|
|
166
201
|
|
|
167
202
|
const eventErrorMessage = (event) =>
|
|
168
203
|
[
|
|
@@ -185,6 +220,7 @@ export {
|
|
|
185
220
|
eventTimestamp,
|
|
186
221
|
newestEvent,
|
|
187
222
|
lineageEventType,
|
|
223
|
+
graphIdentity,
|
|
188
224
|
graphLabel,
|
|
189
225
|
graphScope,
|
|
190
226
|
graphDomain,
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { createGraph, fetchGraph, fetchLineageEvents } from "../../routines";
|
|
2
|
+
import {
|
|
3
|
+
LINEAGE_KNOWN_GRAPH_TYPES_KEY,
|
|
4
|
+
initialState,
|
|
5
|
+
lineageEvents,
|
|
6
|
+
} from "../lineageEvents";
|
|
7
|
+
|
|
8
|
+
describe("reducers: lineageEvents", () => {
|
|
9
|
+
it("should provide the initial state", () => {
|
|
10
|
+
expect(lineageEvents(undefined, {})).toBe(initialState);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("should store deduplicated events after receiving the fetchLineageEvents.SUCCESS action", () => {
|
|
14
|
+
const payload = {
|
|
15
|
+
data: [
|
|
16
|
+
{
|
|
17
|
+
id: "new",
|
|
18
|
+
graph_id: 100,
|
|
19
|
+
graph_data: JSON.stringify(["node-a", "node-b"]),
|
|
20
|
+
type: "lineage",
|
|
21
|
+
status: "COMPLETED",
|
|
22
|
+
inserted_at: "2021-11-27T10:09:27.168999Z",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "old",
|
|
26
|
+
graph_id: 101,
|
|
27
|
+
graph_data: JSON.stringify(["node-b", "node-a"]),
|
|
28
|
+
type: "lineage",
|
|
29
|
+
status: "COMPLETED",
|
|
30
|
+
inserted_at: "2021-11-26T10:09:27.168999Z",
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
expect(
|
|
35
|
+
lineageEvents([], { type: fetchLineageEvents.SUCCESS, payload }),
|
|
36
|
+
).toHaveLength(1);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should ignore unhandled actions", () => {
|
|
40
|
+
expect(lineageEvents([{ id: "x" }], { type: "FOO" })).toEqual([
|
|
41
|
+
{ id: "x" },
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("known graph type", () => {
|
|
46
|
+
it("should apply the type learned from createGraph.SUCCESS to events missing it", () => {
|
|
47
|
+
lineageEvents([], {
|
|
48
|
+
type: createGraph.SUCCESS,
|
|
49
|
+
payload: {
|
|
50
|
+
data: { id: 100, opts: { type: "impact" } },
|
|
51
|
+
status: 201,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const events = [
|
|
56
|
+
{
|
|
57
|
+
id: "new",
|
|
58
|
+
graph_id: 100,
|
|
59
|
+
graph_data: JSON.stringify(["node-a", "node-b"]),
|
|
60
|
+
status: "COMPLETED",
|
|
61
|
+
inserted_at: "2021-11-27T10:09:27.168999Z",
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
const state = lineageEvents([], {
|
|
65
|
+
type: fetchLineageEvents.SUCCESS,
|
|
66
|
+
payload: { data: events },
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect(state[0].graph_type).toBe("impact");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should apply the type learned from fetchGraph.SUCCESS to events missing it", () => {
|
|
73
|
+
lineageEvents([], {
|
|
74
|
+
type: fetchGraph.SUCCESS,
|
|
75
|
+
payload: { id: 200, opts: { type: "lineage" } },
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const events = [
|
|
79
|
+
{
|
|
80
|
+
id: "new",
|
|
81
|
+
graph_id: 200,
|
|
82
|
+
graph_data: JSON.stringify(["node-a", "node-b"]),
|
|
83
|
+
status: "COMPLETED",
|
|
84
|
+
inserted_at: "2021-11-27T10:09:27.168999Z",
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
const state = lineageEvents([], {
|
|
88
|
+
type: fetchLineageEvents.SUCCESS,
|
|
89
|
+
payload: { data: events },
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(state[0].graph_type).toBe("lineage");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("should not overwrite a type already present in the event", () => {
|
|
96
|
+
lineageEvents([], {
|
|
97
|
+
type: createGraph.SUCCESS,
|
|
98
|
+
payload: { data: { id: 300, opts: { type: "impact" } } },
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const events = [
|
|
102
|
+
{
|
|
103
|
+
id: "new",
|
|
104
|
+
graph_id: 300,
|
|
105
|
+
graph_data: JSON.stringify(["node-a", "node-b"]),
|
|
106
|
+
type: "lineage",
|
|
107
|
+
status: "COMPLETED",
|
|
108
|
+
inserted_at: "2021-11-27T10:09:27.168999Z",
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
const state = lineageEvents([], {
|
|
112
|
+
type: fetchLineageEvents.SUCCESS,
|
|
113
|
+
payload: { data: events },
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
expect(state[0].graph_type).toBeUndefined();
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe("persistence across reloads", () => {
|
|
121
|
+
beforeEach(() => {
|
|
122
|
+
localStorage.clear();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("persists a learned graph type to localStorage", () => {
|
|
126
|
+
lineageEvents([], {
|
|
127
|
+
type: createGraph.SUCCESS,
|
|
128
|
+
payload: { data: { id: 500, opts: { type: "lineage" } }, status: 201 },
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
expect(
|
|
132
|
+
JSON.parse(localStorage.getItem(LINEAGE_KNOWN_GRAPH_TYPES_KEY)),
|
|
133
|
+
).toMatchObject({ 500: "lineage" });
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("hydrates known graph types from localStorage on module load", () => {
|
|
137
|
+
localStorage.setItem(
|
|
138
|
+
LINEAGE_KNOWN_GRAPH_TYPES_KEY,
|
|
139
|
+
JSON.stringify({ 999: "impact" }),
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
let reloadedReducer;
|
|
143
|
+
jest.isolateModules(() => {
|
|
144
|
+
reloadedReducer = require("../lineageEvents").lineageEvents;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const events = [
|
|
148
|
+
{
|
|
149
|
+
id: "e",
|
|
150
|
+
graph_id: 999,
|
|
151
|
+
graph_data: JSON.stringify(["node-a"]),
|
|
152
|
+
status: "COMPLETED",
|
|
153
|
+
inserted_at: "2021-11-27T10:09:27.168999Z",
|
|
154
|
+
},
|
|
155
|
+
];
|
|
156
|
+
const state = reloadedReducer([], {
|
|
157
|
+
type: fetchLineageEvents.SUCCESS,
|
|
158
|
+
payload: { data: events },
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
expect(state[0].graph_type).toBe("impact");
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
@@ -80,6 +80,17 @@ describe("reducers: lineageQuery", () => {
|
|
|
80
80
|
).toMatchObject({ ids, excludes, type, levels });
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
+
it("should keep the previous type when the fetchGraph.SUCCESS payload has no type", () => {
|
|
84
|
+
const data = { ids: ["foo"], excludes: [], opts: {} };
|
|
85
|
+
const payload = { data };
|
|
86
|
+
expect(
|
|
87
|
+
lineageQuery(
|
|
88
|
+
{ type: "impact", levels: 3 },
|
|
89
|
+
{ type: fetchGraph.SUCCESS, payload },
|
|
90
|
+
),
|
|
91
|
+
).toMatchObject({ ids: ["foo"], excludes: [], type: "impact", levels: 3 });
|
|
92
|
+
});
|
|
93
|
+
|
|
83
94
|
it("should ignore unhandled actions", () => {
|
|
84
95
|
expect(lineageQuery(fooState, { type: "FOO" })).toBe(fooState);
|
|
85
96
|
});
|
|
@@ -1,12 +1,78 @@
|
|
|
1
1
|
import _ from "lodash/fp";
|
|
2
|
-
import { fetchLineageEvents } from "../routines";
|
|
2
|
+
import { createGraph, fetchGraph, fetchLineageEvents } from "../routines";
|
|
3
|
+
import {
|
|
4
|
+
groupLineageEvents,
|
|
5
|
+
lineageEventType,
|
|
6
|
+
} from "../components/lineageEventHelpers";
|
|
3
7
|
|
|
4
8
|
export const initialState = [];
|
|
5
9
|
|
|
10
|
+
const dedupedLineageEvents = (events = []) => {
|
|
11
|
+
const groups = groupLineageEvents(events);
|
|
12
|
+
return groups
|
|
13
|
+
.flatMap((group) => Object.values(group.eventsByType))
|
|
14
|
+
.sort((a, b) => {
|
|
15
|
+
const tsA = Date.parse(a.inserted_at) || 0;
|
|
16
|
+
const tsB = Date.parse(b.inserted_at) || 0;
|
|
17
|
+
return tsB - tsA;
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const LINEAGE_KNOWN_GRAPH_TYPES_KEY = "td.lineage.knownGraphTypes";
|
|
22
|
+
|
|
23
|
+
const readStoredGraphTypes = () => {
|
|
24
|
+
if (typeof localStorage === "undefined") return {};
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const stored = JSON.parse(
|
|
28
|
+
localStorage.getItem(LINEAGE_KNOWN_GRAPH_TYPES_KEY),
|
|
29
|
+
);
|
|
30
|
+
return stored && typeof stored === "object" ? stored : {};
|
|
31
|
+
} catch (_error) {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const writeStoredGraphTypes = (types) => {
|
|
37
|
+
if (typeof localStorage === "undefined") return;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
localStorage.setItem(LINEAGE_KNOWN_GRAPH_TYPES_KEY, JSON.stringify(types));
|
|
41
|
+
} catch (_error) {}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const knownGraphTypes = new Map(Object.entries(readStoredGraphTypes()));
|
|
45
|
+
|
|
46
|
+
const rememberGraphType = (payload = {}) => {
|
|
47
|
+
const data = payload.data || payload;
|
|
48
|
+
const graphId = data?.id;
|
|
49
|
+
const graphType = data?.opts?.type;
|
|
50
|
+
|
|
51
|
+
if (graphId !== undefined && graphId !== null && graphType) {
|
|
52
|
+
knownGraphTypes.set(String(graphId), graphType);
|
|
53
|
+
writeStoredGraphTypes(Object.fromEntries(knownGraphTypes));
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const applyKnownGraphTypes = (events = []) =>
|
|
58
|
+
events.map((event) => {
|
|
59
|
+
const knownType = knownGraphTypes.get(String(event.graph_id));
|
|
60
|
+
|
|
61
|
+
return knownType && !lineageEventType(event)
|
|
62
|
+
? { ...event, graph_type: knownType }
|
|
63
|
+
: event;
|
|
64
|
+
});
|
|
65
|
+
|
|
6
66
|
export const lineageEvents = (state = initialState, { type, payload }) => {
|
|
7
67
|
switch (type) {
|
|
8
68
|
case fetchLineageEvents.SUCCESS:
|
|
9
|
-
return
|
|
69
|
+
return applyKnownGraphTypes(
|
|
70
|
+
dedupedLineageEvents(_.prop("data")(payload) || []),
|
|
71
|
+
);
|
|
72
|
+
case createGraph.SUCCESS:
|
|
73
|
+
case fetchGraph.SUCCESS:
|
|
74
|
+
rememberGraphType(payload);
|
|
75
|
+
return applyKnownGraphTypes(state);
|
|
10
76
|
default:
|
|
11
77
|
return state;
|
|
12
78
|
}
|
|
@@ -27,13 +27,9 @@ export const lineageQuery = (state = initialState, { type, payload }) => {
|
|
|
27
27
|
}
|
|
28
28
|
case fetchGraph.SUCCESS: {
|
|
29
29
|
const {
|
|
30
|
-
data: {
|
|
31
|
-
ids,
|
|
32
|
-
opts: { type: type },
|
|
33
|
-
excludes: excludes,
|
|
34
|
-
},
|
|
30
|
+
data: { ids, opts: { type: type } = {}, excludes: excludes },
|
|
35
31
|
} = payload;
|
|
36
|
-
return { ids, type, excludes, levels: state.levels };
|
|
32
|
+
return { ids, type: type || state.type, excludes, levels: state.levels };
|
|
37
33
|
}
|
|
38
34
|
case selectGroup.TRIGGER: {
|
|
39
35
|
return { ...state, excludes: [] };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
.lineage-system-color(@accent; @strong; @border; @bg; @shadow) {
|
|
2
|
+
--td-lineage-node-accent: @accent;
|
|
3
|
+
--td-lineage-node-border: @border;
|
|
4
|
+
--td-lineage-group-accent: @accent;
|
|
5
|
+
--td-lineage-group-accent-strong: @strong;
|
|
6
|
+
--td-lineage-group-bg: @bg;
|
|
7
|
+
--td-lineage-group-border: @border;
|
|
8
|
+
--td-lineage-group-shadow: @shadow;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.td-lineage-system-color--unknown {
|
|
12
|
+
.lineage-system-color(#334155; #1f2937; #64748b; rgba(241, 245, 249, 0.78); rgba(15, 23, 42, 0.18));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.td-lineage-system-color--0 {
|
|
16
|
+
.lineage-system-color(#1f6feb; #1d4ed8; #16263a; rgba(31, 111, 235, 0.7); rgba(31, 111, 235, 0.18));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.td-lineage-system-color--1 {
|
|
20
|
+
.lineage-system-color(#168657; #15803d; #bbf7d0; rgba(22, 134, 87, 0.7); rgba(22, 134, 87, 0.18));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.td-lineage-system-color--2 {
|
|
24
|
+
.lineage-system-color(#b45309; #9a3412; #fed7aa; rgba(180, 83, 9, 0.7); rgba(180, 83, 9, 0.18));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.td-lineage-system-color--3 {
|
|
28
|
+
.lineage-system-color(#6d5bd0; #5b46be; #ddd6fe; rgba(109, 91, 208, 0.7); rgba(109, 91, 208, 0.18));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.td-lineage-system-color--4 {
|
|
32
|
+
.lineage-system-color(#0f8f8c; #0f766e; #99f6e4; rgba(15, 143, 140, 0.7); rgba(15, 143, 140, 0.18));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.td-lineage-system-color--5 {
|
|
36
|
+
.lineage-system-color(#be123c; #9f1239; #fecdd3; rgba(190, 18, 60, 0.7); rgba(190, 18, 60, 0.18));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.td-lineage-system-color--6 {
|
|
40
|
+
.lineage-system-color(#7c3aed; #6d28d9; #ddd6fe; rgba(124, 58, 237, 0.7); rgba(124, 58, 237, 0.18));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.td-lineage-system-color--7 {
|
|
44
|
+
.lineage-system-color(#0369a1; #075985; #bae6fd; rgba(3, 105, 161, 0.7); rgba(3, 105, 161, 0.18));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.td-lineage-system-color--8 {
|
|
48
|
+
.lineage-system-color(#047857; #065f46; #a7f3d0; rgba(4, 120, 87, 0.7); rgba(4, 120, 87, 0.18));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.td-lineage-system-color--9 {
|
|
52
|
+
.lineage-system-color(#c2410c; #9a3412; #fed7aa; rgba(194, 65, 12, 0.7); rgba(194, 65, 12, 0.18));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.td-lineage-system-color--10 {
|
|
56
|
+
.lineage-system-color(#4f46e5; #4338ca; #c7d2fe; rgba(79, 70, 229, 0.7); rgba(79, 70, 229, 0.18));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.td-lineage-system-color--11 {
|
|
60
|
+
.lineage-system-color(#0e7490; #155e75; #a5f3fc; rgba(14, 116, 144, 0.7); rgba(14, 116, 144, 0.18));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.lineage-entity-color(@accent) {
|
|
64
|
+
--td-lineage-entity-accent: @accent;
|
|
65
|
+
--td-lineage-node-accent: @accent;
|
|
66
|
+
--td-lineage-node-border: @accent;
|
|
67
|
+
--td-lineage-group-accent: @accent;
|
|
68
|
+
--td-lineage-group-border: @accent;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.td-lineage-entity-color--column {
|
|
72
|
+
.lineage-entity-color(#7c3aed);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.td-lineage-entity-color--database {
|
|
76
|
+
.lineage-entity-color(#0369a1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.td-lineage-entity-color--field {
|
|
80
|
+
.lineage-entity-color(#7c3aed);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.td-lineage-entity-color--process {
|
|
84
|
+
.lineage-entity-color(#b45309);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.td-lineage-entity-color--report {
|
|
88
|
+
.lineage-entity-color(#be123c);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.td-lineage-entity-color--schema {
|
|
92
|
+
.lineage-entity-color(#0f766e);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.td-lineage-entity-color--system {
|
|
96
|
+
.lineage-entity-color(#334155);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.td-lineage-entity-color--table {
|
|
100
|
+
.lineage-entity-color(#3a6d8a);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.td-lineage-entity-color--view {
|
|
104
|
+
.lineage-entity-color(#6d28d9);
|
|
105
|
+
}
|