@principal-ai/principal-view-react 0.14.14 → 0.14.16

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.
Files changed (75) hide show
  1. package/dist/components/SequenceDiagramRenderer.d.ts +61 -0
  2. package/dist/components/SequenceDiagramRenderer.d.ts.map +1 -0
  3. package/dist/components/SequenceDiagramRenderer.js +184 -0
  4. package/dist/components/SequenceDiagramRenderer.js.map +1 -0
  5. package/dist/components/dashboard/DashboardRenderer.d.ts +9 -0
  6. package/dist/components/dashboard/DashboardRenderer.d.ts.map +1 -0
  7. package/dist/components/dashboard/DashboardRenderer.js +179 -0
  8. package/dist/components/dashboard/DashboardRenderer.js.map +1 -0
  9. package/dist/components/dashboard/MetricPanel.d.ts +9 -0
  10. package/dist/components/dashboard/MetricPanel.d.ts.map +1 -0
  11. package/dist/components/dashboard/MetricPanel.js +103 -0
  12. package/dist/components/dashboard/MetricPanel.js.map +1 -0
  13. package/dist/components/dashboard/MockDataProvider.d.ts +30 -0
  14. package/dist/components/dashboard/MockDataProvider.d.ts.map +1 -0
  15. package/dist/components/dashboard/MockDataProvider.js +270 -0
  16. package/dist/components/dashboard/MockDataProvider.js.map +1 -0
  17. package/dist/components/dashboard/components/BarChart.d.ts +9 -0
  18. package/dist/components/dashboard/components/BarChart.d.ts.map +1 -0
  19. package/dist/components/dashboard/components/BarChart.js +167 -0
  20. package/dist/components/dashboard/components/BarChart.js.map +1 -0
  21. package/dist/components/dashboard/components/LineChart.d.ts +9 -0
  22. package/dist/components/dashboard/components/LineChart.d.ts.map +1 -0
  23. package/dist/components/dashboard/components/LineChart.js +141 -0
  24. package/dist/components/dashboard/components/LineChart.js.map +1 -0
  25. package/dist/components/dashboard/components/MetricCard.d.ts +8 -0
  26. package/dist/components/dashboard/components/MetricCard.d.ts.map +1 -0
  27. package/dist/components/dashboard/components/MetricCard.js +163 -0
  28. package/dist/components/dashboard/components/MetricCard.js.map +1 -0
  29. package/dist/components/dashboard/components/SourceLink.d.ts +8 -0
  30. package/dist/components/dashboard/components/SourceLink.d.ts.map +1 -0
  31. package/dist/components/dashboard/components/SourceLink.js +39 -0
  32. package/dist/components/dashboard/components/SourceLink.js.map +1 -0
  33. package/dist/components/dashboard/components/TimeRangeSelector.d.ts +8 -0
  34. package/dist/components/dashboard/components/TimeRangeSelector.d.ts.map +1 -0
  35. package/dist/components/dashboard/components/TimeRangeSelector.js +167 -0
  36. package/dist/components/dashboard/components/TimeRangeSelector.js.map +1 -0
  37. package/dist/components/dashboard/components/index.d.ts +6 -0
  38. package/dist/components/dashboard/components/index.d.ts.map +1 -0
  39. package/dist/components/dashboard/components/index.js +6 -0
  40. package/dist/components/dashboard/components/index.js.map +1 -0
  41. package/dist/components/dashboard/index.d.ts +6 -0
  42. package/dist/components/dashboard/index.d.ts.map +1 -0
  43. package/dist/components/dashboard/index.js +8 -0
  44. package/dist/components/dashboard/index.js.map +1 -0
  45. package/dist/components/dashboard/types.d.ts +74 -0
  46. package/dist/components/dashboard/types.d.ts.map +1 -0
  47. package/dist/components/dashboard/types.js +8 -0
  48. package/dist/components/dashboard/types.js.map +1 -0
  49. package/dist/hooks/useSequenceLayout.d.ts +148 -0
  50. package/dist/hooks/useSequenceLayout.d.ts.map +1 -0
  51. package/dist/hooks/useSequenceLayout.js +225 -0
  52. package/dist/hooks/useSequenceLayout.js.map +1 -0
  53. package/dist/index.d.ts +6 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +4 -0
  56. package/dist/index.js.map +1 -1
  57. package/package.json +3 -3
  58. package/src/components/SequenceDiagramRenderer.tsx +459 -0
  59. package/src/components/dashboard/DashboardRenderer.tsx +317 -0
  60. package/src/components/dashboard/MetricPanel.tsx +254 -0
  61. package/src/components/dashboard/MockDataProvider.ts +330 -0
  62. package/src/components/dashboard/components/BarChart.tsx +299 -0
  63. package/src/components/dashboard/components/LineChart.tsx +279 -0
  64. package/src/components/dashboard/components/MetricCard.tsx +270 -0
  65. package/src/components/dashboard/components/SourceLink.tsx +63 -0
  66. package/src/components/dashboard/components/TimeRangeSelector.tsx +280 -0
  67. package/src/components/dashboard/components/index.ts +5 -0
  68. package/src/components/dashboard/index.ts +47 -0
  69. package/src/components/dashboard/types.ts +126 -0
  70. package/src/hooks/useSequenceLayout.ts +413 -0
  71. package/src/index.ts +62 -0
  72. package/src/stories/SequenceDiagram.stories.tsx +306 -0
  73. package/src/stories/dashboard/DashboardRenderer.stories.tsx +263 -0
  74. package/src/stories/dashboard/sample-dashboards/activity-feed-analytics.dashboard.json +300 -0
  75. package/src/stories/data/graph-converter-test-execution.json +50 -50
