@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,214 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface SpanEvent {
|
|
4
|
+
time: number;
|
|
5
|
+
name: string;
|
|
6
|
+
attributes: Record<string, string | number | boolean>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface TestSpan {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
startTime: number;
|
|
13
|
+
endTime?: number;
|
|
14
|
+
duration?: number;
|
|
15
|
+
attributes: Record<string, string | number | boolean>;
|
|
16
|
+
events: SpanEvent[];
|
|
17
|
+
status: 'OK' | 'ERROR';
|
|
18
|
+
errorMessage?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface TestEventPanelProps {
|
|
22
|
+
spans: TestSpan[];
|
|
23
|
+
currentSpanIndex: number;
|
|
24
|
+
currentEventIndex: number;
|
|
25
|
+
highlightedPhase?: string; // 'setup' | 'execution' | 'assertion'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const TestEventPanel: React.FC<TestEventPanelProps> = ({
|
|
29
|
+
spans,
|
|
30
|
+
currentSpanIndex,
|
|
31
|
+
currentEventIndex,
|
|
32
|
+
highlightedPhase,
|
|
33
|
+
}) => {
|
|
34
|
+
const currentSpan = spans[currentSpanIndex];
|
|
35
|
+
const eventsUpToNow = currentSpan?.events.slice(0, currentEventIndex + 1) || [];
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div
|
|
39
|
+
style={{
|
|
40
|
+
width: '100%',
|
|
41
|
+
height: '100%',
|
|
42
|
+
backgroundColor: '#1a1a1a',
|
|
43
|
+
color: '#ffffff',
|
|
44
|
+
padding: '20px',
|
|
45
|
+
fontFamily: 'monospace',
|
|
46
|
+
fontSize: '12px',
|
|
47
|
+
overflow: 'auto',
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
<div style={{ fontWeight: 'bold', marginBottom: '15px', fontSize: '14px' }}>
|
|
51
|
+
📊 Wide Event Pattern - Code Journey
|
|
52
|
+
</div>
|
|
53
|
+
<div style={{ fontSize: '10px', color: '#888', marginBottom: '15px' }}>
|
|
54
|
+
Test: {currentSpan?.name || 'Loading...'}
|
|
55
|
+
<br />
|
|
56
|
+
Events: {eventsUpToNow.length} / {currentSpan?.events.length || 0}
|
|
57
|
+
</div>
|
|
58
|
+
<div style={{ fontSize: '10px', color: '#a3a3a3', marginBottom: '20px', lineHeight: '1.5' }}>
|
|
59
|
+
Watch how execution flows through files:
|
|
60
|
+
<br />
|
|
61
|
+
<span style={{ color: '#60a5fa' }}>Blue = Test file</span>
|
|
62
|
+
{' • '}
|
|
63
|
+
<span style={{ color: '#4ade80' }}>Green → Code under test</span>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
{currentSpan && (
|
|
67
|
+
<>
|
|
68
|
+
{/* Span Attributes (static context) */}
|
|
69
|
+
<div style={{ marginBottom: '20px' }}>
|
|
70
|
+
<div
|
|
71
|
+
style={{
|
|
72
|
+
color: '#60a5fa',
|
|
73
|
+
fontWeight: 'bold',
|
|
74
|
+
marginBottom: '8px',
|
|
75
|
+
fontSize: '11px',
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
Span Context (Static)
|
|
79
|
+
</div>
|
|
80
|
+
<pre
|
|
81
|
+
style={{
|
|
82
|
+
background: '#0d0d0d',
|
|
83
|
+
padding: '10px',
|
|
84
|
+
borderRadius: '4px',
|
|
85
|
+
margin: 0,
|
|
86
|
+
fontSize: '10px',
|
|
87
|
+
lineHeight: '1.5',
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
{JSON.stringify(
|
|
91
|
+
{
|
|
92
|
+
'test.file': currentSpan.attributes['test.file'],
|
|
93
|
+
'test.suite': currentSpan.attributes['test.suite'],
|
|
94
|
+
'test.result': currentSpan.attributes['test.result'],
|
|
95
|
+
},
|
|
96
|
+
null,
|
|
97
|
+
2
|
|
98
|
+
)}
|
|
99
|
+
</pre>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
{/* Event Timeline (context mutations) */}
|
|
103
|
+
<div>
|
|
104
|
+
<div
|
|
105
|
+
style={{
|
|
106
|
+
color: '#4ade80',
|
|
107
|
+
fontWeight: 'bold',
|
|
108
|
+
marginBottom: '8px',
|
|
109
|
+
fontSize: '11px',
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
Event Timeline (Context Mutations)
|
|
113
|
+
</div>
|
|
114
|
+
{eventsUpToNow.map((event, idx) => {
|
|
115
|
+
const filepath = event.attributes['code.filepath'] as string;
|
|
116
|
+
const lineno = event.attributes['code.lineno'] as number;
|
|
117
|
+
const isCodeUnderTest = filepath && filepath !== 'GraphConverter.test.ts';
|
|
118
|
+
|
|
119
|
+
// Determine which phase this event belongs to
|
|
120
|
+
const eventPhase = event.name.split('.')[0]; // 'setup', 'execution', 'assertion'
|
|
121
|
+
const isHighlighted = highlightedPhase === eventPhase;
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div
|
|
125
|
+
key={idx}
|
|
126
|
+
style={{
|
|
127
|
+
marginBottom: '12px',
|
|
128
|
+
paddingBottom: '12px',
|
|
129
|
+
borderBottom: idx < eventsUpToNow.length - 1 ? '1px solid #333' : 'none',
|
|
130
|
+
opacity: highlightedPhase && !isHighlighted ? 0.4 : 1,
|
|
131
|
+
transition: 'opacity 0.2s ease',
|
|
132
|
+
transform: isHighlighted ? 'scale(1.02)' : 'scale(1)',
|
|
133
|
+
backgroundColor: isHighlighted ? '#1e293b' : 'transparent',
|
|
134
|
+
padding: isHighlighted ? '8px' : '0',
|
|
135
|
+
borderRadius: '4px',
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
138
|
+
<div
|
|
139
|
+
style={{
|
|
140
|
+
display: 'flex',
|
|
141
|
+
justifyContent: 'space-between',
|
|
142
|
+
alignItems: 'center',
|
|
143
|
+
marginBottom: '4px',
|
|
144
|
+
}}
|
|
145
|
+
>
|
|
146
|
+
<div style={{ color: '#f59e0b', fontSize: '10px' }}>
|
|
147
|
+
{idx + 1}. {event.name}
|
|
148
|
+
</div>
|
|
149
|
+
{filepath && (
|
|
150
|
+
<div
|
|
151
|
+
style={{
|
|
152
|
+
fontSize: '9px',
|
|
153
|
+
color: isCodeUnderTest ? '#4ade80' : '#60a5fa',
|
|
154
|
+
fontFamily: 'monospace',
|
|
155
|
+
background: isCodeUnderTest ? '#064e3b' : '#1e3a8a',
|
|
156
|
+
padding: '2px 6px',
|
|
157
|
+
borderRadius: '3px',
|
|
158
|
+
}}
|
|
159
|
+
>
|
|
160
|
+
{isCodeUnderTest && '→ '}
|
|
161
|
+
{filepath}:{lineno}
|
|
162
|
+
</div>
|
|
163
|
+
)}
|
|
164
|
+
</div>
|
|
165
|
+
<pre
|
|
166
|
+
style={{
|
|
167
|
+
background: '#0d0d0d',
|
|
168
|
+
padding: '8px',
|
|
169
|
+
borderRadius: '4px',
|
|
170
|
+
margin: 0,
|
|
171
|
+
fontSize: '9px',
|
|
172
|
+
lineHeight: '1.4',
|
|
173
|
+
}}
|
|
174
|
+
>
|
|
175
|
+
{JSON.stringify(
|
|
176
|
+
Object.fromEntries(
|
|
177
|
+
Object.entries(event.attributes).filter(
|
|
178
|
+
([key]) => key !== 'code.filepath' && key !== 'code.lineno'
|
|
179
|
+
)
|
|
180
|
+
),
|
|
181
|
+
null,
|
|
182
|
+
2
|
|
183
|
+
)}
|
|
184
|
+
</pre>
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
187
|
+
})}
|
|
188
|
+
</div>
|
|
189
|
+
</>
|
|
190
|
+
)}
|
|
191
|
+
|
|
192
|
+
<div
|
|
193
|
+
style={{
|
|
194
|
+
marginTop: '20px',
|
|
195
|
+
paddingTop: '15px',
|
|
196
|
+
borderTop: '1px solid #333',
|
|
197
|
+
fontSize: '10px',
|
|
198
|
+
color: '#888',
|
|
199
|
+
}}
|
|
200
|
+
>
|
|
201
|
+
<div style={{ marginBottom: '8px' }}>
|
|
202
|
+
<strong>Total tests:</strong> {spans.length}
|
|
203
|
+
</div>
|
|
204
|
+
<div style={{ marginBottom: '8px' }}>
|
|
205
|
+
<strong>Pattern:</strong> One span per test + event timeline
|
|
206
|
+
</div>
|
|
207
|
+
<div>
|
|
208
|
+
<strong>Status:</strong>{' '}
|
|
209
|
+
<span style={{ color: '#4ade80' }}>All Passed ✓</span>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
);
|
|
214
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -52,6 +52,9 @@ export type { NodeInfoPanelProps } from './components/NodeInfoPanel';
|
|
|
52
52
|
export { ConfigurationSelector } from './components/ConfigurationSelector';
|
|
53
53
|
export type { ConfigurationSelectorProps } from './components/ConfigurationSelector';
|
|
54
54
|
|
|
55
|
+
export { TestEventPanel } from './components/TestEventPanel';
|
|
56
|
+
export type { TestEventPanelProps } from './components/TestEventPanel';
|
|
57
|
+
|
|
55
58
|
// Export node/edge renderers
|
|
56
59
|
export { GenericNode } from './nodes/GenericNode';
|
|
57
60
|
export type { GenericNodeProps } from './nodes/GenericNode';
|
package/src/nodes/CustomNode.tsx
CHANGED
|
@@ -6,6 +6,24 @@ import { resolveIcon } from '../utils/iconResolver';
|
|
|
6
6
|
import { NodeTooltip } from '../components/NodeTooltip';
|
|
7
7
|
import type { OtelInfo } from '../components/NodeTooltip';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Converts a hex color to a soft/lighter version with transparency
|
|
11
|
+
* @param hexColor - Hex color string (e.g., "#FF5733" or "#888")
|
|
12
|
+
* @param alpha - Transparency level (0-1), defaults to 0.12 for subtle backgrounds
|
|
13
|
+
* @returns rgba color string
|
|
14
|
+
*/
|
|
15
|
+
function hexToSoftColor(hexColor: string, alpha = 0.12): string {
|
|
16
|
+
// Remove # if present
|
|
17
|
+
const hex = hexColor.replace('#', '');
|
|
18
|
+
|
|
19
|
+
// Parse hex to RGB
|
|
20
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
21
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
22
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
23
|
+
|
|
24
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
25
|
+
}
|
|
26
|
+
|
|
9
27
|
export interface CustomNodeData extends Record<string, unknown> {
|
|
10
28
|
name: string;
|
|
11
29
|
typeDefinition: NodeTypeDefinition;
|
|
@@ -19,6 +37,8 @@ export interface CustomNodeData extends Record<string, unknown> {
|
|
|
19
37
|
editable?: boolean;
|
|
20
38
|
// Whether tooltips are enabled (defaults to true)
|
|
21
39
|
tooltipsEnabled?: boolean;
|
|
40
|
+
// Whether this node is highlighted (e.g., during execution playback)
|
|
41
|
+
isHighlighted?: boolean;
|
|
22
42
|
}
|
|
23
43
|
|
|
24
44
|
/**
|
|
@@ -41,6 +61,7 @@ export const CustomNode: React.FC<NodeProps<any>> = ({ data, selected, dragging
|
|
|
41
61
|
animationDuration = 1000,
|
|
42
62
|
editable = false,
|
|
43
63
|
tooltipsEnabled = true,
|
|
64
|
+
isHighlighted = false,
|
|
44
65
|
} = nodeProps;
|
|
45
66
|
|
|
46
67
|
// Extract OTEL info and description for tooltip
|
|
@@ -155,7 +176,7 @@ export const CustomNode: React.FC<NodeProps<any>> = ({ data, selected, dragging
|
|
|
155
176
|
const getShapeStyles = () => {
|
|
156
177
|
const baseStyles = {
|
|
157
178
|
padding: '12px 16px',
|
|
158
|
-
backgroundColor: isGroup ? 'rgba(255, 255, 255, 0.7)' :
|
|
179
|
+
backgroundColor: isGroup ? 'rgba(255, 255, 255, 0.7)' : hexToSoftColor(fillColor),
|
|
159
180
|
color: '#000',
|
|
160
181
|
border: `2px solid ${hasViolations ? '#D0021B' : strokeColor}`,
|
|
161
182
|
fontSize: '12px',
|
|
@@ -171,7 +192,11 @@ export const CustomNode: React.FC<NodeProps<any>> = ({ data, selected, dragging
|
|
|
171
192
|
alignItems: 'center',
|
|
172
193
|
justifyContent: isGroup ? 'flex-start' : 'center',
|
|
173
194
|
gap: '4px',
|
|
174
|
-
boxShadow:
|
|
195
|
+
boxShadow: isHighlighted
|
|
196
|
+
? `0 0 0 3px #3b82f6, 0 0 20px rgba(59, 130, 246, 0.5)`
|
|
197
|
+
: selected
|
|
198
|
+
? `0 0 0 2px ${strokeColor}`
|
|
199
|
+
: '0 2px 4px rgba(0,0,0,0.1)',
|
|
175
200
|
transition: 'box-shadow 0.2s ease',
|
|
176
201
|
animationDuration: animationType ? `${animationDuration}ms` : undefined,
|
|
177
202
|
boxSizing: 'border-box' as const,
|
|
@@ -253,7 +278,7 @@ export const CustomNode: React.FC<NodeProps<any>> = ({ data, selected, dragging
|
|
|
253
278
|
}
|
|
254
279
|
: {};
|
|
255
280
|
|
|
256
|
-
// Hexagon inner fill styles (
|
|
281
|
+
// Hexagon inner fill styles (soft color background inset from border)
|
|
257
282
|
const hexagonInnerStyle: React.CSSProperties = isHexagon
|
|
258
283
|
? {
|
|
259
284
|
position: 'absolute',
|
|
@@ -262,7 +287,7 @@ export const CustomNode: React.FC<NodeProps<any>> = ({ data, selected, dragging
|
|
|
262
287
|
right: hexagonBorderWidth,
|
|
263
288
|
bottom: hexagonBorderWidth,
|
|
264
289
|
clipPath: hexagonClipPath,
|
|
265
|
-
backgroundColor:
|
|
290
|
+
backgroundColor: hexToSoftColor(fillColor),
|
|
266
291
|
color: '#000',
|
|
267
292
|
display: 'flex',
|
|
268
293
|
flexDirection: 'column',
|
|
@@ -295,7 +320,7 @@ export const CustomNode: React.FC<NodeProps<any>> = ({ data, selected, dragging
|
|
|
295
320
|
}
|
|
296
321
|
: {};
|
|
297
322
|
|
|
298
|
-
// Diamond inner fill styles (
|
|
323
|
+
// Diamond inner fill styles (soft color background inset from border)
|
|
299
324
|
const diamondInnerStyle: React.CSSProperties = isDiamond
|
|
300
325
|
? {
|
|
301
326
|
position: 'absolute',
|
|
@@ -304,7 +329,7 @@ export const CustomNode: React.FC<NodeProps<any>> = ({ data, selected, dragging
|
|
|
304
329
|
right: diamondBorderWidth,
|
|
305
330
|
bottom: diamondBorderWidth,
|
|
306
331
|
clipPath: diamondClipPath,
|
|
307
|
-
backgroundColor:
|
|
332
|
+
backgroundColor: hexToSoftColor(fillColor),
|
|
308
333
|
color: '#000',
|
|
309
334
|
display: 'flex',
|
|
310
335
|
flexDirection: 'column',
|