@principal-ai/principal-view-react 0.13.20 → 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.
@@ -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 sources for tooltip
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 => {
@@ -141,24 +147,28 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, se
141
147
  };
142
148
 
143
149
  // Get badge position based on shape - diamonds need badges at their points, not bounding box corners
144
- const getBadgePosition = (position: 'top-left' | 'top-right' | 'left' | 'right'): React.CSSProperties => {
150
+ const getBadgePosition = (position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'left' | 'right' | 'top' | 'bottom'): React.CSSProperties => {
145
151
  const isDiamondShape = typeDefinition.shape === 'diamond';
146
152
 
147
153
  if (isDiamondShape) {
148
154
  // Diamond points are at the middle of each edge of the bounding box
149
155
  switch (position) {
150
156
  case 'top-left':
151
- // Position at the LEFT point of the diamond (center-left)
152
- return { top: '50%', left: 0, transform: 'translate(-50%, -50%)' };
153
- case 'top-right':
154
- // Position at the RIGHT point of the diamond (center-right)
155
- return { top: '50%', right: 0, transform: 'translate(50%, -50%)' };
156
157
  case 'left':
157
158
  // Position at the LEFT point of the diamond (center-left)
158
159
  return { top: '50%', left: 0, transform: 'translate(-50%, -50%)' };
160
+ case 'top-right':
159
161
  case 'right':
160
162
  // Position at the RIGHT point of the diamond (center-right)
161
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%)' };
162
172
  }
163
173
  }
164
174
 
@@ -168,17 +178,27 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, se
168
178
  return { top: -6, left: -6 };
169
179
  case 'top-right':
170
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 };
171
185
  case 'left':
172
186
  return { top: -6, left: -6 };
173
187
  case 'right':
174
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%)' };
175
193
  }
176
194
  };
177
195
 
178
- // Render Sources badge (top-right, or right point for diamonds)
196
+ // Render Sources badge (top-right) - shows "S" for files where event is instrumented
179
197
  const renderSourcesBadge = () => {
180
- const sources = nodeData?.sources as string[] | undefined;
181
- if (!sources || sources.length === 0) return null;
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;
182
202
 
183
203
  const shapeStyles = getBadgeShapeStyles();
184
204
  const positionStyles = getBadgePosition('top-right');
@@ -203,13 +223,47 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, se
203
223
  zIndex: 10,
204
224
  opacity: nodeOpacity,
205
225
  }}
206
- title={`Sources: ${sources.join(', ')}`}
226
+ title={`Source files: ${sourceFiles.join(', ')}`}
207
227
  >
208
228
  <span style={{ transform: shapeStyles.transform ? 'rotate(-45deg)' : undefined }}>S</span>
209
229
  </div>
210
230
  );
211
231
  };
212
232
 
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
+
213
267
  // Render Boundary badge (right/left points) - shown instead of sources badge for boundary nodes