@@ -0,0 +1,459 @@
1
+ /**
2
+ * Sequence Diagram Renderer
3
+ *
4
+ * Renders events in swimlane-based sequence diagram layout.
5
+ * Uses namespaces to determine swimlanes and event order for vertical positioning.
6
+ */
7
+
8
+ import React, { useCallback, useMemo } from 'react';
9
+ import {
10
+ ReactFlow,
11
+ Background,
12
+ BackgroundVariant,
13
+ Controls,
14
+ ReactFlowProvider,
15
+ Panel,
16
+ useViewport,
17
+ Handle,
18
+ Position,
19
+ BaseEdge,
20
+ EdgeLabelRenderer,
21
+ getBezierPath,
22
+ type NodeTypes,
23
+ type EdgeTypes,
24
+ type NodeProps,
25
+ type EdgeProps,
26
+ } from '@xyflow/react';
27
+ import '@xyflow/react/dist/style.css';
28
+ import {
29
+ useSequenceLayout,
30
+ type SequenceEvent,
31
+ type SequenceEdge,
32
+ type UseSequenceLayoutOptions,
33
+ type Swimlane,
34
+ } from '../hooks/useSequenceLayout';
35
+
36
+ /**
37
+ * Minimal marker node for arrow-centric sequence diagrams
38
+ */
39
+ function SequenceMarkerNode({ data }: NodeProps) {
40
+ return (
41
+ <div
42
+ style={{
43
+ width: '100%',
44
+ height: '100%',
45
+ backgroundColor: 'var(--sequence-marker-bg, #6495ED)',
46
+ borderRadius: '50%',
47
+ border: '2px solid var(--sequence-marker-border, #4169E1)',
48
+ boxShadow: '0 1px 3px rgba(0,0,0,0.2)',
49
+ }}
50
+ title={data.fullName as string}
51
+ >
52
+ <Handle
53
+ type="target"
54
+ position={Position.Top}
55
+ style={{ visibility: 'hidden' }}
56
+ />
57
+ <Handle
58
+ type="source"
59
+ position={Position.Bottom}
60
+ style={{ visibility: 'hidden' }}
61
+ />
62
+ </div>
63
+ );
64
+ }
65
+
66
+ /**
67
+ * Sequence arrow edge with label
68
+ */
69
+ function SequenceArrowEdge({
70
+ id,
71
+ sourceX,
72
+ sourceY,
73
+ targetX,
74
+ targetY,
75
+ sourcePosition,
76
+ targetPosition,
77
+ label,
78
+ data,
79
+ }: EdgeProps) {
80
+ // Use a straight line for same-lane, bezier for cross-lane
81
+ const isSameLane = !data?.crossesLanes;
82
+
83
+ const [edgePath, labelX, labelY] = getBezierPath({
84
+ sourceX,
85
+ sourceY,
86
+ sourcePosition,
87
+ targetX,
88
+ targetY,
89
+ targetPosition,
90
+ curvature: isSameLane ? 0.5 : 0.25,
91
+ });
92
+
93
+ return (
94
+ <>
95
+ <BaseEdge
96
+ id={id}
97
+ path={edgePath}
98
+ style={{
99
+ stroke: 'var(--sequence-arrow-color, #4169E1)',
100
+ strokeWidth: 2,
101
+ }}
102
+ markerEnd="url(#sequence-arrow)"
103
+ />
104
+ {label && (
105
+ <EdgeLabelRenderer>
106
+ <div
107
+ style={{
108
+ position: 'absolute',
109
+ transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
110
+ background: 'var(--sequence-label-bg, rgba(255, 255, 255, 0.95))',
111
+ padding: '2px 8px',
112
+ borderRadius: 4,
113
+ fontSize: 12,
114
+ fontWeight: 500,
115
+ color: 'var(--sequence-label-text, #333)',
116
+ border: '1px solid var(--sequence-label-border, #ddd)',
117
+ pointerEvents: 'all',
118
+ whiteSpace: 'nowrap',
119
+ }}
120
+ className="nodrag nopan"
121
+ >
122
+ {label}
123
+ </div>
124
+ </EdgeLabelRenderer>
125
+ )}
126
+ </>
127
+ );
128
+ }
129
+
130
+ /**
131
+ * Default node types including sequence marker
132
+ */
133
+ const defaultSequenceNodeTypes: NodeTypes = {
134
+ sequenceMarker: SequenceMarkerNode,
135
+ };
136
+
137
+ /**
138
+ * Default edge types including sequence arrow
139
+ */
140
+ const defaultSequenceEdgeTypes: EdgeTypes = {
141
+ sequenceArrow: SequenceArrowEdge,
142
+ };
143
+
144
+ /**
145
+ * Props for the swimlane layer
146
+ */
147
+ interface SwimlaneLayerProps {
148
+ swimlanes: Swimlane[];
149
+ laneWidth: number;
150
+ headerHeight: number;
151
+ totalHeight: number;
152
+ onToggleCollapse?: (namespace: string) => void;
153
+ }
154
+
155
+ /**
156
+ * Swimlane layer that renders behind nodes and transforms with viewport
157
+ */
158
+ function SwimlaneLayer({
159
+ swimlanes,
160
+ laneWidth,
161
+ headerHeight,
162
+ totalHeight,
163
+ onToggleCollapse,
164
+ }: SwimlaneLayerProps) {
165
+ const { x, y, zoom } = useViewport();
166
+
167
+ // Calculate the visible area height (use a large value to ensure lanes extend)
168
+ const extendedHeight = Math.max(totalHeight, 2000);
169
+
170
+ return (
171
+ <div
172
+ style={{
173
+ position: 'absolute',
174
+ top: 0,
175
+ left: 0,
176
+ transformOrigin: '0 0',
177
+ transform: `translate(${x}px, ${y}px) scale(${zoom})`,
178
+ pointerEvents: 'none',
179
+ zIndex: -1,
180
+ }}
181
+ >
182
+ {/* Lane backgrounds */}
183
+ {swimlanes.map((lane, index) => {
184
+ const isEven = index % 2 === 0;
185
+ return (
186
+ <div
187
+ key={`bg-${lane.namespace}`}
188
+ style={{
189
+ position: 'absolute',
190
+ left: lane.x - laneWidth / 2,
191
+ top: 0,
192
+ width: laneWidth,
193
+ height: extendedHeight,
194
+ backgroundColor: isEven
195
+ ? 'var(--sequence-lane-even, rgba(100, 149, 237, 0.08))'
196
+ : 'var(--sequence-lane-odd, rgba(100, 149, 237, 0.03))',
197
+ borderRight: '1px solid var(--sequence-lane-border, rgba(100, 149, 237, 0.2))',
198
+ }}
199
+ />
200
+ );
201
+ })}
202
+
203
+ {/* Lane headers */}
204
+ {swimlanes.map((lane) => {
205
+ const hasChildren = lane.children.length > 0;
206
+ return (
207
+ <div
208
+ key={`header-${lane.namespace}`}
209
+ style={{
210
+ position: 'absolute',
211
+ left: lane.x - laneWidth / 2,
212
+ top: 0,
213
+ width: laneWidth,
214
+ height: headerHeight,
215
+ display: 'flex',
216
+ alignItems: 'center',
217
+ justifyContent: 'center',
218
+ backgroundColor: 'var(--sequence-header-bg, rgba(100, 149, 237, 0.15))',
219
+ borderBottom: '2px solid var(--sequence-header-border, rgba(100, 149, 237, 0.4))',
220
+ fontWeight: 600,
221
+ fontSize: 13,
222
+ color: 'var(--sequence-header-text, #333)',
223
+ pointerEvents: 'auto',
224
+ cursor: hasChildren ? 'pointer' : 'default',
225
+ userSelect: 'none',
226
+ }}
227
+ onClick={() => hasChildren && onToggleCollapse?.(lane.namespace)}
228
+ >
229
+ {hasChildren && (
230
+ <span style={{ marginRight: 6, fontSize: 10 }}>
231
+ {lane.isCollapsed ? '▶' : '▼'}
232
+ </span>
233
+ )}
234
+ <span>{lane.label}</span>
235
+ {lane.eventIds.length > 1 && (
236
+ <span style={{ marginLeft: 6, fontSize: 11, opacity: 0.6 }}>
237
+ ({lane.eventIds.length})
238
+ </span>
239
+ )}
240
+ </div>
241
+ );
242
+ })}
243
+
244
+ {/* Vertical lifelines */}
245
+ {swimlanes.map((lane) => (
246
+ <div
247
+ key={`lifeline-${lane.namespace}`}
248
+ style={{
249
+ position: 'absolute',
250
+ left: lane.x,
251
+ top: headerHeight,
252
+ width: 2,
253
+ height: extendedHeight - headerHeight,
254
+ backgroundColor: 'var(--sequence-lifeline, rgba(100, 149, 237, 0.3))',
255
+ transform: 'translateX(-1px)',
256
+ }}
257
+ />
258
+ ))}
259
+ </div>
260
+ );
261
+ }
262
+
263
+ /**
264
+ * Props for SequenceDiagramRenderer
265
+ */
266
+ export interface SequenceDiagramRendererProps {
267
+ /** Events to display in the diagram */
268
+ events: SequenceEvent[];
269
+
270
+ /** Edges connecting events */
271
+ edges: SequenceEdge[];
272
+
273
+ /** Layout options */
274
+ layoutOptions?: UseSequenceLayoutOptions;
275
+
276
+ /** Optional custom node types */
277
+ nodeTypes?: NodeTypes;
278
+
279
+ /** Optional custom edge types */
280
+ edgeTypes?: EdgeTypes;
281
+
282
+ /** Callback when a namespace collapse state is toggled */
283
+ onToggleCollapse?: (namespace: string) => void;
284
+
285
+ /** Callback when a node is clicked */
286
+ onNodeClick?: (nodeId: string, event: React.MouseEvent) => void;
287
+
288
+ /** Optional class name */
289
+ className?: string;
290
+
291
+ /** Optional width */
292
+ width?: number | string;
293
+
294
+ /** Optional height */
295
+ height?: number | string;
296
+
297
+ /** Whether to show controls */
298
+ showControls?: boolean;
299
+
300
+ /** Whether to show background grid */
301
+ showBackground?: boolean;
302
+ }
303
+
304
+ /**
305
+ * Inner component that uses React Flow hooks
306
+ */
307
+ function SequenceDiagramInner({
308
+ events,
309
+ edges: sequenceEdges,
310
+ layoutOptions = {},
311
+ nodeTypes: customNodeTypes,
312
+ edgeTypes: customEdgeTypes,
313
+ onToggleCollapse,
314
+ onNodeClick,
315
+ showControls = true,
316
+ showBackground = false, // Default to false since swimlanes provide visual structure
317
+ }: SequenceDiagramRendererProps) {
318
+ // Extract layout params
319
+ const { laneWidth = 200, headerHeight = 60, arrowCentric = false } = layoutOptions;
320
+
321
+ // Merge custom node/edge types with sequence defaults
322
+ const nodeTypes = useMemo(
323
+ () => ({ ...defaultSequenceNodeTypes, ...customNodeTypes }),
324
+ [customNodeTypes]
325
+ );
326
+ const edgeTypes = useMemo(
327
+ () => ({ ...defaultSequenceEdgeTypes, ...customEdgeTypes }),
328
+ [customEdgeTypes]
329
+ );
330
+
331
+ // Compute layout (pass arrowCentric through layoutOptions)
332
+ const { nodes, edges, swimlanes, totalHeight } = useSequenceLayout(
333
+ events,
334
+ sequenceEdges,
335
+ { ...layoutOptions, arrowCentric }
336
+ );
337
+
338
+ // Handle node click
339
+ const handleNodeClick = useCallback(
340
+ (_event: React.MouseEvent, node: { id: string }) => {
341
+ onNodeClick?.(node.id, _event);
342
+ },
343
+ [onNodeClick]
344
+ );
345
+
346
+ return (
347
+ <ReactFlow
348
+ nodes={nodes}
349
+ edges={edges}
350
+ nodeTypes={nodeTypes}
351
+ edgeTypes={edgeTypes}
352
+ onNodeClick={handleNodeClick}
353
+ fitView
354
+ fitViewOptions={{
355
+ padding: 0.2,
356
+ minZoom: 0.5,
357
+ maxZoom: 1.5,
358
+ }}
359
+ minZoom={0.1}
360
+ maxZoom={2}
361
+ nodesDraggable={false}
362
+ nodesConnectable={false}
363
+ elementsSelectable={true}
364
+ panOnScroll
365
+ zoomOnScroll
366
+ >
367
+ {/* SVG defs for arrow marker */}
368
+ <svg style={{ position: 'absolute', width: 0, height: 0 }}>
369
+ <defs>
370
+ <marker
371
+ id="sequence-arrow"
372
+ viewBox="0 0 10 10"
373
+ refX="8"
374
+ refY="5"
375
+ markerWidth="6"
376
+ markerHeight="6"
377
+ orient="auto-start-reverse"
378
+ >
379
+ <path
380
+ d="M 0 0 L 10 5 L 0 10 z"
381
+ fill="var(--sequence-arrow-color, #4169E1)"
382
+ />
383
+ </marker>
384
+ </defs>
385
+ </svg>
386
+
387
+ {showBackground && (
388
+ <Background variant={BackgroundVariant.Dots} gap={16} size={1} />
389
+ )}
390
+ {showControls && <Controls />}
391
+
392
+ {/* Swimlane layer - renders behind nodes */}
393
+ <SwimlaneLayer
394
+ swimlanes={swimlanes}
395
+ laneWidth={laneWidth}
396
+ headerHeight={headerHeight}
397
+ totalHeight={totalHeight}
398
+ onToggleCollapse={onToggleCollapse}
399
+ />
400
+
401
+ {/* Collapse toggle panel (for namespaces with children) */}
402
+ {swimlanes.some((s) => s.children.length > 0) && (
403
+ <Panel position="top-right">
404
+ <div
405
+ style={{
406
+ background: 'var(--sequence-panel-bg, rgba(255, 255, 255, 0.95))',
407
+ border: '1px solid var(--sequence-panel-border, #ddd)',
408
+ borderRadius: 4,
409
+ padding: '6px 10px',
410
+ fontSize: 11,
411
+ color: '#666',
412
+ }}
413
+ >
414
+ Click lane headers to expand/collapse
415
+ </div>
416
+ </Panel>
417
+ )}
418
+ </ReactFlow>
419
+ );
420
+ }
421
+
422
+ /**
423
+ * Sequence Diagram Renderer
424
+ *
425
+ * Renders events as a sequence diagram with swimlanes based on namespaces.
426
+ *
427
+ * @example
428
+ * ```tsx
429
+ * <SequenceDiagramRenderer
430
+ * events={[
431
+ * { id: '1', name: 'auth.validation.started' },
432
+ * { id: '2', name: 'auth.validation.completed' },
433
+ * { id: '3', name: 'database.query.executed' },
434
+ * ]}
435
+ * edges={[
436
+ * { id: 'e1', fromEvent: '1', toEvent: '2' },
437
+ * { id: 'e2', fromEvent: '2', toEvent: '3' },
438
+ * ]}
439
+ * />
440
+ * ```
441
+ */
442
+ export function SequenceDiagramRenderer(props: SequenceDiagramRendererProps) {
443
+ const { className, width = '100%', height = 600 } = props;
444
+
445
+ return (
446
+ <div
447
+ className={className}
448
+ style={{
449
+ width,
450
+ height,
451
+ position: 'relative',
452
+ }}
453
+ >
454
+ <ReactFlowProvider>
455
+ <SequenceDiagramInner {...props} />
456
+ </ReactFlowProvider>
457
+ </div>
458
+ );
459
+ }