@truedat/core 8.11.5 → 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.
@@ -1,13 +1,22 @@
1
- import { memo } from "react";
1
+ import { memo, useContext } from "react";
2
+ import PropTypes from "prop-types";
2
3
  import { Handle, Position } from "@xyflow/react";
3
- import "./ConceptNode.less";
4
+ import { EdgeHoverContext } from "./ColoredEdge";
4
5
 
5
- const ConceptNode = ({ data }) => {
6
+ const ConceptNode = ({ id, data }) => {
7
+ const hoverCtx = useContext(EdgeHoverContext);
8
+ const isConnected =
9
+ (hoverCtx?.connectedNodeIds && hoverCtx.connectedNodeIds.has(id)) ||
10
+ (hoverCtx?.hoveredEdgeEndpoints &&
11
+ (hoverCtx.hoveredEdgeEndpoints.source === id ||
12
+ hoverCtx.hoveredEdgeEndpoints.target === id));
6
13
  const { label, isActive } = data;
7
14
 
8
15
  return (
9
16
  <div
10
- className={`td-concept-node${isActive ? " td-concept-node--active" : ""}`}
17
+ className={`td-concept-node${isActive ? " td-concept-node--active" : ""}${isConnected ? " td-concept-node--connected" : ""}`}
18
+ onMouseEnter={() => hoverCtx?.setHoveredNodeId(id)}
19
+ onMouseLeave={() => hoverCtx?.setHoveredNodeId(null)}
11
20
  >
12
21
  <Handle type="target" position={Position.Left} />
13
22
  <span className="td-concept-node__label">{label}</span>
@@ -16,4 +25,12 @@ const ConceptNode = ({ data }) => {
16
25
  );
17
26
  };
18
27
 
28
+ ConceptNode.propTypes = {
29
+ id: PropTypes.string,
30
+ data: PropTypes.shape({
31
+ isActive: PropTypes.bool,
32
+ label: PropTypes.node,
33
+ }),
34
+ };
35
+
19
36
  export default memo(ConceptNode);
@@ -0,0 +1 @@
1
+ export { ControlButton, Handle, Position } from "@xyflow/react";
@@ -0,0 +1,320 @@
1
+ import { memo, useContext } from "react";
2
+ import PropTypes from "prop-types";
3
+ import { Icon, Popup } from "semantic-ui-react";
4
+ import { useIntl } from "react-intl";
5
+ import { Handle, Position } from "@xyflow/react";
6
+ import LineageQueryOrigin from "./LineageQueryOrigin";
7
+ import { EdgeHoverContext } from "./ColoredEdge";
8
+ import lineageNodeIcon from "./lineageNodeIcon";
9
+
10
+ const relationTooltip = ({ sources = 0, targets = 0, total = 0 } = {}) =>
11
+ `Hidden relations: ${total}\nSources: ${sources}\nTargets: ${targets}`;
12
+
13
+ const stopNodeAction = (callback) => (event) => {
14
+ event.preventDefault();
15
+ event.stopPropagation();
16
+ callback && callback();
17
+ };
18
+
19
+ const LineageGroupNode = ({ id, data = {} }) => {
20
+ const { formatMessage } = useIntl();
21
+ const hoverCtx = useContext(EdgeHoverContext);
22
+ const isHovered = hoverCtx?.hoveredNodeId === id;
23
+ const isConnected =
24
+ (hoverCtx?.connectedNodeIds && hoverCtx.connectedNodeIds.has(id)) ||
25
+ (hoverCtx?.hoveredEdgeEndpoints &&
26
+ (hoverCtx.hoveredEdgeEndpoints.source === id ||
27
+ hoverCtx.hoveredEdgeEndpoints.target === id));
28
+ const handleMouseEnter = () => hoverCtx?.setHoveredNodeId(id);
29
+ const handleMouseLeave = () => hoverCtx?.setHoveredNodeId(null);
30
+ const {
31
+ canCollapse,
32
+ collapsed,
33
+ collapsedRelationCounts,
34
+ connectedHandleIds = [],
35
+ count,
36
+ designVariant,
37
+ hiddenChildrenCount,
38
+ iconName,
39
+ isActive,
40
+ focusDirect,
41
+ focusDimmed,
42
+ queryOrigin,
43
+ label,
44
+ nodeCount,
45
+ groupCount,
46
+ onExpandDescendants,
47
+ onToggleCollapse,
48
+ onConfirmOversizedExpand,
49
+ onDismissOversizedWarning,
50
+ oversized,
51
+ oversizedWarningOpen,
52
+ resourceContainer,
53
+ systemTypeClassName,
54
+ systemTypeLabel,
55
+ systemTypeMissing,
56
+ variant,
57
+ z,
58
+ } = data;
59
+ const connectedHandles = new Set(connectedHandleIds);
60
+ const connectedHandleClassName = (handleId) =>
61
+ connectedHandles.has(handleId) ? "td-lineage-node-handle--connected" : "";
62
+ const hasHiddenChildren = Number(hiddenChildrenCount) > 0;
63
+ const hasCollapsedRelations =
64
+ collapsed && Number(collapsedRelationCounts?.total) > 0;
65
+ const collapsedRelationsTooltip = relationTooltip(collapsedRelationCounts);
66
+ const hiddenChildrenTooltip = `Hidden child nodes: ${hiddenChildrenCount}`;
67
+ const contentSummary = [
68
+ nodeCount ? `${nodeCount} ${nodeCount === 1 ? "nodo" : "nodos"}` : "",
69
+ groupCount ? `${groupCount} ${groupCount === 1 ? "grupo" : "grupos"}` : "",
70
+ ]
71
+ .filter(Boolean)
72
+ .join(" · ");
73
+ const showContentSummary = collapsed && contentSummary;
74
+ const oversizedWarningMessage = formatMessage(
75
+ {
76
+ id: "lineage.graph.oversized.warning",
77
+ defaultMessage:
78
+ "This element contains more than {count} nodes. Its rendering may be affected.",
79
+ },
80
+ { count: 20 },
81
+ );
82
+ const oversizedWarningTitle = formatMessage({
83
+ id: "lineage.graph.oversized.header",
84
+ defaultMessage: "Warning",
85
+ });
86
+ const oversizedWarningTrigger = (
87
+ <button
88
+ aria-label={oversizedWarningMessage}
89
+ className="td-lineage-group-node__oversized-warning nodrag nopan"
90
+ type="button"
91
+ >
92
+ <Icon name="warning sign" />
93
+ </button>
94
+ );
95
+ const oversizedWarningContent = (
96
+ <div className="td-lineage-group-node__oversized-content">
97
+ <span className="td-lineage-group-node__oversized-header">
98
+ {oversizedWarningTitle}
99
+ </span>
100
+ <span>{oversizedWarningMessage}</span>
101
+ {oversizedWarningOpen && onConfirmOversizedExpand ? (
102
+ <button
103
+ className="td-lineage-group-node__oversized-open"
104
+ onClick={stopNodeAction(onConfirmOversizedExpand)}
105
+ type="button"
106
+ >
107
+ {formatMessage({
108
+ id: "lineage.graph.oversized.open",
109
+ defaultMessage: "Open",
110
+ })}
111
+ </button>
112
+ ) : null}
113
+ </div>
114
+ );
115
+ const oversizedWarningBadge = oversized ? (
116
+ <Popup
117
+ className="td-lineage-group-node__oversized-popup"
118
+ content={oversizedWarningContent}
119
+ on={oversizedWarningOpen ? "click" : "hover"}
120
+ onClose={oversizedWarningOpen ? onDismissOversizedWarning : undefined}
121
+ open={oversizedWarningOpen ? true : undefined}
122
+ position="top left"
123
+ trigger={oversizedWarningTrigger}
124
+ />
125
+ ) : null;
126
+ const groupIcon =
127
+ iconName || lineageNodeIcon({ kind: "group", resourceContainer, z });
128
+ const className = [
129
+ "td-lineage-group-node",
130
+ systemTypeClassName
131
+ ? `td-lineage-group-node--system-type-${systemTypeClassName}`
132
+ : "",
133
+ variant ? `td-lineage-group-node--${variant}` : "",
134
+ designVariant ? `td-lineage-group-node--design-${designVariant}` : "",
135
+ z !== undefined
136
+ ? `td-lineage-group-node--depth-${Math.min(Number(z) || 0, 6)}`
137
+ : "",
138
+ z === 0 ? "td-lineage-group-node--root" : "",
139
+ isActive ? "td-lineage-group-node--active" : "",
140
+ focusDirect ? "td-lineage-group-node--focus-direct" : "",
141
+ focusDimmed ? "td-lineage-group-node--focus-dimmed" : "",
142
+ queryOrigin ? "td-lineage-group-node--query-origin" : "",
143
+ isHovered ? "td-lineage-group-node--hovered" : "",
144
+ collapsed ? "td-lineage-group-node--collapsed" : "",
145
+ oversized ? "td-lineage-group-node--oversized" : "",
146
+ resourceContainer ? "td-lineage-group-node--resource-container" : "",
147
+ isConnected ? "td-lineage-group-node--connected" : "",
148
+ ]
149
+ .filter(Boolean)
150
+ .join(" ");
151
+
152
+ const handles = (
153
+ <>
154
+ <Handle
155
+ className={connectedHandleClassName("start-node")}
156
+ id="start-node"
157
+ type="target"
158
+ position={Position.Left}
159
+ />
160
+ <Handle
161
+ className={connectedHandleClassName("end-node")}
162
+ id="end-node"
163
+ type="source"
164
+ position={Position.Right}
165
+ />
166
+ <Handle
167
+ className={connectedHandleClassName("source-left-node")}
168
+ id="source-left-node"
169
+ type="source"
170
+ position={Position.Left}
171
+ />
172
+ <Handle
173
+ className={connectedHandleClassName("target-right-node")}
174
+ id="target-right-node"
175
+ type="target"
176
+ position={Position.Right}
177
+ />
178
+ </>
179
+ );
180
+
181
+ const absoluteBody = (
182
+ <>
183
+ {label ? (
184
+ <div className="td-lineage-group-node__absolute-header">
185
+ <Icon
186
+ aria-hidden="true"
187
+ className="td-lineage-group-node__type-icon"
188
+ name={groupIcon}
189
+ />
190
+ <span className="td-lineage-group-node__absolute-label" title={label}>
191
+ {label}
192
+ <LineageQueryOrigin origin={queryOrigin} />
193
+ </span>
194
+ {!systemTypeMissing && systemTypeLabel ? (
195
+ <span className="td-lineage-group-node__system-type">
196
+ {systemTypeLabel}
197
+ </span>
198
+ ) : null}
199
+ </div>
200
+ ) : null}
201
+ {showContentSummary ? (
202
+ <div className="td-lineage-group-node__footer">
203
+ <span className="td-lineage-group-node__content-summary">
204
+ {contentSummary}
205
+ </span>
206
+ </div>
207
+ ) : null}
208
+ {canCollapse && (onToggleCollapse || onExpandDescendants) ? (
209
+ <span className="td-lineage-group-node__actions">
210
+ {onToggleCollapse ? (
211
+ <button
212
+ aria-label={collapsed ? "Expand group" : "Collapse group"}
213
+ className="td-lineage-group-node__toggle nodrag nopan"
214
+ onClick={stopNodeAction(onToggleCollapse)}
215
+ type="button"
216
+ >
217
+ {collapsed ? "+" : "-"}
218
+ </button>
219
+ ) : null}
220
+ {onExpandDescendants ? (
221
+ <button
222
+ aria-label="Expand group and descendants"
223
+ className="td-lineage-group-node__toggle td-lineage-group-node__toggle--descendants nodrag nopan"
224
+ onClick={stopNodeAction(onExpandDescendants)}
225
+ type="button"
226
+ >
227
+ ++
228
+ </button>
229
+ ) : null}
230
+ </span>
231
+ ) : null}
232
+ {oversizedWarningBadge}
233
+ {hasCollapsedRelations ? (
234
+ <span
235
+ aria-label={collapsedRelationsTooltip}
236
+ className="td-lineage-group-node__collapsed-relations"
237
+ data-tooltip={collapsedRelationsTooltip}
238
+ >
239
+ {collapsedRelationCounts.total}
240
+ </span>
241
+ ) : null}
242
+ {collapsed && hasHiddenChildren ? (
243
+ <span
244
+ aria-label={hiddenChildrenTooltip}
245
+ className="td-lineage-group-node__collapsed-count"
246
+ data-tooltip={hiddenChildrenTooltip}
247
+ >
248
+ {hiddenChildrenCount}
249
+ </span>
250
+ ) : null}
251
+ </>
252
+ );
253
+
254
+ const defaultBody = (
255
+ <div className="td-lineage-group-node__header">
256
+ <Icon
257
+ aria-hidden="true"
258
+ className="td-lineage-group-node__type-icon"
259
+ name={groupIcon}
260
+ />
261
+ <span className="td-lineage-group-node__label" title={label}>
262
+ {label}
263
+ <LineageQueryOrigin origin={queryOrigin} />
264
+ </span>
265
+ {count ? (
266
+ <span className="td-lineage-group-node__count">{count}</span>
267
+ ) : null}
268
+ </div>
269
+ );
270
+
271
+ return (
272
+ <div
273
+ className={className}
274
+ onMouseEnter={handleMouseEnter}
275
+ onMouseLeave={handleMouseLeave}
276
+ >
277
+ {handles}
278
+ {variant === "absolute" ? absoluteBody : defaultBody}
279
+ </div>
280
+ );
281
+ };
282
+
283
+ LineageGroupNode.propTypes = {
284
+ id: PropTypes.string,
285
+ data: PropTypes.shape({
286
+ canCollapse: PropTypes.bool,
287
+ collapsed: PropTypes.bool,
288
+ collapsedRelationCounts: PropTypes.shape({
289
+ sources: PropTypes.number,
290
+ targets: PropTypes.number,
291
+ total: PropTypes.number,
292
+ }),
293
+ connectedHandleIds: PropTypes.arrayOf(PropTypes.string),
294
+ count: PropTypes.number,
295
+ designVariant: PropTypes.oneOf(["classic", "redesigned"]),
296
+ hiddenChildrenCount: PropTypes.number,
297
+ iconName: PropTypes.string,
298
+ isActive: PropTypes.bool,
299
+ focusDirect: PropTypes.bool,
300
+ focusDimmed: PropTypes.bool,
301
+ queryOrigin: PropTypes.oneOf(["direct", "contained"]),
302
+ label: PropTypes.node,
303
+ nodeCount: PropTypes.number,
304
+ groupCount: PropTypes.number,
305
+ onExpandDescendants: PropTypes.func,
306
+ onToggleCollapse: PropTypes.func,
307
+ onConfirmOversizedExpand: PropTypes.func,
308
+ onDismissOversizedWarning: PropTypes.func,
309
+ oversized: PropTypes.bool,
310
+ oversizedWarningOpen: PropTypes.bool,
311
+ resourceContainer: PropTypes.bool,
312
+ systemTypeClassName: PropTypes.string,
313
+ systemTypeLabel: PropTypes.string,
314
+ systemTypeMissing: PropTypes.bool,
315
+ variant: PropTypes.string,
316
+ z: PropTypes.number,
317
+ }),
318
+ };
319
+
320
+ export default memo(LineageGroupNode);
@@ -0,0 +1,27 @@
1
+ import PropTypes from "prop-types";
2
+ import { useIntl } from "react-intl";
3
+ import { queryOriginMessages } from "./lineageQueryOriginMessages";
4
+
5
+ const LineageQueryOrigin = ({ origin }) => {
6
+ const { formatMessage, locale } = useIntl();
7
+ if (!origin) return null;
8
+ const id =
9
+ origin === "direct"
10
+ ? "lineage.graph.query_origin"
11
+ : "lineage.graph.query_origin.contained";
12
+
13
+ const defaults =
14
+ queryOriginMessages[locale.split("-")[0]] || queryOriginMessages.en;
15
+ const label = formatMessage({ id, defaultMessage: defaults[id] });
16
+ return (
17
+ <span className="td-lineage-query-origin" title={label}>
18
+ {label}
19
+ </span>
20
+ );
21
+ };
22
+
23
+ LineageQueryOrigin.propTypes = {
24
+ origin: PropTypes.oneOf(["direct", "contained"]),
25
+ };
26
+
27
+ export default LineageQueryOrigin;
@@ -1,12 +1,15 @@
1
- const TARGET_SLOT_STEP = 10;
2
- const MAX_TARGET_SLOT_SPAN = 22;
1
+ const TARGET_SLOT_STEP = 8;
3
2
 
4
- export const getTargetSlotOffset = (slotIndex = 0, slotCount = 1) => {
3
+ export const getTargetSlotOffset = (
4
+ slotIndex = 0,
5
+ slotCount = 1,
6
+ availableSpan = Number.POSITIVE_INFINITY,
7
+ ) => {
5
8
  if (!slotCount || slotCount <= 1) return 0;
6
9
 
7
10
  const totalSpan = Math.min(
8
11
  (slotCount - 1) * TARGET_SLOT_STEP,
9
- MAX_TARGET_SLOT_SPAN,
12
+ Math.max(0, availableSpan),
10
13
  );
11
14
  const step = totalSpan / (slotCount - 1);
12
15
 
@@ -0,0 +1,17 @@
1
+ export const lineageNodeIcon = ({
2
+ kind,
3
+ lineageKind,
4
+ resourceContainer,
5
+ z,
6
+ } = {}) => {
7
+ const resolvedKind = kind || lineageKind;
8
+
9
+ if (resolvedKind === "process") return "random";
10
+ if (resolvedKind === "resource") return "table";
11
+ if (resourceContainer) return "table";
12
+ if (z !== undefined) return Number(z) >= 2 ? "database" : "server";
13
+
14
+ return "database";
15
+ };
16
+
17
+ export default lineageNodeIcon;
@@ -0,0 +1,10 @@
1
+ export const queryOriginMessages = {
2
+ en: {
3
+ "lineage.graph.query_origin": "Query origin",
4
+ "lineage.graph.query_origin.contained": "Contains query origin",
5
+ },
6
+ es: {
7
+ "lineage.graph.query_origin": "Origen de consulta",
8
+ "lineage.graph.query_origin.contained": "Contiene origen de consulta",
9
+ },
10
+ };
@@ -0,0 +1,3 @@
1
+ .td-graph-edge {
2
+ vector-effect: non-scaling-stroke;
3
+ }
@@ -0,0 +1,76 @@
1
+ .td-graph-container {
2
+ height: var(--td-graph-container-height, 640px);
3
+
4
+ .react-flow {
5
+ background: var(--td-graph-bg, #f7f8fa);
6
+ }
7
+
8
+ .td-graph__space-pan-active,
9
+ .td-graph__space-pan-active .react-flow__pane,
10
+ .td-graph__space-pan-active .react-flow__node {
11
+ cursor: grab;
12
+ }
13
+
14
+ .td-graph__space-pan-active:active,
15
+ .td-graph__space-pan-active:active .react-flow__pane,
16
+ .td-graph__space-pan-active:active .react-flow__node {
17
+ cursor: grabbing;
18
+ }
19
+
20
+ .react-flow__controls {
21
+ margin: 8px;
22
+ border-radius: 12px;
23
+ overflow: hidden;
24
+ }
25
+
26
+ .react-flow__controls-button.td-graph-controls-return {
27
+ width: auto;
28
+ min-width: 128px;
29
+ padding: 0 12px;
30
+ font-size: 11px;
31
+ font-weight: 700;
32
+ white-space: nowrap;
33
+ }
34
+
35
+ .react-flow__controls.td-graph-controls--vertical {
36
+ display: flex;
37
+ flex-direction: column;
38
+ align-items: center;
39
+ gap: 0;
40
+ overflow: visible;
41
+ }
42
+
43
+ .react-flow__controls.td-graph-controls--vertical > span {
44
+ display: inline-flex;
45
+ flex: 0 0 auto;
46
+ }
47
+
48
+ .react-flow__controls.td-graph-controls--vertical
49
+ .react-flow__controls-button {
50
+ width: 34px;
51
+ min-width: 34px;
52
+ height: 34px;
53
+ min-height: 34px;
54
+ padding: 0;
55
+ }
56
+
57
+ .react-flow__controls-button svg {
58
+ width: auto;
59
+ max-width: none;
60
+ max-height: none;
61
+ fill: none;
62
+ }
63
+ }
64
+
65
+ .td-graph-edge-label {
66
+ position: absolute;
67
+ background: rgba(0, 0, 0, 0.75);
68
+ color: #fff;
69
+ padding: 2px 6px;
70
+ border-radius: 4px;
71
+ font-size: 11px;
72
+ font-weight: 500;
73
+ pointer-events: none;
74
+ white-space: nowrap;
75
+ z-index: 1000;
76
+ }
@@ -0,0 +1,6 @@
1
+ @import "./ConceptNode.less";
2
+
3
+ .td-concept-node.td-concept-node--connected {
4
+ border-color: var(--td-graph-accent, #ed5c17);
5
+ box-shadow: 0 0 0 2px rgba(237, 92, 23, 0.22);
6
+ }