@principal-ai/principal-view-react 0.13.19 → 0.13.21
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/dist/components/NodeTooltip.d.ts +4 -1
- package/dist/components/NodeTooltip.d.ts.map +1 -1
- package/dist/components/NodeTooltip.js +20 -13
- package/dist/components/NodeTooltip.js.map +1 -1
- package/dist/nodes/CustomNode.d.ts.map +1 -1
- package/dist/nodes/CustomNode.js +100 -20
- package/dist/nodes/CustomNode.js.map +1 -1
- package/package.json +2 -2
- package/src/components/NodeTooltip.tsx +40 -21
- package/src/nodes/CustomNode.tsx +130 -19
- package/src/stories/DiamondBadges.stories.tsx +629 -0
- package/src/stories/GraphRenderer.stories.tsx +0 -4
- package/src/stories/NodeDefinitionComparison.stories.tsx +2 -4
- package/src/stories/OtelComponents.stories.tsx +1 -4
- package/src/stories/data/graph-converter-test-execution.json +48 -48
package/src/nodes/CustomNode.tsx
CHANGED
|
@@ -90,6 +90,9 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, se
|
|
|
90
90
|
console.log('[CustomNode] Node data:', {
|
|
91
91
|
name: nodeProps.name,
|
|
92
92
|
nodeDataKeys: nodeData ? Object.keys(nodeData).join(', ') : 'undefined',
|
|
93
|
+
references: nodeData?.references,
|
|
94
|
+
otelFiles: (nodeData?.otel as { files?: string[] })?.files,
|
|
95
|
+
sources: nodeData?.sources,
|
|
93
96
|
fullNodeData: nodeData,
|
|
94
97
|
});
|
|
95
98
|
|
|
@@ -99,10 +102,13 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, se
|
|
|
99
102
|
const showTooltip =
|
|
100
103
|
(isHovered && !dragging && shiftKeyPressed) || (!editable && !!selected);
|
|
101
104
|
|
|
102
|
-
// Extract OTEL info, description, and
|
|
103
|
-
const otelInfo = nodeData?.otel as OtelInfo | undefined;
|
|
105
|
+
// Extract OTEL info, description, sources/files, and references for tooltip
|
|
106
|
+
const otelInfo = nodeData?.otel as (OtelInfo & { files?: string[] }) | undefined;
|
|
104
107
|
const description = nodeData?.description as string | undefined;
|
|
105
|
-
const sources = nodeData?.sources as string[] | undefined;
|
|
108
|
+
const sources = nodeData?.sources as string[] | undefined; // deprecated
|
|
109
|
+
const references = nodeData?.references as string[] | undefined;
|
|
110
|
+
// Files from otel.files - these are source code files where the event is instrumented
|
|
111
|
+
const files = otelInfo?.files;
|
|
106
112
|
|
|
107
113
|
// Get badge shape styles based on node shape
|
|
108
114
|
const getBadgeShapeStyles = (): React.CSSProperties => {
|
|
@@ -140,20 +146,71 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, se
|
|
|
140
146
|
}
|
|
141
147
|
};
|
|
142
148
|
|
|
143
|
-
//
|
|
149
|
+
// Get badge position based on shape - diamonds need badges at their points, not bounding box corners
|
|
150
|
+
const getBadgePosition = (position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'left' | 'right' | 'top' | 'bottom'): React.CSSProperties => {
|
|
151
|
+
const isDiamondShape = typeDefinition.shape === 'diamond';
|
|
152
|
+
|
|
153
|
+
if (isDiamondShape) {
|
|
154
|
+
// Diamond points are at the middle of each edge of the bounding box
|
|
155
|
+
switch (position) {
|
|
156
|
+
case 'top-left':
|
|
157
|
+
case 'left':
|
|
158
|
+
// Position at the LEFT point of the diamond (center-left)
|
|
159
|
+
return { top: '50%', left: 0, transform: 'translate(-50%, -50%)' };
|
|
160
|
+
case 'top-right':
|
|
161
|
+
case 'right':
|
|
162
|
+
// Position at the RIGHT point of the diamond (center-right)
|
|
163
|
+
return { top: '50%', right: 0, transform: 'translate(50%, -50%)' };
|
|
164
|
+
case 'top':
|
|
165
|
+
// Position at the TOP point of the diamond
|
|
166
|
+
return { top: 0, left: '50%', transform: 'translate(-50%, -50%)' };
|
|
167
|
+
case 'bottom':
|
|
168
|
+
case 'bottom-left':
|
|
169
|
+
case 'bottom-right':
|
|
170
|
+
// Position at the BOTTOM point of the diamond
|
|
171
|
+
return { bottom: 0, left: '50%', transform: 'translate(-50%, 50%)' };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Default positioning for rectangles, circles, hexagons (bounding box corners)
|
|
176
|
+
switch (position) {
|
|
177
|
+
case 'top-left':
|
|
178
|
+
return { top: -6, left: -6 };
|
|
179
|
+
case 'top-right':
|
|
180
|
+
return { top: -6, right: -6 };
|
|
181
|
+
case 'bottom-left':
|
|
182
|
+
return { bottom: -6, left: -6 };
|
|
183
|
+
case 'bottom-right':
|
|
184
|
+
return { bottom: -6, right: -6 };
|
|
185
|
+
case 'left':
|
|
186
|
+
return { top: -6, left: -6 };
|
|
187
|
+
case 'right':
|
|
188
|
+
return { top: -6, right: -6 };
|
|
189
|
+
case 'top':
|
|
190
|
+
return { top: -6, left: '50%', transform: 'translateX(-50%)' };
|
|
191
|
+
case 'bottom':
|
|
192
|
+
return { bottom: -6, left: '50%', transform: 'translateX(-50%)' };
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// Render Sources badge (top-right) - shows "S" for files where event is instrumented
|
|
144
197
|
const renderSourcesBadge = () => {
|
|
145
|
-
|
|
146
|
-
|
|
198
|
+
// Use otel.files (source code files where event is instrumented)
|
|
199
|
+
// Fall back to deprecated sources field for backwards compatibility
|
|
200
|
+
const sourceFiles = files || sources;
|
|
201
|
+
if (!sourceFiles || sourceFiles.length === 0) return null;
|
|
147
202
|
|
|
148
203
|
const shapeStyles = getBadgeShapeStyles();
|
|
204
|
+
const positionStyles = getBadgePosition('top-right');
|
|
149
205
|
|
|
150
206
|
return (
|
|
151
207
|
<div
|
|
152
208
|
style={{
|
|
153
209
|
position: 'absolute',
|
|
154
|
-
|
|
155
|
-
right: -6,
|
|
210
|
+
...positionStyles,
|
|
156
211
|
...shapeStyles,
|
|
212
|
+
// Override transform if shape has rotation but we already have a position transform
|
|
213
|
+
...(typeDefinition.shape === 'diamond' ? { transform: `${positionStyles.transform} rotate(45deg)` } : {}),
|
|
157
214
|
backgroundColor: '#10b981', // Green for sources
|
|
158
215
|
color: 'white',
|
|
159
216
|
fontSize: theme.fontSizes[0],
|
|
@@ -166,14 +223,48 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, se
|
|
|
166
223
|
zIndex: 10,
|
|
167
224
|
opacity: nodeOpacity,
|
|
168
225
|
}}
|
|
169
|
-
title={`
|
|
226
|
+
title={`Source files: ${sourceFiles.join(', ')}`}
|
|
170
227
|
>
|
|
171
228
|
<span style={{ transform: shapeStyles.transform ? 'rotate(-45deg)' : undefined }}>S</span>
|
|
172
229
|
</div>
|
|
173
230
|
);
|
|
174
231
|
};
|
|
175
232
|
|
|
176
|
-
// Render
|
|
233
|
+
// Render References badge (bottom-left for rectangles, bottom for diamonds) - shows "R" for external references
|
|
234
|
+
const renderReferencesBadge = () => {
|
|
235
|
+
if (!references || references.length === 0) return null;
|
|
236
|
+
|
|
237
|
+
const shapeStyles = getBadgeShapeStyles();
|
|
238
|
+
const positionStyles = getBadgePosition('bottom-left');
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<div
|
|
242
|
+
style={{
|
|
243
|
+
position: 'absolute',
|
|
244
|
+
...positionStyles,
|
|
245
|
+
...shapeStyles,
|
|
246
|
+
// Override transform if shape has rotation but we already have a position transform
|
|
247
|
+
...(typeDefinition.shape === 'diamond' ? { transform: `${positionStyles.transform} rotate(45deg)` } : {}),
|
|
248
|
+
backgroundColor: '#8b5cf6', // Purple for references
|
|
249
|
+
color: 'white',
|
|
250
|
+
fontSize: theme.fontSizes[0],
|
|
251
|
+
fontWeight: theme.fontWeights.bold,
|
|
252
|
+
fontFamily: theme.fonts.body,
|
|
253
|
+
display: 'flex',
|
|
254
|
+
alignItems: 'center',
|
|
255
|
+
justifyContent: 'center',
|
|
256
|
+
boxShadow: '0 1px 3px rgba(0,0,0,0.3)',
|
|
257
|
+
zIndex: 10,
|
|
258
|
+
opacity: nodeOpacity,
|
|
259
|
+
}}
|
|
260
|
+
title={`References: ${references.join(', ')}`}
|
|
261
|
+
>
|
|
262
|
+
<span style={{ transform: shapeStyles.transform ? 'rotate(-45deg)' : undefined }}>R</span>
|
|
263
|
+
</div>
|
|
264
|
+
);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// Render Boundary badge (right/left points) - shown instead of sources badge for boundary nodes
|
|
177
268
|
const renderBoundaryBadge = () => {
|
|
178
269
|
const boundary = nodeData?.boundary as { direction?: 'outbound' | 'inbound'; node?: Record<string, string> } | undefined;
|
|
179
270
|
if (!boundary) return null;
|
|
@@ -185,13 +276,13 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, se
|
|
|
185
276
|
const directionIcon = isOutbound ? '↗' : '↙';
|
|
186
277
|
const directionTitle = isOutbound ? 'Outbound boundary (calls external system)' : 'Inbound boundary (called by external system)';
|
|
187
278
|
|
|
279
|
+
const positionStyles = getBadgePosition(isOutbound ? 'right' : 'left');
|
|
280
|
+
|
|
188
281
|
return (
|
|
189
282
|
<div
|
|
190
283
|
style={{
|
|
191
284
|
position: 'absolute',
|
|
192
|
-
|
|
193
|
-
// Inbound badge on left, outbound badge on right
|
|
194
|
-
...(isOutbound ? { right: -6 } : { left: -6 }),
|
|
285
|
+
...positionStyles,
|
|
195
286
|
width: 18,
|
|
196
287
|
height: 18,
|
|
197
288
|
borderRadius: '50%', // Always circular for boundary badge
|
|
@@ -217,7 +308,7 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, se
|
|
|
217
308
|
// Check if this is a boundary node
|
|
218
309
|
const isBoundaryNode = nodeData?.nodeType === 'boundary';
|
|
219
310
|
|
|
220
|
-
// Render Status badge (top-left)
|
|
311
|
+
// Render Status badge (top-left, or top point for diamonds)
|
|
221
312
|
const renderStatusBadge = () => {
|
|
222
313
|
const status = nodeData?.status as 'draft' | 'approved' | 'implemented' | undefined;
|
|
223
314
|
if (!status) return null;
|
|
@@ -242,14 +333,16 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, se
|
|
|
242
333
|
};
|
|
243
334
|
|
|
244
335
|
const shapeStyles = getBadgeShapeStyles();
|
|
336
|
+
const positionStyles = getBadgePosition('top-left');
|
|
245
337
|
|
|
246
338
|
return (
|
|
247
339
|
<div
|
|
248
340
|
style={{
|
|
249
341
|
position: 'absolute',
|
|
250
|
-
|
|
251
|
-
left: -6,
|
|
342
|
+
...positionStyles,
|
|
252
343
|
...shapeStyles,
|
|
344
|
+
// Override transform if shape has rotation but we already have a position transform
|
|
345
|
+
...(typeDefinition.shape === 'diamond' ? { transform: `${positionStyles.transform} rotate(45deg)` } : {}),
|
|
253
346
|
backgroundColor: statusColors[status],
|
|
254
347
|
color: 'white',
|
|
255
348
|
fontSize: theme.fontSizes[0],
|
|
@@ -623,7 +716,12 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, se
|
|
|
623
716
|
onMouseLeave={() => setIsHovered(false)}
|
|
624
717
|
>
|
|
625
718
|
{renderStatusBadge()}
|
|
626
|
-
{isBoundaryNode ? renderBoundaryBadge() :
|
|
719
|
+
{isBoundaryNode ? renderBoundaryBadge() : (
|
|
720
|
+
<>
|
|
721
|
+
{renderSourcesBadge()}
|
|
722
|
+
{renderReferencesBadge()}
|
|
723
|
+
</>
|
|
724
|
+
)}
|
|
627
725
|
<div style={hexagonBorderStyle} className={animationClass}>
|
|
628
726
|
<div style={hexagonInnerStyle}>
|
|
629
727
|
{icon && (
|
|
@@ -666,6 +764,7 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, se
|
|
|
666
764
|
description={description}
|
|
667
765
|
otel={otelInfo}
|
|
668
766
|
sources={sources}
|
|
767
|
+
references={references}
|
|
669
768
|
visible={showTooltip}
|
|
670
769
|
nodeRef={nodeRef}
|
|
671
770
|
/>
|
|
@@ -689,7 +788,12 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, se
|
|
|
689
788
|
onMouseLeave={() => setIsHovered(false)}
|
|
690
789
|
>
|
|
691
790
|
{renderStatusBadge()}
|
|
692
|
-
{isBoundaryNode ? renderBoundaryBadge() :
|
|
791
|
+
{isBoundaryNode ? renderBoundaryBadge() : (
|
|
792
|
+
<>
|
|
793
|
+
{renderSourcesBadge()}
|
|
794
|
+
{renderReferencesBadge()}
|
|
795
|
+
</>
|
|
796
|
+
)}
|
|
693
797
|
<div style={diamondBorderStyle} className={animationClass}>
|
|
694
798
|
<div style={diamondInnerStyle}>
|
|
695
799
|
{icon && (
|
|
@@ -732,6 +836,7 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, se
|
|
|
732
836
|
description={description}
|
|
733
837
|
otel={otelInfo}
|
|
734
838
|
sources={sources}
|
|
839
|
+
references={references}
|
|
735
840
|
visible={showTooltip}
|
|
736
841
|
nodeRef={nodeRef}
|
|
737
842
|
/>
|
|
@@ -745,7 +850,12 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, se
|
|
|
745
850
|
onMouseLeave={() => setIsHovered(false)}
|
|
746
851
|
>
|
|
747
852
|
{renderStatusBadge()}
|
|
748
|
-
{isBoundaryNode ? renderBoundaryBadge() :
|
|
853
|
+
{isBoundaryNode ? renderBoundaryBadge() : (
|
|
854
|
+
<>
|
|
855
|
+
{renderSourcesBadge()}
|
|
856
|
+
{renderReferencesBadge()}
|
|
857
|
+
</>
|
|
858
|
+
)}
|
|
749
859
|
<div style={getShapeStyles()} className={animationClass}>
|
|
750
860
|
{/* Inner content */}
|
|
751
861
|
<div
|
|
@@ -813,6 +923,7 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, se
|
|
|
813
923
|
description={description}
|
|
814
924
|
otel={otelInfo}
|
|
815
925
|
sources={sources}
|
|
926
|
+
references={references}
|
|
816
927
|
visible={showTooltip}
|
|
817
928
|
nodeRef={nodeRef}
|
|
818
929
|
/>
|