@truedat/core 8.11.6 → 8.11.9

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,307 @@
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
+ iconName,
38
+ isActive,
39
+ focusDirect,
40
+ focusDimmed,
41
+ queryOrigin,
42
+ label,
43
+ nodeCount,
44
+ groupCount,
45
+ onExpandDescendants,
46
+ onToggleCollapse,
47
+ onConfirmOversizedExpand,
48
+ onDismissOversizedWarning,
49
+ oversized,
50
+ oversizedWarningOpen,
51
+ resourceContainer,
52
+ systemTypeClassName,
53
+ systemTypeLabel,
54
+ systemTypeMissing,
55
+ variant,
56
+ z,
57
+ } = data;
58
+ const connectedHandles = new Set(connectedHandleIds);
59
+ const connectedHandleClassName = (handleId) =>
60
+ connectedHandles.has(handleId) ? "td-lineage-node-handle--connected" : "";
61
+ const hasCollapsedRelations =
62
+ collapsed && Number(collapsedRelationCounts?.total) > 0;
63
+ const collapsedRelationsTooltip = relationTooltip(collapsedRelationCounts);
64
+ const contentSummary = [
65
+ nodeCount ? `${nodeCount} ${nodeCount === 1 ? "nodo" : "nodos"}` : "",
66
+ groupCount ? `${groupCount} ${groupCount === 1 ? "grupo" : "grupos"}` : "",
67
+ ]
68
+ .filter(Boolean)
69
+ .join(" · ");
70
+ const showContentSummary = collapsed && contentSummary;
71
+ const oversizedWarningMessage = formatMessage(
72
+ {
73
+ id: "lineage.graph.oversized.warning",
74
+ defaultMessage:
75
+ "This element contains more than {count} nodes. Its rendering may be affected.",
76
+ },
77
+ { count: 20 },
78
+ );
79
+ const oversizedWarningTitle = formatMessage({
80
+ id: "lineage.graph.oversized.header",
81
+ defaultMessage: "Warning",
82
+ });
83
+ const oversizedWarningTrigger = (
84
+ <button
85
+ aria-label={oversizedWarningMessage}
86
+ className="td-lineage-group-node__oversized-warning nodrag nopan"
87
+ type="button"
88
+ >
89
+ <Icon name="warning sign" />
90
+ </button>
91
+ );
92
+ const oversizedWarningContent = (
93
+ <div className="td-lineage-group-node__oversized-content">
94
+ <span className="td-lineage-group-node__oversized-header">
95
+ {oversizedWarningTitle}
96
+ </span>
97
+ <span>{oversizedWarningMessage}</span>
98
+ {oversizedWarningOpen && onConfirmOversizedExpand ? (
99
+ <button
100
+ className="td-lineage-group-node__oversized-open"
101
+ onClick={stopNodeAction(onConfirmOversizedExpand)}
102
+ type="button"
103
+ >
104
+ {formatMessage({
105
+ id: "lineage.graph.oversized.open",
106
+ defaultMessage: "Open",
107
+ })}
108
+ </button>
109
+ ) : null}
110
+ </div>
111
+ );
112
+ const oversizedWarningBadge = oversized ? (
113
+ <Popup
114
+ className="td-lineage-group-node__oversized-popup"
115
+ content={oversizedWarningContent}
116
+ on={oversizedWarningOpen ? "click" : "hover"}
117
+ onClose={oversizedWarningOpen ? onDismissOversizedWarning : undefined}
118
+ open={oversizedWarningOpen ? true : undefined}
119
+ position="top left"
120
+ trigger={oversizedWarningTrigger}
121
+ />
122
+ ) : null;
123
+ const groupIcon =
124
+ iconName || lineageNodeIcon({ kind: "group", resourceContainer, z });
125
+ const className = [
126
+ "td-lineage-group-node",
127
+ systemTypeClassName
128
+ ? `td-lineage-group-node--system-type-${systemTypeClassName}`
129
+ : "",
130
+ variant ? `td-lineage-group-node--${variant}` : "",
131
+ designVariant ? `td-lineage-group-node--design-${designVariant}` : "",
132
+ z !== undefined
133
+ ? `td-lineage-group-node--depth-${Math.min(Number(z) || 0, 6)}`
134
+ : "",
135
+ z === 0 ? "td-lineage-group-node--root" : "",
136
+ isActive ? "td-lineage-group-node--active" : "",
137
+ focusDirect ? "td-lineage-group-node--focus-direct" : "",
138
+ focusDimmed ? "td-lineage-group-node--focus-dimmed" : "",
139
+ queryOrigin ? "td-lineage-group-node--query-origin" : "",
140
+ isHovered ? "td-lineage-group-node--hovered" : "",
141
+ collapsed ? "td-lineage-group-node--collapsed" : "",
142
+ oversized ? "td-lineage-group-node--oversized" : "",
143
+ resourceContainer ? "td-lineage-group-node--resource-container" : "",
144
+ isConnected ? "td-lineage-group-node--connected" : "",
145
+ ]
146
+ .filter(Boolean)
147
+ .join(" ");
148
+
149
+ const handles = (
150
+ <>
151
+ <Handle
152
+ className={connectedHandleClassName("start-node")}
153
+ id="start-node"
154
+ type="target"
155
+ position={Position.Left}
156
+ />
157
+ <Handle
158
+ className={connectedHandleClassName("end-node")}
159
+ id="end-node"
160
+ type="source"
161
+ position={Position.Right}
162
+ />
163
+ <Handle
164
+ className={connectedHandleClassName("source-left-node")}
165
+ id="source-left-node"
166
+ type="source"
167
+ position={Position.Left}
168
+ />
169
+ <Handle
170
+ className={connectedHandleClassName("target-right-node")}
171
+ id="target-right-node"
172
+ type="target"
173
+ position={Position.Right}
174
+ />
175
+ </>
176
+ );
177
+
178
+ const absoluteBody = (
179
+ <>
180
+ {label ? (
181
+ <div className="td-lineage-group-node__absolute-header">
182
+ <Icon
183
+ aria-hidden="true"
184
+ className="td-lineage-group-node__type-icon"
185
+ name={groupIcon}
186
+ />
187
+ <span className="td-lineage-group-node__absolute-label" title={label}>
188
+ {label}
189
+ <LineageQueryOrigin origin={queryOrigin} />
190
+ </span>
191
+ {!systemTypeMissing && systemTypeLabel ? (
192
+ <span className="td-lineage-group-node__system-type">
193
+ {systemTypeLabel}
194
+ </span>
195
+ ) : null}
196
+ </div>
197
+ ) : null}
198
+ {showContentSummary ? (
199
+ <div className="td-lineage-group-node__footer">
200
+ <span className="td-lineage-group-node__content-summary">
201
+ {contentSummary}
202
+ </span>
203
+ </div>
204
+ ) : null}
205
+ {canCollapse && (onToggleCollapse || onExpandDescendants) ? (
206
+ <span className="td-lineage-group-node__actions">
207
+ {onToggleCollapse ? (
208
+ <button
209
+ aria-label={collapsed ? "Expand group" : "Collapse group"}
210
+ className="td-lineage-group-node__toggle nodrag nopan"
211
+ onClick={stopNodeAction(onToggleCollapse)}
212
+ type="button"
213
+ >
214
+ {collapsed ? "+" : "-"}
215
+ </button>
216
+ ) : null}
217
+ {onExpandDescendants ? (
218
+ <button
219
+ aria-label="Expand group and descendants"
220
+ className="td-lineage-group-node__toggle td-lineage-group-node__toggle--descendants nodrag nopan"
221
+ onClick={stopNodeAction(onExpandDescendants)}
222
+ type="button"
223
+ >
224
+ ++
225
+ </button>
226
+ ) : null}
227
+ </span>
228
+ ) : null}
229
+ {oversizedWarningBadge}
230
+ {hasCollapsedRelations ? (
231
+ <span
232
+ aria-label={collapsedRelationsTooltip}
233
+ className="td-lineage-group-node__collapsed-relations"
234
+ data-tooltip={collapsedRelationsTooltip}
235
+ >
236
+ {collapsedRelationCounts.total}
237
+ </span>
238
+ ) : null}
239
+ </>
240
+ );
241
+
242
+ const defaultBody = (
243
+ <div className="td-lineage-group-node__header">
244
+ <Icon
245
+ aria-hidden="true"
246
+ className="td-lineage-group-node__type-icon"
247
+ name={groupIcon}
248
+ />
249
+ <span className="td-lineage-group-node__label" title={label}>
250
+ {label}
251
+ <LineageQueryOrigin origin={queryOrigin} />
252
+ </span>
253
+ {count ? (
254
+ <span className="td-lineage-group-node__count">{count}</span>
255
+ ) : null}
256
+ </div>
257
+ );
258
+
259
+ return (
260
+ <div
261
+ className={className}
262
+ onMouseEnter={handleMouseEnter}
263
+ onMouseLeave={handleMouseLeave}
264
+ >
265
+ {handles}
266
+ {variant === "absolute" ? absoluteBody : defaultBody}
267
+ </div>
268
+ );
269
+ };
270
+
271
+ LineageGroupNode.propTypes = {
272
+ id: PropTypes.string,
273
+ data: PropTypes.shape({
274
+ canCollapse: PropTypes.bool,
275
+ collapsed: PropTypes.bool,
276
+ collapsedRelationCounts: PropTypes.shape({
277
+ sources: PropTypes.number,
278
+ targets: PropTypes.number,
279
+ total: PropTypes.number,
280
+ }),
281
+ connectedHandleIds: PropTypes.arrayOf(PropTypes.string),
282
+ count: PropTypes.number,
283
+ designVariant: PropTypes.oneOf(["classic", "redesigned"]),
284
+ iconName: PropTypes.string,
285
+ isActive: PropTypes.bool,
286
+ focusDirect: PropTypes.bool,
287
+ focusDimmed: PropTypes.bool,
288
+ queryOrigin: PropTypes.oneOf(["direct", "contained"]),
289
+ label: PropTypes.node,
290
+ nodeCount: PropTypes.number,
291
+ groupCount: PropTypes.number,
292
+ onExpandDescendants: PropTypes.func,
293
+ onToggleCollapse: PropTypes.func,
294
+ onConfirmOversizedExpand: PropTypes.func,
295
+ onDismissOversizedWarning: PropTypes.func,
296
+ oversized: PropTypes.bool,
297
+ oversizedWarningOpen: PropTypes.bool,
298
+ resourceContainer: PropTypes.bool,
299
+ systemTypeClassName: PropTypes.string,
300
+ systemTypeLabel: PropTypes.string,
301
+ systemTypeMissing: PropTypes.bool,
302
+ variant: PropTypes.string,
303
+ z: PropTypes.number,
304
+ }),
305
+ };
306
+
307
+ 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
+ }