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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/components/SequenceDiagramRenderer.d.ts +61 -0
  2. package/dist/components/SequenceDiagramRenderer.d.ts.map +1 -0
  3. package/dist/components/SequenceDiagramRenderer.js +184 -0
  4. package/dist/components/SequenceDiagramRenderer.js.map +1 -0
  5. package/dist/components/dashboard/DashboardRenderer.d.ts +9 -0
  6. package/dist/components/dashboard/DashboardRenderer.d.ts.map +1 -0
  7. package/dist/components/dashboard/DashboardRenderer.js +179 -0
  8. package/dist/components/dashboard/DashboardRenderer.js.map +1 -0
  9. package/dist/components/dashboard/MetricPanel.d.ts +9 -0
  10. package/dist/components/dashboard/MetricPanel.d.ts.map +1 -0
  11. package/dist/components/dashboard/MetricPanel.js +103 -0
  12. package/dist/components/dashboard/MetricPanel.js.map +1 -0
  13. package/dist/components/dashboard/MockDataProvider.d.ts +30 -0
  14. package/dist/components/dashboard/MockDataProvider.d.ts.map +1 -0
  15. package/dist/components/dashboard/MockDataProvider.js +270 -0
  16. package/dist/components/dashboard/MockDataProvider.js.map +1 -0
  17. package/dist/components/dashboard/components/BarChart.d.ts +9 -0
  18. package/dist/components/dashboard/components/BarChart.d.ts.map +1 -0
  19. package/dist/components/dashboard/components/BarChart.js +167 -0
  20. package/dist/components/dashboard/components/BarChart.js.map +1 -0
  21. package/dist/components/dashboard/components/LineChart.d.ts +9 -0
  22. package/dist/components/dashboard/components/LineChart.d.ts.map +1 -0
  23. package/dist/components/dashboard/components/LineChart.js +141 -0
  24. package/dist/components/dashboard/components/LineChart.js.map +1 -0
  25. package/dist/components/dashboard/components/MetricCard.d.ts +8 -0
  26. package/dist/components/dashboard/components/MetricCard.d.ts.map +1 -0
  27. package/dist/components/dashboard/components/MetricCard.js +163 -0
  28. package/dist/components/dashboard/components/MetricCard.js.map +1 -0
  29. package/dist/components/dashboard/components/SourceLink.d.ts +8 -0
  30. package/dist/components/dashboard/components/SourceLink.d.ts.map +1 -0
  31. package/dist/components/dashboard/components/SourceLink.js +39 -0
  32. package/dist/components/dashboard/components/SourceLink.js.map +1 -0
  33. package/dist/components/dashboard/components/TimeRangeSelector.d.ts +8 -0
  34. package/dist/components/dashboard/components/TimeRangeSelector.d.ts.map +1 -0
  35. package/dist/components/dashboard/components/TimeRangeSelector.js +167 -0
  36. package/dist/components/dashboard/components/TimeRangeSelector.js.map +1 -0
  37. package/dist/components/dashboard/components/index.d.ts +6 -0
  38. package/dist/components/dashboard/components/index.d.ts.map +1 -0
  39. package/dist/components/dashboard/components/index.js +6 -0
  40. package/dist/components/dashboard/components/index.js.map +1 -0
  41. package/dist/components/dashboard/index.d.ts +6 -0
  42. package/dist/components/dashboard/index.d.ts.map +1 -0
  43. package/dist/components/dashboard/index.js +8 -0
  44. package/dist/components/dashboard/index.js.map +1 -0
  45. package/dist/components/dashboard/types.d.ts +74 -0
  46. package/dist/components/dashboard/types.d.ts.map +1 -0
  47. package/dist/components/dashboard/types.js +8 -0
  48. package/dist/components/dashboard/types.js.map +1 -0
  49. package/dist/hooks/useSequenceLayout.d.ts +148 -0
  50. package/dist/hooks/useSequenceLayout.d.ts.map +1 -0
  51. package/dist/hooks/useSequenceLayout.js +225 -0
  52. package/dist/hooks/useSequenceLayout.js.map +1 -0
  53. package/dist/index.d.ts +6 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +4 -0
  56. package/dist/index.js.map +1 -1
  57. package/package.json +3 -3
  58. package/src/components/SequenceDiagramRenderer.tsx +459 -0
  59. package/src/components/dashboard/DashboardRenderer.tsx +317 -0
  60. package/src/components/dashboard/MetricPanel.tsx +254 -0
  61. package/src/components/dashboard/MockDataProvider.ts +330 -0
  62. package/src/components/dashboard/components/BarChart.tsx +299 -0
  63. package/src/components/dashboard/components/LineChart.tsx +279 -0
  64. package/src/components/dashboard/components/MetricCard.tsx +270 -0
  65. package/src/components/dashboard/components/SourceLink.tsx +63 -0
  66. package/src/components/dashboard/components/TimeRangeSelector.tsx +280 -0
  67. package/src/components/dashboard/components/index.ts +5 -0
  68. package/src/components/dashboard/index.ts +47 -0
  69. package/src/components/dashboard/types.ts +126 -0
  70. package/src/hooks/useSequenceLayout.ts +413 -0
  71. package/src/index.ts +62 -0
  72. package/src/stories/SequenceDiagram.stories.tsx +306 -0
  73. package/src/stories/dashboard/DashboardRenderer.stories.tsx +263 -0
  74. package/src/stories/dashboard/sample-dashboards/activity-feed-analytics.dashboard.json +300 -0
  75. package/src/stories/data/graph-converter-test-execution.json +50 -50
