@principal-ai/principal-view-react 0.7.10 → 0.7.12
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.map +1 -1
- package/dist/components/GraphRenderer.js +16 -3
- package/dist/components/GraphRenderer.js.map +1 -1
- package/dist/components/TestEventPanel.d.ts +11 -0
- package/dist/components/TestEventPanel.d.ts.map +1 -1
- package/dist/components/TestEventPanel.js +229 -68
- package/dist/components/TestEventPanel.js.map +1 -1
- package/dist/nodes/CustomNode.d.ts.map +1 -1
- package/dist/nodes/CustomNode.js +17 -10
- package/dist/nodes/CustomNode.js.map +1 -1
- package/package.json +1 -1
- package/src/components/GraphRenderer.tsx +15 -3
- package/src/components/TestEventPanel.tsx +370 -79
- package/src/nodes/CustomNode.tsx +18 -10
- package/src/stories/MultiConfig.stories.tsx +1 -1
- package/src/stories/MultiDirectionalConnections.stories.tsx +0 -1
- package/src/stories/RealTestExecution.stories.tsx +45 -172
- package/src/stories/ValidatedExecution.stories.tsx +3 -5
- package/src/stories/data/graph-converter-test-execution.json +326 -204
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
3
|
import { GraphRenderer } from '../components/GraphRenderer';
|
|
4
4
|
import { TestEventPanel } from '../components/TestEventPanel';
|
|
@@ -183,159 +183,37 @@ const testExecutionCanvas: ExtendedCanvas = {
|
|
|
183
183
|
};
|
|
184
184
|
|
|
185
185
|
// ============================================================================
|
|
186
|
-
//
|
|
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
|
|
186
|
+
// Interactive Story (No Animation)
|
|
283
187
|
// ============================================================================
|
|
284
188
|
|
|
285
189
|
const AnimatedTestExecution = () => {
|
|
286
|
-
const [events
|
|
287
|
-
const [currentSpanIndex
|
|
288
|
-
|
|
190
|
+
const [events] = useState<GraphEvent[]>([]);
|
|
191
|
+
const [currentSpanIndex] = useState(0);
|
|
192
|
+
// Show all events by default - set to a large number
|
|
193
|
+
const [currentEventIndex] = useState(999);
|
|
289
194
|
const [highlightedPhase, setHighlightedPhase] = useState<string | undefined>();
|
|
290
195
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
};
|
|
196
|
+
// Extract spans and logs from test data
|
|
197
|
+
const testData = testSpans as any;
|
|
198
|
+
const spans = Array.isArray(testData) ? testData : testData.spans || testData;
|
|
199
|
+
const logs = testData.logs || [];
|
|
333
200
|
|
|
334
201
|
return (
|
|
335
|
-
<div style={{ display: 'flex', width: '
|
|
336
|
-
{/*
|
|
202
|
+
<div style={{ display: 'flex', width: '100vw', height: '100vh' }}>
|
|
203
|
+
{/* Event Panel - Left Side */}
|
|
204
|
+
<div style={{ flex: '0 0 50%', height: '100%', borderRight: `1px solid #333`, overflow: 'hidden' }}>
|
|
205
|
+
<TestEventPanel
|
|
206
|
+
spans={spans}
|
|
207
|
+
logs={logs}
|
|
208
|
+
currentSpanIndex={currentSpanIndex}
|
|
209
|
+
currentEventIndex={currentEventIndex}
|
|
210
|
+
highlightedPhase={highlightedPhase}
|
|
211
|
+
/>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
{/* Graph Visualization - Right Side */}
|
|
337
215
|
<div
|
|
338
|
-
style={{ flex: '0 0
|
|
216
|
+
style={{ flex: '0 0 50%', height: '100%', position: 'relative' }}
|
|
339
217
|
onMouseLeave={() => setHighlightedPhase(undefined)}
|
|
340
218
|
>
|
|
341
219
|
<div
|
|
@@ -351,28 +229,17 @@ const AnimatedTestExecution = () => {
|
|
|
351
229
|
>
|
|
352
230
|
<GraphRenderer
|
|
353
231
|
canvas={testExecutionCanvas}
|
|
354
|
-
showMinimap={true}
|
|
355
232
|
showControls={true}
|
|
356
233
|
events={events}
|
|
357
234
|
/>
|
|
358
235
|
</div>
|
|
359
236
|
</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
237
|
</div>
|
|
371
238
|
);
|
|
372
239
|
};
|
|
373
240
|
|
|
374
241
|
/**
|
|
375
|
-
*
|
|
242
|
+
* Interactive visualization of real test execution data using the "wide event" pattern.
|
|
376
243
|
*
|
|
377
244
|
* This demonstrates the key concept from loggingsucks.com:
|
|
378
245
|
* - ONE comprehensive span per test (not multiple child spans)
|
|
@@ -383,7 +250,7 @@ const AnimatedTestExecution = () => {
|
|
|
383
250
|
* **Interaction:**
|
|
384
251
|
* - Hover over graph nodes (Setup, Execution, Assertion) to highlight related events
|
|
385
252
|
* - Watch the code journey: blue = test file, green = code under test
|
|
386
|
-
* -
|
|
253
|
+
* - All events are shown immediately for easy review
|
|
387
254
|
*/
|
|
388
255
|
export const Animated: Story = {
|
|
389
256
|
render: () => <AnimatedTestExecution />,
|
|
@@ -395,7 +262,6 @@ export const Animated: Story = {
|
|
|
395
262
|
export const StaticView: Story = {
|
|
396
263
|
args: {
|
|
397
264
|
canvas: testExecutionCanvas,
|
|
398
|
-
showMinimap: true,
|
|
399
265
|
showControls: true,
|
|
400
266
|
},
|
|
401
267
|
};
|
|
@@ -403,18 +269,25 @@ export const StaticView: Story = {
|
|
|
403
269
|
/**
|
|
404
270
|
* Event panel component showing test execution narrative with file/line information.
|
|
405
271
|
*
|
|
406
|
-
* Shows how events
|
|
407
|
-
* capture from stack traces and
|
|
272
|
+
* Shows how events and logs are interleaved in chronological order, with automatic
|
|
273
|
+
* file/line capture from stack traces and severity-based color coding for logs.
|
|
408
274
|
*/
|
|
409
275
|
export const EventPanelOnly: StoryObj = {
|
|
410
|
-
render: () =>
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
276
|
+
render: () => {
|
|
277
|
+
const testData = testSpans as any;
|
|
278
|
+
const spans = Array.isArray(testData) ? testData : testData.spans || testData;
|
|
279
|
+
const logs = testData.logs || [];
|
|
280
|
+
|
|
281
|
+
return (
|
|
282
|
+
<div style={{ width: '600px', height: '100vh' }}>
|
|
283
|
+
<TestEventPanel
|
|
284
|
+
spans={spans}
|
|
285
|
+
logs={logs}
|
|
286
|
+
currentSpanIndex={0}
|
|
287
|
+
currentEventIndex={999} // Show all events
|
|
288
|
+
highlightedPhase={undefined}
|
|
289
|
+
/>
|
|
290
|
+
</div>
|
|
291
|
+
);
|
|
292
|
+
},
|
|
420
293
|
};
|
|
@@ -52,7 +52,6 @@ type Story = StoryObj<typeof meta>;
|
|
|
52
52
|
export const ExecutionFlow: Story = {
|
|
53
53
|
args: {
|
|
54
54
|
canvas: executionCanvas as ExtendedCanvas,
|
|
55
|
-
showMinimap: true,
|
|
56
55
|
showControls: true,
|
|
57
56
|
},
|
|
58
57
|
};
|
|
@@ -68,7 +67,7 @@ export const ExecutionFlow: Story = {
|
|
|
68
67
|
*
|
|
69
68
|
* All events in this panel passed schema validation.
|
|
70
69
|
*/
|
|
71
|
-
export const ValidatedEvents:
|
|
70
|
+
export const ValidatedEvents: Story = {
|
|
72
71
|
render: () => (
|
|
73
72
|
<div style={{ width: '800px', height: '100vh' }}>
|
|
74
73
|
<TestEventPanel
|
|
@@ -93,14 +92,13 @@ export const ValidatedEvents: StoryObj = {
|
|
|
93
92
|
*
|
|
94
93
|
* This ensures production code emits events that match the architecture.
|
|
95
94
|
*/
|
|
96
|
-
export const FlowWithValidation:
|
|
95
|
+
export const FlowWithValidation: Story = {
|
|
97
96
|
render: () => (
|
|
98
97
|
<div style={{ display: 'flex', width: '100%', height: '100%' }}>
|
|
99
98
|
{/* Graph Visualization - Left Side */}
|
|
100
99
|
<div style={{ flex: '0 0 60%', height: '100%', position: 'relative' }}>
|
|
101
100
|
<GraphRenderer
|
|
102
101
|
canvas={executionCanvas as ExtendedCanvas}
|
|
103
|
-
showMinimap={true}
|
|
104
102
|
showControls={true}
|
|
105
103
|
/>
|
|
106
104
|
</div>
|
|
@@ -136,7 +134,7 @@ export const FlowWithValidation: StoryObj = {
|
|
|
136
134
|
* - Event descriptions
|
|
137
135
|
* - Field schemas with types and requirements
|
|
138
136
|
*/
|
|
139
|
-
export const CanvasSchema:
|
|
137
|
+
export const CanvasSchema: Story = {
|
|
140
138
|
render: () => (
|
|
141
139
|
<div
|
|
142
140
|
style={{
|