214
268
  const renderBoundaryBadge = () => {
215
269
  const boundary = nodeData?.boundary as { direction?: 'outbound' | 'inbound'; node?: Record<string, string> } | undefined;
@@ -662,7 +716,12 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, se
662
716
  onMouseLeave={() => setIsHovered(false)}
663
717
  >
664
718
  {renderStatusBadge()}
665
- {isBoundaryNode ? renderBoundaryBadge() : renderSourcesBadge()}
719
+ {isBoundaryNode ? renderBoundaryBadge() : (
720
+ <>
721
+ {renderSourcesBadge()}
722
+ {renderReferencesBadge()}
723
+ </>
724
+ )}
666
725
  <div style={hexagonBorderStyle} className={animationClass}>
667
726
  <div style={hexagonInnerStyle}>
668
727
  {icon && (
@@ -705,6 +764,7 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, se
705
764
  description={description}
706
765
  otel={otelInfo}
707
766
  sources={sources}
767
+ references={references}
708
768
  visible={showTooltip}
709
769
  nodeRef={nodeRef}
710
770
  />
@@ -728,7 +788,12 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, se
728
788
  onMouseLeave={() => setIsHovered(false)}
729
789
  >
730
790
  {renderStatusBadge()}
731
- {isBoundaryNode ? renderBoundaryBadge() : renderSourcesBadge()}
791
+ {isBoundaryNode ? renderBoundaryBadge() : (
792
+ <>
793
+ {renderSourcesBadge()}
794
+ {renderReferencesBadge()}
795
+ </>
796
+ )}
732
797
  <div style={diamondBorderStyle} className={animationClass}>
733
798
  <div style={diamondInnerStyle}>
734
799
  {icon && (
@@ -771,6 +836,7 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, se
771
836
  description={description}
772
837
  otel={otelInfo}
773
838
  sources={sources}
839
+ references={references}
774
840
  visible={showTooltip}
775
841
  nodeRef={nodeRef}
776
842
  />
@@ -784,7 +850,12 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, se
784
850
  onMouseLeave={() => setIsHovered(false)}
785
851
  >
786
852
  {renderStatusBadge()}
787
- {isBoundaryNode ? renderBoundaryBadge() : renderSourcesBadge()}
853
+ {isBoundaryNode ? renderBoundaryBadge() : (
854
+ <>
855
+ {renderSourcesBadge()}
856
+ {renderReferencesBadge()}
857
+ </>
858
+ )}
788
859
  <div style={getShapeStyles()} className={animationClass}>
789
860
  {/* Inner content */}
790
861
  <div
@@ -852,6 +923,7 @@ export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = ({ data, se
852
923
  description={description}
853
924
  otel={otelInfo}
854
925
  sources={sources}
926
+ references={references}
855
927
  visible={showTooltip}
856
928
  nodeRef={nodeRef}
857
929
  />
@@ -443,3 +443,187 @@ The badges on diamond nodes are positioned at the actual points of the diamond s
443
443
  },
444
444
  },
445
445
  };
446
+
447
+ /**
448
+ * Canvas demonstrating the new files (S) and references (R) badges
449
+ */
450
+ const filesAndReferencesCanvas: ExtendedCanvas = {
451
+ nodes: [
452
+ // Node with otel.files only (S badge)
453
+ {
454
+ id: 'files-only',
455
+ type: 'text',
456
+ x: 100,
457
+ y: 100,
458
+ width: 140,
459
+ height: 80,
460
+ text: 'Files Only',
461
+ color: 4, // green
462
+ pv: {
463
+ nodeType: 'event',
464
+ shape: 'rectangle',
465
+ icon: 'FileCode',
466
+ otel: {
467
+ kind: 'type',
468
+ files: ['src/auth/login.ts', 'src/auth/logout.ts'],
469
+ },
470
+ },
471
+ },
472
+ // Node with references only (R badge)
473
+ {
474
+ id: 'refs-only',
475
+ type: 'text',
476
+ x: 300,
477
+ y: 100,
478
+ width: 140,
479
+ height: 80,
480
+ text: 'Refs Only',
481
+ color: 6, // purple
482
+ pv: {
483
+ nodeType: 'event',
484
+ shape: 'rectangle',
485
+ icon: 'ExternalLink',
486
+ references: ['@opentelemetry/api', 'https://opentelemetry.io/docs'],
487
+ },
488
+ },
489
+ // Node with both files and references (S + R badges)
490
+ {
491
+ id: 'both-badges',
492
+ type: 'text',
493
+ x: 500,
494
+ y: 100,
495
+ width: 140,
496
+ height: 80,
497
+ text: 'Both Badges',
498
+ color: 2, // orange
499
+ pv: {
500
+ nodeType: 'event',
501
+ shape: 'rectangle',
502
+ icon: 'Layers',
503
+ otel: {
504
+ kind: 'service',
505
+ files: ['src/api/handler.ts'],
506
+ },
507
+ references: ['@principal-ai/core'],
508
+ },
509
+ },
510
+ // Diamond with files
511
+ {
512
+ id: 'diamond-files',
513
+ type: 'text',
514
+ x: 100,
515
+ y: 250,
516
+ width: 100,
517
+ height: 100,
518
+ text: 'Files',
519
+ color: 4,
520
+ pv: {
521
+ nodeType: 'decision',
522
+ shape: 'diamond',
523
+ icon: 'FileCode',
524
+ otel: {
525
+ kind: 'type',
526
+ files: ['src/router.ts'],
527
+ },
528
+ },
529
+ },
530
+ // Diamond with references
531
+ {
532
+ id: 'diamond-refs',
533
+ type: 'text',
534
+ x: 280,
535
+ y: 250,
536
+ width: 100,
537
+ height: 100,
538
+ text: 'Refs',
539
+ color: 6,
540
+ pv: {
541
+ nodeType: 'decision',
542
+ shape: 'diamond',
543
+ icon: 'ExternalLink',
544
+ references: ['https://docs.example.com'],
545
+ },
546
+ },
547
+ // Diamond with both
548
+ {
549
+ id: 'diamond-both',
550
+ type: 'text',
551
+ x: 460,
552
+ y: 250,
553
+ width: 100,
554
+ height: 100,
555
+ text: 'Both',
556
+ color: 2,
557
+ pv: {
558
+ nodeType: 'decision',
559
+ shape: 'diamond',
560
+ icon: 'Layers',
561
+ otel: {
562
+ kind: 'instance',
563
+ files: ['src/decision.ts'],
564
+ },
565
+ references: ['@some/package'],
566
+ },
567
+ },
568
+ // Circle with both badges
569
+ {
570
+ id: 'circle-both',
571
+ type: 'text',
572
+ x: 640,
573
+ y: 250,
574
+ width: 100,
575
+ height: 100,
576
+ text: 'Circle',
577
+ color: 5,
578
+ pv: {
579
+ nodeType: 'event',
580
+ shape: 'circle',
581
+ icon: 'Circle',
582
+ otel: {
583
+ kind: 'service',
584
+ files: ['src/service.ts'],
585
+ },
586
+ references: ['https://api.docs.com'],
587
+ },
588
+ },
589
+ ],
590
+ edges: [],
591
+ pv: {
592
+ version: '1.0.0',
593
+ name: 'Files and References Badges',
594
+ description: 'Demonstrates otel.files (S) and references (R) badges',
595
+ edgeTypes: {},
596
+ },
597
+ };
598
+
599
+ export const FilesAndReferences: Story = {
600
+ args: {
601
+ canvas: filesAndReferencesCanvas,
602
+ width: 850,
603
+ height: 450,
604
+ },
605
+ parameters: {
606
+ docs: {
607
+ description: {
608
+ story: `
609
+ **Files (S) and References (R) Badges**
610
+
611
+ Two types of badges indicate different metadata:
612
+
613
+ - **S badge** (green): Shows source files where the event is instrumented (\`pv.otel.files\`)
614
+ - **R badge** (purple): Shows external references like packages or documentation (\`pv.references\`)
615
+
616
+ **Row 1 - Rectangles:**
617
+ - Files Only: Has \`otel.files\` → shows S badge
618
+ - Refs Only: Has \`references\` → shows R badge
619
+ - Both Badges: Has both → shows S and R badges side by side
620
+
621
+ **Row 2 - Other Shapes:**
622
+ - Diamond, Circle shapes with various badge combinations
623
+
624
+ Hover over nodes to see the full file paths and reference URLs in the tooltip.
625
+ `,
626
+ },
627
+ },
628
+ },
629
+ };
@@ -676,7 +676,6 @@ const otelLogAssociationCanvas: ExtendedCanvas = {
676
676
  otel: {
677
677
  kind: 'type',
678
678
  category: 'log',
679
- isNew: true,
680
679
  },
681
680
  shape: 'rectangle',
682
681
  icon: 'FileText',
@@ -737,7 +736,6 @@ const otelLogAssociationCanvas: ExtendedCanvas = {
737
736
  otel: {
738
737
  kind: 'service',
739
738
  category: 'router',
740
- isNew: true,
741
739
  },
742
740
  shape: 'hexagon',
743
741
  icon: 'GitBranch',
@@ -861,7 +859,6 @@ const otelLogAssociationCanvas: ExtendedCanvas = {
861
859
  otel: {
862
860
  kind: 'service',
863
861
  category: 'collector',
864
- isNew: true,
865
862
  },
866
863
  shape: 'hexagon',
867
864
  icon: 'BarChart2',
@@ -884,7 +881,6 @@ const otelLogAssociationCanvas: ExtendedCanvas = {
884
881
  otel: {
885
882
  kind: 'type',
886
883
  category: 'audit',
887
- isNew: true,
888
884
  },
889
885
  shape: 'rectangle',
890
886
  icon: 'AlertTriangle',
@@ -149,8 +149,7 @@ export const BasicOtelEventNode: Story = {
149
149
  icon: "Play",
150
150
  otel: {
151
151
  kind: "event",
152
- category: "lifecycle",
153
- isNew: true
152
+ category: "lifecycle"
154
153
  },
155
154
  event: {
156
155
  name: "analysis.started",
@@ -343,8 +342,7 @@ export const NodeWithStateAndViolations: Story = {
343
342
  icon: "XCircle",
344
343
  otel: {
345
344
  kind: "event",
346
- category: "error",
347
- isNew: false
345
+ category: "error"
348
346
  },
349
347
  event: {
350
348
  name: "validation.error",
@@ -37,7 +37,6 @@ interface OtelLog {
37
37
  otel: {
38
38
  kind: 'type',
39
39
  category: 'log',
40
- isNew: true,
41
40
  },
42
41
  shape: 'rectangle',
43
42
  icon: 'FileText',
@@ -69,7 +68,6 @@ Routes incoming OTEL logs to canvas nodes based on matching criteria.
69
68
  otel: {
70
69
  kind: 'service',
71
70
  category: 'router',
72
- isNew: true,
73
71
  },
74
72
  shape: 'hexagon',
75
73
  icon: 'GitBranch',
@@ -92,7 +90,6 @@ Routes incoming OTEL logs to canvas nodes based on matching criteria.
92
90
  otel: {
93
91
  kind: 'type',
94
92
  category: 'audit',
95
- isNew: true,
96
93
  },
97
94
  shape: 'rectangle',
98
95
  icon: 'AlertTriangle',
@@ -210,7 +207,7 @@ export const TooltipVariants: StoryObj = {
210
207
  </div>
211
208
  <NodeTooltip
212
209
  description="OpenTelemetry log record with timestamp, severity, body"
213
- otel={{ kind: 'type', category: 'log', isNew: true }}
210
+ otel={{ kind: 'type', category: 'log' }}
214
211
  visible={true}
215
212
  />
216
213
  </div>