@@ -0,0 +1,306 @@
1
+ import React, { useState } from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { SequenceDiagramRenderer } from '../components/SequenceDiagramRenderer';
4
+ import type { SequenceEvent, SequenceEdge } from '../hooks/useSequenceLayout';
5
+ import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
6
+
7
+ const meta = {
8
+ title: 'Components/SequenceDiagram',
9
+ component: SequenceDiagramRenderer,
10
+ parameters: {
11
+ layout: 'fullscreen',
12
+ },
13
+ tags: ['autodocs'],
14
+ decorators: [
15
+ (Story) => (
16
+ <ThemeProvider theme={defaultEditorTheme}>
17
+ <div style={{ padding: 20, height: '100vh', boxSizing: 'border-box' }}>
18
+ <Story />
19
+ </div>
20
+ </ThemeProvider>
21
+ ),
22
+ ],
23
+ } satisfies Meta<typeof SequenceDiagramRenderer>;
24
+
25
+ export default meta;
26
+ type Story = StoryObj<typeof meta>;
27
+
28
+ // ============================================================================
29
+ // Sample Data
30
+ // ============================================================================
31
+
32
+ /**
33
+ * Simple authentication flow across two namespaces
34
+ */
35
+ const simpleAuthFlow: { events: SequenceEvent[]; edges: SequenceEdge[] } = {
36
+ events: [
37
+ { id: '1', name: 'auth.validation.started', label: 'Validation Started' },
38
+ { id: '2', name: 'database.query.findUser', label: 'Find User' },
39
+ { id: '3', name: 'auth.validation.completed', label: 'Validation Completed' },
40
+ { id: '4', name: 'auth.token.generated', label: 'Token Generated' },
41
+ ],
42
+ edges: [
43
+ { id: 'e1', fromEvent: '1', toEvent: '2' },
44
+ { id: 'e2', fromEvent: '2', toEvent: '3' },
45
+ { id: 'e3', fromEvent: '3', toEvent: '4' },
46
+ ],
47
+ };
48
+
49
+ /**
50
+ * More complex flow with multiple actors
51
+ */
52
+ const complexOrderFlow: { events: SequenceEvent[]; edges: SequenceEdge[] } = {
53
+ events: [
54
+ { id: '1', name: 'api.request.received', label: 'Request Received' },
55
+ { id: '2', name: 'auth.session.validate', label: 'Validate Session' },
56
+ { id: '3', name: 'order.validation.started', label: 'Validate Order' },
57
+ { id: '4', name: 'inventory.check.availability', label: 'Check Stock' },
58
+ { id: '5', name: 'order.validation.completed', label: 'Order Valid' },
59
+ { id: '6', name: 'payment.charge.initiated', label: 'Initiate Charge' },
60
+ { id: '7', name: 'payment.charge.completed', label: 'Charge Complete' },
61
+ { id: '8', name: 'order.fulfillment.started', label: 'Start Fulfillment' },
62
+ { id: '9', name: 'inventory.reserve.items', label: 'Reserve Items' },
63
+ { id: '10', name: 'notification.email.sent', label: 'Confirmation Email' },
64
+ { id: '11', name: 'api.response.sent', label: 'Response Sent' },
65
+ ],
66
+ edges: [
67
+ { id: 'e1', fromEvent: '1', toEvent: '2' },
68
+ { id: 'e2', fromEvent: '2', toEvent: '3' },
69
+ { id: 'e3', fromEvent: '3', toEvent: '4' },
70
+ { id: 'e4', fromEvent: '4', toEvent: '5' },
71
+ { id: 'e5', fromEvent: '5', toEvent: '6' },
72
+ { id: 'e6', fromEvent: '6', toEvent: '7' },
73
+ { id: 'e7', fromEvent: '7', toEvent: '8' },
74
+ { id: 'e8', fromEvent: '8', toEvent: '9' },
75
+ { id: 'e9', fromEvent: '9', toEvent: '10' },
76
+ { id: 'e10', fromEvent: '10', toEvent: '11' },
77
+ ],
78
+ };
79
+
80
+ /**
81
+ * Flow with hierarchical namespaces (sub-namespaces)
82
+ */
83
+ const hierarchicalFlow: { events: SequenceEvent[]; edges: SequenceEdge[] } = {
84
+ events: [
85
+ { id: '1', name: 'auth.login.started', label: 'Login Started' },
86
+ { id: '2', name: 'auth.login.validation.credentials', label: 'Check Credentials' },
87
+ { id: '3', name: 'auth.login.validation.mfa', label: 'Check MFA' },
88
+ { id: '4', name: 'auth.login.session.create', label: 'Create Session' },
89
+ { id: '5', name: 'auth.login.completed', label: 'Login Completed' },
90
+ ],
91
+ edges: [
92
+ { id: 'e1', fromEvent: '1', toEvent: '2' },
93
+ { id: 'e2', fromEvent: '2', toEvent: '3' },
94
+ { id: 'e3', fromEvent: '3', toEvent: '4' },
95
+ { id: 'e4', fromEvent: '4', toEvent: '5' },
96
+ ],
97
+ };
98
+
99
+ /**
100
+ * Flow with same-namespace sequential events
101
+ */
102
+ const sameNamespaceFlow: { events: SequenceEvent[]; edges: SequenceEdge[] } = {
103
+ events: [
104
+ { id: '1', name: 'processor.step1', label: 'Step 1' },
105
+ { id: '2', name: 'processor.step2', label: 'Step 2' },
106
+ { id: '3', name: 'processor.step3', label: 'Step 3' },
107
+ { id: '4', name: 'external.callback', label: 'External Callback' },
108
+ { id: '5', name: 'processor.step4', label: 'Step 4' },
109
+ ],
110
+ edges: [
111
+ { id: 'e1', fromEvent: '1', toEvent: '2' },
112
+ { id: 'e2', fromEvent: '2', toEvent: '3' },
113
+ { id: 'e3', fromEvent: '3', toEvent: '4' },
114
+ { id: 'e4', fromEvent: '4', toEvent: '5' },
115
+ ],
116
+ };
117
+
118
+ // ============================================================================
119
+ // Stories
120
+ // ============================================================================
121
+
122
+ /**
123
+ * Basic two-namespace flow showing authentication
124
+ */
125
+ export const SimpleFlow: Story = {
126
+ args: {
127
+ events: simpleAuthFlow.events,
128
+ edges: simpleAuthFlow.edges,
129
+ height: 500,
130
+ },
131
+ };
132
+
133
+ /**
134
+ * Complex multi-actor e-commerce order flow
135
+ */
136
+ export const ComplexFlow: Story = {
137
+ args: {
138
+ events: complexOrderFlow.events,
139
+ edges: complexOrderFlow.edges,
140
+ height: 700,
141
+ },
142
+ };
143
+
144
+ /**
145
+ * Flow with hierarchical namespaces to test sub-namespace handling
146
+ */
147
+ export const HierarchicalNamespaces: Story = {
148
+ args: {
149
+ events: hierarchicalFlow.events,
150
+ edges: hierarchicalFlow.edges,
151
+ height: 500,
152
+ layoutOptions: {
153
+ namespaceStrategy: 'all-but-last',
154
+ },
155
+ },
156
+ };
157
+
158
+ /**
159
+ * Same story but with first-segment namespace strategy
160
+ */
161
+ export const HierarchicalCollapsedToFirst: Story = {
162
+ args: {
163
+ events: hierarchicalFlow.events,
164
+ edges: hierarchicalFlow.edges,
165
+ height: 500,
166
+ layoutOptions: {
167
+ namespaceStrategy: 'first',
168
+ },
169
+ },
170
+ };
171
+
172
+ /**
173
+ * Flow testing same-namespace edge rendering
174
+ */
175
+ export const SameNamespaceEdges: Story = {
176
+ args: {
177
+ events: sameNamespaceFlow.events,
178
+ edges: sameNamespaceFlow.edges,
179
+ height: 500,
180
+ },
181
+ };
182
+
183
+ /**
184
+ * Interactive example with collapse toggle
185
+ */
186
+ export const WithCollapseToggle: Story = {
187
+ render: () => {
188
+ const [collapsed, setCollapsed] = useState<string[]>([]);
189
+
190
+ const handleToggle = (namespace: string) => {
191
+ setCollapsed((prev) =>
192
+ prev.includes(namespace)
193
+ ? prev.filter((n) => n !== namespace)
194
+ : [...prev, namespace]
195
+ );
196
+ };
197
+
198
+ return (
199
+ <div>
200
+ <div style={{ marginBottom: 16 }}>
201
+ <strong>Collapsed namespaces:</strong>{' '}
202
+ {collapsed.length ? collapsed.join(', ') : 'none'}
203
+ <br />
204
+ <small>Click on lane headers with children to toggle collapse</small>
205
+ </div>
206
+ <SequenceDiagramRenderer
207
+ events={hierarchicalFlow.events}
208
+ edges={hierarchicalFlow.edges}
209
+ height={500}
210
+ layoutOptions={{
211
+ collapsedNamespaces: collapsed,
212
+ }}
213
+ onToggleCollapse={handleToggle}
214
+ />
215
+ </div>
216
+ );
217
+ },
218
+ };
219
+
220
+ /**
221
+ * Custom layout options
222
+ */
223
+ export const CustomLayout: Story = {
224
+ args: {
225
+ events: simpleAuthFlow.events,
226
+ edges: simpleAuthFlow.edges,
227
+ height: 500,
228
+ layoutOptions: {
229
+ laneWidth: 250,
230
+ laneGap: 80,
231
+ eventSpacing: 100,
232
+ nodeWidth: 220,
233
+ nodeHeight: 60,
234
+ },
235
+ },
236
+ };
237
+
238
+ /**
239
+ * Arrow-centric mode: minimal markers with labels on arrows
240
+ * This is the traditional UML sequence diagram style
241
+ */
242
+ export const ArrowCentric: Story = {
243
+ args: {
244
+ events: simpleAuthFlow.events,
245
+ edges: simpleAuthFlow.edges,
246
+ height: 500,
247
+ layoutOptions: {
248
+ arrowCentric: true,
249
+ },
250
+ },
251
+ };
252
+
253
+ /**
254
+ * Arrow-centric with complex multi-actor flow
255
+ */
256
+ export const ArrowCentricComplex: Story = {
257
+ args: {
258
+ events: complexOrderFlow.events,
259
+ edges: complexOrderFlow.edges,
260
+ height: 700,
261
+ layoutOptions: {
262
+ arrowCentric: true,
263
+ eventSpacing: 60,
264
+ },
265
+ },
266
+ };
267
+
268
+ /**
269
+ * Compact layout for smaller diagrams
270
+ */
271
+ export const CompactLayout: Story = {
272
+ args: {
273
+ events: simpleAuthFlow.events,
274
+ edges: simpleAuthFlow.edges,
275
+ height: 400,
276
+ layoutOptions: {
277
+ laneWidth: 150,
278
+ laneGap: 30,
279
+ eventSpacing: 60,
280
+ nodeWidth: 130,
281
+ nodeHeight: 40,
282
+ },
283
+ },
284
+ };
285
+
286
+ /**
287
+ * Empty state
288
+ */
289
+ export const EmptyDiagram: Story = {
290
+ args: {
291
+ events: [],
292
+ edges: [],
293
+ height: 300,
294
+ },
295
+ };
296
+
297
+ /**
298
+ * Single event (edge case)
299
+ */
300
+ export const SingleEvent: Story = {
301
+ args: {
302
+ events: [{ id: '1', name: 'system.startup', label: 'System Startup' }],
303
+ edges: [],
304
+ height: 300,
305
+ },
306
+ };
@@ -0,0 +1,263 @@
1
+ /**
2
+ * DashboardRenderer Stories
3
+ *
4
+ * Storybook stories for testing and iterating on dashboard rendering.
5
+ */
6
+
7
+ import type { Meta, StoryObj } from '@storybook/react';
8
+ import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
9
+ import { DashboardRenderer } from '../../components/dashboard';
10
+ import activityFeedDashboard from './sample-dashboards/activity-feed-analytics.dashboard.json';
11
+ import type { DashboardDefinition } from '../../components/dashboard';
12
+
13
+ const meta: Meta<typeof DashboardRenderer> = {
14
+ title: 'Dashboard/DashboardRenderer',
15
+ component: DashboardRenderer,
16
+ parameters: {
17
+ layout: 'fullscreen',
18
+ },
19
+ decorators: [
20
+ (Story) => (
21
+ <ThemeProvider theme={defaultEditorTheme}>
22
+ <div style={{ minHeight: '100vh' }}>
23
+ <Story />
24
+ </div>
25
+ </ThemeProvider>
26
+ ),
27
+ ],
28
+ };
29
+
30
+ export default meta;
31
+ type Story = StoryObj<typeof DashboardRenderer>;
32
+
33
+ /**
34
+ * Activity Feed Analytics Dashboard
35
+ *
36
+ * Full dashboard example based on the web-ade activity feed use case.
37
+ * Shows mobile vs desktop view percentages, traffic trends, and engagement metrics.
38
+ */
39
+ export const ActivityFeedAnalytics: Story = {
40
+ args: {
41
+ dashboard: activityFeedDashboard as DashboardDefinition,
42
+ },
43
+ };
44
+
45
+ /**
46
+ * With Metric Click Handler
47
+ *
48
+ * Demonstrates handling metric clicks - useful for drill-down navigation.
49
+ */
50
+ export const WithClickHandlers: Story = {
51
+ args: {
52
+ dashboard: activityFeedDashboard as DashboardDefinition,
53
+ onMetricClick: (metricId) => {
54
+ alert(`Clicked metric: ${metricId}`);
55
+ },
56
+ onSourceClick: (source) => {
57
+ alert(`Navigate to: ${source.storyboard}/${source.workflow}`);
58
+ },
59
+ },
60
+ };
61
+
62
+ /**
63
+ * Minimal Dashboard
64
+ *
65
+ * A simple dashboard with just a few metrics.
66
+ */
67
+ export const MinimalDashboard: Story = {
68
+ args: {
69
+ dashboard: {
70
+ id: 'minimal',
71
+ name: 'Minimal Dashboard',
72
+ description: 'A simple dashboard with basic metrics',
73
+ metrics: [
74
+ {
75
+ id: 'total-requests',
76
+ name: 'Total Requests',
77
+ type: 'counter',
78
+ unit: 'requests',
79
+ sources: [{ storyboard: 'api', workflow: 'request-handler' }],
80
+ query: { derivation: 'count', window: '1h' },
81
+ _mockData: { current: 145892, previous: 132456, trend: 'up' },
82
+ },
83
+ {
84
+ id: 'error-rate',
85
+ name: 'Error Rate',
86
+ type: 'gauge',
87
+ unit: '%',
88
+ sources: [{ storyboard: 'api', workflow: 'request-handler' }],
89
+ query: { derivation: 'error_rate', window: '1h' },
90
+ thresholds: { warning: 1.0, critical: 5.0 },
91
+ _mockData: { current: 0.3, previous: 0.5, trend: 'down' },
92
+ },
93
+ {
94
+ id: 'p99-latency',
95
+ name: 'P99 Latency',
96
+ type: 'histogram',
97
+ unit: 'ms',
98
+ sources: [{ storyboard: 'api', workflow: 'request-handler' }],
99
+ query: { derivation: 'p99' },
100
+ _mockData: { current: 245, previous: 312, trend: 'down' },
101
+ },
102
+ ],
103
+ layout: {
104
+ rows: [
105
+ {
106
+ title: 'Key Metrics',
107
+ panels: [
108
+ { id: 'total-requests', span: 4 },
109
+ { id: 'error-rate', span: 4 },
110
+ { id: 'p99-latency', span: 4 },
111
+ ],
112
+ },
113
+ ],
114
+ },
115
+ } as DashboardDefinition,
116
+ },
117
+ };
118
+
119
+ /**
120
+ * Time Series Focus
121
+ *
122
+ * Dashboard focused on time series visualizations.
123
+ */
124
+ export const TimeSeriesFocus: Story = {
125
+ args: {
126
+ dashboard: {
127
+ id: 'time-series',
128
+ name: 'Time Series Dashboard',
129
+ description: 'Focus on time-based trends',
130
+ metrics: [
131
+ {
132
+ id: 'hourly-requests',
133
+ name: 'Hourly Requests',
134
+ type: 'counter',
135
+ unit: 'requests',
136
+ sources: [{ storyboard: 'api', workflow: 'handler' }],
137
+ query: { derivation: 'count', timeGroup: 'hour' },
138
+ display: { component: 'LineChart', size: 'large' },
139
+ _mockData: {
140
+ series: [
141
+ { date: '00:00', value: 1200 },
142
+ { date: '04:00', value: 800 },
143
+ { date: '08:00', value: 2400 },
144
+ { date: '12:00', value: 3200 },
145
+ { date: '16:00', value: 2800 },
146
+ { date: '20:00', value: 1800 },
147
+ ],
148
+ },
149
+ },
150
+ {
151
+ id: 'requests-by-status',
152
+ name: 'Requests by Status',
153
+ type: 'counter',
154
+ unit: 'requests',
155
+ sources: [{ storyboard: 'api', workflow: 'handler' }],
156
+ query: { derivation: 'count', groupBy: ['status'], timeGroup: 'hour' },
157
+ display: { component: 'StackedBarChart', size: 'large' },
158
+ _mockData: {
159
+ series: [
160
+ { date: '00:00', success: 1150, error: 50 },
161
+ { date: '04:00', success: 780, error: 20 },
162
+ { date: '08:00', success: 2300, error: 100 },
163
+ { date: '12:00', success: 3050, error: 150 },
164
+ { date: '16:00', success: 2650, error: 150 },
165
+ { date: '20:00', success: 1720, error: 80 },
166
+ ],
167
+ },
168
+ },
169
+ ],
170
+ layout: {
171
+ rows: [
172
+ {
173
+ title: 'Traffic Over Time',
174
+ panels: [{ id: 'hourly-requests', span: 12 }],
175
+ },
176
+ {
177
+ title: 'Status Breakdown',
178
+ panels: [{ id: 'requests-by-status', span: 12 }],
179
+ },
180
+ ],
181
+ },
182
+ } as DashboardDefinition,
183
+ },
184
+ };
185
+
186
+ /**
187
+ * With Thresholds
188
+ *
189
+ * Shows metrics with warning and critical thresholds.
190
+ */
191
+ export const WithThresholds: Story = {
192
+ args: {
193
+ dashboard: {
194
+ id: 'thresholds',
195
+ name: 'SLO Dashboard',
196
+ description: 'Service level objectives with thresholds',
197
+ metrics: [
198
+ {
199
+ id: 'availability',
200
+ name: 'Availability',
201
+ type: 'gauge',
202
+ unit: '%',
203
+ sources: [{ storyboard: 'infra', workflow: 'health-check' }],
204
+ query: { derivation: 'success_rate' },
205
+ thresholds: { warning: 99.9, critical: 99.0 },
206
+ _mockData: { current: 99.95, trend: 'flat' },
207
+ },
208
+ {
209
+ id: 'error-budget',
210
+ name: 'Error Budget Remaining',
211
+ type: 'gauge',
212
+ unit: '%',
213
+ sources: [{ storyboard: 'infra', workflow: 'slo-tracker' }],
214
+ query: { derivation: 'percentage' },
215
+ thresholds: { warning: 25, critical: 10 },
216
+ _mockData: { current: 67.3, previous: 72.1, trend: 'down' },
217
+ },
218
+ {
219
+ id: 'latency-slo',
220
+ name: 'Latency SLO',
221
+ type: 'gauge',
222
+ unit: '%',
223
+ description: 'Percentage of requests under 200ms',
224
+ sources: [{ storyboard: 'api', workflow: 'handler' }],
225
+ query: { derivation: 'percentage', filter: 'duration < 200' },
226
+ thresholds: { warning: 95, critical: 90 },
227
+ _mockData: { current: 97.2, previous: 96.8, trend: 'up' },
228
+ },
229
+ ],
230
+ layout: {
231
+ rows: [
232
+ {
233
+ title: 'Service Level Objectives',
234
+ panels: [
235
+ { id: 'availability', span: 4 },
236
+ { id: 'error-budget', span: 4 },
237
+ { id: 'latency-slo', span: 4 },
238
+ ],
239
+ },
240
+ ],
241
+ },
242
+ } as DashboardDefinition,
243
+ },
244
+ };
245
+
246
+ /**
247
+ * Empty Dashboard
248
+ *
249
+ * Edge case: dashboard with no metrics.
250
+ */
251
+ export const EmptyDashboard: Story = {
252
+ args: {
253
+ dashboard: {
254
+ id: 'empty',
255
+ name: 'Empty Dashboard',
256
+ description: 'This dashboard has no metrics configured yet',
257
+ metrics: [],
258
+ layout: {
259
+ rows: [],
260
+ },
261
+ } as DashboardDefinition,
262
+ },
263
+ };