@principal-ai/principal-view-react 0.7.9 → 0.7.10
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/GraphRenderer.d.ts +5 -0
- package/dist/components/GraphRenderer.d.ts.map +1 -1
- package/dist/components/GraphRenderer.js +8 -5
- package/dist/components/GraphRenderer.js.map +1 -1
- package/dist/components/LayerPanel.d.ts +31 -0
- package/dist/components/LayerPanel.d.ts.map +1 -0
- package/dist/components/LayerPanel.js +207 -0
- package/dist/components/LayerPanel.js.map +1 -0
- package/dist/components/TestEventPanel.d.ts +26 -0
- package/dist/components/TestEventPanel.d.ts.map +1 -0
- package/dist/components/TestEventPanel.js +84 -0
- package/dist/components/TestEventPanel.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/nodes/CustomNode.d.ts +1 -0
- package/dist/nodes/CustomNode.d.ts.map +1 -1
- package/dist/nodes/CustomNode.js +26 -7
- package/dist/nodes/CustomNode.js.map +1 -1
- package/package.json +2 -2
- package/src/components/GraphRenderer.tsx +21 -2
- package/src/components/TestEventPanel.tsx +214 -0
- package/src/index.ts +3 -0
- package/src/nodes/CustomNode.tsx +31 -6
- package/src/stories/RealTestExecution.stories.tsx +420 -0
- package/src/stories/ValidatedExecution.stories.tsx +160 -0
- package/src/stories/data/graph-converter-test-execution.json +225 -0
- package/src/stories/data/graph-converter-validated-execution.json +58 -0
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { GraphRenderer } from '../components/GraphRenderer';
|
|
4
|
+
import { TestEventPanel } from '../components/TestEventPanel';
|
|
5
|
+
import type { ExtendedCanvas, GraphEvent } from '@principal-ai/principal-view-core';
|
|
6
|
+
import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
|
|
7
|
+
import testSpans from './data/graph-converter-test-execution.json';
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
title: 'Features/Real Test Execution',
|
|
11
|
+
component: GraphRenderer,
|
|
12
|
+
parameters: {
|
|
13
|
+
layout: 'fullscreen',
|
|
14
|
+
docs: {
|
|
15
|
+
description: {
|
|
16
|
+
component:
|
|
17
|
+
'Visualizes REAL test execution data from instrumented Bun tests using the "wide event" pattern. Shows actual spans with file/line information collected from running GraphConverter.test.ts. Hover over graph nodes to highlight related events in the panel.',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
tags: ['autodocs'],
|
|
22
|
+
decorators: [
|
|
23
|
+
(Story) => (
|
|
24
|
+
<ThemeProvider theme={defaultEditorTheme}>
|
|
25
|
+
<div style={{ width: '100vw', height: '100vh', background: '#0a0a0a' }}>
|
|
26
|
+
<Story />
|
|
27
|
+
</div>
|
|
28
|
+
</ThemeProvider>
|
|
29
|
+
),
|
|
30
|
+
],
|
|
31
|
+
} satisfies Meta<typeof GraphRenderer>;
|
|
32
|
+
|
|
33
|
+
export default meta;
|
|
34
|
+
type Story = StoryObj<typeof meta>;
|
|
35
|
+
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// Test Execution Flow Canvas
|
|
38
|
+
// ============================================================================
|
|
39
|
+
|
|
40
|
+
const testExecutionCanvas: ExtendedCanvas = {
|
|
41
|
+
nodes: [
|
|
42
|
+
// Test Suite
|
|
43
|
+
{
|
|
44
|
+
id: 'test-suite',
|
|
45
|
+
type: 'text',
|
|
46
|
+
text: 'GraphConverter Test Suite',
|
|
47
|
+
x: -100,
|
|
48
|
+
y: -100,
|
|
49
|
+
width: 240,
|
|
50
|
+
height: 80,
|
|
51
|
+
pv: {
|
|
52
|
+
nodeType: 'test-suite',
|
|
53
|
+
name: 'Test Suite',
|
|
54
|
+
description: 'Collection of GraphConverter tests',
|
|
55
|
+
shape: 'rectangle',
|
|
56
|
+
fill: '#3b82f6',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
// Test Phase Nodes
|
|
61
|
+
{
|
|
62
|
+
id: 'setup-phase',
|
|
63
|
+
type: 'text',
|
|
64
|
+
text: 'Setup',
|
|
65
|
+
x: -250,
|
|
66
|
+
y: 50,
|
|
67
|
+
width: 120,
|
|
68
|
+
height: 80,
|
|
69
|
+
pv: {
|
|
70
|
+
nodeType: 'test-phase',
|
|
71
|
+
name: 'Setup Phase',
|
|
72
|
+
description: 'Test data preparation',
|
|
73
|
+
shape: 'hexagon',
|
|
74
|
+
fill: '#10b981',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: 'execution-phase',
|
|
79
|
+
type: 'text',
|
|
80
|
+
text: 'Execution',
|
|
81
|
+
x: -80,
|
|
82
|
+
y: 50,
|
|
83
|
+
width: 120,
|
|
84
|
+
height: 80,
|
|
85
|
+
pv: {
|
|
86
|
+
nodeType: 'test-phase',
|
|
87
|
+
name: 'Execution Phase',
|
|
88
|
+
description: 'Code under test runs',
|
|
89
|
+
shape: 'hexagon',
|
|
90
|
+
fill: '#f59e0b',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'assertion-phase',
|
|
95
|
+
type: 'text',
|
|
96
|
+
text: 'Assertion',
|
|
97
|
+
x: 90,
|
|
98
|
+
y: 50,
|
|
99
|
+
width: 120,
|
|
100
|
+
height: 80,
|
|
101
|
+
pv: {
|
|
102
|
+
nodeType: 'test-phase',
|
|
103
|
+
name: 'Assertion Phase',
|
|
104
|
+
description: 'Verify results',
|
|
105
|
+
shape: 'hexagon',
|
|
106
|
+
fill: '#8b5cf6',
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
// Result Node
|
|
111
|
+
{
|
|
112
|
+
id: 'test-result',
|
|
113
|
+
type: 'text',
|
|
114
|
+
text: 'Test Result',
|
|
115
|
+
x: -100,
|
|
116
|
+
y: 200,
|
|
117
|
+
width: 240,
|
|
118
|
+
height: 80,
|
|
119
|
+
pv: {
|
|
120
|
+
nodeType: 'result',
|
|
121
|
+
name: 'Test Result',
|
|
122
|
+
description: 'Pass/Fail outcome',
|
|
123
|
+
shape: 'rectangle',
|
|
124
|
+
fill: '#10b981',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
edges: [
|
|
129
|
+
{
|
|
130
|
+
id: 'suite-to-setup',
|
|
131
|
+
fromNode: 'test-suite',
|
|
132
|
+
toNode: 'setup-phase',
|
|
133
|
+
fromSide: 'bottom',
|
|
134
|
+
toSide: 'top',
|
|
135
|
+
label: 'start test',
|
|
136
|
+
pv: {
|
|
137
|
+
edgeType: 'flow',
|
|
138
|
+
style: 'solid',
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: 'setup-to-execution',
|
|
143
|
+
fromNode: 'setup-phase',
|
|
144
|
+
toNode: 'execution-phase',
|
|
145
|
+
fromSide: 'right',
|
|
146
|
+
toSide: 'left',
|
|
147
|
+
label: 'data ready',
|
|
148
|
+
pv: {
|
|
149
|
+
edgeType: 'flow',
|
|
150
|
+
style: 'solid',
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
id: 'execution-to-assertion',
|
|
155
|
+
fromNode: 'execution-phase',
|
|
156
|
+
toNode: 'assertion-phase',
|
|
157
|
+
fromSide: 'right',
|
|
158
|
+
toSide: 'left',
|
|
159
|
+
label: 'got result',
|
|
160
|
+
pv: {
|
|
161
|
+
edgeType: 'flow',
|
|
162
|
+
style: 'solid',
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: 'assertion-to-result',
|
|
167
|
+
fromNode: 'assertion-phase',
|
|
168
|
+
toNode: 'test-result',
|
|
169
|
+
fromSide: 'bottom',
|
|
170
|
+
toSide: 'top',
|
|
171
|
+
label: 'complete',
|
|
172
|
+
pv: {
|
|
173
|
+
edgeType: 'flow',
|
|
174
|
+
style: 'solid',
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
pv: {
|
|
179
|
+
version: '1.0.0',
|
|
180
|
+
name: 'Test Execution Flow',
|
|
181
|
+
description: 'Visualizes the flow of test execution through phases',
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// ============================================================================
|
|
186
|
+
// Convert Test Spans to Graph Events
|
|
187
|
+
// ============================================================================
|
|
188
|
+
|
|
189
|
+
function convertSpansToEvents(spans: typeof testSpans): GraphEvent[] {
|
|
190
|
+
const events: GraphEvent[] = [];
|
|
191
|
+
let time = 0;
|
|
192
|
+
|
|
193
|
+
spans.forEach((testSpan) => {
|
|
194
|
+
// Pulse test suite node at start of each test
|
|
195
|
+
events.push({
|
|
196
|
+
timestamp: time,
|
|
197
|
+
category: 'node',
|
|
198
|
+
operation: 'animate',
|
|
199
|
+
payload: {
|
|
200
|
+
nodeId: 'test-suite',
|
|
201
|
+
animation: { type: 'pulse', duration: 500 },
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
time += 600;
|
|
205
|
+
|
|
206
|
+
// Animate through events in the span
|
|
207
|
+
testSpan.events.forEach((event) => {
|
|
208
|
+
const eventName = event.name;
|
|
209
|
+
|
|
210
|
+
// Determine which phase based on event name
|
|
211
|
+
let nodeId = '';
|
|
212
|
+
let edgeId = '';
|
|
213
|
+
|
|
214
|
+
if (eventName.startsWith('setup.')) {
|
|
215
|
+
nodeId = 'setup-phase';
|
|
216
|
+
edgeId = 'suite-to-setup';
|
|
217
|
+
} else if (eventName.startsWith('execution.')) {
|
|
218
|
+
nodeId = 'execution-phase';
|
|
219
|
+
edgeId = 'setup-to-execution';
|
|
220
|
+
} else if (eventName.startsWith('assertion.')) {
|
|
221
|
+
nodeId = 'assertion-phase';
|
|
222
|
+
edgeId = 'execution-to-assertion';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Animate edge when phase starts
|
|
226
|
+
if (eventName.endsWith('.started') && edgeId) {
|
|
227
|
+
events.push({
|
|
228
|
+
timestamp: time,
|
|
229
|
+
category: 'edge',
|
|
230
|
+
operation: 'animate',
|
|
231
|
+
payload: {
|
|
232
|
+
edgeId,
|
|
233
|
+
animation: { type: 'particle', duration: 500 },
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
time += 600;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Pulse node
|
|
240
|
+
if (nodeId) {
|
|
241
|
+
events.push({
|
|
242
|
+
timestamp: time,
|
|
243
|
+
category: 'node',
|
|
244
|
+
operation: 'animate',
|
|
245
|
+
payload: {
|
|
246
|
+
nodeId,
|
|
247
|
+
animation: { type: 'pulse', duration: 600 },
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
time += 700;
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Animate to result
|
|
255
|
+
events.push({
|
|
256
|
+
timestamp: time,
|
|
257
|
+
category: 'edge',
|
|
258
|
+
operation: 'animate',
|
|
259
|
+
payload: {
|
|
260
|
+
edgeId: 'assertion-to-result',
|
|
261
|
+
animation: { type: 'particle', duration: 500 },
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
time += 600;
|
|
265
|
+
|
|
266
|
+
events.push({
|
|
267
|
+
timestamp: time,
|
|
268
|
+
category: 'node',
|
|
269
|
+
operation: 'animate',
|
|
270
|
+
payload: {
|
|
271
|
+
nodeId: 'test-result',
|
|
272
|
+
animation: { type: 'pulse', duration: 800 },
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
time += 1200; // Pause between tests
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
return events;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ============================================================================
|
|
282
|
+
// Animated Story
|
|
283
|
+
// ============================================================================
|
|
284
|
+
|
|
285
|
+
const AnimatedTestExecution = () => {
|
|
286
|
+
const [events, setEvents] = useState<GraphEvent[]>([]);
|
|
287
|
+
const [currentSpanIndex, setCurrentSpanIndex] = useState(0);
|
|
288
|
+
const [currentEventIndex, setCurrentEventIndex] = useState(0);
|
|
289
|
+
const [highlightedPhase, setHighlightedPhase] = useState<string | undefined>();
|
|
290
|
+
|
|
291
|
+
useEffect(() => {
|
|
292
|
+
const graphEvents = convertSpansToEvents(testSpans);
|
|
293
|
+
const timers: NodeJS.Timeout[] = [];
|
|
294
|
+
|
|
295
|
+
let spanIndex = 0;
|
|
296
|
+
let eventIndex = 0;
|
|
297
|
+
let eventsPerTest = testSpans[0].events.length * 2 + 2; // ~2 graph events per span event + suite + result
|
|
298
|
+
|
|
299
|
+
graphEvents.forEach((event, index) => {
|
|
300
|
+
const timer = setTimeout(() => {
|
|
301
|
+
setEvents((prev) => [...prev, event]);
|
|
302
|
+
|
|
303
|
+
// Track which span and event we're on
|
|
304
|
+
spanIndex = Math.floor(index / eventsPerTest);
|
|
305
|
+
eventIndex = Math.floor((index % eventsPerTest) / 2);
|
|
306
|
+
|
|
307
|
+
setCurrentSpanIndex(Math.min(spanIndex, testSpans.length - 1));
|
|
308
|
+
setCurrentEventIndex(eventIndex);
|
|
309
|
+
}, event.timestamp);
|
|
310
|
+
timers.push(timer);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Reset animation
|
|
314
|
+
const resetTimer = setTimeout(() => {
|
|
315
|
+
setEvents([]);
|
|
316
|
+
setCurrentSpanIndex(0);
|
|
317
|
+
setCurrentEventIndex(0);
|
|
318
|
+
}, graphEvents[graphEvents.length - 1].timestamp + 2000);
|
|
319
|
+
|
|
320
|
+
return () => {
|
|
321
|
+
timers.forEach(clearTimeout);
|
|
322
|
+
clearTimeout(resetTimer);
|
|
323
|
+
};
|
|
324
|
+
}, []);
|
|
325
|
+
|
|
326
|
+
// Map node IDs to phase names
|
|
327
|
+
const getPhaseFromNodeId = (nodeId: string): string | undefined => {
|
|
328
|
+
if (nodeId === 'setup-phase') return 'setup';
|
|
329
|
+
if (nodeId === 'execution-phase') return 'execution';
|
|
330
|
+
if (nodeId === 'assertion-phase') return 'assertion';
|
|
331
|
+
return undefined;
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
return (
|
|
335
|
+
<div style={{ display: 'flex', width: '100%', height: '100%' }}>
|
|
336
|
+
{/* Graph Visualization - Left Side */}
|
|
337
|
+
<div
|
|
338
|
+
style={{ flex: '0 0 60%', height: '100%', position: 'relative' }}
|
|
339
|
+
onMouseLeave={() => setHighlightedPhase(undefined)}
|
|
340
|
+
>
|
|
341
|
+
<div
|
|
342
|
+
style={{ width: '100%', height: '100%' }}
|
|
343
|
+
onMouseOver={(e) => {
|
|
344
|
+
// Check if hovering over a phase node
|
|
345
|
+
const target = e.target as HTMLElement;
|
|
346
|
+
const textContent = target.textContent;
|
|
347
|
+
if (textContent === 'Setup') setHighlightedPhase('setup');
|
|
348
|
+
else if (textContent === 'Execution') setHighlightedPhase('execution');
|
|
349
|
+
else if (textContent === 'Assertion') setHighlightedPhase('assertion');
|
|
350
|
+
}}
|
|
351
|
+
>
|
|
352
|
+
<GraphRenderer
|
|
353
|
+
canvas={testExecutionCanvas}
|
|
354
|
+
showMinimap={true}
|
|
355
|
+
showControls={true}
|
|
356
|
+
events={events}
|
|
357
|
+
/>
|
|
358
|
+
</div>
|
|
359
|
+
</div>
|
|
360
|
+
|
|
361
|
+
{/* Event Panel - Right Side */}
|
|
362
|
+
<div style={{ flex: '0 0 40%', height: '100%', borderLeft: '1px solid #333' }}>
|
|
363
|
+
<TestEventPanel
|
|
364
|
+
spans={testSpans as any}
|
|
365
|
+
currentSpanIndex={currentSpanIndex}
|
|
366
|
+
currentEventIndex={currentEventIndex}
|
|
367
|
+
highlightedPhase={highlightedPhase}
|
|
368
|
+
/>
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
);
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Animated visualization of real test execution data using the "wide event" pattern.
|
|
376
|
+
*
|
|
377
|
+
* This demonstrates the key concept from loggingsucks.com:
|
|
378
|
+
* - ONE comprehensive span per test (not multiple child spans)
|
|
379
|
+
* - Events show the narrative of what happened during execution
|
|
380
|
+
* - Context accumulates through event attributes with file/line information
|
|
381
|
+
* - Easy to search by test.name to get full execution story
|
|
382
|
+
*
|
|
383
|
+
* **Interaction:**
|
|
384
|
+
* - Hover over graph nodes (Setup, Execution, Assertion) to highlight related events
|
|
385
|
+
* - Watch the code journey: blue = test file, green = code under test
|
|
386
|
+
* - See how context builds up through events as the animation plays
|
|
387
|
+
*/
|
|
388
|
+
export const Animated: Story = {
|
|
389
|
+
render: () => <AnimatedTestExecution />,
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Static view of the test execution flow showing phases.
|
|
394
|
+
*/
|
|
395
|
+
export const StaticView: Story = {
|
|
396
|
+
args: {
|
|
397
|
+
canvas: testExecutionCanvas,
|
|
398
|
+
showMinimap: true,
|
|
399
|
+
showControls: true,
|
|
400
|
+
},
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Event panel component showing test execution narrative with file/line information.
|
|
405
|
+
*
|
|
406
|
+
* Shows how events accumulate context as tests execute, with automatic file/line
|
|
407
|
+
* capture from stack traces and manual override for code under test.
|
|
408
|
+
*/
|
|
409
|
+
export const EventPanelOnly: StoryObj = {
|
|
410
|
+
render: () => (
|
|
411
|
+
<div style={{ width: '600px', height: '100vh' }}>
|
|
412
|
+
<TestEventPanel
|
|
413
|
+
spans={testSpans as any}
|
|
414
|
+
currentSpanIndex={0}
|
|
415
|
+
currentEventIndex={5} // Show all events
|
|
416
|
+
highlightedPhase={undefined}
|
|
417
|
+
/>
|
|
418
|
+
</div>
|
|
419
|
+
),
|
|
420
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { GraphRenderer } from '../components/GraphRenderer';
|
|
4
|
+
import { TestEventPanel } from '../components/TestEventPanel';
|
|
5
|
+
import type { ExtendedCanvas } from '@principal-ai/principal-view-core';
|
|
6
|
+
import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
|
|
7
|
+
import executionCanvas from '../../../../.principal-views/graph-converter-execution.otel.canvas';
|
|
8
|
+
import validatedSpans from './data/graph-converter-validated-execution.json';
|
|
9
|
+
|
|
10
|
+
const meta = {
|
|
11
|
+
title: 'Features/Validated Execution',
|
|
12
|
+
component: GraphRenderer,
|
|
13
|
+
parameters: {
|
|
14
|
+
layout: 'fullscreen',
|
|
15
|
+
docs: {
|
|
16
|
+
description: {
|
|
17
|
+
component:
|
|
18
|
+
'Demonstrates type-safe event emission with schema validation. The canvas defines expected events, and production code is validated against this schema. Shows how events match the schema defined in graph-converter-execution.otel.canvas.',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
tags: ['autodocs'],
|
|
23
|
+
decorators: [
|
|
24
|
+
(Story) => (
|
|
25
|
+
<ThemeProvider theme={defaultEditorTheme}>
|
|
26
|
+
<div style={{ width: '100vw', height: '100vh', background: '#0a0a0a' }}>
|
|
27
|
+
<Story />
|
|
28
|
+
</div>
|
|
29
|
+
</ThemeProvider>
|
|
30
|
+
),
|
|
31
|
+
],
|
|
32
|
+
} satisfies Meta<typeof GraphRenderer>;
|
|
33
|
+
|
|
34
|
+
export default meta;
|
|
35
|
+
type Story = StoryObj<typeof meta>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Graph visualization of the execution flow with event schema definitions.
|
|
39
|
+
*
|
|
40
|
+
* This canvas defines:
|
|
41
|
+
* - `graph-converter` node with 5 event types
|
|
42
|
+
* - `validation` node with 2 event types
|
|
43
|
+
* - `graph-output` node with 1 event type
|
|
44
|
+
*
|
|
45
|
+
* Each event type has a schema defining:
|
|
46
|
+
* - Required/optional fields
|
|
47
|
+
* - Field types (string, number, boolean, etc.)
|
|
48
|
+
* - Field descriptions
|
|
49
|
+
*
|
|
50
|
+
* See `.principal-views/graph-converter-execution.otel.canvas` for the full schema.
|
|
51
|
+
*/
|
|
52
|
+
export const ExecutionFlow: Story = {
|
|
53
|
+
args: {
|
|
54
|
+
canvas: executionCanvas as ExtendedCanvas,
|
|
55
|
+
showMinimap: true,
|
|
56
|
+
showControls: true,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Event panel showing validated execution data.
|
|
62
|
+
*
|
|
63
|
+
* These events were emitted using `createValidatedSpanEmitter()` which:
|
|
64
|
+
* - Validates events against the canvas schema
|
|
65
|
+
* - Ensures required fields are present
|
|
66
|
+
* - Checks field types match the schema
|
|
67
|
+
* - Throws errors in strict mode if validation fails
|
|
68
|
+
*
|
|
69
|
+
* All events in this panel passed schema validation.
|
|
70
|
+
*/
|
|
71
|
+
export const ValidatedEvents: StoryObj = {
|
|
72
|
+
render: () => (
|
|
73
|
+
<div style={{ width: '800px', height: '100vh' }}>
|
|
74
|
+
<TestEventPanel
|
|
75
|
+
spans={validatedSpans as any}
|
|
76
|
+
currentSpanIndex={0}
|
|
77
|
+
currentEventIndex={10} // Show all events
|
|
78
|
+
highlightedPhase={undefined}
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Side-by-side view of execution flow and validated events.
|
|
86
|
+
*
|
|
87
|
+
* **How it works:**
|
|
88
|
+
* 1. Canvas defines event schemas (what events should be emitted)
|
|
89
|
+
* 2. Tests use `createValidatedSpanEmitter()` to emit events
|
|
90
|
+
* 3. Events are validated against the schema in strict mode
|
|
91
|
+
* 4. If validation fails, test throws `EventValidationError`
|
|
92
|
+
* 5. If validation passes, events are emitted and collected
|
|
93
|
+
*
|
|
94
|
+
* This ensures production code emits events that match the architecture.
|
|
95
|
+
*/
|
|
96
|
+
export const FlowWithValidation: StoryObj = {
|
|
97
|
+
render: () => (
|
|
98
|
+
<div style={{ display: 'flex', width: '100%', height: '100%' }}>
|
|
99
|
+
{/* Graph Visualization - Left Side */}
|
|
100
|
+
<div style={{ flex: '0 0 60%', height: '100%', position: 'relative' }}>
|
|
101
|
+
<GraphRenderer
|
|
102
|
+
canvas={executionCanvas as ExtendedCanvas}
|
|
103
|
+
showMinimap={true}
|
|
104
|
+
showControls={true}
|
|
105
|
+
/>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
{/* Event Panel - Right Side */}
|
|
109
|
+
<div style={{ flex: '0 0 40%', height: '100%', borderLeft: '1px solid #333' }}>
|
|
110
|
+
<div style={{ padding: '20px', color: '#fff', borderBottom: '1px solid #333' }}>
|
|
111
|
+
<h3 style={{ margin: '0 0 10px 0', fontSize: '16px' }}>
|
|
112
|
+
Type-Safe Validated Events
|
|
113
|
+
</h3>
|
|
114
|
+
<p style={{ margin: 0, fontSize: '12px', color: '#888' }}>
|
|
115
|
+
Events validated against canvas schema. See{' '}
|
|
116
|
+
<code>.principal-views/graph-converter-execution.otel.canvas</code>
|
|
117
|
+
</p>
|
|
118
|
+
</div>
|
|
119
|
+
<TestEventPanel
|
|
120
|
+
spans={validatedSpans as any}
|
|
121
|
+
currentSpanIndex={0}
|
|
122
|
+
currentEventIndex={10}
|
|
123
|
+
highlightedPhase={undefined}
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
),
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Canvas with event schema definitions (JSON view).
|
|
132
|
+
*
|
|
133
|
+
* Shows the raw canvas structure including event schemas.
|
|
134
|
+
* Notice the `pv.events` property on each node defining:
|
|
135
|
+
* - Event names (e.g., "conversion.started")
|
|
136
|
+
* - Event descriptions
|
|
137
|
+
* - Field schemas with types and requirements
|
|
138
|
+
*/
|
|
139
|
+
export const CanvasSchema: StoryObj = {
|
|
140
|
+
render: () => (
|
|
141
|
+
<div
|
|
142
|
+
style={{
|
|
143
|
+
padding: '20px',
|
|
144
|
+
color: '#fff',
|
|
145
|
+
fontFamily: 'monospace',
|
|
146
|
+
fontSize: '12px',
|
|
147
|
+
overflow: 'auto',
|
|
148
|
+
height: '100vh',
|
|
149
|
+
}}
|
|
150
|
+
>
|
|
151
|
+
<h2>Event Schema Definition</h2>
|
|
152
|
+
<p>
|
|
153
|
+
This canvas defines event schemas for type-safe telemetry validation.
|
|
154
|
+
</p>
|
|
155
|
+
<pre style={{ background: '#1e1e1e', padding: '20px', borderRadius: '8px' }}>
|
|
156
|
+
{JSON.stringify(executionCanvas, null, 2)}
|
|
157
|
+
</pre>
|
|
158
|
+
</div>
|
|
159
|
+
),
|
|
160
|
+
};
|