@truedat/dd 8.10.2 → 8.10.3
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/Graphs.js +109 -0
- package/src/components/LineageDomainSelectorSkeleton.js +8 -0
- package/src/components/LineageDownloadButton.js +29 -6
- package/src/components/LineageEventsPanel.js +206 -0
- package/src/components/LineageForm.js +265 -144
- package/src/components/LineageGraph.js +0 -1
- package/src/components/LineageHeader.js +33 -0
- package/src/components/LineageResourceToggle.js +56 -0
- package/src/components/LineageRoutes.js +2 -31
- package/src/components/LineageSelectionPanel.js +165 -0
- package/src/components/__tests__/LineageForm.spec.js +343 -3
- package/src/components/__tests__/LineageRoutes.spec.js +62 -76
- package/src/components/__tests__/__snapshots__/LineageForm.spec.js.snap +291 -250
- package/src/components/__tests__/__snapshots__/LineageRoutes.spec.js.snap +19 -43
- package/src/components/index.js +12 -0
- package/src/components/lineageDomainCache.js +101 -0
- package/src/components/lineageEventHelpers.js +195 -0
- package/src/styles/lineageGraph.less +1213 -47
- package/src/components/Lineage.js +0 -20
- package/src/components/LineageSample.js +0 -20
- package/src/components/__tests__/Lineage.spec.js +0 -16
- package/src/components/__tests__/__snapshots__/Lineage.spec.js.snap +0 -163
|
@@ -1,52 +1,28 @@
|
|
|
1
1
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
2
|
|
|
3
|
-
exports[`<LineageRoutes /> renders correctly with GRAPH route 1`] = `
|
|
4
|
-
<div>
|
|
5
|
-
<div>
|
|
6
|
-
LineageForm
|
|
7
|
-
</div>
|
|
8
|
-
<div>
|
|
9
|
-
GraphRedirector
|
|
10
|
-
</div>
|
|
11
|
-
<div>
|
|
12
|
-
LineageGraph
|
|
13
|
-
</div>
|
|
14
|
-
<div>
|
|
15
|
-
GraphLoader
|
|
16
|
-
</div>
|
|
17
|
-
</div>
|
|
18
|
-
`;
|
|
19
|
-
|
|
20
3
|
exports[`<LineageRoutes /> renders correctly with GRAPHS route 1`] = `
|
|
21
4
|
<div>
|
|
22
|
-
<div
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
</
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
exports[`<LineageRoutes /> renders correctly with SAMPLE route 1`] = `
|
|
43
|
-
<div>
|
|
44
|
-
<div>
|
|
45
|
-
LineageSample
|
|
5
|
+
<div
|
|
6
|
+
class="ui icon visible bottom attached message center"
|
|
7
|
+
>
|
|
8
|
+
<i
|
|
9
|
+
aria-hidden="true"
|
|
10
|
+
class="warning sign icon"
|
|
11
|
+
/>
|
|
12
|
+
<div
|
|
13
|
+
class="content"
|
|
14
|
+
>
|
|
15
|
+
<div
|
|
16
|
+
class="header"
|
|
17
|
+
>
|
|
18
|
+
view.unauthorized.head
|
|
19
|
+
</div>
|
|
20
|
+
<p>
|
|
21
|
+
view.unauthorized.content
|
|
22
|
+
</p>
|
|
23
|
+
</div>
|
|
46
24
|
</div>
|
|
47
25
|
</div>
|
|
48
26
|
`;
|
|
49
27
|
|
|
50
28
|
exports[`<LineageRoutes /> renders correctly with default route 1`] = `<div />`;
|
|
51
|
-
|
|
52
|
-
exports[`<LineageRoutes /> renders unauthorized component when not authorized 1`] = `<div />`;
|
package/src/components/index.js
CHANGED
|
@@ -12,6 +12,12 @@ import LineageDownloadButton from "./LineageDownloadButton";
|
|
|
12
12
|
import LineageGroup from "./LineageGroup";
|
|
13
13
|
import LineageLink from "./LineageLink";
|
|
14
14
|
import LineageRoutes from "./LineageRoutes";
|
|
15
|
+
import LineageHeader from "./LineageHeader";
|
|
16
|
+
import LineageEventsPanel from "./LineageEventsPanel";
|
|
17
|
+
import LineageSelectionPanel from "./LineageSelectionPanel";
|
|
18
|
+
import Graphs from "./Graphs";
|
|
19
|
+
import { LineageResourceToggle } from "./LineageResourceToggle";
|
|
20
|
+
import LineageDomainSelectorSkeleton from "./LineageDomainSelectorSkeleton";
|
|
15
21
|
import StructureCrumbs from "./StructureCrumbs";
|
|
16
22
|
import StructureEvents from "./StructureEvents";
|
|
17
23
|
import StructureFieldRow from "./StructureFieldRow";
|
|
@@ -66,6 +72,12 @@ export {
|
|
|
66
72
|
LineageGroup,
|
|
67
73
|
LineageLink,
|
|
68
74
|
LineageRoutes,
|
|
75
|
+
LineageHeader,
|
|
76
|
+
LineageEventsPanel,
|
|
77
|
+
LineageSelectionPanel,
|
|
78
|
+
Graphs,
|
|
79
|
+
LineageResourceToggle,
|
|
80
|
+
LineageDomainSelectorSkeleton,
|
|
69
81
|
StructureCrumbs,
|
|
70
82
|
StructureEvents,
|
|
71
83
|
StructureFieldRow,
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import { apiJson, JSON_OPTS } from "@truedat/core/services/api";
|
|
3
|
+
import { API_NODES } from "../api";
|
|
4
|
+
|
|
5
|
+
export const LINEAGE_DOMAIN_CACHE_KEY = "truedat.lineage.domains";
|
|
6
|
+
export const LINEAGE_DOMAIN_AVAILABILITY_CACHE_KEY =
|
|
7
|
+
"truedat.lineage.domainAvailability";
|
|
8
|
+
export const LINEAGE_DOMAIN_CACHE_TTL = 60 * 1000;
|
|
9
|
+
|
|
10
|
+
const DOMAIN_AVAILABILITY_CONCURRENCY = 4;
|
|
11
|
+
|
|
12
|
+
const hasLineageNodes = (nodes = []) => {
|
|
13
|
+
if (_.isEmpty(nodes)) return false;
|
|
14
|
+
|
|
15
|
+
const hasVisibleNodes = _.some(
|
|
16
|
+
(node) => !_.isEmpty(node.groups) || !_.isEmpty(node.resources),
|
|
17
|
+
)(nodes);
|
|
18
|
+
const hasOnlyExplicitEmptyNodes = _.every(
|
|
19
|
+
(node) =>
|
|
20
|
+
(_.has("groups")(node) || _.has("resources")(node)) &&
|
|
21
|
+
_.isEmpty(node.groups) &&
|
|
22
|
+
_.isEmpty(node.resources),
|
|
23
|
+
)(nodes);
|
|
24
|
+
|
|
25
|
+
return hasVisibleNodes || !hasOnlyExplicitEmptyNodes;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const checkDomainLineage = async ({ id }) => {
|
|
29
|
+
try {
|
|
30
|
+
const { data = [] } = await apiJson(
|
|
31
|
+
`${API_NODES}?domain_id=${encodeURIComponent(id)}`,
|
|
32
|
+
JSON_OPTS,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return [`${id}`, hasLineageNodes(data)];
|
|
36
|
+
} catch (_error) {
|
|
37
|
+
return [`${id}`, true];
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const checkDomainsLineage = async (domains = []) => {
|
|
42
|
+
const results = [];
|
|
43
|
+
|
|
44
|
+
const chunkCount = Math.ceil(
|
|
45
|
+
domains.length / DOMAIN_AVAILABILITY_CONCURRENCY,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
for (const chunkIndex of Array.from({ length: chunkCount }, (_, i) => i)) {
|
|
49
|
+
const start = chunkIndex * DOMAIN_AVAILABILITY_CONCURRENCY;
|
|
50
|
+
const chunk = domains.slice(start, start + DOMAIN_AVAILABILITY_CONCURRENCY);
|
|
51
|
+
const chunkResults = await Promise.all(chunk.map(checkDomainLineage));
|
|
52
|
+
|
|
53
|
+
results.push(...chunkResults);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return results;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const domainIdsSignature = (domains = []) =>
|
|
60
|
+
domains
|
|
61
|
+
.map(({ id }) => `${id}`)
|
|
62
|
+
.sort()
|
|
63
|
+
.join("|");
|
|
64
|
+
|
|
65
|
+
export const readDomainAvailabilityCache = (domains = []) => {
|
|
66
|
+
if (typeof localStorage === "undefined") return null;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const cached = JSON.parse(
|
|
70
|
+
localStorage.getItem(LINEAGE_DOMAIN_AVAILABILITY_CACHE_KEY),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
if (!cached?.expiresAt || cached.expiresAt <= Date.now()) {
|
|
74
|
+
localStorage.removeItem(LINEAGE_DOMAIN_AVAILABILITY_CACHE_KEY);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (
|
|
79
|
+
(cached.domainIds || []).sort().join("|") !== domainIdsSignature(domains)
|
|
80
|
+
)
|
|
81
|
+
return null;
|
|
82
|
+
|
|
83
|
+
return cached.checks || null;
|
|
84
|
+
} catch (_error) {
|
|
85
|
+
localStorage.removeItem(LINEAGE_DOMAIN_AVAILABILITY_CACHE_KEY);
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const writeDomainAvailabilityCache = (domains = [], checks = []) => {
|
|
91
|
+
if (typeof localStorage === "undefined") return;
|
|
92
|
+
|
|
93
|
+
localStorage.setItem(
|
|
94
|
+
LINEAGE_DOMAIN_AVAILABILITY_CACHE_KEY,
|
|
95
|
+
JSON.stringify({
|
|
96
|
+
checks,
|
|
97
|
+
domainIds: domains.map(({ id }) => `${id}`),
|
|
98
|
+
expiresAt: Date.now() + LINEAGE_DOMAIN_CACHE_TTL,
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
101
|
+
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
const isCompletedStatus = (status) =>
|
|
2
|
+
["completed", "finalizado", "success", "succeeded"].includes(
|
|
3
|
+
`${status || ""}`.toLowerCase(),
|
|
4
|
+
);
|
|
5
|
+
|
|
6
|
+
const isFailedStatus = (status) =>
|
|
7
|
+
["failed", "fallido", "error"].includes(`${status || ""}`.toLowerCase());
|
|
8
|
+
|
|
9
|
+
const parseGraphData = (graphData) => {
|
|
10
|
+
if (!graphData) return null;
|
|
11
|
+
if (typeof graphData !== "string") return graphData;
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(graphData);
|
|
15
|
+
} catch (_error) {
|
|
16
|
+
return graphData;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const uniqueSortedStrings = (values = []) =>
|
|
21
|
+
[...new Set(values.map((value) => `${value}`))].sort();
|
|
22
|
+
|
|
23
|
+
const graphDataOrderedNodes = (graphData) => {
|
|
24
|
+
const parsedGraphData = parseGraphData(graphData);
|
|
25
|
+
|
|
26
|
+
if (Array.isArray(parsedGraphData))
|
|
27
|
+
return parsedGraphData.map((node) => `${node}`);
|
|
28
|
+
if (parsedGraphData?.ids) {
|
|
29
|
+
return parsedGraphData.ids.map((node) => `${node}`);
|
|
30
|
+
}
|
|
31
|
+
if (parsedGraphData?.nodes) {
|
|
32
|
+
return parsedGraphData.nodes.map((node) => `${node}`);
|
|
33
|
+
}
|
|
34
|
+
if (parsedGraphData?.resources) {
|
|
35
|
+
return parsedGraphData.resources.map((node) => `${node}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return parsedGraphData
|
|
39
|
+
? [
|
|
40
|
+
typeof parsedGraphData === "string"
|
|
41
|
+
? parsedGraphData
|
|
42
|
+
: JSON.stringify(parsedGraphData),
|
|
43
|
+
]
|
|
44
|
+
: [];
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const graphDataNodes = (graphData) => {
|
|
48
|
+
return uniqueSortedStrings(graphDataOrderedNodes(graphData));
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const eventTimestamp = (event = {}) => {
|
|
52
|
+
const timestamp = Date.parse(event.inserted_at);
|
|
53
|
+
|
|
54
|
+
return Number.isNaN(timestamp) ? 0 : timestamp;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const newestEvent = (left, right) =>
|
|
58
|
+
eventTimestamp(right) >= eventTimestamp(left) ? right : left;
|
|
59
|
+
|
|
60
|
+
const lineageEventType = (event = {}) => {
|
|
61
|
+
const parsedGraphData = parseGraphData(event.graph_data);
|
|
62
|
+
const type =
|
|
63
|
+
event.type ||
|
|
64
|
+
event.graph_type ||
|
|
65
|
+
event.graph_kind ||
|
|
66
|
+
event.opts?.type ||
|
|
67
|
+
parsedGraphData?.type ||
|
|
68
|
+
parsedGraphData?.opts?.type;
|
|
69
|
+
|
|
70
|
+
return `${type || "lineage"}`.toLowerCase() === "impact"
|
|
71
|
+
? "impact"
|
|
72
|
+
: "lineage";
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const lineageEventGroupKey = (event = {}) => {
|
|
76
|
+
const nodes = graphDataNodes(event.graph_data);
|
|
77
|
+
|
|
78
|
+
return nodes.length ? nodes.join("|") : `${event.graph_id || event.id || ""}`;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const groupLineageEvents = (events = []) => {
|
|
82
|
+
const groupedEvents = events.reduce((groups, event) => {
|
|
83
|
+
const key = lineageEventGroupKey(event);
|
|
84
|
+
const type = lineageEventType(event);
|
|
85
|
+
const group = groups[key] || {
|
|
86
|
+
key,
|
|
87
|
+
nodes: graphDataNodes(event.graph_data),
|
|
88
|
+
eventsByType: {},
|
|
89
|
+
};
|
|
90
|
+
const currentEvent = group.eventsByType[type];
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
...groups,
|
|
94
|
+
[key]: {
|
|
95
|
+
...group,
|
|
96
|
+
eventsByType: {
|
|
97
|
+
...group.eventsByType,
|
|
98
|
+
[type]: currentEvent ? newestEvent(currentEvent, event) : event,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}, {});
|
|
103
|
+
|
|
104
|
+
return Object.values(groupedEvents).sort((left, right) => {
|
|
105
|
+
const leftLastEvent = Object.values(left.eventsByType).reduce(newestEvent);
|
|
106
|
+
const rightLastEvent = Object.values(right.eventsByType).reduce(
|
|
107
|
+
newestEvent,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
return eventTimestamp(rightLastEvent) - eventTimestamp(leftLastEvent);
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const graphLabel = (event, index) =>
|
|
115
|
+
event.graph_id
|
|
116
|
+
? `#${event.graph_id}`
|
|
117
|
+
: event.id
|
|
118
|
+
? `#${event.id}`
|
|
119
|
+
: `#${index + 1}`;
|
|
120
|
+
|
|
121
|
+
const graphScope = (graphData) => {
|
|
122
|
+
const parsedGraphData = parseGraphData(graphData);
|
|
123
|
+
|
|
124
|
+
if (!parsedGraphData) return "";
|
|
125
|
+
if (Array.isArray(parsedGraphData)) return parsedGraphData.join(" -> ");
|
|
126
|
+
if (typeof parsedGraphData === "string") return parsedGraphData;
|
|
127
|
+
return JSON.stringify(parsedGraphData);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const graphDomain = (event) => {
|
|
131
|
+
const parsedGraphData = parseGraphData(event.graph_data);
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
event.domain?.name ||
|
|
135
|
+
event.domain_name ||
|
|
136
|
+
event.domain ||
|
|
137
|
+
parsedGraphData?.domain?.name ||
|
|
138
|
+
parsedGraphData?.domain_name ||
|
|
139
|
+
parsedGraphData?.domain ||
|
|
140
|
+
""
|
|
141
|
+
);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const graphFirstLevel = (event) => {
|
|
145
|
+
const parsedGraphData = parseGraphData(event.graph_data);
|
|
146
|
+
const nodes = graphDataOrderedNodes(event.graph_data);
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
event.first_level ||
|
|
150
|
+
event.firstLevel ||
|
|
151
|
+
event.level_name ||
|
|
152
|
+
parsedGraphData?.first_level ||
|
|
153
|
+
parsedGraphData?.firstLevel ||
|
|
154
|
+
parsedGraphData?.level_name ||
|
|
155
|
+
nodes[0] ||
|
|
156
|
+
""
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const graphTypeMessageId = (type) =>
|
|
161
|
+
type === "impact"
|
|
162
|
+
? "navigation.lineage.impact"
|
|
163
|
+
: "navigation.lineage.lineage";
|
|
164
|
+
|
|
165
|
+
const graphTypeIcon = (type) => (type === "impact" ? "bullseye" : "sitemap");
|
|
166
|
+
|
|
167
|
+
const eventErrorMessage = (event) =>
|
|
168
|
+
[
|
|
169
|
+
event.message,
|
|
170
|
+
event.error,
|
|
171
|
+
event.error_message,
|
|
172
|
+
event.errors,
|
|
173
|
+
event.reason,
|
|
174
|
+
].reduce((message, value) => {
|
|
175
|
+
if (message || !value) return message;
|
|
176
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
177
|
+
}, null);
|
|
178
|
+
|
|
179
|
+
export {
|
|
180
|
+
isCompletedStatus,
|
|
181
|
+
isFailedStatus,
|
|
182
|
+
parseGraphData,
|
|
183
|
+
graphDataOrderedNodes,
|
|
184
|
+
graphDataNodes,
|
|
185
|
+
eventTimestamp,
|
|
186
|
+
newestEvent,
|
|
187
|
+
lineageEventType,
|
|
188
|
+
graphLabel,
|
|
189
|
+
graphScope,
|
|
190
|
+
graphDomain,
|
|
191
|
+
graphFirstLevel,
|
|
192
|
+
graphTypeMessageId,
|
|
193
|
+
graphTypeIcon,
|
|
194
|
+
eventErrorMessage,
|
|
195
|
+
};
|