@principal-ai/subsystems-react 0.17.0 → 0.19.0
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/README.md +11 -85
- package/dist/index.d.ts +7 -44
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -32
- package/dist/index.js.map +1 -1
- package/dist/subsystem/SubsystemCarousel.d.ts +36 -0
- package/dist/subsystem/SubsystemCarousel.d.ts.map +1 -0
- package/dist/subsystem/SubsystemCarousel.js +163 -0
- package/dist/subsystem/SubsystemCarousel.js.map +1 -0
- package/dist/subsystem/SubsystemComponentGraph.d.ts +25 -0
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +329 -68
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +9 -199
- package/src/subsystem/SubsystemComponentGraph.tsx +396 -50
- package/src/components/ConfigurationSelector.tsx +0 -133
- package/src/components/GraphRenderer.tsx +0 -3195
- package/src/components/MultiCanvasRenderer.tsx +0 -295
- package/src/components/NodeTooltip.tsx +0 -279
- package/src/components/PendingChanges.test.tsx +0 -375
- package/src/components/SelectionSidebar.tsx +0 -352
- package/src/components/SequenceDiagramRenderer.tsx +0 -1156
- package/src/components/WorkflowSequenceDiagram.tsx +0 -270
- package/src/components/dashboard/DashboardRenderer.tsx +0 -317
- package/src/components/dashboard/MetricPanel.tsx +0 -254
- package/src/components/dashboard/MockDataProvider.ts +0 -330
- package/src/components/dashboard/components/BarChart.tsx +0 -299
- package/src/components/dashboard/components/LineChart.tsx +0 -279
- package/src/components/dashboard/components/MetricCard.tsx +0 -270
- package/src/components/dashboard/components/SourceLink.tsx +0 -63
- package/src/components/dashboard/components/TimeRangeSelector.tsx +0 -280
- package/src/components/dashboard/components/index.ts +0 -5
- package/src/components/dashboard/index.ts +0 -47
- package/src/components/dashboard/types.ts +0 -126
- package/src/components/state-view/PipelineView.tsx +0 -347
- package/src/components/state-view/index.ts +0 -14
- package/src/components/state-view/types.ts +0 -261
- package/src/components/state-view/useStateView.ts +0 -205
- package/src/contexts/GraphEditContext.tsx +0 -27
- package/src/contexts/TooltipPortalContext.ts +0 -8
- package/src/edges/CustomEdge.tsx +0 -402
- package/src/edges/GenericEdge.tsx +0 -36
- package/src/hooks/usePathBasedEvents.ts +0 -192
- package/src/hooks/useSequenceLayout.ts +0 -543
- package/src/hooks/useUndoRedo.ts +0 -144
- package/src/nodes/CustomNode.tsx +0 -1231
- package/src/nodes/GenericNode.tsx +0 -55
- package/src/nodes/otel/OtelBoundaryNode.tsx +0 -224
- package/src/nodes/otel/OtelEventNode.tsx +0 -225
- package/src/nodes/otel/OtelResourceNode.tsx +0 -251
- package/src/nodes/otel/OtelScopeNode.tsx +0 -221
- package/src/nodes/otel/OtelSpanConventionNode.tsx +0 -295
- package/src/nodes/otel/index.ts +0 -23
- package/src/nodes/otel/shared/NodeBadges.tsx +0 -295
- package/src/nodes/otel/shared/NodeContent.tsx +0 -204
- package/src/nodes/otel/shared/index.ts +0 -8
- package/src/nodes/otel/shared/types.ts +0 -138
- package/src/nodes/otel/shared/useNodeBehavior.ts +0 -114
- package/src/stories/Audit/BoundaryNodes.stories.tsx +0 -385
- package/src/stories/Audit/CanvasEdgeTypes.stories.tsx +0 -1448
- package/src/stories/Audit/CanvasNodeTypes.stories.tsx +0 -1049
- package/src/stories/Audit/ColorPriority.stories.tsx +0 -895
- package/src/stories/Audit/DiamondBadges.stories.tsx +0 -629
- package/src/stories/Audit/NodeDimensionsTesting.stories.tsx +0 -724
- package/src/stories/Audit/NodeFieldsAudit.stories.tsx +0 -996
- package/src/stories/Audit/NodeFontSizeTesting.stories.tsx +0 -715
- package/src/stories/Audit/NodeShapes.stories.tsx +0 -1208
- package/src/stories/Audit/WideNodesTesting.stories.tsx +0 -1045
- package/src/stories/Components/DashboardRenderer.stories.tsx +0 -263
- package/src/stories/Components/GraphRenderer.stories.tsx +0 -3236
- package/src/stories/Components/MultiCanvasRenderer.stories.tsx +0 -370
- package/src/stories/Components/SequenceDiagram.stories.tsx +0 -255
- package/src/stories/Components/StateView.stories.tsx +0 -417
- package/src/stories/Components/TraceViewer.stories.tsx +0 -442
- package/src/stories/Components/WorkflowSequenceDiagram.stories.tsx +0 -802
- package/src/stories/Features/AnimationWorkshop.stories.tsx +0 -727
- package/src/stories/Features/ChangeTypeVisual.stories.tsx +0 -67
- package/src/stories/Features/EventDrivenAnimations.stories.tsx +0 -513
- package/src/stories/Features/MultiConfig.stories.tsx +0 -539
- package/src/stories/Layout/ElkEdgeRouting.stories.tsx +0 -539
- package/src/stories/Layout/GraphOrientation.stories.tsx +0 -500
- package/src/stories/Layout/MultiDirectionalConnections.stories.tsx +0 -352
- package/src/stories/Nodes/NodeDefinitionComparison.stories.tsx +0 -560
- package/src/stories/Nodes/NodeNameFallbacks.stories.tsx +0 -491
- package/src/stories/Nodes/NumericColors.stories.tsx +0 -440
- package/src/stories/OTEL/EventNameDisplay.stories.tsx +0 -694
- package/src/stories/OTEL/EventNameFallback.stories.tsx +0 -578
- package/src/stories/OTEL/OtelComponents.stories.tsx +0 -422
- package/src/stories/OTEL/OtelNodeTypesPrototype.stories.tsx +0 -806
- package/src/stories/OTEL/RawEventCards.stories.tsx +0 -57
- package/src/stories/OTEL/SpanBadges.stories.tsx +0 -840
- package/src/stories/OTEL/ValidationEventsCanvas.stories.tsx +0 -209
- package/src/stories/RealWorld/BacklogMdCanvases.stories.tsx +0 -721
- package/src/stories/RealWorld/FileCitySequence.stories.tsx +0 -516
- package/src/stories/RealWorld/RealCanvasFiles.stories.tsx +0 -336
- package/src/stories/__traces__/test-run.canvas.json +0 -241
- package/src/stories/data/activity-feed-analytics.dashboard.json +0 -300
- package/src/stories/data/file-city-images-transformed.otel.canvas.json +0 -295
- package/src/stories/data/file-city-workflows.json +0 -269
- package/src/stories/data/graph-converter-test-execution.json +0 -443
- package/src/stories/data/graph-converter-test.workflow.json +0 -105
- package/src/stories/data/graph-converter-validated-execution.json +0 -58
- package/src/utils/animationMapping.ts +0 -166
- package/src/utils/canvasBounds.ts +0 -146
- package/src/utils/graphConverter.ts +0 -148
- package/src/utils/iconResolver.tsx +0 -53
- package/src/utils/orientationUtils.ts +0 -73
|
@@ -1,3236 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
-
import '@xyflow/react/dist/style.css';
|
|
4
|
-
import { GraphRenderer } from '../../components/GraphRenderer';
|
|
5
|
-
import type { GraphRendererHandle, PendingChanges } from '../../components/GraphRenderer';
|
|
6
|
-
import type { ExtendedCanvas, PVEventSchema } from '@principal-ai/subsystems-core';
|
|
7
|
-
import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
|
|
8
|
-
|
|
9
|
-
const meta = {
|
|
10
|
-
title: 'Components/GraphRenderer',
|
|
11
|
-
component: GraphRenderer,
|
|
12
|
-
parameters: {
|
|
13
|
-
layout: 'fullscreen',
|
|
14
|
-
},
|
|
15
|
-
tags: ['autodocs'],
|
|
16
|
-
decorators: [
|
|
17
|
-
(Story) => (
|
|
18
|
-
<ThemeProvider theme={defaultEditorTheme}>
|
|
19
|
-
<Story />
|
|
20
|
-
</ThemeProvider>
|
|
21
|
-
),
|
|
22
|
-
],
|
|
23
|
-
} satisfies Meta<typeof GraphRenderer>;
|
|
24
|
-
|
|
25
|
-
export default meta;
|
|
26
|
-
type Story = StoryObj<typeof meta>;
|
|
27
|
-
|
|
28
|
-
// ============================================================================
|
|
29
|
-
// Sample Canvas Documents
|
|
30
|
-
// ============================================================================
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Basic canvas with process and data nodes
|
|
34
|
-
*/
|
|
35
|
-
const sampleCanvas: ExtendedCanvas = {
|
|
36
|
-
nodes: [
|
|
37
|
-
{
|
|
38
|
-
id: 'node-1',
|
|
39
|
-
type: 'text',
|
|
40
|
-
x: 100,
|
|
41
|
-
y: 100,
|
|
42
|
-
width: 140,
|
|
43
|
-
height: 70,
|
|
44
|
-
text: 'Input Processor',
|
|
45
|
-
color: '#4A90E2',
|
|
46
|
-
pv: {
|
|
47
|
-
nodeType: 'process',
|
|
48
|
-
shape: 'rectangle',
|
|
49
|
-
icon: 'Settings',
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
id: 'node-2',
|
|
54
|
-
type: 'text',
|
|
55
|
-
x: 300,
|
|
56
|
-
y: 100,
|
|
57
|
-
width: 100,
|
|
58
|
-
height: 100,
|
|
59
|
-
text: 'Database',
|
|
60
|
-
color: '#7B68EE',
|
|
61
|
-
pv: {
|
|
62
|
-
nodeType: 'data',
|
|
63
|
-
shape: 'circle',
|
|
64
|
-
icon: 'Database',
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
id: 'node-3',
|
|
69
|
-
type: 'text',
|
|
70
|
-
x: 500,
|
|
71
|
-
y: 100,
|
|
72
|
-
width: 140,
|
|
73
|
-
height: 70,
|
|
74
|
-
text: 'Output Handler',
|
|
75
|
-
color: '#4A90E2',
|
|
76
|
-
pv: {
|
|
77
|
-
nodeType: 'process',
|
|
78
|
-
shape: 'rectangle',
|
|
79
|
-
icon: 'Settings',
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
],
|
|
83
|
-
edges: [
|
|
84
|
-
{
|
|
85
|
-
id: 'edge-1',
|
|
86
|
-
fromNode: 'node-1',
|
|
87
|
-
toNode: 'node-2',
|
|
88
|
-
edgeType: 'dataflow',
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
id: 'edge-2',
|
|
92
|
-
fromNode: 'node-2',
|
|
93
|
-
toNode: 'node-3',
|
|
94
|
-
edgeType: 'dataflow',
|
|
95
|
-
},
|
|
96
|
-
],
|
|
97
|
-
name: 'Sample Validation Graph',
|
|
98
|
-
description: 'Example graph configuration',
|
|
99
|
-
edgeTypes: {
|
|
100
|
-
dataflow: {
|
|
101
|
-
style: 'solid',
|
|
102
|
-
color: '#50E3C2',
|
|
103
|
-
directed: true,
|
|
104
|
-
},
|
|
105
|
-
dependency: {
|
|
106
|
-
style: 'dashed',
|
|
107
|
-
color: '#F5A623',
|
|
108
|
-
directed: true,
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Empty canvas
|
|
115
|
-
*/
|
|
116
|
-
const emptyCanvas: ExtendedCanvas = {
|
|
117
|
-
nodes: [],
|
|
118
|
-
edges: [],
|
|
119
|
-
name: 'Empty Graph',
|
|
120
|
-
edgeTypes: {},
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Single node canvas
|
|
125
|
-
*/
|
|
126
|
-
const singleNodeCanvas: ExtendedCanvas = {
|
|
127
|
-
nodes: [
|
|
128
|
-
{
|
|
129
|
-
id: 'node-1',
|
|
130
|
-
type: 'text',
|
|
131
|
-
x: 100,
|
|
132
|
-
y: 100,
|
|
133
|
-
width: 140,
|
|
134
|
-
height: 70,
|
|
135
|
-
text: 'Input Processor',
|
|
136
|
-
color: '#4A90E2',
|
|
137
|
-
pv: {
|
|
138
|
-
nodeType: 'process',
|
|
139
|
-
shape: 'rectangle',
|
|
140
|
-
icon: 'Settings',
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
],
|
|
144
|
-
edges: [],
|
|
145
|
-
name: 'Single Node',
|
|
146
|
-
edgeTypes: {},
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Larger canvas with more nodes
|
|
151
|
-
*/
|
|
152
|
-
const largeCanvas: ExtendedCanvas = {
|
|
153
|
-
nodes: [
|
|
154
|
-
{
|
|
155
|
-
id: 'node-1',
|
|
156
|
-
type: 'text',
|
|
157
|
-
x: 100,
|
|
158
|
-
y: 100,
|
|
159
|
-
width: 140,
|
|
160
|
-
height: 70,
|
|
161
|
-
text: 'Input Processor',
|
|
162
|
-
color: '#4A90E2',
|
|
163
|
-
pv: {
|
|
164
|
-
nodeType: 'process',
|
|
165
|
-
shape: 'rectangle',
|
|
166
|
-
icon: 'Settings',
|
|
167
|
-
},
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
id: 'node-2',
|
|
171
|
-
type: 'text',
|
|
172
|
-
x: 300,
|
|
173
|
-
y: 100,
|
|
174
|
-
width: 100,
|
|
175
|
-
height: 100,
|
|
176
|
-
text: 'Database',
|
|
177
|
-
color: '#7B68EE',
|
|
178
|
-
pv: {
|
|
179
|
-
nodeType: 'data',
|
|
180
|
-
shape: 'circle',
|
|
181
|
-
icon: 'Database',
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
id: 'node-3',
|
|
186
|
-
type: 'text',
|
|
187
|
-
x: 500,
|
|
188
|
-
y: 100,
|
|
189
|
-
width: 140,
|
|
190
|
-
height: 70,
|
|
191
|
-
text: 'Output Handler',
|
|
192
|
-
color: '#4A90E2',
|
|
193
|
-
pv: {
|
|
194
|
-
nodeType: 'process',
|
|
195
|
-
shape: 'rectangle',
|
|
196
|
-
icon: 'Settings',
|
|
197
|
-
},
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
id: 'node-4',
|
|
201
|
-
type: 'text',
|
|
202
|
-
x: 200,
|
|
203
|
-
y: 250,
|
|
204
|
-
width: 140,
|
|
205
|
-
height: 70,
|
|
206
|
-
text: 'Validator',
|
|
207
|
-
color: '#4A90E2',
|
|
208
|
-
pv: {
|
|
209
|
-
nodeType: 'process',
|
|
210
|
-
shape: 'rectangle',
|
|
211
|
-
icon: 'Check',
|
|
212
|
-
},
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
id: 'node-5',
|
|
216
|
-
type: 'text',
|
|
217
|
-
x: 400,
|
|
218
|
-
y: 250,
|
|
219
|
-
width: 100,
|
|
220
|
-
height: 100,
|
|
221
|
-
text: 'Cache',
|
|
222
|
-
color: '#7B68EE',
|
|
223
|
-
pv: {
|
|
224
|
-
nodeType: 'data',
|
|
225
|
-
shape: 'circle',
|
|
226
|
-
icon: 'Zap',
|
|
227
|
-
},
|
|
228
|
-
},
|
|
229
|
-
],
|
|
230
|
-
edges: [
|
|
231
|
-
{
|
|
232
|
-
id: 'edge-1',
|
|
233
|
-
fromNode: 'node-1',
|
|
234
|
-
toNode: 'node-2',
|
|
235
|
-
edgeType: 'dataflow',
|
|
236
|
-
},
|
|
237
|
-
{
|
|
238
|
-
id: 'edge-2',
|
|
239
|
-
fromNode: 'node-2',
|
|
240
|
-
toNode: 'node-3',
|
|
241
|
-
edgeType: 'dataflow',
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
id: 'edge-3',
|
|
245
|
-
fromNode: 'node-1',
|
|
246
|
-
toNode: 'node-4',
|
|
247
|
-
edgeType: 'dataflow',
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
id: 'edge-4',
|
|
251
|
-
fromNode: 'node-4',
|
|
252
|
-
toNode: 'node-5',
|
|
253
|
-
edgeType: 'dataflow',
|
|
254
|
-
},
|
|
255
|
-
],
|
|
256
|
-
name: 'Large Graph',
|
|
257
|
-
edgeTypes: {
|
|
258
|
-
dataflow: {
|
|
259
|
-
style: 'solid',
|
|
260
|
-
color: '#50E3C2',
|
|
261
|
-
directed: true,
|
|
262
|
-
},
|
|
263
|
-
},
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Service architecture canvas (richer example)
|
|
268
|
-
*/
|
|
269
|
-
const serviceArchitectureCanvas: ExtendedCanvas = {
|
|
270
|
-
nodes: [
|
|
271
|
-
{
|
|
272
|
-
id: 'client',
|
|
273
|
-
type: 'text',
|
|
274
|
-
x: 100,
|
|
275
|
-
y: 200,
|
|
276
|
-
width: 120,
|
|
277
|
-
height: 120,
|
|
278
|
-
text: '# Client',
|
|
279
|
-
color: 5, // cyan preset
|
|
280
|
-
pv: {
|
|
281
|
-
nodeType: 'client',
|
|
282
|
-
shape: 'circle',
|
|
283
|
-
icon: 'User',
|
|
284
|
-
states: {
|
|
285
|
-
idle: { color: '#94a3b8', icon: 'User' },
|
|
286
|
-
connected: { color: '#22c55e', icon: 'UserCheck' },
|
|
287
|
-
error: { color: '#ef4444', icon: 'UserX' },
|
|
288
|
-
},
|
|
289
|
-
},
|
|
290
|
-
},
|
|
291
|
-
{
|
|
292
|
-
id: 'api-server',
|
|
293
|
-
type: 'text',
|
|
294
|
-
x: 350,
|
|
295
|
-
y: 180,
|
|
296
|
-
width: 200,
|
|
297
|
-
height: 140,
|
|
298
|
-
text: '# API Server\n\nHandles REST endpoints',
|
|
299
|
-
color: 6, // purple preset
|
|
300
|
-
pv: {
|
|
301
|
-
nodeType: 'api-server',
|
|
302
|
-
shape: 'rectangle',
|
|
303
|
-
icon: 'Server',
|
|
304
|
-
states: {
|
|
305
|
-
idle: { color: '#94a3b8' },
|
|
306
|
-
processing: { color: '#3b82f6' },
|
|
307
|
-
error: { color: '#ef4444' },
|
|
308
|
-
},
|
|
309
|
-
},
|
|
310
|
-
},
|
|
311
|
-
{
|
|
312
|
-
id: 'database',
|
|
313
|
-
type: 'text',
|
|
314
|
-
x: 620,
|
|
315
|
-
y: 200,
|
|
316
|
-
width: 150,
|
|
317
|
-
height: 100,
|
|
318
|
-
text: '# Database',
|
|
319
|
-
color: 4, // green preset
|
|
320
|
-
pv: {
|
|
321
|
-
nodeType: 'database',
|
|
322
|
-
shape: 'hexagon',
|
|
323
|
-
icon: 'Database',
|
|
324
|
-
},
|
|
325
|
-
},
|
|
326
|
-
{
|
|
327
|
-
id: 'cache',
|
|
328
|
-
type: 'text',
|
|
329
|
-
x: 350,
|
|
330
|
-
y: 380,
|
|
331
|
-
width: 140,
|
|
332
|
-
height: 80,
|
|
333
|
-
text: '# Cache',
|
|
334
|
-
color: 2, // orange preset
|
|
335
|
-
pv: {
|
|
336
|
-
nodeType: 'cache',
|
|
337
|
-
shape: 'diamond',
|
|
338
|
-
icon: 'Zap',
|
|
339
|
-
},
|
|
340
|
-
},
|
|
341
|
-
],
|
|
342
|
-
edges: [
|
|
343
|
-
{
|
|
344
|
-
id: 'client-to-api',
|
|
345
|
-
fromNode: 'client',
|
|
346
|
-
toNode: 'api-server',
|
|
347
|
-
fromSide: 'right',
|
|
348
|
-
toSide: 'left',
|
|
349
|
-
label: 'HTTP',
|
|
350
|
-
pv: {
|
|
351
|
-
edgeType: 'http-request',
|
|
352
|
-
},
|
|
353
|
-
},
|
|
354
|
-
{
|
|
355
|
-
id: 'api-to-db',
|
|
356
|
-
fromNode: 'api-server',
|
|
357
|
-
toNode: 'database',
|
|
358
|
-
fromSide: 'right',
|
|
359
|
-
toSide: 'left',
|
|
360
|
-
label: 'SQL',
|
|
361
|
-
pv: {
|
|
362
|
-
edgeType: 'db-query',
|
|
363
|
-
},
|
|
364
|
-
},
|
|
365
|
-
{
|
|
366
|
-
id: 'api-to-cache',
|
|
367
|
-
fromNode: 'api-server',
|
|
368
|
-
toNode: 'cache',
|
|
369
|
-
fromSide: 'bottom',
|
|
370
|
-
toSide: 'top',
|
|
371
|
-
label: 'GET/SET',
|
|
372
|
-
edgeType: 'cache-access',
|
|
373
|
-
},
|
|
374
|
-
],
|
|
375
|
-
name: 'Simple Service Architecture',
|
|
376
|
-
description: 'A basic client-server architecture with caching',
|
|
377
|
-
edgeTypes: {
|
|
378
|
-
'http-request': {
|
|
379
|
-
style: 'solid',
|
|
380
|
-
color: '#3b82f6',
|
|
381
|
-
width: 3,
|
|
382
|
-
directed: true,
|
|
383
|
-
animation: {
|
|
384
|
-
type: 'flow',
|
|
385
|
-
duration: 1500,
|
|
386
|
-
},
|
|
387
|
-
},
|
|
388
|
-
'db-query': {
|
|
389
|
-
style: 'dashed',
|
|
390
|
-
color: '#22c55e',
|
|
391
|
-
width: 2,
|
|
392
|
-
directed: true,
|
|
393
|
-
},
|
|
394
|
-
'cache-access': {
|
|
395
|
-
style: 'dotted',
|
|
396
|
-
color: '#f97316',
|
|
397
|
-
width: 2,
|
|
398
|
-
directed: true,
|
|
399
|
-
animation: {
|
|
400
|
-
type: 'pulse',
|
|
401
|
-
duration: 1000,
|
|
402
|
-
},
|
|
403
|
-
},
|
|
404
|
-
},
|
|
405
|
-
display: {
|
|
406
|
-
layout: 'manual',
|
|
407
|
-
animations: {
|
|
408
|
-
enabled: true,
|
|
409
|
-
speed: 1.0,
|
|
410
|
-
},
|
|
411
|
-
},
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
// ============================================================================
|
|
415
|
-
// Stories
|
|
416
|
-
// ============================================================================
|
|
417
|
-
|
|
418
|
-
const DefaultTemplate = () => (
|
|
419
|
-
<div style={{ width: '100%', height: '600px' }}>
|
|
420
|
-
<GraphRenderer canvas={sampleCanvas} />
|
|
421
|
-
</div>
|
|
422
|
-
);
|
|
423
|
-
|
|
424
|
-
export const Default: Story = {
|
|
425
|
-
render: () => <DefaultTemplate />,
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
const EmptyGraphTemplate = () => (
|
|
429
|
-
<div style={{ width: '100%', height: '600px' }}>
|
|
430
|
-
<GraphRenderer canvas={emptyCanvas} />
|
|
431
|
-
</div>
|
|
432
|
-
);
|
|
433
|
-
|
|
434
|
-
export const EmptyGraph: Story = {
|
|
435
|
-
render: () => <EmptyGraphTemplate />,
|
|
436
|
-
};
|
|
437
|
-
|
|
438
|
-
const SingleNodeTemplate = () => (
|
|
439
|
-
<div style={{ width: '100%', height: '600px' }}>
|
|
440
|
-
<GraphRenderer canvas={singleNodeCanvas} />
|
|
441
|
-
</div>
|
|
442
|
-
);
|
|
443
|
-
|
|
444
|
-
export const SingleNode: Story = {
|
|
445
|
-
render: () => <SingleNodeTemplate />,
|
|
446
|
-
};
|
|
447
|
-
|
|
448
|
-
const LargeGraphTemplate = () => (
|
|
449
|
-
<div style={{ width: '100%', height: '600px' }}>
|
|
450
|
-
<GraphRenderer canvas={largeCanvas} />
|
|
451
|
-
</div>
|
|
452
|
-
);
|
|
453
|
-
|
|
454
|
-
export const LargeGraph: Story = {
|
|
455
|
-
render: () => <LargeGraphTemplate />,
|
|
456
|
-
};
|
|
457
|
-
|
|
458
|
-
const ServiceArchitectureTemplate = () => (
|
|
459
|
-
<div style={{ width: '100%', height: '600px' }}>
|
|
460
|
-
<GraphRenderer canvas={serviceArchitectureCanvas} />
|
|
461
|
-
</div>
|
|
462
|
-
);
|
|
463
|
-
|
|
464
|
-
export const ServiceArchitecture: Story = {
|
|
465
|
-
render: () => <ServiceArchitectureTemplate />,
|
|
466
|
-
parameters: {
|
|
467
|
-
docs: {
|
|
468
|
-
description: {
|
|
469
|
-
story: `
|
|
470
|
-
**Service Architecture** - A realistic example showing:
|
|
471
|
-
- Multiple node shapes (circle, rectangle, hexagon, diamond)
|
|
472
|
-
- Edge types with different styles (solid, dashed, dotted)
|
|
473
|
-
- Edge animations (flow, pulse)
|
|
474
|
-
- State definitions for dynamic coloring
|
|
475
|
-
`,
|
|
476
|
-
},
|
|
477
|
-
},
|
|
478
|
-
},
|
|
479
|
-
};
|
|
480
|
-
|
|
481
|
-
// Interactive story with editable mode
|
|
482
|
-
const EditableTemplate = () => {
|
|
483
|
-
const graphRef = React.useRef<GraphRendererHandle>(null);
|
|
484
|
-
const [hasChanges, setHasChanges] = React.useState(false);
|
|
485
|
-
const [lastSavedChanges, setLastSavedChanges] = React.useState<PendingChanges | null>(null);
|
|
486
|
-
|
|
487
|
-
const handleSave = () => {
|
|
488
|
-
const changes = graphRef.current?.getPendingChanges();
|
|
489
|
-
if (changes?.hasChanges) {
|
|
490
|
-
console.log('Saving changes:', changes);
|
|
491
|
-
setLastSavedChanges(changes);
|
|
492
|
-
}
|
|
493
|
-
};
|
|
494
|
-
|
|
495
|
-
const handleReset = () => {
|
|
496
|
-
graphRef.current?.resetEditState();
|
|
497
|
-
setHasChanges(false);
|
|
498
|
-
setLastSavedChanges(null);
|
|
499
|
-
};
|
|
500
|
-
|
|
501
|
-
return (
|
|
502
|
-
<div>
|
|
503
|
-
<div style={{ marginBottom: 16, padding: 12, backgroundColor: '#f5f5f5', borderRadius: 4 }}>
|
|
504
|
-
<strong>Interactive Graph Editor</strong>
|
|
505
|
-
<div style={{ marginTop: 8, fontSize: 12, color: '#666' }}>
|
|
506
|
-
<ul style={{ margin: '4px 0', paddingLeft: 20 }}>
|
|
507
|
-
<li>Drag nodes to reposition them</li>
|
|
508
|
-
<li>Drag from a node handle to another node to create an edge</li>
|
|
509
|
-
<li>Click an edge to view info and delete it</li>
|
|
510
|
-
<li>Click a node to view info, edit name/type, or delete it</li>
|
|
511
|
-
</ul>
|
|
512
|
-
</div>
|
|
513
|
-
<div style={{ marginTop: 12, display: 'flex', gap: 8, alignItems: 'center' }}>
|
|
514
|
-
<button
|
|
515
|
-
onClick={handleSave}
|
|
516
|
-
disabled={!hasChanges}
|
|
517
|
-
style={{
|
|
518
|
-
padding: '6px 12px',
|
|
519
|
-
backgroundColor: hasChanges ? '#4A90E2' : '#ccc',
|
|
520
|
-
color: 'white',
|
|
521
|
-
border: 'none',
|
|
522
|
-
borderRadius: 4,
|
|
523
|
-
cursor: hasChanges ? 'pointer' : 'not-allowed',
|
|
524
|
-
}}
|
|
525
|
-
>
|
|
526
|
-
Save Changes
|
|
527
|
-
</button>
|
|
528
|
-
<button
|
|
529
|
-
onClick={handleReset}
|
|
530
|
-
style={{
|
|
531
|
-
padding: '6px 12px',
|
|
532
|
-
backgroundColor: '#f0f0f0',
|
|
533
|
-
color: '#333',
|
|
534
|
-
border: '1px solid #ccc',
|
|
535
|
-
borderRadius: 4,
|
|
536
|
-
cursor: 'pointer',
|
|
537
|
-
}}
|
|
538
|
-
>
|
|
539
|
-
Reset
|
|
540
|
-
</button>
|
|
541
|
-
{hasChanges && (
|
|
542
|
-
<span style={{ fontSize: 12, color: '#f5a623', fontStyle: 'italic' }}>
|
|
543
|
-
Unsaved changes
|
|
544
|
-
</span>
|
|
545
|
-
)}
|
|
546
|
-
</div>
|
|
547
|
-
{lastSavedChanges && (
|
|
548
|
-
<div style={{ marginTop: 12 }}>
|
|
549
|
-
<div style={{ fontSize: 11, fontWeight: 'bold', color: '#666' }}>
|
|
550
|
-
Last saved changes:
|
|
551
|
-
</div>
|
|
552
|
-
<pre
|
|
553
|
-
style={{
|
|
554
|
-
marginTop: 4,
|
|
555
|
-
fontSize: 10,
|
|
556
|
-
backgroundColor: '#fff',
|
|
557
|
-
padding: 8,
|
|
558
|
-
borderRadius: 4,
|
|
559
|
-
maxHeight: 150,
|
|
560
|
-
overflow: 'auto',
|
|
561
|
-
}}
|
|
562
|
-
>
|
|
563
|
-
{JSON.stringify(lastSavedChanges, null, 2)}
|
|
564
|
-
</pre>
|
|
565
|
-
</div>
|
|
566
|
-
)}
|
|
567
|
-
</div>
|
|
568
|
-
<div style={{ width: '100%', height: '500px' }}>
|
|
569
|
-
<GraphRenderer
|
|
570
|
-
ref={graphRef}
|
|
571
|
-
canvas={sampleCanvas}
|
|
572
|
-
editable={true}
|
|
573
|
-
onPendingChangesChange={setHasChanges}
|
|
574
|
-
/>
|
|
575
|
-
</div>
|
|
576
|
-
</div>
|
|
577
|
-
);
|
|
578
|
-
};
|
|
579
|
-
|
|
580
|
-
export const Editable: Story = {
|
|
581
|
-
render: () => <EditableTemplate />,
|
|
582
|
-
parameters: {
|
|
583
|
-
docs: {
|
|
584
|
-
description: {
|
|
585
|
-
story:
|
|
586
|
-
'Full editing mode with internal state management. Drag nodes, create/delete edges, delete nodes, and edit node properties. Changes are tracked internally and can be retrieved via the ref when saving.',
|
|
587
|
-
},
|
|
588
|
-
},
|
|
589
|
-
},
|
|
590
|
-
};
|
|
591
|
-
|
|
592
|
-
const ServiceArchitectureEditableTemplate = () => {
|
|
593
|
-
const graphRef = React.useRef<GraphRendererHandle>(null);
|
|
594
|
-
const [hasChanges, setHasChanges] = React.useState(false);
|
|
595
|
-
|
|
596
|
-
return (
|
|
597
|
-
<div>
|
|
598
|
-
<div
|
|
599
|
-
style={{
|
|
600
|
-
marginBottom: 16,
|
|
601
|
-
padding: 12,
|
|
602
|
-
backgroundColor: '#f0f9ff',
|
|
603
|
-
borderRadius: 4,
|
|
604
|
-
border: '1px solid #3b82f6',
|
|
605
|
-
}}
|
|
606
|
-
>
|
|
607
|
-
<strong style={{ color: '#1e40af' }}>Service Architecture + Editing</strong>
|
|
608
|
-
<div style={{ marginTop: 8, fontSize: 12, color: '#1e40af' }}>
|
|
609
|
-
This graph is rendered from an ExtendedCanvas document. You can edit it interactively.
|
|
610
|
-
{hasChanges && <span style={{ marginLeft: 8, color: '#f97316' }}>(unsaved changes)</span>}
|
|
611
|
-
</div>
|
|
612
|
-
</div>
|
|
613
|
-
<div style={{ width: '100%', height: '500px' }}>
|
|
614
|
-
<GraphRenderer
|
|
615
|
-
ref={graphRef}
|
|
616
|
-
canvas={serviceArchitectureCanvas}
|
|
617
|
-
editable={true}
|
|
618
|
-
onPendingChangesChange={setHasChanges}
|
|
619
|
-
/>
|
|
620
|
-
</div>
|
|
621
|
-
</div>
|
|
622
|
-
);
|
|
623
|
-
};
|
|
624
|
-
|
|
625
|
-
export const ServiceArchitectureEditable: Story = {
|
|
626
|
-
render: () => <ServiceArchitectureEditableTemplate />,
|
|
627
|
-
parameters: {
|
|
628
|
-
docs: {
|
|
629
|
-
description: {
|
|
630
|
-
story:
|
|
631
|
-
'Service architecture canvas with editing enabled. Edit nodes, create edges, and track changes.',
|
|
632
|
-
},
|
|
633
|
-
},
|
|
634
|
-
},
|
|
635
|
-
};
|
|
636
|
-
|
|
637
|
-
// ============================================================================
|
|
638
|
-
// OTEL Log Association Canvas
|
|
639
|
-
// ============================================================================
|
|
640
|
-
|
|
641
|
-
/**
|
|
642
|
-
* OTEL Log Association architecture canvas
|
|
643
|
-
* Demonstrates OTEL badges, tooltips, and info panel integration
|
|
644
|
-
*/
|
|
645
|
-
const otelLogAssociationCanvas: ExtendedCanvas = {
|
|
646
|
-
nodes: [
|
|
647
|
-
{
|
|
648
|
-
id: 'otel-log',
|
|
649
|
-
type: 'text',
|
|
650
|
-
text: 'OtelLog',
|
|
651
|
-
x: -304,
|
|
652
|
-
y: 147,
|
|
653
|
-
width: 120,
|
|
654
|
-
height: 118,
|
|
655
|
-
pv: {
|
|
656
|
-
nodeType: 'otel-type',
|
|
657
|
-
name: 'OtelLog',
|
|
658
|
-
description:
|
|
659
|
-
'OpenTelemetry log record with timestamp, severity, body, resource, and trace context',
|
|
660
|
-
otel: {
|
|
661
|
-
kind: 'type',
|
|
662
|
-
category: 'log',
|
|
663
|
-
},
|
|
664
|
-
shape: 'rectangle',
|
|
665
|
-
icon: 'FileText',
|
|
666
|
-
fill: '#4A90E2',
|
|
667
|
-
},
|
|
668
|
-
},
|
|
669
|
-
{
|
|
670
|
-
id: 'otel-resource',
|
|
671
|
-
type: 'text',
|
|
672
|
-
text: 'OtelResource',
|
|
673
|
-
x: -305,
|
|
674
|
-
y: 316,
|
|
675
|
-
width: 120,
|
|
676
|
-
height: 117,
|
|
677
|
-
pv: {
|
|
678
|
-
nodeType: 'otel-type',
|
|
679
|
-
name: 'OtelResource',
|
|
680
|
-
description: 'OTEL resource attributes like service.name, k8s.*, deployment.*',
|
|
681
|
-
otel: {
|
|
682
|
-
kind: 'type',
|
|
683
|
-
category: 'resource',
|
|
684
|
-
},
|
|
685
|
-
shape: 'rectangle',
|
|
686
|
-
icon: 'Box',
|
|
687
|
-
fill: '#4A90E2',
|
|
688
|
-
},
|
|
689
|
-
},
|
|
690
|
-
{
|
|
691
|
-
id: 'canvas-scope',
|
|
692
|
-
type: 'text',
|
|
693
|
-
text: 'CanvasScope',
|
|
694
|
-
x: -77,
|
|
695
|
-
y: -48,
|
|
696
|
-
width: 130,
|
|
697
|
-
height: 118,
|
|
698
|
-
pv: {
|
|
699
|
-
nodeType: 'service',
|
|
700
|
-
name: 'CanvasScope',
|
|
701
|
-
description: 'Filters which logs are relevant to this canvas based on resource attributes',
|
|
702
|
-
shape: 'hexagon',
|
|
703
|
-
icon: 'Filter',
|
|
704
|
-
fill: '#9B59B6',
|
|
705
|
-
},
|
|
706
|
-
},
|
|
707
|
-
{
|
|
708
|
-
id: 'log-router',
|
|
709
|
-
type: 'text',
|
|
710
|
-
text: 'LogRouter',
|
|
711
|
-
x: -80,
|
|
712
|
-
y: 150,
|
|
713
|
-
width: 130,
|
|
714
|
-
height: 118,
|
|
715
|
-
pv: {
|
|
716
|
-
nodeType: 'otel-service',
|
|
717
|
-
name: 'LogRouter',
|
|
718
|
-
description:
|
|
719
|
-
'Routes incoming OTEL logs to canvas nodes based on scope and resourceMatch criteria',
|
|
720
|
-
otel: {
|
|
721
|
-
kind: 'service',
|
|
722
|
-
category: 'router',
|
|
723
|
-
},
|
|
724
|
-
shape: 'hexagon',
|
|
725
|
-
icon: 'GitBranch',
|
|
726
|
-
fill: '#7ED321',
|
|
727
|
-
},
|
|
728
|
-
},
|
|
729
|
-
{
|
|
730
|
-
id: 'resource-matcher',
|
|
731
|
-
type: 'text',
|
|
732
|
-
text: 'ResourceMatcher',
|
|
733
|
-
x: -82,
|
|
734
|
-
y: 440,
|
|
735
|
-
width: 130,
|
|
736
|
-
height: 119,
|
|
737
|
-
pv: {
|
|
738
|
-
nodeType: 'service',
|
|
739
|
-
name: 'ResourceMatcher',
|
|
740
|
-
description: 'Matches log resources against node resourceMatch criteria',
|
|
741
|
-
otel: {
|
|
742
|
-
kind: 'service',
|
|
743
|
-
category: 'match',
|
|
744
|
-
},
|
|
745
|
-
shape: 'hexagon',
|
|
746
|
-
icon: 'Search',
|
|
747
|
-
fill: '#7ED321',
|
|
748
|
-
},
|
|
749
|
-
},
|
|
750
|
-
{
|
|
751
|
-
id: 'resource-match',
|
|
752
|
-
type: 'text',
|
|
753
|
-
text: 'ResourceMatch',
|
|
754
|
-
x: 179,
|
|
755
|
-
y: 445,
|
|
756
|
-
width: 120,
|
|
757
|
-
height: 117,
|
|
758
|
-
pv: {
|
|
759
|
-
nodeType: 'otel-type',
|
|
760
|
-
name: 'ResourceMatch',
|
|
761
|
-
description: 'Criteria for matching OTEL resources to canvas nodes',
|
|
762
|
-
otel: {
|
|
763
|
-
kind: 'type',
|
|
764
|
-
category: 'match',
|
|
765
|
-
},
|
|
766
|
-
shape: 'rectangle',
|
|
767
|
-
icon: 'Target',
|
|
768
|
-
fill: '#4A90E2',
|
|
769
|
-
},
|
|
770
|
-
},
|
|
771
|
-
{
|
|
772
|
-
id: 'match-operator',
|
|
773
|
-
type: 'text',
|
|
774
|
-
text: 'MatchOperator',
|
|
775
|
-
x: 178,
|
|
776
|
-
y: 632,
|
|
777
|
-
width: 120,
|
|
778
|
-
height: 117,
|
|
779
|
-
pv: {
|
|
780
|
-
nodeType: 'otel-type',
|
|
781
|
-
name: 'MatchOperator',
|
|
782
|
-
description: 'Matching operators: exact, glob, regex, exists, oneOf',
|
|
783
|
-
otel: {
|
|
784
|
-
kind: 'type',
|
|
785
|
-
category: 'match',
|
|
786
|
-
},
|
|
787
|
-
shape: 'diamond',
|
|
788
|
-
icon: 'Code',
|
|
789
|
-
fill: '#F5A623',
|
|
790
|
-
},
|
|
791
|
-
},
|
|
792
|
-
{
|
|
793
|
-
id: 'canvas-node',
|
|
794
|
-
type: 'text',
|
|
795
|
-
text: 'Canvas Node',
|
|
796
|
-
x: 172,
|
|
797
|
-
y: 277,
|
|
798
|
-
width: 132,
|
|
799
|
-
height: 121,
|
|
800
|
-
pv: {
|
|
801
|
-
nodeType: 'service',
|
|
802
|
-
name: 'Canvas Node',
|
|
803
|
-
description: 'A node in the canvas that can receive routed logs',
|
|
804
|
-
shape: 'rectangle',
|
|
805
|
-
icon: 'Square',
|
|
806
|
-
fill: '#00BCD4',
|
|
807
|
-
},
|
|
808
|
-
},
|
|
809
|
-
{
|
|
810
|
-
id: 'routing-result',
|
|
811
|
-
type: 'text',
|
|
812
|
-
text: 'LogRoutingResult',
|
|
813
|
-
x: 169,
|
|
814
|
-
y: 80,
|
|
815
|
-
width: 150,
|
|
816
|
-
height: 135,
|
|
817
|
-
pv: {
|
|
818
|
-
nodeType: 'otel-type',
|
|
819
|
-
name: 'LogRoutingResult',
|
|
820
|
-
description: 'Result of routing a log: routed, orphaned, or out-of-scope',
|
|
821
|
-
otel: {
|
|
822
|
-
kind: 'type',
|
|
823
|
-
category: 'audit',
|
|
824
|
-
},
|
|
825
|
-
shape: 'rectangle',
|
|
826
|
-
icon: 'CheckCircle',
|
|
827
|
-
fill: '#4A90E2',
|
|
828
|
-
},
|
|
829
|
-
},
|
|
830
|
-
{
|
|
831
|
-
id: 'audit-collector',
|
|
832
|
-
type: 'text',
|
|
833
|
-
text: 'AuditCollector',
|
|
834
|
-
x: 380,
|
|
835
|
-
y: 150,
|
|
836
|
-
width: 130,
|
|
837
|
-
height: 117,
|
|
838
|
-
pv: {
|
|
839
|
-
nodeType: 'otel-service',
|
|
840
|
-
name: 'AuditCollector',
|
|
841
|
-
description:
|
|
842
|
-
'Tracks routing metrics, orphaned logs, silent nodes, and generates coverage reports',
|
|
843
|
-
otel: {
|
|
844
|
-
kind: 'service',
|
|
845
|
-
category: 'collector',
|
|
846
|
-
},
|
|
847
|
-
shape: 'hexagon',
|
|
848
|
-
icon: 'BarChart2',
|
|
849
|
-
fill: '#7ED321',
|
|
850
|
-
},
|
|
851
|
-
},
|
|
852
|
-
{
|
|
853
|
-
id: 'orphaned-log',
|
|
854
|
-
type: 'text',
|
|
855
|
-
text: 'OrphanedLog',
|
|
856
|
-
x: 618,
|
|
857
|
-
y: 150,
|
|
858
|
-
width: 127,
|
|
859
|
-
height: 118,
|
|
860
|
-
pv: {
|
|
861
|
-
nodeType: 'otel-type',
|
|
862
|
-
name: 'OrphanedLog',
|
|
863
|
-
description:
|
|
864
|
-
'Logs that matched canvas scope but no node resourceMatch - indicates missing nodes or misconfiguration',
|
|
865
|
-
otel: {
|
|
866
|
-
kind: 'type',
|
|
867
|
-
category: 'audit',
|
|
868
|
-
},
|
|
869
|
-
shape: 'rectangle',
|
|
870
|
-
icon: 'AlertTriangle',
|
|
871
|
-
fill: '#D0021B',
|
|
872
|
-
},
|
|
873
|
-
},
|
|
874
|
-
{
|
|
875
|
-
id: 'audit-report',
|
|
876
|
-
type: 'text',
|
|
877
|
-
text: 'AuditReport',
|
|
878
|
-
x: 618,
|
|
879
|
-
y: 288,
|
|
880
|
-
width: 124,
|
|
881
|
-
height: 123,
|
|
882
|
-
pv: {
|
|
883
|
-
nodeType: 'otel-type',
|
|
884
|
-
name: 'AuditReport',
|
|
885
|
-
description: 'Coverage report with node activity, orphans, and recommendations',
|
|
886
|
-
otel: {
|
|
887
|
-
kind: 'type',
|
|
888
|
-
category: 'audit',
|
|
889
|
-
},
|
|
890
|
-
shape: 'rectangle',
|
|
891
|
-
icon: 'FileText',
|
|
892
|
-
fill: '#4A90E2',
|
|
893
|
-
},
|
|
894
|
-
},
|
|
895
|
-
],
|
|
896
|
-
edges: [
|
|
897
|
-
{
|
|
898
|
-
id: 'log-to-router',
|
|
899
|
-
fromNode: 'otel-log',
|
|
900
|
-
toNode: 'log-router',
|
|
901
|
-
fromSide: 'right',
|
|
902
|
-
toSide: 'left',
|
|
903
|
-
label: 'route()',
|
|
904
|
-
edgeType: 'http-request',
|
|
905
|
-
},
|
|
906
|
-
{
|
|
907
|
-
id: 'router-checks-scope',
|
|
908
|
-
fromNode: 'log-router',
|
|
909
|
-
toNode: 'canvas-scope',
|
|
910
|
-
fromSide: 'top',
|
|
911
|
-
toSide: 'bottom',
|
|
912
|
-
label: 'isInScope()',
|
|
913
|
-
edgeType: 'http-request',
|
|
914
|
-
},
|
|
915
|
-
{
|
|
916
|
-
id: 'router-uses-matcher',
|
|
917
|
-
fromNode: 'log-router',
|
|
918
|
-
toNode: 'resource-matcher',
|
|
919
|
-
fromSide: 'bottom',
|
|
920
|
-
toSide: 'top',
|
|
921
|
-
label: 'match()',
|
|
922
|
-
edgeType: 'http-request',
|
|
923
|
-
},
|
|
924
|
-
{
|
|
925
|
-
id: 'matcher-uses-match',
|
|
926
|
-
fromNode: 'resource-matcher',
|
|
927
|
-
toNode: 'resource-match',
|
|
928
|
-
fromSide: 'right',
|
|
929
|
-
toSide: 'left',
|
|
930
|
-
label: 'criteria',
|
|
931
|
-
edgeType: 'db-query',
|
|
932
|
-
},
|
|
933
|
-
{
|
|
934
|
-
id: 'match-has-operator',
|
|
935
|
-
fromNode: 'resource-match',
|
|
936
|
-
toNode: 'match-operator',
|
|
937
|
-
fromSide: 'bottom',
|
|
938
|
-
toSide: 'top',
|
|
939
|
-
label: 'operators',
|
|
940
|
-
edgeType: 'db-query',
|
|
941
|
-
},
|
|
942
|
-
{
|
|
943
|
-
id: 'router-to-node',
|
|
944
|
-
fromNode: 'log-router',
|
|
945
|
-
toNode: 'canvas-node',
|
|
946
|
-
fromSide: 'right',
|
|
947
|
-
toSide: 'left',
|
|
948
|
-
label: 'routed',
|
|
949
|
-
edgeType: 'http-request',
|
|
950
|
-
},
|
|
951
|
-
{
|
|
952
|
-
id: 'router-returns-result',
|
|
953
|
-
fromNode: 'log-router',
|
|
954
|
-
toNode: 'routing-result',
|
|
955
|
-
fromSide: 'right',
|
|
956
|
-
toSide: 'left',
|
|
957
|
-
label: 'returns',
|
|
958
|
-
edgeType: 'http-request',
|
|
959
|
-
},
|
|
960
|
-
{
|
|
961
|
-
id: 'result-to-audit',
|
|
962
|
-
fromNode: 'routing-result',
|
|
963
|
-
toNode: 'audit-collector',
|
|
964
|
-
fromSide: 'right',
|
|
965
|
-
toSide: 'left',
|
|
966
|
-
label: 'track()',
|
|
967
|
-
edgeType: 'http-request',
|
|
968
|
-
},
|
|
969
|
-
{
|
|
970
|
-
id: 'audit-tracks-orphans',
|
|
971
|
-
fromNode: 'audit-collector',
|
|
972
|
-
toNode: 'orphaned-log',
|
|
973
|
-
fromSide: 'right',
|
|
974
|
-
toSide: 'left',
|
|
975
|
-
label: 'unmatched',
|
|
976
|
-
edgeType: 'db-query',
|
|
977
|
-
},
|
|
978
|
-
{
|
|
979
|
-
id: 'audit-generates-report',
|
|
980
|
-
fromNode: 'audit-collector',
|
|
981
|
-
toNode: 'audit-report',
|
|
982
|
-
fromSide: 'right',
|
|
983
|
-
toSide: 'left',
|
|
984
|
-
label: 'generate()',
|
|
985
|
-
edgeType: 'db-query',
|
|
986
|
-
},
|
|
987
|
-
{
|
|
988
|
-
id: 'node-has-match',
|
|
989
|
-
fromNode: 'canvas-node',
|
|
990
|
-
toNode: 'resource-match',
|
|
991
|
-
fromSide: 'bottom',
|
|
992
|
-
toSide: 'top',
|
|
993
|
-
label: 'resourceMatch',
|
|
994
|
-
edgeType: 'db-query',
|
|
995
|
-
},
|
|
996
|
-
{
|
|
997
|
-
id: 'log-has-resource',
|
|
998
|
-
fromNode: 'otel-log',
|
|
999
|
-
toNode: 'otel-resource',
|
|
1000
|
-
fromSide: 'bottom',
|
|
1001
|
-
toSide: 'top',
|
|
1002
|
-
label: 'resource',
|
|
1003
|
-
edgeType: 'db-query',
|
|
1004
|
-
},
|
|
1005
|
-
],
|
|
1006
|
-
name: 'OTEL Log Association Architecture',
|
|
1007
|
-
description: 'Shows how OtelLog flows through LogRouter to Canvas Nodes, with audit tracking',
|
|
1008
|
-
nodeTypes: {
|
|
1009
|
-
service: {
|
|
1010
|
-
label: 'Component',
|
|
1011
|
-
description: 'System component',
|
|
1012
|
-
shape: 'hexagon',
|
|
1013
|
-
},
|
|
1014
|
-
'otel-type': {
|
|
1015
|
-
label: 'Type',
|
|
1016
|
-
description: 'TypeScript type or interface representing OTEL concepts',
|
|
1017
|
-
shape: 'rectangle',
|
|
1018
|
-
},
|
|
1019
|
-
'otel-service': {
|
|
1020
|
-
label: 'Service',
|
|
1021
|
-
description: 'Runtime service component for OTEL processing',
|
|
1022
|
-
shape: 'hexagon',
|
|
1023
|
-
},
|
|
1024
|
-
},
|
|
1025
|
-
edgeTypes: {
|
|
1026
|
-
'http-request': {
|
|
1027
|
-
label: 'Flow',
|
|
1028
|
-
style: 'solid',
|
|
1029
|
-
color: '#4A90E2',
|
|
1030
|
-
directed: true,
|
|
1031
|
-
},
|
|
1032
|
-
'db-query': {
|
|
1033
|
-
label: 'Reference',
|
|
1034
|
-
style: 'dashed',
|
|
1035
|
-
color: '#999',
|
|
1036
|
-
directed: true,
|
|
1037
|
-
},
|
|
1038
|
-
},
|
|
1039
|
-
};
|
|
1040
|
-
|
|
1041
|
-
const OtelLogAssociationTemplate = () => (
|
|
1042
|
-
<div style={{ width: '100%', height: '800px' }}>
|
|
1043
|
-
<GraphRenderer canvas={otelLogAssociationCanvas} />
|
|
1044
|
-
</div>
|
|
1045
|
-
);
|
|
1046
|
-
|
|
1047
|
-
export const OtelLogAssociation: Story = {
|
|
1048
|
-
render: () => <OtelLogAssociationTemplate />,
|
|
1049
|
-
parameters: {
|
|
1050
|
-
docs: {
|
|
1051
|
-
description: {
|
|
1052
|
-
story: `
|
|
1053
|
-
**OTEL Log Association Architecture** - Demonstrates the new OTEL visualization features:
|
|
1054
|
-
- **OTEL Badges**: Circular badges (T/S/I) in the top-right corner of nodes indicating Type, Service, or Instance
|
|
1055
|
-
- **Hover Tooltips**: Hover over any node to see the OTEL kind, category, NEW indicator, and description
|
|
1056
|
-
- **Node Info Panel**: Click a node to see the OpenTelemetry section in the info panel
|
|
1057
|
-
|
|
1058
|
-
Color coding:
|
|
1059
|
-
- **Blue (T)**: Type nodes - TypeScript types/interfaces
|
|
1060
|
-
- **Green (S)**: Service nodes - Runtime services
|
|
1061
|
-
- **Purple (I)**: Instance nodes - Specific running instances
|
|
1062
|
-
`,
|
|
1063
|
-
},
|
|
1064
|
-
},
|
|
1065
|
-
},
|
|
1066
|
-
};
|
|
1067
|
-
|
|
1068
|
-
// ============================================================================
|
|
1069
|
-
// Sources Tooltip Canvas
|
|
1070
|
-
// ============================================================================
|
|
1071
|
-
|
|
1072
|
-
/**
|
|
1073
|
-
* Canvas demonstrating source file associations in tooltips
|
|
1074
|
-
*/
|
|
1075
|
-
const sourcesTooltipCanvas: ExtendedCanvas = {
|
|
1076
|
-
nodes: [
|
|
1077
|
-
{
|
|
1078
|
-
id: 'auth-service',
|
|
1079
|
-
type: 'text',
|
|
1080
|
-
x: 100,
|
|
1081
|
-
y: 150,
|
|
1082
|
-
width: 160,
|
|
1083
|
-
height: 100,
|
|
1084
|
-
text: '# Auth Service\n\nHandles user authentication',
|
|
1085
|
-
color: '#3b82f6',
|
|
1086
|
-
pv: {
|
|
1087
|
-
nodeType: 'service',
|
|
1088
|
-
shape: 'rectangle',
|
|
1089
|
-
icon: 'Lock',
|
|
1090
|
-
sources: [
|
|
1091
|
-
'src/services/auth/AuthService.ts',
|
|
1092
|
-
'src/services/auth/TokenManager.ts',
|
|
1093
|
-
],
|
|
1094
|
-
},
|
|
1095
|
-
},
|
|
1096
|
-
{
|
|
1097
|
-
id: 'user-controller',
|
|
1098
|
-
type: 'text',
|
|
1099
|
-
x: 350,
|
|
1100
|
-
y: 150,
|
|
1101
|
-
width: 160,
|
|
1102
|
-
height: 100,
|
|
1103
|
-
text: '# User Controller\n\nManages user CRUD operations',
|
|
1104
|
-
color: '#8b5cf6',
|
|
1105
|
-
pv: {
|
|
1106
|
-
nodeType: 'controller',
|
|
1107
|
-
shape: 'rectangle',
|
|
1108
|
-
icon: 'User',
|
|
1109
|
-
sources: [
|
|
1110
|
-
'src/controllers/UserController.ts',
|
|
1111
|
-
],
|
|
1112
|
-
},
|
|
1113
|
-
},
|
|
1114
|
-
{
|
|
1115
|
-
id: 'database',
|
|
1116
|
-
type: 'text',
|
|
1117
|
-
x: 600,
|
|
1118
|
-
y: 150,
|
|
1119
|
-
width: 140,
|
|
1120
|
-
height: 100,
|
|
1121
|
-
text: '# Database\n\nPostgreSQL instance',
|
|
1122
|
-
color: '#22c55e',
|
|
1123
|
-
pv: {
|
|
1124
|
-
nodeType: 'database',
|
|
1125
|
-
shape: 'hexagon',
|
|
1126
|
-
icon: 'Database',
|
|
1127
|
-
sources: [
|
|
1128
|
-
'src/db/connection.ts',
|
|
1129
|
-
'src/db/migrations/001_init.sql',
|
|
1130
|
-
'src/db/queries/users.ts',
|
|
1131
|
-
],
|
|
1132
|
-
},
|
|
1133
|
-
},
|
|
1134
|
-
{
|
|
1135
|
-
id: 'cache-layer',
|
|
1136
|
-
type: 'text',
|
|
1137
|
-
x: 225,
|
|
1138
|
-
y: 320,
|
|
1139
|
-
width: 150,
|
|
1140
|
-
height: 90,
|
|
1141
|
-
text: '# Cache Layer\n\nRedis caching',
|
|
1142
|
-
color: '#f59e0b',
|
|
1143
|
-
pv: {
|
|
1144
|
-
nodeType: 'cache',
|
|
1145
|
-
shape: 'diamond',
|
|
1146
|
-
icon: 'Zap',
|
|
1147
|
-
sources: [
|
|
1148
|
-
'src/cache/RedisCache.ts',
|
|
1149
|
-
'src/cache/CacheManager.ts',
|
|
1150
|
-
],
|
|
1151
|
-
},
|
|
1152
|
-
},
|
|
1153
|
-
{
|
|
1154
|
-
id: 'logger',
|
|
1155
|
-
type: 'text',
|
|
1156
|
-
x: 450,
|
|
1157
|
-
y: 320,
|
|
1158
|
-
width: 140,
|
|
1159
|
-
height: 90,
|
|
1160
|
-
text: '# Logger\n\nCentralized logging',
|
|
1161
|
-
color: '#6366f1',
|
|
1162
|
-
pv: {
|
|
1163
|
-
nodeType: 'utility',
|
|
1164
|
-
shape: 'circle',
|
|
1165
|
-
icon: 'FileText',
|
|
1166
|
-
sources: [
|
|
1167
|
-
'src/utils/logger.ts',
|
|
1168
|
-
],
|
|
1169
|
-
},
|
|
1170
|
-
},
|
|
1171
|
-
],
|
|
1172
|
-
edges: [
|
|
1173
|
-
{
|
|
1174
|
-
id: 'auth-to-user',
|
|
1175
|
-
fromNode: 'auth-service',
|
|
1176
|
-
toNode: 'user-controller',
|
|
1177
|
-
fromSide: 'right',
|
|
1178
|
-
toSide: 'left',
|
|
1179
|
-
label: 'validates',
|
|
1180
|
-
edgeType: 'dataflow',
|
|
1181
|
-
},
|
|
1182
|
-
{
|
|
1183
|
-
id: 'user-to-db',
|
|
1184
|
-
fromNode: 'user-controller',
|
|
1185
|
-
toNode: 'database',
|
|
1186
|
-
fromSide: 'right',
|
|
1187
|
-
toSide: 'left',
|
|
1188
|
-
label: 'queries',
|
|
1189
|
-
edgeType: 'dataflow',
|
|
1190
|
-
},
|
|
1191
|
-
{
|
|
1192
|
-
id: 'auth-to-cache',
|
|
1193
|
-
fromNode: 'auth-service',
|
|
1194
|
-
toNode: 'cache-layer',
|
|
1195
|
-
fromSide: 'bottom',
|
|
1196
|
-
toSide: 'top',
|
|
1197
|
-
label: 'stores tokens',
|
|
1198
|
-
edgeType: 'dataflow',
|
|
1199
|
-
},
|
|
1200
|
-
{
|
|
1201
|
-
id: 'user-to-cache',
|
|
1202
|
-
fromNode: 'user-controller',
|
|
1203
|
-
toNode: 'cache-layer',
|
|
1204
|
-
fromSide: 'bottom',
|
|
1205
|
-
toSide: 'top',
|
|
1206
|
-
label: 'caches',
|
|
1207
|
-
edgeType: 'dataflow',
|
|
1208
|
-
},
|
|
1209
|
-
{
|
|
1210
|
-
id: 'auth-to-logger',
|
|
1211
|
-
fromNode: 'auth-service',
|
|
1212
|
-
toNode: 'logger',
|
|
1213
|
-
fromSide: 'bottom',
|
|
1214
|
-
toSide: 'top',
|
|
1215
|
-
label: 'logs',
|
|
1216
|
-
edgeType: 'logging',
|
|
1217
|
-
},
|
|
1218
|
-
{
|
|
1219
|
-
id: 'user-to-logger',
|
|
1220
|
-
fromNode: 'user-controller',
|
|
1221
|
-
toNode: 'logger',
|
|
1222
|
-
fromSide: 'bottom',
|
|
1223
|
-
toSide: 'top',
|
|
1224
|
-
label: 'logs',
|
|
1225
|
-
edgeType: 'logging',
|
|
1226
|
-
},
|
|
1227
|
-
],
|
|
1228
|
-
pv: {
|
|
1229
|
-
version: '1.0.0',
|
|
1230
|
-
name: 'Service Architecture with Sources',
|
|
1231
|
-
description: 'Demonstrates source file associations in shift-hover tooltips',
|
|
1232
|
-
edgeTypes: {
|
|
1233
|
-
dataflow: {
|
|
1234
|
-
style: 'solid',
|
|
1235
|
-
color: '#3b82f6',
|
|
1236
|
-
width: 2,
|
|
1237
|
-
directed: true,
|
|
1238
|
-
},
|
|
1239
|
-
logging: {
|
|
1240
|
-
style: 'dashed',
|
|
1241
|
-
color: '#94a3b8',
|
|
1242
|
-
width: 2,
|
|
1243
|
-
directed: true,
|
|
1244
|
-
},
|
|
1245
|
-
},
|
|
1246
|
-
},
|
|
1247
|
-
};
|
|
1248
|
-
|
|
1249
|
-
const SourcesInTooltipTemplate = () => {
|
|
1250
|
-
return (
|
|
1251
|
-
<div style={{ width: '100%', height: '550px' }}>
|
|
1252
|
-
<GraphRenderer
|
|
1253
|
-
canvas={sourcesTooltipCanvas}
|
|
1254
|
-
/>
|
|
1255
|
-
</div>
|
|
1256
|
-
);
|
|
1257
|
-
};
|
|
1258
|
-
|
|
1259
|
-
export const SourcesInTooltip: Story = {
|
|
1260
|
-
render: () => <SourcesInTooltipTemplate />,
|
|
1261
|
-
parameters: {
|
|
1262
|
-
docs: {
|
|
1263
|
-
description: {
|
|
1264
|
-
story: `
|
|
1265
|
-
**[DEPRECATED]** This story previously demonstrated source file associations in tooltips and info panels.
|
|
1266
|
-
|
|
1267
|
-
**Note:** The sources display feature has been removed from the node rendering. Nodes can still have source data in their canvas definition, but it will not be displayed in the UI.
|
|
1268
|
-
`,
|
|
1269
|
-
},
|
|
1270
|
-
},
|
|
1271
|
-
},
|
|
1272
|
-
};
|
|
1273
|
-
|
|
1274
|
-
const EditModeToggleTemplate = () => {
|
|
1275
|
-
const [isEditMode, setIsEditMode] = React.useState(false);
|
|
1276
|
-
const graphRef = React.useRef<GraphRendererHandle>(null);
|
|
1277
|
-
const [hasChanges, setHasChanges] = React.useState(false);
|
|
1278
|
-
|
|
1279
|
-
const handleSave = () => {
|
|
1280
|
-
if (graphRef.current) {
|
|
1281
|
-
const changes = graphRef.current.getPendingChanges();
|
|
1282
|
-
console.log('Saving changes:', changes);
|
|
1283
|
-
alert('Changes saved! Check console for details.');
|
|
1284
|
-
graphRef.current.resetEditState();
|
|
1285
|
-
}
|
|
1286
|
-
};
|
|
1287
|
-
|
|
1288
|
-
return (
|
|
1289
|
-
<div>
|
|
1290
|
-
<div
|
|
1291
|
-
style={{
|
|
1292
|
-
marginBottom: 16,
|
|
1293
|
-
padding: 12,
|
|
1294
|
-
backgroundColor: '#f0f9ff',
|
|
1295
|
-
borderRadius: 4,
|
|
1296
|
-
border: '1px solid #3b82f6',
|
|
1297
|
-
}}
|
|
1298
|
-
>
|
|
1299
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
|
1300
|
-
<button
|
|
1301
|
-
onClick={() => setIsEditMode(!isEditMode)}
|
|
1302
|
-
style={{
|
|
1303
|
-
padding: '8px 16px',
|
|
1304
|
-
backgroundColor: isEditMode ? '#ef4444' : '#3b82f6',
|
|
1305
|
-
color: 'white',
|
|
1306
|
-
border: 'none',
|
|
1307
|
-
borderRadius: 4,
|
|
1308
|
-
cursor: 'pointer',
|
|
1309
|
-
fontWeight: 'bold',
|
|
1310
|
-
fontSize: 14,
|
|
1311
|
-
}}
|
|
1312
|
-
>
|
|
1313
|
-
{isEditMode ? '🔓 Exit Edit Mode' : '🔒 Enter Edit Mode'}
|
|
1314
|
-
</button>
|
|
1315
|
-
{isEditMode && hasChanges && (
|
|
1316
|
-
<button
|
|
1317
|
-
onClick={handleSave}
|
|
1318
|
-
style={{
|
|
1319
|
-
padding: '8px 16px',
|
|
1320
|
-
backgroundColor: '#10b981',
|
|
1321
|
-
color: 'white',
|
|
1322
|
-
border: 'none',
|
|
1323
|
-
borderRadius: 4,
|
|
1324
|
-
cursor: 'pointer',
|
|
1325
|
-
fontWeight: 'bold',
|
|
1326
|
-
fontSize: 14,
|
|
1327
|
-
}}
|
|
1328
|
-
>
|
|
1329
|
-
💾 Save Changes
|
|
1330
|
-
</button>
|
|
1331
|
-
)}
|
|
1332
|
-
<div style={{ fontSize: 13, color: '#1e40af' }}>
|
|
1333
|
-
Mode: <strong>{isEditMode ? 'Editable' : 'Read-only'}</strong>
|
|
1334
|
-
{isEditMode && hasChanges && (
|
|
1335
|
-
<span style={{ marginLeft: 8, color: '#f97316', fontWeight: 'bold' }}>
|
|
1336
|
-
(unsaved changes)
|
|
1337
|
-
</span>
|
|
1338
|
-
)}
|
|
1339
|
-
</div>
|
|
1340
|
-
</div>
|
|
1341
|
-
<div style={{ marginTop: 8, fontSize: 12, color: '#475569' }}>
|
|
1342
|
-
Toggle between read-only and edit mode. Try dragging nodes after switching modes to verify
|
|
1343
|
-
the NaN error fix.
|
|
1344
|
-
</div>
|
|
1345
|
-
</div>
|
|
1346
|
-
<div style={{ width: '100%', height: '500px' }}>
|
|
1347
|
-
<GraphRenderer
|
|
1348
|
-
ref={graphRef}
|
|
1349
|
-
canvas={sampleCanvas}
|
|
1350
|
-
editable={isEditMode}
|
|
1351
|
-
onPendingChangesChange={setHasChanges}
|
|
1352
|
-
/>
|
|
1353
|
-
</div>
|
|
1354
|
-
</div>
|
|
1355
|
-
);
|
|
1356
|
-
};
|
|
1357
|
-
|
|
1358
|
-
export const EditModeToggle: Story = {
|
|
1359
|
-
render: () => <EditModeToggleTemplate />,
|
|
1360
|
-
parameters: {
|
|
1361
|
-
docs: {
|
|
1362
|
-
description: {
|
|
1363
|
-
story: `
|
|
1364
|
-
**Edit Mode Toggle** - Demonstrates toggling between read-only and editable modes without remounting.
|
|
1365
|
-
|
|
1366
|
-
This story tests the fix for the NaN coordinate error that occurred when transitioning between modes:
|
|
1367
|
-
- Click "Enter Edit Mode" to enable editing
|
|
1368
|
-
- Try dragging nodes (should work without NaN errors)
|
|
1369
|
-
- Click "Exit Edit Mode" to return to read-only
|
|
1370
|
-
- Toggle back to edit mode and drag again (should still work)
|
|
1371
|
-
|
|
1372
|
-
Previously, this would cause \`NaN\` errors in edge coordinates due to ReactFlow's internal state corruption.
|
|
1373
|
-
The fix uses \`updateNodeInternals()\` to reset ReactFlow's measurement tracking when entering edit mode.
|
|
1374
|
-
`,
|
|
1375
|
-
},
|
|
1376
|
-
},
|
|
1377
|
-
},
|
|
1378
|
-
};
|
|
1379
|
-
|
|
1380
|
-
/**
|
|
1381
|
-
* Canvas for demonstrating the selected prop styling
|
|
1382
|
-
*/
|
|
1383
|
-
const selectionStylingCanvas: ExtendedCanvas = {
|
|
1384
|
-
nodes: [
|
|
1385
|
-
{
|
|
1386
|
-
id: 'node-1',
|
|
1387
|
-
type: 'text',
|
|
1388
|
-
x: 100,
|
|
1389
|
-
y: 100,
|
|
1390
|
-
width: 140,
|
|
1391
|
-
height: 70,
|
|
1392
|
-
text: 'Click Me!',
|
|
1393
|
-
color: '#4A90E2',
|
|
1394
|
-
pv: {
|
|
1395
|
-
nodeType: 'process',
|
|
1396
|
-
shape: 'rectangle',
|
|
1397
|
-
icon: 'MousePointer',
|
|
1398
|
-
eventRef: 'user.clicked',
|
|
1399
|
-
},
|
|
1400
|
-
},
|
|
1401
|
-
{
|
|
1402
|
-
id: 'node-2',
|
|
1403
|
-
type: 'text',
|
|
1404
|
-
x: 300,
|
|
1405
|
-
y: 100,
|
|
1406
|
-
width: 100,
|
|
1407
|
-
height: 100,
|
|
1408
|
-
text: 'Or Me!',
|
|
1409
|
-
color: '#7B68EE',
|
|
1410
|
-
pv: {
|
|
1411
|
-
nodeType: 'data',
|
|
1412
|
-
shape: 'circle',
|
|
1413
|
-
icon: 'Circle',
|
|
1414
|
-
eventRef: 'data.processed',
|
|
1415
|
-
},
|
|
1416
|
-
},
|
|
1417
|
-
{
|
|
1418
|
-
id: 'node-3',
|
|
1419
|
-
type: 'text',
|
|
1420
|
-
x: 500,
|
|
1421
|
-
y: 100,
|
|
1422
|
-
width: 140,
|
|
1423
|
-
height: 70,
|
|
1424
|
-
text: 'Me Three!',
|
|
1425
|
-
color: '#22c55e',
|
|
1426
|
-
pv: {
|
|
1427
|
-
nodeType: 'process',
|
|
1428
|
-
shape: 'hexagon',
|
|
1429
|
-
icon: 'Hexagon',
|
|
1430
|
-
eventRef: 'workflow.executed',
|
|
1431
|
-
},
|
|
1432
|
-
},
|
|
1433
|
-
{
|
|
1434
|
-
id: 'node-4',
|
|
1435
|
-
type: 'text',
|
|
1436
|
-
x: 200,
|
|
1437
|
-
y: 250,
|
|
1438
|
-
width: 120,
|
|
1439
|
-
height: 120,
|
|
1440
|
-
text: 'Diamond',
|
|
1441
|
-
color: '#f59e0b',
|
|
1442
|
-
pv: {
|
|
1443
|
-
nodeType: 'decision',
|
|
1444
|
-
shape: 'diamond',
|
|
1445
|
-
icon: 'GitBranch',
|
|
1446
|
-
eventRef: 'decision.evaluated',
|
|
1447
|
-
},
|
|
1448
|
-
},
|
|
1449
|
-
{
|
|
1450
|
-
id: 'node-5',
|
|
1451
|
-
type: 'text',
|
|
1452
|
-
x: 400,
|
|
1453
|
-
y: 250,
|
|
1454
|
-
width: 140,
|
|
1455
|
-
height: 80,
|
|
1456
|
-
text: 'Inline Event',
|
|
1457
|
-
color: '#8b5cf6',
|
|
1458
|
-
pv: {
|
|
1459
|
-
nodeType: 'event',
|
|
1460
|
-
shape: 'rectangle',
|
|
1461
|
-
icon: 'Zap',
|
|
1462
|
-
event: {
|
|
1463
|
-
name: 'order.completed',
|
|
1464
|
-
description: 'Triggered when an order is successfully completed and payment is confirmed',
|
|
1465
|
-
attributes: {
|
|
1466
|
-
orderId: { type: 'string', required: true, description: 'Unique order identifier' },
|
|
1467
|
-
userId: { type: 'string', required: true, description: 'User who placed the order' },
|
|
1468
|
-
amount: { type: 'number', required: true, description: 'Order total amount' },
|
|
1469
|
-
timestamp: { type: 'string', description: 'Order completion timestamp' },
|
|
1470
|
-
},
|
|
1471
|
-
},
|
|
1472
|
-
},
|
|
1473
|
-
},
|
|
1474
|
-
],
|
|
1475
|
-
edges: [
|
|
1476
|
-
{
|
|
1477
|
-
id: 'edge-1',
|
|
1478
|
-
fromNode: 'node-1',
|
|
1479
|
-
toNode: 'node-2',
|
|
1480
|
-
edgeType: 'dataflow',
|
|
1481
|
-
},
|
|
1482
|
-
{
|
|
1483
|
-
id: 'edge-2',
|
|
1484
|
-
fromNode: 'node-2',
|
|
1485
|
-
toNode: 'node-3',
|
|
1486
|
-
edgeType: 'dataflow',
|
|
1487
|
-
},
|
|
1488
|
-
{
|
|
1489
|
-
id: 'edge-3',
|
|
1490
|
-
fromNode: 'node-1',
|
|
1491
|
-
toNode: 'node-4',
|
|
1492
|
-
edgeType: 'dataflow',
|
|
1493
|
-
},
|
|
1494
|
-
],
|
|
1495
|
-
pv: {
|
|
1496
|
-
version: '1.0.0',
|
|
1497
|
-
name: 'Selection Styling Demo',
|
|
1498
|
-
description: 'Demonstrates ReactFlow selected prop visual styling',
|
|
1499
|
-
edgeTypes: {
|
|
1500
|
-
dataflow: {
|
|
1501
|
-
style: 'solid',
|
|
1502
|
-
color: '#50E3C2',
|
|
1503
|
-
directed: true,
|
|
1504
|
-
},
|
|
1505
|
-
},
|
|
1506
|
-
},
|
|
1507
|
-
};
|
|
1508
|
-
|
|
1509
|
-
const SelectionStylingTemplate = () => {
|
|
1510
|
-
return (
|
|
1511
|
-
<div>
|
|
1512
|
-
<div
|
|
1513
|
-
style={{
|
|
1514
|
-
marginBottom: 16,
|
|
1515
|
-
padding: 12,
|
|
1516
|
-
backgroundColor: '#f0f9ff',
|
|
1517
|
-
borderRadius: 4,
|
|
1518
|
-
border: '1px solid #3b82f6',
|
|
1519
|
-
}}
|
|
1520
|
-
>
|
|
1521
|
-
<strong style={{ color: '#1e40af' }}>ReactFlow Selection Styling Demo</strong>
|
|
1522
|
-
<div style={{ marginTop: 8, fontSize: 12, color: '#475569' }}>
|
|
1523
|
-
<p style={{ margin: '4px 0' }}>
|
|
1524
|
-
This demonstrates the <code>selected</code> prop that ReactFlow passes to CustomNode.
|
|
1525
|
-
</p>
|
|
1526
|
-
<p style={{ margin: '4px 0' }}>
|
|
1527
|
-
<strong>Visual effects when a node is selected:</strong>
|
|
1528
|
-
</p>
|
|
1529
|
-
<ul style={{ margin: '4px 0', paddingLeft: 20 }}>
|
|
1530
|
-
<li><strong>2px box-shadow outline</strong> in the node's stroke color</li>
|
|
1531
|
-
<li><strong>Resize handles</strong> (only visible in edit mode - not shown in this demo)</li>
|
|
1532
|
-
</ul>
|
|
1533
|
-
<p style={{ margin: '8px 0 4px 0' }}>
|
|
1534
|
-
<strong>Try these interactions:</strong>
|
|
1535
|
-
</p>
|
|
1536
|
-
<ul style={{ margin: '4px 0', paddingLeft: 20 }}>
|
|
1537
|
-
<li><strong>Click</strong> any node → see the outline appear</li>
|
|
1538
|
-
<li><strong>Click another</strong> node → outline moves to new selection</li>
|
|
1539
|
-
<li><strong>Click background</strong> → outline disappears</li>
|
|
1540
|
-
<li><strong>Shift+Drag</strong> box around multiple nodes → all get outlined</li>
|
|
1541
|
-
</ul>
|
|
1542
|
-
<p style={{ margin: '8px 0 4px 0', fontSize: 11, fontStyle: 'italic', color: '#64748b' }}>
|
|
1543
|
-
Note: The <code>selected</code> prop is purely for visual feedback.
|
|
1544
|
-
The info panel uses a separate <code>selectedNodeIds</code> state.
|
|
1545
|
-
</p>
|
|
1546
|
-
</div>
|
|
1547
|
-
</div>
|
|
1548
|
-
<div style={{ width: '100%', height: '500px' }}>
|
|
1549
|
-
<GraphRenderer
|
|
1550
|
-
canvas={selectionStylingCanvas}
|
|
1551
|
-
showNodeDetailPanel={false}
|
|
1552
|
-
/>
|
|
1553
|
-
</div>
|
|
1554
|
-
</div>
|
|
1555
|
-
);
|
|
1556
|
-
};
|
|
1557
|
-
|
|
1558
|
-
export const SelectionStyling: Story = {
|
|
1559
|
-
render: () => <SelectionStylingTemplate />,
|
|
1560
|
-
parameters: {
|
|
1561
|
-
docs: {
|
|
1562
|
-
description: {
|
|
1563
|
-
story: `
|
|
1564
|
-
**Selection Styling** - Demonstrates the visual effects of ReactFlow's \`selected\` prop in read-only mode.
|
|
1565
|
-
|
|
1566
|
-
When a node is selected (via click or box selection), ReactFlow sets \`selected={true}\` on the node component.
|
|
1567
|
-
This triggers visual changes in \`CustomNode\`:
|
|
1568
|
-
|
|
1569
|
-
1. **Box-shadow outline** (line 253-254 in CustomNode.tsx):
|
|
1570
|
-
- 2px solid outline using the node's stroke color
|
|
1571
|
-
- Appears immediately on selection
|
|
1572
|
-
- Removed when selection is cleared
|
|
1573
|
-
- **Click any node to see the outline appear!**
|
|
1574
|
-
|
|
1575
|
-
2. **NodeResizer visibility** (line 441 in CustomNode.tsx):
|
|
1576
|
-
- Shows resize handles when \`selected={true}\` AND \`editable={true}\`
|
|
1577
|
-
- Not visible in this demo (read-only mode)
|
|
1578
|
-
- See the "Editable" story for resize handle demos
|
|
1579
|
-
|
|
1580
|
-
**Key Point:** The \`selected\` prop is purely for ReactFlow's visual feedback. It's separate from
|
|
1581
|
-
\`selectedNodeIds\` state which controls the info panel display.
|
|
1582
|
-
|
|
1583
|
-
In this demo, the info panel is hidden (\`showNodeDetailPanel={false}\`) and edit mode is disabled,
|
|
1584
|
-
so you can focus on just the selection outline effect.
|
|
1585
|
-
`,
|
|
1586
|
-
},
|
|
1587
|
-
},
|
|
1588
|
-
},
|
|
1589
|
-
};
|
|
1590
|
-
|
|
1591
|
-
const HighlightedNodeSyncTemplate = () => {
|
|
1592
|
-
const [highlightedNodeId, setHighlightedNodeId] = React.useState<string | null>(null);
|
|
1593
|
-
const nodeIds = ['node-1', 'node-2', 'node-3', 'node-4', 'node-5'];
|
|
1594
|
-
const [currentIndex, setCurrentIndex] = React.useState(0);
|
|
1595
|
-
|
|
1596
|
-
const handleCycleNodes = () => {
|
|
1597
|
-
const nextIndex = (currentIndex + 1) % nodeIds.length;
|
|
1598
|
-
setCurrentIndex(nextIndex);
|
|
1599
|
-
setHighlightedNodeId(nodeIds[nextIndex]);
|
|
1600
|
-
};
|
|
1601
|
-
|
|
1602
|
-
const handleClearHighlight = () => {
|
|
1603
|
-
setHighlightedNodeId(null);
|
|
1604
|
-
setCurrentIndex(0);
|
|
1605
|
-
};
|
|
1606
|
-
|
|
1607
|
-
// Mock event registry to demonstrate resolveEventRef
|
|
1608
|
-
const resolveEventRef = (eventRef: string) => {
|
|
1609
|
-
const eventRegistry: Record<string, PVEventSchema> = {
|
|
1610
|
-
'user.clicked': {
|
|
1611
|
-
name: 'user.clicked',
|
|
1612
|
-
description: 'Triggered when a user clicks on an interactive element',
|
|
1613
|
-
attributes: {
|
|
1614
|
-
elementId: { type: 'string', required: true, description: 'ID of the clicked element' },
|
|
1615
|
-
userId: { type: 'string', required: true, description: 'ID of the user who clicked' },
|
|
1616
|
-
timestamp: { type: 'string', required: true, description: 'ISO 8601 timestamp' },
|
|
1617
|
-
position: { type: 'object', description: 'Click coordinates (x, y)' },
|
|
1618
|
-
},
|
|
1619
|
-
},
|
|
1620
|
-
'data.processed': {
|
|
1621
|
-
name: 'data.processed',
|
|
1622
|
-
description: 'Emitted when data processing completes successfully',
|
|
1623
|
-
attributes: {
|
|
1624
|
-
dataId: { type: 'string', required: true, description: 'Unique identifier for the data' },
|
|
1625
|
-
recordsProcessed: { type: 'number', required: true, description: 'Number of records processed' },
|
|
1626
|
-
duration: { type: 'number', description: 'Processing time in milliseconds' },
|
|
1627
|
-
status: { type: 'string', required: true, description: 'Processing status (success/partial/failed)' },
|
|
1628
|
-
},
|
|
1629
|
-
},
|
|
1630
|
-
'workflow.executed': {
|
|
1631
|
-
name: 'workflow.executed',
|
|
1632
|
-
description: 'Fired when a workflow completes execution',
|
|
1633
|
-
attributes: {
|
|
1634
|
-
workflowId: { type: 'string', required: true, description: 'Workflow identifier' },
|
|
1635
|
-
executionId: { type: 'string', required: true, description: 'Execution run identifier' },
|
|
1636
|
-
steps: { type: 'array', description: 'List of executed step IDs' },
|
|
1637
|
-
result: { type: 'object', description: 'Workflow execution result' },
|
|
1638
|
-
},
|
|
1639
|
-
},
|
|
1640
|
-
'decision.evaluated': {
|
|
1641
|
-
name: 'decision.evaluated',
|
|
1642
|
-
description: 'Published when a decision point is evaluated in the flow',
|
|
1643
|
-
attributes: {
|
|
1644
|
-
decisionId: { type: 'string', required: true, description: 'Decision point identifier' },
|
|
1645
|
-
condition: { type: 'string', description: 'Evaluated condition expression' },
|
|
1646
|
-
result: { type: 'boolean', required: true, description: 'Evaluation result' },
|
|
1647
|
-
branchTaken: { type: 'string', required: true, description: 'Branch selected (true/false/error)' },
|
|
1648
|
-
},
|
|
1649
|
-
},
|
|
1650
|
-
};
|
|
1651
|
-
|
|
1652
|
-
return eventRegistry[eventRef];
|
|
1653
|
-
};
|
|
1654
|
-
|
|
1655
|
-
return (
|
|
1656
|
-
<div>
|
|
1657
|
-
<div
|
|
1658
|
-
style={{
|
|
1659
|
-
marginBottom: 16,
|
|
1660
|
-
padding: 12,
|
|
1661
|
-
backgroundColor: '#f0f9ff',
|
|
1662
|
-
borderRadius: 4,
|
|
1663
|
-
border: '1px solid #3b82f6',
|
|
1664
|
-
}}
|
|
1665
|
-
>
|
|
1666
|
-
<strong style={{ color: '#1e40af' }}>Highlighted Node Sync Demo</strong>
|
|
1667
|
-
<div style={{ marginTop: 8, fontSize: 12, color: '#475569' }}>
|
|
1668
|
-
<p style={{ margin: '4px 0' }}>
|
|
1669
|
-
This demonstrates how <code>highlightedNodeId</code> automatically syncs with selection
|
|
1670
|
-
and shows the node info panel.
|
|
1671
|
-
</p>
|
|
1672
|
-
<p style={{ margin: '8px 0 4px 0' }}>
|
|
1673
|
-
<strong>Current highlighted node:</strong>{' '}
|
|
1674
|
-
{highlightedNodeId ? (
|
|
1675
|
-
<span style={{ fontFamily: 'monospace', color: '#3b82f6' }}>{highlightedNodeId}</span>
|
|
1676
|
-
) : (
|
|
1677
|
-
<span style={{ color: '#94a3b8' }}>None</span>
|
|
1678
|
-
)}
|
|
1679
|
-
</p>
|
|
1680
|
-
<div style={{ display: 'flex', gap: 8, marginTop: 12 }}>
|
|
1681
|
-
<button
|
|
1682
|
-
onClick={handleCycleNodes}
|
|
1683
|
-
style={{
|
|
1684
|
-
padding: '8px 16px',
|
|
1685
|
-
backgroundColor: '#3b82f6',
|
|
1686
|
-
color: 'white',
|
|
1687
|
-
border: 'none',
|
|
1688
|
-
borderRadius: 4,
|
|
1689
|
-
cursor: 'pointer',
|
|
1690
|
-
fontWeight: 'bold',
|
|
1691
|
-
fontSize: 14,
|
|
1692
|
-
}}
|
|
1693
|
-
>
|
|
1694
|
-
Cycle Nodes ({nodeIds[currentIndex]})
|
|
1695
|
-
</button>
|
|
1696
|
-
<button
|
|
1697
|
-
onClick={handleClearHighlight}
|
|
1698
|
-
style={{
|
|
1699
|
-
padding: '8px 16px',
|
|
1700
|
-
backgroundColor: '#94a3b8',
|
|
1701
|
-
color: 'white',
|
|
1702
|
-
border: 'none',
|
|
1703
|
-
borderRadius: 4,
|
|
1704
|
-
cursor: 'pointer',
|
|
1705
|
-
fontSize: 14,
|
|
1706
|
-
}}
|
|
1707
|
-
>
|
|
1708
|
-
Clear Highlight
|
|
1709
|
-
</button>
|
|
1710
|
-
</div>
|
|
1711
|
-
<p style={{ margin: '12px 0 4px 0', fontSize: 11, fontStyle: 'italic', color: '#64748b' }}>
|
|
1712
|
-
When you cycle nodes, watch how:
|
|
1713
|
-
</p>
|
|
1714
|
-
<ul style={{ margin: '4px 0', paddingLeft: 20, fontSize: 11, color: '#64748b' }}>
|
|
1715
|
-
<li>The highlighted node gets a <strong>blue glow</strong></li>
|
|
1716
|
-
<li>The node is automatically <strong>selected</strong> (selection outline)</li>
|
|
1717
|
-
<li>The <strong>info panel shows</strong> at the bottom with node details</li>
|
|
1718
|
-
</ul>
|
|
1719
|
-
</div>
|
|
1720
|
-
</div>
|
|
1721
|
-
<div style={{ width: '100%', height: '500px' }}>
|
|
1722
|
-
<GraphRenderer
|
|
1723
|
-
canvas={selectionStylingCanvas}
|
|
1724
|
-
highlightedNodeId={highlightedNodeId}
|
|
1725
|
-
showNodeDetailPanel={true}
|
|
1726
|
-
resolveEventRef={resolveEventRef}
|
|
1727
|
-
/>
|
|
1728
|
-
</div>
|
|
1729
|
-
</div>
|
|
1730
|
-
);
|
|
1731
|
-
};
|
|
1732
|
-
|
|
1733
|
-
export const HighlightedNodeSync: Story = {
|
|
1734
|
-
render: () => <HighlightedNodeSyncTemplate />,
|
|
1735
|
-
parameters: {
|
|
1736
|
-
docs: {
|
|
1737
|
-
description: {
|
|
1738
|
-
story: `
|
|
1739
|
-
**Highlighted Node Sync** - Demonstrates automatic synchronization between \`highlightedNodeId\` and selection state.
|
|
1740
|
-
|
|
1741
|
-
This simulates what happens during execution playback in the CanvasDetailPanel:
|
|
1742
|
-
|
|
1743
|
-
**How it works:**
|
|
1744
|
-
1. When \`highlightedNodeId\` prop changes, GraphRenderer automatically:
|
|
1745
|
-
- Selects that node (adds it to \`selectedNodeIds\`)
|
|
1746
|
-
- Shows the node info panel (\`showNodePanel = true\`)
|
|
1747
|
-
- Applies both the highlight glow and selection outline
|
|
1748
|
-
|
|
1749
|
-
2. This creates a seamless connection between:
|
|
1750
|
-
- Workflow events on the left (which set \`highlightedNodeId\`)
|
|
1751
|
-
- Visual feedback on the canvas (glow + outline)
|
|
1752
|
-
- Node details panel at the bottom (showing node info)
|
|
1753
|
-
|
|
1754
|
-
**Use case:**
|
|
1755
|
-
In the CanvasDetailPanel, when users:
|
|
1756
|
-
- Click a scenario template → Nodes involved in that scenario are highlighted
|
|
1757
|
-
- Click a workflow event → The corresponding node on the canvas is highlighted
|
|
1758
|
-
- Click a node on the canvas → The corresponding workflow event is highlighted
|
|
1759
|
-
|
|
1760
|
-
All of these actions update \`highlightedNodeId\`, which now automatically shows the node info panel!
|
|
1761
|
-
`,
|
|
1762
|
-
},
|
|
1763
|
-
},
|
|
1764
|
-
},
|
|
1765
|
-
};
|
|
1766
|
-
|
|
1767
|
-
// ============================================================================
|
|
1768
|
-
// Real-World Test Data: Version Registry Canvas
|
|
1769
|
-
// ============================================================================
|
|
1770
|
-
|
|
1771
|
-
/**
|
|
1772
|
-
* Version Registry Canvas from web-ade project
|
|
1773
|
-
* Real production canvas showing version registration workflows
|
|
1774
|
-
*/
|
|
1775
|
-
const versionRegistryCanvas: ExtendedCanvas = {
|
|
1776
|
-
"name": "Version Registry",
|
|
1777
|
-
"description": "Maps customer version strings (semver, git SHA, build numbers) to git commit SHAs for contract-based observability",
|
|
1778
|
-
"markdown": ".principal-views/version-registry/version-registry.md",
|
|
1779
|
-
"edgeTypes": {
|
|
1780
|
-
"flow": {
|
|
1781
|
-
"label": "Flow"
|
|
1782
|
-
},
|
|
1783
|
-
"error": {
|
|
1784
|
-
"label": "Error Path"
|
|
1785
|
-
},
|
|
1786
|
-
"cache": {
|
|
1787
|
-
"label": "Cache Path"
|
|
1788
|
-
}
|
|
1789
|
-
},
|
|
1790
|
-
"nodes": [
|
|
1791
|
-
{
|
|
1792
|
-
"id": "registration-started",
|
|
1793
|
-
"type": "text",
|
|
1794
|
-
"text": "# Registration Started\n\nVersion registration request received",
|
|
1795
|
-
"x": 100,
|
|
1796
|
-
"y": 50,
|
|
1797
|
-
"width": 200,
|
|
1798
|
-
"height": 100,
|
|
1799
|
-
"pv": {
|
|
1800
|
-
"event": {
|
|
1801
|
-
"name": "version.registration.started",
|
|
1802
|
-
"attributes": {}
|
|
1803
|
-
},
|
|
1804
|
-
"sources": [
|
|
1805
|
-
"src/app/api/versions/route.ts"
|
|
1806
|
-
],
|
|
1807
|
-
"status": "implemented",
|
|
1808
|
-
"otel": {
|
|
1809
|
-
"kind": "event",
|
|
1810
|
-
"category": "lifecycle"
|
|
1811
|
-
},
|
|
1812
|
-
"shape": "rectangle",
|
|
1813
|
-
"fill": "#22c55e",
|
|
1814
|
-
"stroke": "#16a34a",
|
|
1815
|
-
"dataSchema": {
|
|
1816
|
-
"service.name": {
|
|
1817
|
-
"type": "string",
|
|
1818
|
-
"required": true
|
|
1819
|
-
},
|
|
1820
|
-
"version": {
|
|
1821
|
-
"type": "string",
|
|
1822
|
-
"required": true
|
|
1823
|
-
},
|
|
1824
|
-
"repository.url": {
|
|
1825
|
-
"type": "string",
|
|
1826
|
-
"required": true
|
|
1827
|
-
},
|
|
1828
|
-
"environment": {
|
|
1829
|
-
"type": "string",
|
|
1830
|
-
"required": true
|
|
1831
|
-
},
|
|
1832
|
-
"git.sha": {
|
|
1833
|
-
"type": "string",
|
|
1834
|
-
"required": true
|
|
1835
|
-
}
|
|
1836
|
-
}
|
|
1837
|
-
}
|
|
1838
|
-
},
|
|
1839
|
-
{
|
|
1840
|
-
"id": "registration-validated",
|
|
1841
|
-
"type": "text",
|
|
1842
|
-
"text": "# Validate Request\n\nRequest validated successfully",
|
|
1843
|
-
"x": 89,
|
|
1844
|
-
"y": 199,
|
|
1845
|
-
"width": 200,
|
|
1846
|
-
"height": 100,
|
|
1847
|
-
"pv": {
|
|
1848
|
-
"event": {
|
|
1849
|
-
"name": "version.registration.validated",
|
|
1850
|
-
"attributes": {}
|
|
1851
|
-
},
|
|
1852
|
-
"sources": [
|
|
1853
|
-
"src/lib/version-registry/version-manager.ts"
|
|
1854
|
-
],
|
|
1855
|
-
"status": "approved",
|
|
1856
|
-
"otel": {
|
|
1857
|
-
"kind": "event",
|
|
1858
|
-
"category": "operation"
|
|
1859
|
-
},
|
|
1860
|
-
"shape": "rectangle",
|
|
1861
|
-
"fill": "#3b82f6",
|
|
1862
|
-
"stroke": "#1d4ed8",
|
|
1863
|
-
"dataSchema": {
|
|
1864
|
-
"customer.id": {
|
|
1865
|
-
"type": "string",
|
|
1866
|
-
"required": true
|
|
1867
|
-
},
|
|
1868
|
-
"service.name": {
|
|
1869
|
-
"type": "string",
|
|
1870
|
-
"required": true
|
|
1871
|
-
},
|
|
1872
|
-
"version": {
|
|
1873
|
-
"type": "string",
|
|
1874
|
-
"required": true
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
}
|
|
1878
|
-
},
|
|
1879
|
-
{
|
|
1880
|
-
"id": "registration-s3-stored",
|
|
1881
|
-
"type": "text",
|
|
1882
|
-
"text": "# Store to S3\n\nVersion mapping stored to S3",
|
|
1883
|
-
"x": 92,
|
|
1884
|
-
"y": 350,
|
|
1885
|
-
"width": 200,
|
|
1886
|
-
"height": 100,
|
|
1887
|
-
"pv": {
|
|
1888
|
-
"event": {
|
|
1889
|
-
"name": "version.registration.s3.stored",
|
|
1890
|
-
"attributes": {}
|
|
1891
|
-
},
|
|
1892
|
-
"sources": [
|
|
1893
|
-
"src/lib/version-registry/s3-storage.ts"
|
|
1894
|
-
],
|
|
1895
|
-
"status": "draft",
|
|
1896
|
-
"otel": {
|
|
1897
|
-
"kind": "event",
|
|
1898
|
-
"category": "operation"
|
|
1899
|
-
},
|
|
1900
|
-
"shape": "rectangle",
|
|
1901
|
-
"fill": "#3b82f6",
|
|
1902
|
-
"stroke": "#1d4ed8",
|
|
1903
|
-
"dataSchema": {
|
|
1904
|
-
"s3.key": {
|
|
1905
|
-
"type": "string",
|
|
1906
|
-
"required": true
|
|
1907
|
-
},
|
|
1908
|
-
"s3.bucket": {
|
|
1909
|
-
"type": "string",
|
|
1910
|
-
"required": true
|
|
1911
|
-
},
|
|
1912
|
-
"already.exists": {
|
|
1913
|
-
"type": "boolean",
|
|
1914
|
-
"required": false
|
|
1915
|
-
}
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
1918
|
-
},
|
|
1919
|
-
{
|
|
1920
|
-
"id": "schematic-fetching",
|
|
1921
|
-
"type": "text",
|
|
1922
|
-
"text": "# Fetch Schematic\n\nFetching schematic from GitHub at commit SHA",
|
|
1923
|
-
"x": 91,
|
|
1924
|
-
"y": 498,
|
|
1925
|
-
"width": 200,
|
|
1926
|
-
"height": 100,
|
|
1927
|
-
"pv": {
|
|
1928
|
-
"event": {
|
|
1929
|
-
"name": "version.registration.schematic.fetching",
|
|
1930
|
-
"attributes": {}
|
|
1931
|
-
},
|
|
1932
|
-
"sources": [
|
|
1933
|
-
"src/lib/version-registry/version-manager.ts"
|
|
1934
|
-
],
|
|
1935
|
-
"otel": {
|
|
1936
|
-
"kind": "event",
|
|
1937
|
-
"category": "operation"
|
|
1938
|
-
},
|
|
1939
|
-
"shape": "rectangle",
|
|
1940
|
-
"fill": "#3b82f6",
|
|
1941
|
-
"stroke": "#1d4ed8",
|
|
1942
|
-
"dataSchema": {
|
|
1943
|
-
"repository.url": {
|
|
1944
|
-
"type": "string",
|
|
1945
|
-
"required": true
|
|
1946
|
-
},
|
|
1947
|
-
"git.sha": {
|
|
1948
|
-
"type": "string",
|
|
1949
|
-
"required": true
|
|
1950
|
-
},
|
|
1951
|
-
"schematic.cached": {
|
|
1952
|
-
"type": "boolean",
|
|
1953
|
-
"required": false
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
}
|
|
1957
|
-
},
|
|
1958
|
-
{
|
|
1959
|
-
"id": "schematic-fetched",
|
|
1960
|
-
"type": "text",
|
|
1961
|
-
"text": "# Schematic Retrieved\n\nSchematic fetched from GitHub successfully",
|
|
1962
|
-
"x": 91,
|
|
1963
|
-
"y": 649,
|
|
1964
|
-
"width": 200,
|
|
1965
|
-
"height": 100,
|
|
1966
|
-
"pv": {
|
|
1967
|
-
"event": {
|
|
1968
|
-
"name": "version.registration.schematic.fetched",
|
|
1969
|
-
"attributes": {}
|
|
1970
|
-
},
|
|
1971
|
-
"sources": [
|
|
1972
|
-
"src/lib/version-registry/schematic-fetcher.ts"
|
|
1973
|
-
],
|
|
1974
|
-
"otel": {
|
|
1975
|
-
"kind": "event",
|
|
1976
|
-
"category": "operation"
|
|
1977
|
-
},
|
|
1978
|
-
"shape": "rectangle",
|
|
1979
|
-
"fill": "#3b82f6",
|
|
1980
|
-
"stroke": "#1d4ed8",
|
|
1981
|
-
"dataSchema": {
|
|
1982
|
-
"schematic.id": {
|
|
1983
|
-
"type": "string",
|
|
1984
|
-
"required": true
|
|
1985
|
-
},
|
|
1986
|
-
"canvases.count": {
|
|
1987
|
-
"type": "number",
|
|
1988
|
-
"required": true
|
|
1989
|
-
},
|
|
1990
|
-
"storyboards.count": {
|
|
1991
|
-
"type": "number",
|
|
1992
|
-
"required": true
|
|
1993
|
-
},
|
|
1994
|
-
"workflows.count": {
|
|
1995
|
-
"type": "number",
|
|
1996
|
-
"required": true
|
|
1997
|
-
},
|
|
1998
|
-
"errors.count": {
|
|
1999
|
-
"type": "number",
|
|
2000
|
-
"required": false
|
|
2001
|
-
}
|
|
2002
|
-
}
|
|
2003
|
-
}
|
|
2004
|
-
},
|
|
2005
|
-
{
|
|
2006
|
-
"id": "schematic-stored",
|
|
2007
|
-
"type": "text",
|
|
2008
|
-
"text": "# Store Schematic\n\nSchematic stored to S3",
|
|
2009
|
-
"x": 86,
|
|
2010
|
-
"y": 801,
|
|
2011
|
-
"width": 200,
|
|
2012
|
-
"height": 100,
|
|
2013
|
-
"pv": {
|
|
2014
|
-
"event": {
|
|
2015
|
-
"name": "version.registration.schematic.stored",
|
|
2016
|
-
"attributes": {}
|
|
2017
|
-
},
|
|
2018
|
-
"sources": [
|
|
2019
|
-
"src/lib/version-registry/s3-storage.ts"
|
|
2020
|
-
],
|
|
2021
|
-
"otel": {
|
|
2022
|
-
"kind": "event",
|
|
2023
|
-
"category": "operation"
|
|
2024
|
-
},
|
|
2025
|
-
"shape": "rectangle",
|
|
2026
|
-
"fill": "#3b82f6",
|
|
2027
|
-
"stroke": "#1d4ed8",
|
|
2028
|
-
"dataSchema": {
|
|
2029
|
-
"s3.key": {
|
|
2030
|
-
"type": "string",
|
|
2031
|
-
"required": true
|
|
2032
|
-
},
|
|
2033
|
-
"s3.bucket": {
|
|
2034
|
-
"type": "string",
|
|
2035
|
-
"required": true
|
|
2036
|
-
},
|
|
2037
|
-
"schematic.id": {
|
|
2038
|
-
"type": "string",
|
|
2039
|
-
"required": true
|
|
2040
|
-
}
|
|
2041
|
-
}
|
|
2042
|
-
}
|
|
2043
|
-
},
|
|
2044
|
-
{
|
|
2045
|
-
"id": "registration-complete",
|
|
2046
|
-
"type": "text",
|
|
2047
|
-
"text": "# Registration Complete\n\nVersion registered successfully",
|
|
2048
|
-
"x": 97,
|
|
2049
|
-
"y": 954,
|
|
2050
|
-
"width": 200,
|
|
2051
|
-
"height": 100,
|
|
2052
|
-
"pv": {
|
|
2053
|
-
"event": {
|
|
2054
|
-
"name": "version.registration.complete",
|
|
2055
|
-
"attributes": {}
|
|
2056
|
-
},
|
|
2057
|
-
"sources": [
|
|
2058
|
-
"src/app/api/versions/route.ts"
|
|
2059
|
-
],
|
|
2060
|
-
"otel": {
|
|
2061
|
-
"kind": "event",
|
|
2062
|
-
"category": "lifecycle"
|
|
2063
|
-
},
|
|
2064
|
-
"shape": "rectangle",
|
|
2065
|
-
"fill": "#22c55e",
|
|
2066
|
-
"stroke": "#16a34a",
|
|
2067
|
-
"dataSchema": {
|
|
2068
|
-
"registration.id": {
|
|
2069
|
-
"type": "string",
|
|
2070
|
-
"required": true
|
|
2071
|
-
},
|
|
2072
|
-
"schematic.id": {
|
|
2073
|
-
"type": "string",
|
|
2074
|
-
"required": true
|
|
2075
|
-
},
|
|
2076
|
-
"duration.ms": {
|
|
2077
|
-
"type": "number",
|
|
2078
|
-
"required": false
|
|
2079
|
-
}
|
|
2080
|
-
}
|
|
2081
|
-
}
|
|
2082
|
-
},
|
|
2083
|
-
{
|
|
2084
|
-
"id": "registration-error",
|
|
2085
|
-
"type": "text",
|
|
2086
|
-
"text": "# Registration Error\n\nVersion registration failed",
|
|
2087
|
-
"x": 410,
|
|
2088
|
-
"y": 358,
|
|
2089
|
-
"width": 200,
|
|
2090
|
-
"height": 100,
|
|
2091
|
-
"pv": {
|
|
2092
|
-
"event": {
|
|
2093
|
-
"name": "version.registration.error",
|
|
2094
|
-
"attributes": {}
|
|
2095
|
-
},
|
|
2096
|
-
"sources": [
|
|
2097
|
-
"src/app/api/versions/route.ts",
|
|
2098
|
-
"src/lib/version-registry/version-manager.ts"
|
|
2099
|
-
],
|
|
2100
|
-
"otel": {
|
|
2101
|
-
"kind": "event",
|
|
2102
|
-
"category": "error"
|
|
2103
|
-
},
|
|
2104
|
-
"shape": "rectangle",
|
|
2105
|
-
"fill": "#ef4444",
|
|
2106
|
-
"stroke": "#dc2626",
|
|
2107
|
-
"dataSchema": {
|
|
2108
|
-
"error.type": {
|
|
2109
|
-
"type": "string",
|
|
2110
|
-
"required": true
|
|
2111
|
-
},
|
|
2112
|
-
"error.message": {
|
|
2113
|
-
"type": "string",
|
|
2114
|
-
"required": true
|
|
2115
|
-
},
|
|
2116
|
-
"error.stage": {
|
|
2117
|
-
"type": "string",
|
|
2118
|
-
"required": true
|
|
2119
|
-
},
|
|
2120
|
-
"service.name": {
|
|
2121
|
-
"type": "string",
|
|
2122
|
-
"required": false
|
|
2123
|
-
},
|
|
2124
|
-
"version": {
|
|
2125
|
-
"type": "string",
|
|
2126
|
-
"required": false
|
|
2127
|
-
}
|
|
2128
|
-
}
|
|
2129
|
-
}
|
|
2130
|
-
},
|
|
2131
|
-
{
|
|
2132
|
-
"id": "lookup-started",
|
|
2133
|
-
"type": "text",
|
|
2134
|
-
"text": "# Lookup Started\n\nVersion lookup request received",
|
|
2135
|
-
"x": 1137,
|
|
2136
|
-
"y": 172,
|
|
2137
|
-
"width": 200,
|
|
2138
|
-
"height": 100,
|
|
2139
|
-
"pv": {
|
|
2140
|
-
"event": {
|
|
2141
|
-
"name": "version.lookup.started",
|
|
2142
|
-
"attributes": {}
|
|
2143
|
-
},
|
|
2144
|
-
"sources": [
|
|
2145
|
-
"src/app/api/versions/lookup/route.ts"
|
|
2146
|
-
],
|
|
2147
|
-
"otel": {
|
|
2148
|
-
"kind": "event",
|
|
2149
|
-
"category": "lifecycle"
|
|
2150
|
-
},
|
|
2151
|
-
"shape": "rectangle",
|
|
2152
|
-
"fill": "#22c55e",
|
|
2153
|
-
"stroke": "#16a34a",
|
|
2154
|
-
"dataSchema": {
|
|
2155
|
-
"customer.id": {
|
|
2156
|
-
"type": "string",
|
|
2157
|
-
"required": true
|
|
2158
|
-
},
|
|
2159
|
-
"service.name": {
|
|
2160
|
-
"type": "string",
|
|
2161
|
-
"required": true
|
|
2162
|
-
},
|
|
2163
|
-
"version": {
|
|
2164
|
-
"type": "string",
|
|
2165
|
-
"required": true
|
|
2166
|
-
},
|
|
2167
|
-
"environment": {
|
|
2168
|
-
"type": "string",
|
|
2169
|
-
"required": true
|
|
2170
|
-
}
|
|
2171
|
-
}
|
|
2172
|
-
}
|
|
2173
|
-
},
|
|
2174
|
-
{
|
|
2175
|
-
"id": "lookup-s3-retrieved",
|
|
2176
|
-
"type": "text",
|
|
2177
|
-
"text": "# Retrieve from S3\n\nVersion mapping retrieved from S3",
|
|
2178
|
-
"x": 1138,
|
|
2179
|
-
"y": 386,
|
|
2180
|
-
"width": 200,
|
|
2181
|
-
"height": 100,
|
|
2182
|
-
"pv": {
|
|
2183
|
-
"event": {
|
|
2184
|
-
"name": "version.lookup.s3.retrieved",
|
|
2185
|
-
"attributes": {}
|
|
2186
|
-
},
|
|
2187
|
-
"sources": [
|
|
2188
|
-
"src/lib/version-registry/s3-storage.ts"
|
|
2189
|
-
],
|
|
2190
|
-
"otel": {
|
|
2191
|
-
"kind": "event",
|
|
2192
|
-
"category": "operation"
|
|
2193
|
-
},
|
|
2194
|
-
"shape": "rectangle",
|
|
2195
|
-
"fill": "#3b82f6",
|
|
2196
|
-
"stroke": "#1d4ed8",
|
|
2197
|
-
"dataSchema": {
|
|
2198
|
-
"s3.key": {
|
|
2199
|
-
"type": "string",
|
|
2200
|
-
"required": true
|
|
2201
|
-
},
|
|
2202
|
-
"git.sha": {
|
|
2203
|
-
"type": "string",
|
|
2204
|
-
"required": true
|
|
2205
|
-
},
|
|
2206
|
-
"repository.url": {
|
|
2207
|
-
"type": "string",
|
|
2208
|
-
"required": true
|
|
2209
|
-
},
|
|
2210
|
-
"deployed.at": {
|
|
2211
|
-
"type": "string",
|
|
2212
|
-
"required": true
|
|
2213
|
-
}
|
|
2214
|
-
}
|
|
2215
|
-
}
|
|
2216
|
-
},
|
|
2217
|
-
{
|
|
2218
|
-
"id": "lookup-complete",
|
|
2219
|
-
"type": "text",
|
|
2220
|
-
"text": "# Lookup Complete\n\nVersion lookup successful",
|
|
2221
|
-
"x": 1138,
|
|
2222
|
-
"y": 596,
|
|
2223
|
-
"width": 200,
|
|
2224
|
-
"height": 100,
|
|
2225
|
-
"pv": {
|
|
2226
|
-
"event": {
|
|
2227
|
-
"name": "version.lookup.complete",
|
|
2228
|
-
"attributes": {}
|
|
2229
|
-
},
|
|
2230
|
-
"sources": [
|
|
2231
|
-
"src/app/api/versions/lookup/route.ts"
|
|
2232
|
-
],
|
|
2233
|
-
"otel": {
|
|
2234
|
-
"kind": "event",
|
|
2235
|
-
"category": "lifecycle"
|
|
2236
|
-
},
|
|
2237
|
-
"shape": "rectangle",
|
|
2238
|
-
"fill": "#22c55e",
|
|
2239
|
-
"stroke": "#16a34a",
|
|
2240
|
-
"dataSchema": {
|
|
2241
|
-
"git.sha": {
|
|
2242
|
-
"type": "string",
|
|
2243
|
-
"required": true
|
|
2244
|
-
},
|
|
2245
|
-
"duration.ms": {
|
|
2246
|
-
"type": "number",
|
|
2247
|
-
"required": false
|
|
2248
|
-
}
|
|
2249
|
-
}
|
|
2250
|
-
}
|
|
2251
|
-
},
|
|
2252
|
-
{
|
|
2253
|
-
"id": "lookup-not-found",
|
|
2254
|
-
"type": "text",
|
|
2255
|
-
"text": "# Not Found\n\nVersion not registered",
|
|
2256
|
-
"x": 1470,
|
|
2257
|
-
"y": 262,
|
|
2258
|
-
"width": 200,
|
|
2259
|
-
"height": 100,
|
|
2260
|
-
"pv": {
|
|
2261
|
-
"event": {
|
|
2262
|
-
"name": "version.lookup.not_found",
|
|
2263
|
-
"attributes": {}
|
|
2264
|
-
},
|
|
2265
|
-
"sources": [
|
|
2266
|
-
"src/app/api/versions/lookup/route.ts",
|
|
2267
|
-
"src/lib/version-registry/s3-storage.ts"
|
|
2268
|
-
],
|
|
2269
|
-
"otel": {
|
|
2270
|
-
"kind": "event",
|
|
2271
|
-
"category": "operation"
|
|
2272
|
-
},
|
|
2273
|
-
"shape": "rectangle",
|
|
2274
|
-
"fill": "#f59e0b",
|
|
2275
|
-
"stroke": "#d97706",
|
|
2276
|
-
"dataSchema": {
|
|
2277
|
-
"customer.id": {
|
|
2278
|
-
"type": "string",
|
|
2279
|
-
"required": true
|
|
2280
|
-
},
|
|
2281
|
-
"service.name": {
|
|
2282
|
-
"type": "string",
|
|
2283
|
-
"required": true
|
|
2284
|
-
},
|
|
2285
|
-
"version": {
|
|
2286
|
-
"type": "string",
|
|
2287
|
-
"required": true
|
|
2288
|
-
}
|
|
2289
|
-
}
|
|
2290
|
-
}
|
|
2291
|
-
},
|
|
2292
|
-
{
|
|
2293
|
-
"id": "lookup-error",
|
|
2294
|
-
"type": "text",
|
|
2295
|
-
"text": "# Lookup Error\n\nVersion lookup failed",
|
|
2296
|
-
"x": 1471,
|
|
2297
|
-
"y": 93,
|
|
2298
|
-
"width": 200,
|
|
2299
|
-
"height": 100,
|
|
2300
|
-
"pv": {
|
|
2301
|
-
"event": {
|
|
2302
|
-
"name": "version.lookup.error",
|
|
2303
|
-
"attributes": {}
|
|
2304
|
-
},
|
|
2305
|
-
"sources": [
|
|
2306
|
-
"src/app/api/versions/lookup/route.ts",
|
|
2307
|
-
"src/lib/version-registry/version-manager.ts"
|
|
2308
|
-
],
|
|
2309
|
-
"otel": {
|
|
2310
|
-
"kind": "event",
|
|
2311
|
-
"category": "error"
|
|
2312
|
-
},
|
|
2313
|
-
"shape": "rectangle",
|
|
2314
|
-
"fill": "#ef4444",
|
|
2315
|
-
"stroke": "#dc2626",
|
|
2316
|
-
"dataSchema": {
|
|
2317
|
-
"error.type": {
|
|
2318
|
-
"type": "string",
|
|
2319
|
-
"required": true
|
|
2320
|
-
},
|
|
2321
|
-
"error.message": {
|
|
2322
|
-
"type": "string",
|
|
2323
|
-
"required": true
|
|
2324
|
-
},
|
|
2325
|
-
"error.stage": {
|
|
2326
|
-
"type": "string",
|
|
2327
|
-
"required": true
|
|
2328
|
-
}
|
|
2329
|
-
}
|
|
2330
|
-
}
|
|
2331
|
-
},
|
|
2332
|
-
{
|
|
2333
|
-
"id": "list-started",
|
|
2334
|
-
"type": "text",
|
|
2335
|
-
"text": "# List Started\n\nVersion list request received",
|
|
2336
|
-
"x": 678,
|
|
2337
|
-
"y": 292,
|
|
2338
|
-
"width": 200,
|
|
2339
|
-
"height": 100,
|
|
2340
|
-
"pv": {
|
|
2341
|
-
"event": {
|
|
2342
|
-
"name": "version.list.started",
|
|
2343
|
-
"attributes": {}
|
|
2344
|
-
},
|
|
2345
|
-
"sources": [
|
|
2346
|
-
"src/app/api/versions/list/route.ts"
|
|
2347
|
-
],
|
|
2348
|
-
"otel": {
|
|
2349
|
-
"kind": "event",
|
|
2350
|
-
"category": "lifecycle"
|
|
2351
|
-
},
|
|
2352
|
-
"shape": "rectangle",
|
|
2353
|
-
"fill": "#22c55e",
|
|
2354
|
-
"stroke": "#16a34a",
|
|
2355
|
-
"dataSchema": {
|
|
2356
|
-
"customer.id": {
|
|
2357
|
-
"type": "string",
|
|
2358
|
-
"required": true
|
|
2359
|
-
}
|
|
2360
|
-
}
|
|
2361
|
-
}
|
|
2362
|
-
},
|
|
2363
|
-
{
|
|
2364
|
-
"id": "list-s3-retrieved",
|
|
2365
|
-
"type": "text",
|
|
2366
|
-
"text": "# Fetch All Versions\n\nAll registrations fetched from S3",
|
|
2367
|
-
"x": 678,
|
|
2368
|
-
"y": 465,
|
|
2369
|
-
"width": 200,
|
|
2370
|
-
"height": 100,
|
|
2371
|
-
"pv": {
|
|
2372
|
-
"event": {
|
|
2373
|
-
"name": "version.list.s3.retrieved",
|
|
2374
|
-
"attributes": {}
|
|
2375
|
-
},
|
|
2376
|
-
"sources": [
|
|
2377
|
-
"src/lib/version-registry/s3-storage.ts"
|
|
2378
|
-
],
|
|
2379
|
-
"otel": {
|
|
2380
|
-
"kind": "event",
|
|
2381
|
-
"category": "operation"
|
|
2382
|
-
},
|
|
2383
|
-
"shape": "rectangle",
|
|
2384
|
-
"fill": "#3b82f6",
|
|
2385
|
-
"stroke": "#2563eb",
|
|
2386
|
-
"dataSchema": {
|
|
2387
|
-
"customer.id": {
|
|
2388
|
-
"type": "string",
|
|
2389
|
-
"required": true
|
|
2390
|
-
},
|
|
2391
|
-
"registration.count": {
|
|
2392
|
-
"type": "number",
|
|
2393
|
-
"required": true
|
|
2394
|
-
}
|
|
2395
|
-
}
|
|
2396
|
-
}
|
|
2397
|
-
},
|
|
2398
|
-
{
|
|
2399
|
-
"id": "list-complete",
|
|
2400
|
-
"type": "text",
|
|
2401
|
-
"text": "# List Complete\n\nVersion list successful",
|
|
2402
|
-
"x": 678,
|
|
2403
|
-
"y": 675,
|
|
2404
|
-
"width": 200,
|
|
2405
|
-
"height": 100,
|
|
2406
|
-
"pv": {
|
|
2407
|
-
"event": {
|
|
2408
|
-
"name": "version.list.complete",
|
|
2409
|
-
"attributes": {}
|
|
2410
|
-
},
|
|
2411
|
-
"sources": [
|
|
2412
|
-
"src/app/api/versions/list/route.ts"
|
|
2413
|
-
],
|
|
2414
|
-
"otel": {
|
|
2415
|
-
"kind": "event",
|
|
2416
|
-
"category": "lifecycle"
|
|
2417
|
-
},
|
|
2418
|
-
"shape": "rectangle",
|
|
2419
|
-
"fill": "#22c55e",
|
|
2420
|
-
"stroke": "#16a34a",
|
|
2421
|
-
"dataSchema": {
|
|
2422
|
-
"customer.id": {
|
|
2423
|
-
"type": "string",
|
|
2424
|
-
"required": true
|
|
2425
|
-
},
|
|
2426
|
-
"registration.count": {
|
|
2427
|
-
"type": "number",
|
|
2428
|
-
"required": true
|
|
2429
|
-
}
|
|
2430
|
-
}
|
|
2431
|
-
}
|
|
2432
|
-
},
|
|
2433
|
-
{
|
|
2434
|
-
"id": "list-error",
|
|
2435
|
-
"type": "text",
|
|
2436
|
-
"text": "# List Error\n\nVersion list failed",
|
|
2437
|
-
"x": 921,
|
|
2438
|
-
"y": 291,
|
|
2439
|
-
"width": 200,
|
|
2440
|
-
"height": 100,
|
|
2441
|
-
"pv": {
|
|
2442
|
-
"event": {
|
|
2443
|
-
"name": "version.list.error",
|
|
2444
|
-
"attributes": {}
|
|
2445
|
-
},
|
|
2446
|
-
"sources": [
|
|
2447
|
-
"src/app/api/versions/list/route.ts",
|
|
2448
|
-
"src/lib/version-registry/s3-storage.ts"
|
|
2449
|
-
],
|
|
2450
|
-
"otel": {
|
|
2451
|
-
"kind": "event",
|
|
2452
|
-
"category": "error"
|
|
2453
|
-
},
|
|
2454
|
-
"shape": "rectangle",
|
|
2455
|
-
"fill": "#ef4444",
|
|
2456
|
-
"stroke": "#dc2626",
|
|
2457
|
-
"dataSchema": {
|
|
2458
|
-
"error.type": {
|
|
2459
|
-
"type": "string",
|
|
2460
|
-
"required": true
|
|
2461
|
-
},
|
|
2462
|
-
"error.message": {
|
|
2463
|
-
"type": "string",
|
|
2464
|
-
"required": true
|
|
2465
|
-
},
|
|
2466
|
-
"error.stage": {
|
|
2467
|
-
"type": "string",
|
|
2468
|
-
"required": true
|
|
2469
|
-
}
|
|
2470
|
-
}
|
|
2471
|
-
}
|
|
2472
|
-
}
|
|
2473
|
-
],
|
|
2474
|
-
"edges": [
|
|
2475
|
-
{
|
|
2476
|
-
"id": "edge-reg-start-to-validate",
|
|
2477
|
-
"fromNode": "registration-started",
|
|
2478
|
-
"fromSide": "bottom",
|
|
2479
|
-
"toNode": "registration-validated",
|
|
2480
|
-
"toSide": "top",
|
|
2481
|
-
"edgeType": "flow"
|
|
2482
|
-
},
|
|
2483
|
-
{
|
|
2484
|
-
"id": "edge-reg-validate-to-s3",
|
|
2485
|
-
"fromNode": "registration-validated",
|
|
2486
|
-
"fromSide": "bottom",
|
|
2487
|
-
"toNode": "registration-s3-stored",
|
|
2488
|
-
"toSide": "top",
|
|
2489
|
-
"edgeType": "flow"
|
|
2490
|
-
},
|
|
2491
|
-
{
|
|
2492
|
-
"id": "edge-reg-s3-to-schematic-fetching",
|
|
2493
|
-
"fromNode": "registration-s3-stored",
|
|
2494
|
-
"fromSide": "bottom",
|
|
2495
|
-
"toNode": "schematic-fetching",
|
|
2496
|
-
"toSide": "top",
|
|
2497
|
-
"edgeType": "flow"
|
|
2498
|
-
},
|
|
2499
|
-
{
|
|
2500
|
-
"id": "edge-schematic-fetching-to-fetched",
|
|
2501
|
-
"fromNode": "schematic-fetching",
|
|
2502
|
-
"fromSide": "bottom",
|
|
2503
|
-
"toNode": "schematic-fetched",
|
|
2504
|
-
"toSide": "top",
|
|
2505
|
-
"edgeType": "flow"
|
|
2506
|
-
},
|
|
2507
|
-
{
|
|
2508
|
-
"id": "edge-schematic-fetched-to-stored",
|
|
2509
|
-
"fromNode": "schematic-fetched",
|
|
2510
|
-
"fromSide": "bottom",
|
|
2511
|
-
"toNode": "schematic-stored",
|
|
2512
|
-
"toSide": "top",
|
|
2513
|
-
"edgeType": "flow"
|
|
2514
|
-
},
|
|
2515
|
-
{
|
|
2516
|
-
"id": "edge-schematic-stored-to-complete",
|
|
2517
|
-
"fromNode": "schematic-stored",
|
|
2518
|
-
"fromSide": "bottom",
|
|
2519
|
-
"toNode": "registration-complete",
|
|
2520
|
-
"toSide": "top",
|
|
2521
|
-
"edgeType": "flow"
|
|
2522
|
-
},
|
|
2523
|
-
{
|
|
2524
|
-
"id": "edge-schematic-fetching-to-error",
|
|
2525
|
-
"fromNode": "schematic-fetching",
|
|
2526
|
-
"fromSide": "right",
|
|
2527
|
-
"toNode": "registration-error",
|
|
2528
|
-
"toSide": "left",
|
|
2529
|
-
"label": "schematic fetch failed",
|
|
2530
|
-
"edgeType": "error"
|
|
2531
|
-
},
|
|
2532
|
-
{
|
|
2533
|
-
"id": "edge-schematic-stored-to-error",
|
|
2534
|
-
"fromNode": "schematic-stored",
|
|
2535
|
-
"fromSide": "right",
|
|
2536
|
-
"toNode": "registration-error",
|
|
2537
|
-
"toSide": "left",
|
|
2538
|
-
"label": "storage failed",
|
|
2539
|
-
"edgeType": "error"
|
|
2540
|
-
},
|
|
2541
|
-
{
|
|
2542
|
-
"id": "edge-reg-start-to-error",
|
|
2543
|
-
"fromNode": "registration-started",
|
|
2544
|
-
"fromSide": "right",
|
|
2545
|
-
"toNode": "registration-error",
|
|
2546
|
-
"toSide": "left",
|
|
2547
|
-
"label": "validation error",
|
|
2548
|
-
"edgeType": "error"
|
|
2549
|
-
},
|
|
2550
|
-
{
|
|
2551
|
-
"id": "edge-reg-validate-to-error",
|
|
2552
|
-
"fromNode": "registration-validated",
|
|
2553
|
-
"fromSide": "right",
|
|
2554
|
-
"toNode": "registration-error",
|
|
2555
|
-
"toSide": "left",
|
|
2556
|
-
"label": "storage error",
|
|
2557
|
-
"edgeType": "error"
|
|
2558
|
-
},
|
|
2559
|
-
{
|
|
2560
|
-
"id": "edge-lookup-start-to-s3",
|
|
2561
|
-
"fromNode": "lookup-started",
|
|
2562
|
-
"fromSide": "bottom",
|
|
2563
|
-
"toNode": "lookup-s3-retrieved",
|
|
2564
|
-
"toSide": "top",
|
|
2565
|
-
"edgeType": "flow"
|
|
2566
|
-
},
|
|
2567
|
-
{
|
|
2568
|
-
"id": "edge-lookup-s3-to-complete",
|
|
2569
|
-
"fromNode": "lookup-s3-retrieved",
|
|
2570
|
-
"fromSide": "bottom",
|
|
2571
|
-
"toNode": "lookup-complete",
|
|
2572
|
-
"toSide": "top",
|
|
2573
|
-
"edgeType": "flow"
|
|
2574
|
-
},
|
|
2575
|
-
{
|
|
2576
|
-
"id": "edge-lookup-start-to-not-found",
|
|
2577
|
-
"fromNode": "lookup-started",
|
|
2578
|
-
"fromSide": "right",
|
|
2579
|
-
"toNode": "lookup-not-found",
|
|
2580
|
-
"toSide": "left",
|
|
2581
|
-
"label": "not registered",
|
|
2582
|
-
"edgeType": "error"
|
|
2583
|
-
},
|
|
2584
|
-
{
|
|
2585
|
-
"id": "edge-lookup-start-to-error",
|
|
2586
|
-
"fromNode": "lookup-started",
|
|
2587
|
-
"fromSide": "right",
|
|
2588
|
-
"toNode": "lookup-error",
|
|
2589
|
-
"toSide": "left",
|
|
2590
|
-
"label": "storage error",
|
|
2591
|
-
"edgeType": "error"
|
|
2592
|
-
},
|
|
2593
|
-
{
|
|
2594
|
-
"id": "edge-list-start-to-s3",
|
|
2595
|
-
"fromNode": "list-started",
|
|
2596
|
-
"fromSide": "bottom",
|
|
2597
|
-
"toNode": "list-s3-retrieved",
|
|
2598
|
-
"toSide": "top",
|
|
2599
|
-
"edgeType": "flow"
|
|
2600
|
-
},
|
|
2601
|
-
{
|
|
2602
|
-
"id": "edge-list-s3-to-complete",
|
|
2603
|
-
"fromNode": "list-s3-retrieved",
|
|
2604
|
-
"fromSide": "bottom",
|
|
2605
|
-
"toNode": "list-complete",
|
|
2606
|
-
"toSide": "top",
|
|
2607
|
-
"edgeType": "flow"
|
|
2608
|
-
},
|
|
2609
|
-
{
|
|
2610
|
-
"id": "edge-list-start-to-error",
|
|
2611
|
-
"fromNode": "list-started",
|
|
2612
|
-
"fromSide": "right",
|
|
2613
|
-
"toNode": "list-error",
|
|
2614
|
-
"toSide": "left",
|
|
2615
|
-
"label": "storage error",
|
|
2616
|
-
"edgeType": "error"
|
|
2617
|
-
}
|
|
2618
|
-
]
|
|
2619
|
-
};
|
|
2620
|
-
|
|
2621
|
-
const VersionRegistryTemplate = () => {
|
|
2622
|
-
return (
|
|
2623
|
-
<div>
|
|
2624
|
-
<div
|
|
2625
|
-
style={{
|
|
2626
|
-
marginBottom: 16,
|
|
2627
|
-
padding: 12,
|
|
2628
|
-
backgroundColor: '#f0fdf4',
|
|
2629
|
-
borderRadius: 4,
|
|
2630
|
-
border: '1px solid #22c55e',
|
|
2631
|
-
}}
|
|
2632
|
-
>
|
|
2633
|
-
<strong style={{ color: '#15803d' }}>Version Registry Canvas (Real Production Data)</strong>
|
|
2634
|
-
<div style={{ marginTop: 8, fontSize: 12, color: '#166534' }}>
|
|
2635
|
-
<p style={{ margin: '4px 0' }}>
|
|
2636
|
-
This is a real production canvas from the web-ade project showing a version registry system.
|
|
2637
|
-
</p>
|
|
2638
|
-
<p style={{ margin: '8px 0 4px 0' }}>
|
|
2639
|
-
<strong>Features demonstrated:</strong>
|
|
2640
|
-
</p>
|
|
2641
|
-
<ul style={{ margin: '4px 0', paddingLeft: 20 }}>
|
|
2642
|
-
<li><strong>16 nodes</strong> across 3 separate workflows (Registration, Lookup, List)</li>
|
|
2643
|
-
<li><strong>OTEL event metadata</strong> with dataSchemas defining expected attributes</li>
|
|
2644
|
-
<li><strong>Multiple edge types</strong>: flow (normal), error (failure paths), cache</li>
|
|
2645
|
-
<li><strong>Color-coded events</strong>: Green (lifecycle), Blue (operation), Red (error), Orange (warning)</li>
|
|
2646
|
-
<li><strong>Source file associations</strong> linking events to implementation files</li>
|
|
2647
|
-
<li><strong>Complex workflow patterns</strong> including happy paths and error handling</li>
|
|
2648
|
-
</ul>
|
|
2649
|
-
<p style={{ margin: '8px 0 4px 0' }}>
|
|
2650
|
-
<strong>Try clicking nodes</strong> to see their OTEL metadata, dataSchemas, and source files!
|
|
2651
|
-
</p>
|
|
2652
|
-
</div>
|
|
2653
|
-
</div>
|
|
2654
|
-
<div style={{ width: '100%', height: '800px' }}>
|
|
2655
|
-
<GraphRenderer
|
|
2656
|
-
canvas={versionRegistryCanvas}
|
|
2657
|
-
showNodeDetailPanel={true}
|
|
2658
|
-
/>
|
|
2659
|
-
</div>
|
|
2660
|
-
</div>
|
|
2661
|
-
);
|
|
2662
|
-
};
|
|
2663
|
-
|
|
2664
|
-
export const VersionRegistry: Story = {
|
|
2665
|
-
render: () => <VersionRegistryTemplate />,
|
|
2666
|
-
parameters: {
|
|
2667
|
-
docs: {
|
|
2668
|
-
description: {
|
|
2669
|
-
story: `
|
|
2670
|
-
**Version Registry - Real Production Canvas**
|
|
2671
|
-
|
|
2672
|
-
This story uses actual production data from the web-ade project's version registry system.
|
|
2673
|
-
It demonstrates how Principal View handles complex, real-world workflows with:
|
|
2674
|
-
|
|
2675
|
-
**Three Main Workflows:**
|
|
2676
|
-
|
|
2677
|
-
1. **Registration Flow (Left)**:
|
|
2678
|
-
- \`version.registration.started\` → validated → s3.stored → schematic.fetching → schematic.fetched → schematic.stored → complete
|
|
2679
|
-
- Handles registering new version mappings from customer version strings to git SHAs
|
|
2680
|
-
- Includes error paths for validation, storage, and schematic fetch failures
|
|
2681
|
-
|
|
2682
|
-
2. **Lookup Flow (Middle)**:
|
|
2683
|
-
- \`version.lookup.started\` → s3.retrieved → complete
|
|
2684
|
-
- Retrieves existing version mappings from S3
|
|
2685
|
-
- Error paths for not found and lookup failures
|
|
2686
|
-
|
|
2687
|
-
3. **List Flow (Right)**:
|
|
2688
|
-
- \`version.list.started\` → s3.retrieved → complete
|
|
2689
|
-
- Lists all version registrations for a customer
|
|
2690
|
-
- Error path for storage errors
|
|
2691
|
-
|
|
2692
|
-
**OTEL Integration:**
|
|
2693
|
-
- Each node has \`pv.event\` with event name and attributes
|
|
2694
|
-
- \`dataSchema\` defines expected OTEL attributes (type, required, description)
|
|
2695
|
-
- OTEL categories: lifecycle (green), operation (blue), error (red)
|
|
2696
|
-
- Source file associations show where events are emitted
|
|
2697
|
-
|
|
2698
|
-
**Use Case:**
|
|
2699
|
-
This canvas serves as the "contract" for version registry observability:
|
|
2700
|
-
- Developers instrument these events in their code
|
|
2701
|
-
- Runtime telemetry is validated against these schemas
|
|
2702
|
-
- Missing or orphaned events are detected automatically
|
|
2703
|
-
- Coverage reports show which nodes are "silent" vs "active"
|
|
2704
|
-
`,
|
|
2705
|
-
},
|
|
2706
|
-
},
|
|
2707
|
-
},
|
|
2708
|
-
};
|
|
2709
|
-
|
|
2710
|
-
// ============================================================================
|
|
2711
|
-
// Fit View to Nodes Story
|
|
2712
|
-
// ============================================================================
|
|
2713
|
-
|
|
2714
|
-
/**
|
|
2715
|
-
* Helper to select random nodes from an array
|
|
2716
|
-
*/
|
|
2717
|
-
function selectRandomNodes(allNodeIds: string[], count: number): string[] {
|
|
2718
|
-
const shuffled = [...allNodeIds].sort(() => Math.random() - 0.5);
|
|
2719
|
-
return shuffled.slice(0, Math.min(count, allNodeIds.length));
|
|
2720
|
-
}
|
|
2721
|
-
|
|
2722
|
-
const FitViewToNodesTemplate = () => {
|
|
2723
|
-
// Get all node IDs from the version registry canvas
|
|
2724
|
-
const allNodeIds = versionRegistryCanvas.nodes.map((n) => n.id);
|
|
2725
|
-
|
|
2726
|
-
const [selectedNodeIds, setSelectedNodeIds] = React.useState<string[]>([]);
|
|
2727
|
-
const [nodeCount, setNodeCount] = React.useState(3);
|
|
2728
|
-
|
|
2729
|
-
const handleSelectRandom = () => {
|
|
2730
|
-
const randomNodes = selectRandomNodes(allNodeIds, nodeCount);
|
|
2731
|
-
setSelectedNodeIds(randomNodes);
|
|
2732
|
-
};
|
|
2733
|
-
|
|
2734
|
-
const handleClear = () => {
|
|
2735
|
-
setSelectedNodeIds([]);
|
|
2736
|
-
};
|
|
2737
|
-
|
|
2738
|
-
// Pre-defined scenarios for quick testing
|
|
2739
|
-
const handleSelectRegistrationFlow = () => {
|
|
2740
|
-
setSelectedNodeIds([
|
|
2741
|
-
'registration-started',
|
|
2742
|
-
'registration-validated',
|
|
2743
|
-
'registration-s3-stored',
|
|
2744
|
-
'schematic-fetching',
|
|
2745
|
-
'schematic-fetched',
|
|
2746
|
-
'schematic-stored',
|
|
2747
|
-
'registration-complete',
|
|
2748
|
-
]);
|
|
2749
|
-
};
|
|
2750
|
-
|
|
2751
|
-
const handleSelectErrorNodes = () => {
|
|
2752
|
-
setSelectedNodeIds([
|
|
2753
|
-
'registration-error',
|
|
2754
|
-
'lookup-error',
|
|
2755
|
-
'list-error',
|
|
2756
|
-
'lookup-not-found',
|
|
2757
|
-
]);
|
|
2758
|
-
};
|
|
2759
|
-
|
|
2760
|
-
const handleSelectLookupFlow = () => {
|
|
2761
|
-
setSelectedNodeIds([
|
|
2762
|
-
'lookup-started',
|
|
2763
|
-
'lookup-s3-retrieved',
|
|
2764
|
-
'lookup-complete',
|
|
2765
|
-
]);
|
|
2766
|
-
};
|
|
2767
|
-
|
|
2768
|
-
return (
|
|
2769
|
-
<div>
|
|
2770
|
-
<div
|
|
2771
|
-
style={{
|
|
2772
|
-
marginBottom: 16,
|
|
2773
|
-
padding: 12,
|
|
2774
|
-
backgroundColor: '#f0f9ff',
|
|
2775
|
-
borderRadius: 4,
|
|
2776
|
-
border: '1px solid #3b82f6',
|
|
2777
|
-
}}
|
|
2778
|
-
>
|
|
2779
|
-
<strong style={{ color: '#1e40af' }}>Fit View to Specific Nodes</strong>
|
|
2780
|
-
<div style={{ marginTop: 8, fontSize: 12, color: '#475569' }}>
|
|
2781
|
-
<p style={{ margin: '4px 0' }}>
|
|
2782
|
-
This demonstrates the <code>fitViewToNodeIds</code> prop that zooms/pans the viewport
|
|
2783
|
-
to show only the specified nodes.
|
|
2784
|
-
</p>
|
|
2785
|
-
<p style={{ margin: '8px 0 4px 0' }}>
|
|
2786
|
-
<strong>Currently showing:</strong>{' '}
|
|
2787
|
-
{selectedNodeIds.length > 0 ? (
|
|
2788
|
-
<span style={{ fontFamily: 'monospace', color: '#3b82f6' }}>
|
|
2789
|
-
{selectedNodeIds.length} nodes: [{selectedNodeIds.join(', ')}]
|
|
2790
|
-
</span>
|
|
2791
|
-
) : (
|
|
2792
|
-
<span style={{ color: '#94a3b8' }}>All nodes (default fitView)</span>
|
|
2793
|
-
)}
|
|
2794
|
-
</p>
|
|
2795
|
-
</div>
|
|
2796
|
-
|
|
2797
|
-
<div style={{ marginTop: 12, display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
|
|
2798
|
-
<label style={{ fontSize: 12, color: '#475569' }}>
|
|
2799
|
-
Random count:
|
|
2800
|
-
<select
|
|
2801
|
-
value={nodeCount}
|
|
2802
|
-
onChange={(e) => setNodeCount(Number(e.target.value))}
|
|
2803
|
-
style={{
|
|
2804
|
-
marginLeft: 4,
|
|
2805
|
-
padding: '4px 8px',
|
|
2806
|
-
borderRadius: 4,
|
|
2807
|
-
border: '1px solid #cbd5e1',
|
|
2808
|
-
}}
|
|
2809
|
-
>
|
|
2810
|
-
{[1, 2, 3, 4, 5, 6, 7, 8].map((n) => (
|
|
2811
|
-
<option key={n} value={n}>
|
|
2812
|
-
{n}
|
|
2813
|
-
</option>
|
|
2814
|
-
))}
|
|
2815
|
-
</select>
|
|
2816
|
-
</label>
|
|
2817
|
-
<button
|
|
2818
|
-
onClick={handleSelectRandom}
|
|
2819
|
-
style={{
|
|
2820
|
-
padding: '6px 12px',
|
|
2821
|
-
backgroundColor: '#3b82f6',
|
|
2822
|
-
color: 'white',
|
|
2823
|
-
border: 'none',
|
|
2824
|
-
borderRadius: 4,
|
|
2825
|
-
cursor: 'pointer',
|
|
2826
|
-
fontSize: 12,
|
|
2827
|
-
fontWeight: 'bold',
|
|
2828
|
-
}}
|
|
2829
|
-
>
|
|
2830
|
-
Select Random Nodes
|
|
2831
|
-
</button>
|
|
2832
|
-
<button
|
|
2833
|
-
onClick={handleClear}
|
|
2834
|
-
style={{
|
|
2835
|
-
padding: '6px 12px',
|
|
2836
|
-
backgroundColor: '#94a3b8',
|
|
2837
|
-
color: 'white',
|
|
2838
|
-
border: 'none',
|
|
2839
|
-
borderRadius: 4,
|
|
2840
|
-
cursor: 'pointer',
|
|
2841
|
-
fontSize: 12,
|
|
2842
|
-
}}
|
|
2843
|
-
>
|
|
2844
|
-
Clear (Show All)
|
|
2845
|
-
</button>
|
|
2846
|
-
</div>
|
|
2847
|
-
|
|
2848
|
-
<div style={{ marginTop: 12, display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
|
2849
|
-
<span style={{ fontSize: 11, color: '#64748b', alignSelf: 'center' }}>Quick scenarios:</span>
|
|
2850
|
-
<button
|
|
2851
|
-
onClick={handleSelectRegistrationFlow}
|
|
2852
|
-
style={{
|
|
2853
|
-
padding: '4px 10px',
|
|
2854
|
-
backgroundColor: '#22c55e',
|
|
2855
|
-
color: 'white',
|
|
2856
|
-
border: 'none',
|
|
2857
|
-
borderRadius: 4,
|
|
2858
|
-
cursor: 'pointer',
|
|
2859
|
-
fontSize: 11,
|
|
2860
|
-
}}
|
|
2861
|
-
>
|
|
2862
|
-
Registration Flow (7 nodes)
|
|
2863
|
-
</button>
|
|
2864
|
-
<button
|
|
2865
|
-
onClick={handleSelectLookupFlow}
|
|
2866
|
-
style={{
|
|
2867
|
-
padding: '4px 10px',
|
|
2868
|
-
backgroundColor: '#3b82f6',
|
|
2869
|
-
color: 'white',
|
|
2870
|
-
border: 'none',
|
|
2871
|
-
borderRadius: 4,
|
|
2872
|
-
cursor: 'pointer',
|
|
2873
|
-
fontSize: 11,
|
|
2874
|
-
}}
|
|
2875
|
-
>
|
|
2876
|
-
Lookup Flow (3 nodes)
|
|
2877
|
-
</button>
|
|
2878
|
-
<button
|
|
2879
|
-
onClick={handleSelectErrorNodes}
|
|
2880
|
-
style={{
|
|
2881
|
-
padding: '4px 10px',
|
|
2882
|
-
backgroundColor: '#ef4444',
|
|
2883
|
-
color: 'white',
|
|
2884
|
-
border: 'none',
|
|
2885
|
-
borderRadius: 4,
|
|
2886
|
-
cursor: 'pointer',
|
|
2887
|
-
fontSize: 11,
|
|
2888
|
-
}}
|
|
2889
|
-
>
|
|
2890
|
-
Error Nodes (4 nodes)
|
|
2891
|
-
</button>
|
|
2892
|
-
</div>
|
|
2893
|
-
</div>
|
|
2894
|
-
<div style={{ width: '100%', height: '600px' }}>
|
|
2895
|
-
<GraphRenderer
|
|
2896
|
-
canvas={versionRegistryCanvas}
|
|
2897
|
-
fitViewToNodeIds={selectedNodeIds.length > 0 ? selectedNodeIds : undefined}
|
|
2898
|
-
fitViewPadding={0.1}
|
|
2899
|
-
activeNodeIds={selectedNodeIds.length > 0 ? selectedNodeIds : undefined}
|
|
2900
|
-
showNodeDetailPanel={true}
|
|
2901
|
-
/>
|
|
2902
|
-
</div>
|
|
2903
|
-
</div>
|
|
2904
|
-
);
|
|
2905
|
-
};
|
|
2906
|
-
|
|
2907
|
-
export const FitViewToNodes: Story = {
|
|
2908
|
-
render: () => <FitViewToNodesTemplate />,
|
|
2909
|
-
parameters: {
|
|
2910
|
-
docs: {
|
|
2911
|
-
description: {
|
|
2912
|
-
story: `
|
|
2913
|
-
**Fit View to Specific Nodes** - Demonstrates the \`fitViewToNodeIds\` prop for focusing on subsets of a graph.
|
|
2914
|
-
|
|
2915
|
-
**Use Cases:**
|
|
2916
|
-
- **Scenario Playback**: When playing back a workflow scenario, fit the view to show only the nodes involved
|
|
2917
|
-
- **Search Results**: When searching for specific events, zoom to show matching nodes
|
|
2918
|
-
- **Error Investigation**: Focus on error paths without manually panning/zooming
|
|
2919
|
-
- **Step-by-Step Tutorials**: Highlight different parts of the graph as users progress
|
|
2920
|
-
|
|
2921
|
-
**How It Works:**
|
|
2922
|
-
1. Pass an array of node IDs to \`fitViewToNodeIds\`
|
|
2923
|
-
2. The viewport animates to fit those specific nodes
|
|
2924
|
-
3. Pass \`undefined\` or empty array to return to default behavior (fit all nodes)
|
|
2925
|
-
|
|
2926
|
-
**Props:**
|
|
2927
|
-
- \`fitViewToNodeIds\`: Array of node IDs to fit in view
|
|
2928
|
-
- \`fitViewPadding\`: Padding around the nodes (default: 0.2)
|
|
2929
|
-
|
|
2930
|
-
**In This Demo:**
|
|
2931
|
-
- Select random nodes to see how the view adjusts
|
|
2932
|
-
- Try the pre-defined scenarios (Registration Flow, Lookup Flow, Error Nodes)
|
|
2933
|
-
- Clear selection to fit all nodes again
|
|
2934
|
-
`,
|
|
2935
|
-
},
|
|
2936
|
-
},
|
|
2937
|
-
},
|
|
2938
|
-
};
|
|
2939
|
-
|
|
2940
|
-
// ============================================================================
|
|
2941
|
-
// Initial Viewport Comparison Story
|
|
2942
|
-
// ============================================================================
|
|
2943
|
-
|
|
2944
|
-
const InitialViewportComparisonTemplate = () => {
|
|
2945
|
-
const [key, setKey] = React.useState(0);
|
|
2946
|
-
|
|
2947
|
-
const handleRemount = () => {
|
|
2948
|
-
setKey((k) => k + 1);
|
|
2949
|
-
};
|
|
2950
|
-
|
|
2951
|
-
return (
|
|
2952
|
-
<div>
|
|
2953
|
-
<div
|
|
2954
|
-
style={{
|
|
2955
|
-
marginBottom: 16,
|
|
2956
|
-
padding: 12,
|
|
2957
|
-
backgroundColor: '#fef3c7',
|
|
2958
|
-
borderRadius: 4,
|
|
2959
|
-
border: '1px solid #f59e0b',
|
|
2960
|
-
}}
|
|
2961
|
-
>
|
|
2962
|
-
<strong style={{ color: '#92400e' }}>Initial Viewport Comparison</strong>
|
|
2963
|
-
<div style={{ marginTop: 8, fontSize: 12, color: '#78350f' }}>
|
|
2964
|
-
<p style={{ margin: '4px 0' }}>
|
|
2965
|
-
Compare the loading behavior with and without pre-calculated viewport dimensions.
|
|
2966
|
-
</p>
|
|
2967
|
-
<p style={{ margin: '8px 0 4px 0' }}>
|
|
2968
|
-
<strong>Left:</strong> Default behavior - starts at zoom=1, then animates to fit
|
|
2969
|
-
</p>
|
|
2970
|
-
<p style={{ margin: '4px 0' }}>
|
|
2971
|
-
<strong>Right:</strong> With <code>containerWidth</code>/<code>containerHeight</code> - instant correct positioning
|
|
2972
|
-
</p>
|
|
2973
|
-
<button
|
|
2974
|
-
onClick={handleRemount}
|
|
2975
|
-
style={{
|
|
2976
|
-
marginTop: 12,
|
|
2977
|
-
padding: '8px 16px',
|
|
2978
|
-
backgroundColor: '#f59e0b',
|
|
2979
|
-
color: 'white',
|
|
2980
|
-
border: 'none',
|
|
2981
|
-
borderRadius: 4,
|
|
2982
|
-
cursor: 'pointer',
|
|
2983
|
-
fontWeight: 'bold',
|
|
2984
|
-
fontSize: 14,
|
|
2985
|
-
}}
|
|
2986
|
-
>
|
|
2987
|
-
Remount Both (to see initial load behavior)
|
|
2988
|
-
</button>
|
|
2989
|
-
</div>
|
|
2990
|
-
</div>
|
|
2991
|
-
<div style={{ display: 'flex', gap: 16 }}>
|
|
2992
|
-
<div style={{ flex: 1 }}>
|
|
2993
|
-
<div
|
|
2994
|
-
style={{
|
|
2995
|
-
padding: '8px 12px',
|
|
2996
|
-
backgroundColor: '#fee2e2',
|
|
2997
|
-
borderRadius: '4px 4px 0 0',
|
|
2998
|
-
border: '1px solid #fca5a5',
|
|
2999
|
-
borderBottom: 'none',
|
|
3000
|
-
fontSize: 12,
|
|
3001
|
-
fontWeight: 'bold',
|
|
3002
|
-
color: '#991b1b',
|
|
3003
|
-
}}
|
|
3004
|
-
>
|
|
3005
|
-
Without containerWidth/Height (animated)
|
|
3006
|
-
</div>
|
|
3007
|
-
<div
|
|
3008
|
-
key={`animated-${key}`}
|
|
3009
|
-
style={{ width: '100%', height: '500px', border: '1px solid #fca5a5' }}
|
|
3010
|
-
>
|
|
3011
|
-
<GraphRenderer canvas={versionRegistryCanvas} />
|
|
3012
|
-
</div>
|
|
3013
|
-
</div>
|
|
3014
|
-
<div style={{ flex: 1 }}>
|
|
3015
|
-
<div
|
|
3016
|
-
style={{
|
|
3017
|
-
padding: '8px 12px',
|
|
3018
|
-
backgroundColor: '#dcfce7',
|
|
3019
|
-
borderRadius: '4px 4px 0 0',
|
|
3020
|
-
border: '1px solid #86efac',
|
|
3021
|
-
borderBottom: 'none',
|
|
3022
|
-
fontSize: 12,
|
|
3023
|
-
fontWeight: 'bold',
|
|
3024
|
-
color: '#166534',
|
|
3025
|
-
}}
|
|
3026
|
-
>
|
|
3027
|
-
With containerWidth/Height (instant)
|
|
3028
|
-
</div>
|
|
3029
|
-
<div
|
|
3030
|
-
key={`instant-${key}`}
|
|
3031
|
-
style={{ width: '100%', height: '500px', border: '1px solid #86efac' }}
|
|
3032
|
-
>
|
|
3033
|
-
<GraphRenderer
|
|
3034
|
-
canvas={versionRegistryCanvas}
|
|
3035
|
-
containerWidth={600}
|
|
3036
|
-
containerHeight={500}
|
|
3037
|
-
/>
|
|
3038
|
-
</div>
|
|
3039
|
-
</div>
|
|
3040
|
-
</div>
|
|
3041
|
-
</div>
|
|
3042
|
-
);
|
|
3043
|
-
};
|
|
3044
|
-
|
|
3045
|
-
export const InitialViewportComparison: Story = {
|
|
3046
|
-
render: () => <InitialViewportComparisonTemplate />,
|
|
3047
|
-
parameters: {
|
|
3048
|
-
docs: {
|
|
3049
|
-
description: {
|
|
3050
|
-
story: `
|
|
3051
|
-
**Initial Viewport Comparison** - Demonstrates the difference between default and pre-calculated viewport behavior.
|
|
3052
|
-
|
|
3053
|
-
**The Problem:**
|
|
3054
|
-
By default, React Flow renders at zoom=1 centered at origin, then after ~100ms calls \`fitView()\` with a 200ms animation.
|
|
3055
|
-
This creates a visible "zoom in then zoom out" effect on initial load.
|
|
3056
|
-
|
|
3057
|
-
**The Solution:**
|
|
3058
|
-
When you provide \`containerWidth\` and \`containerHeight\` props, the component calculates the correct viewport
|
|
3059
|
-
(zoom level and pan position) synchronously before the first render. This eliminates the animation entirely.
|
|
3060
|
-
|
|
3061
|
-
**How to Use:**
|
|
3062
|
-
\`\`\`tsx
|
|
3063
|
-
<GraphRenderer
|
|
3064
|
-
canvas={myCanvas}
|
|
3065
|
-
containerWidth={800} // Your container's width in pixels
|
|
3066
|
-
containerHeight={600} // Your container's height in pixels
|
|
3067
|
-
/>
|
|
3068
|
-
\`\`\`
|
|
3069
|
-
|
|
3070
|
-
**When to Use:**
|
|
3071
|
-
- When you know the container dimensions ahead of time (fixed layouts)
|
|
3072
|
-
- When the container size comes from a layout system (CSS grid, flexbox with known dimensions)
|
|
3073
|
-
- When you want to eliminate any visual "settling" on initial load
|
|
3074
|
-
|
|
3075
|
-
**When NOT to Use:**
|
|
3076
|
-
- When container dimensions are dynamic/unknown until mount
|
|
3077
|
-
- When using percentage-based sizing without a parent with known dimensions
|
|
3078
|
-
- In these cases, the default delayed fitView behavior is still appropriate
|
|
3079
|
-
`,
|
|
3080
|
-
},
|
|
3081
|
-
},
|
|
3082
|
-
},
|
|
3083
|
-
};
|
|
3084
|
-
|
|
3085
|
-
// ============================================================================
|
|
3086
|
-
// ELK Layout with Edit Mode
|
|
3087
|
-
// ============================================================================
|
|
3088
|
-
|
|
3089
|
-
const elkEditableCanvas: ExtendedCanvas = {
|
|
3090
|
-
nodes: [
|
|
3091
|
-
{
|
|
3092
|
-
id: 'a',
|
|
3093
|
-
type: 'text',
|
|
3094
|
-
x: 50,
|
|
3095
|
-
y: 50,
|
|
3096
|
-
width: 120,
|
|
3097
|
-
height: 60,
|
|
3098
|
-
text: 'Node A',
|
|
3099
|
-
color: 5,
|
|
3100
|
-
pv: { nodeType: 'component', shape: 'rectangle' },
|
|
3101
|
-
},
|
|
3102
|
-
{
|
|
3103
|
-
id: 'b',
|
|
3104
|
-
type: 'text',
|
|
3105
|
-
x: 250,
|
|
3106
|
-
y: 50,
|
|
3107
|
-
width: 120,
|
|
3108
|
-
height: 60,
|
|
3109
|
-
text: 'Node B',
|
|
3110
|
-
color: 4,
|
|
3111
|
-
pv: { nodeType: 'component', shape: 'rectangle' },
|
|
3112
|
-
},
|
|
3113
|
-
{
|
|
3114
|
-
id: 'c',
|
|
3115
|
-
type: 'text',
|
|
3116
|
-
x: 250,
|
|
3117
|
-
y: 180,
|
|
3118
|
-
width: 120,
|
|
3119
|
-
height: 60,
|
|
3120
|
-
text: 'Node C',
|
|
3121
|
-
color: 2,
|
|
3122
|
-
pv: { nodeType: 'component', shape: 'rectangle' },
|
|
3123
|
-
},
|
|
3124
|
-
],
|
|
3125
|
-
edges: [
|
|
3126
|
-
{ id: 'e1', fromNode: 'a', toNode: 'b', fromSide: 'right', toSide: 'left', edgeType: 'flow' },
|
|
3127
|
-
{ id: 'e2', fromNode: 'a', toNode: 'c', fromSide: 'bottom', toSide: 'left', edgeType: 'flow' },
|
|
3128
|
-
],
|
|
3129
|
-
pv: {
|
|
3130
|
-
version: '1.0.0',
|
|
3131
|
-
name: 'ELK Editable Test',
|
|
3132
|
-
edgeTypes: {
|
|
3133
|
-
flow: { style: 'solid', color: '#3b82f6', width: 2, directed: true },
|
|
3134
|
-
},
|
|
3135
|
-
nodeTypes: {
|
|
3136
|
-
component: { color: '#4A90E2' },
|
|
3137
|
-
},
|
|
3138
|
-
},
|
|
3139
|
-
};
|
|
3140
|
-
|
|
3141
|
-
const ElkEditModeTemplate = () => {
|
|
3142
|
-
const [isEditMode, setIsEditMode] = React.useState(false);
|
|
3143
|
-
const [hasChanges, setHasChanges] = React.useState(false);
|
|
3144
|
-
const graphRef = React.useRef<GraphRendererHandle>(null);
|
|
3145
|
-
|
|
3146
|
-
return (
|
|
3147
|
-
<div style={{ height: '500px', display: 'flex', flexDirection: 'column' }}>
|
|
3148
|
-
<div style={{ padding: '10px', background: '#f0f0f0', display: 'flex', gap: '10px', alignItems: 'center' }}>
|
|
3149
|
-
<button
|
|
3150
|
-
onClick={() => setIsEditMode(!isEditMode)}
|
|
3151
|
-
style={{
|
|
3152
|
-
padding: '8px 16px',
|
|
3153
|
-
background: isEditMode ? '#ef4444' : '#22c55e',
|
|
3154
|
-
color: 'white',
|
|
3155
|
-
border: 'none',
|
|
3156
|
-
borderRadius: '4px',
|
|
3157
|
-
cursor: 'pointer',
|
|
3158
|
-
}}
|
|
3159
|
-
>
|
|
3160
|
-
{isEditMode ? 'Exit Edit Mode' : 'Enter Edit Mode'}
|
|
3161
|
-
</button>
|
|
3162
|
-
{isEditMode && hasChanges && (
|
|
3163
|
-
<>
|
|
3164
|
-
<button
|
|
3165
|
-
onClick={() => {
|
|
3166
|
-
graphRef.current?.getPendingChanges();
|
|
3167
|
-
setHasChanges(false);
|
|
3168
|
-
}}
|
|
3169
|
-
style={{
|
|
3170
|
-
padding: '8px 16px',
|
|
3171
|
-
background: '#3b82f6',
|
|
3172
|
-
color: 'white',
|
|
3173
|
-
border: 'none',
|
|
3174
|
-
borderRadius: '4px',
|
|
3175
|
-
cursor: 'pointer',
|
|
3176
|
-
}}
|
|
3177
|
-
>
|
|
3178
|
-
Save
|
|
3179
|
-
</button>
|
|
3180
|
-
<button
|
|
3181
|
-
onClick={() => {
|
|
3182
|
-
graphRef.current?.resetEditState();
|
|
3183
|
-
setHasChanges(false);
|
|
3184
|
-
}}
|
|
3185
|
-
style={{
|
|
3186
|
-
padding: '8px 16px',
|
|
3187
|
-
background: '#6b7280',
|
|
3188
|
-
color: 'white',
|
|
3189
|
-
border: 'none',
|
|
3190
|
-
borderRadius: '4px',
|
|
3191
|
-
cursor: 'pointer',
|
|
3192
|
-
}}
|
|
3193
|
-
>
|
|
3194
|
-
Discard
|
|
3195
|
-
</button>
|
|
3196
|
-
</>
|
|
3197
|
-
)}
|
|
3198
|
-
<span style={{ marginLeft: 'auto', color: '#666' }}>
|
|
3199
|
-
{isEditMode ? 'Drag nodes to test edge routing' : 'Click Edit to enable node dragging'}
|
|
3200
|
-
</span>
|
|
3201
|
-
</div>
|
|
3202
|
-
<div style={{ flex: 1 }}>
|
|
3203
|
-
<GraphRenderer
|
|
3204
|
-
ref={graphRef}
|
|
3205
|
-
canvas={elkEditableCanvas}
|
|
3206
|
-
width="100%"
|
|
3207
|
-
height="100%"
|
|
3208
|
-
editable={isEditMode}
|
|
3209
|
-
onPendingChangesChange={setHasChanges}
|
|
3210
|
-
showBackground={true}
|
|
3211
|
-
backgroundVariant="dots"
|
|
3212
|
-
elkLayout={{ enabled: true, routingStyle: 'orthogonal' }}
|
|
3213
|
-
/>
|
|
3214
|
-
</div>
|
|
3215
|
-
</div>
|
|
3216
|
-
);
|
|
3217
|
-
};
|
|
3218
|
-
|
|
3219
|
-
export const ElkLayoutEditMode: Story = {
|
|
3220
|
-
render: () => <ElkEditModeTemplate />,
|
|
3221
|
-
parameters: {
|
|
3222
|
-
docs: {
|
|
3223
|
-
description: {
|
|
3224
|
-
story: `
|
|
3225
|
-
**ELK Layout with Edit Mode** - Tests orthogonal edge routing with editable nodes.
|
|
3226
|
-
|
|
3227
|
-
**Test Cases:**
|
|
3228
|
-
1. Enter edit mode and drag Node A - edges should maintain connection points and reroute
|
|
3229
|
-
2. Drag Node C - bottom-to-left edge should stay connected correctly
|
|
3230
|
-
3. Save/Discard should preserve or reset positions
|
|
3231
|
-
4. Edges should not disappear when entering edit mode
|
|
3232
|
-
`,
|
|
3233
|
-
},
|
|
3234
|
-
},
|
|
3235
|
-
},
|
|
3236
|
-
};
|