@principal-ai/storybook-addon-otel 0.2.0 → 0.2.2
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/.npmignore +20 -0
- package/package.json +3 -3
- package/src/collector.ts +0 -122
- package/src/components/TelemetryPanel.tsx +0 -279
- package/src/constants.ts +0 -9
- package/src/decorator.tsx +0 -103
- package/src/index.ts +0 -7
- package/src/manager.tsx +0 -19
- package/src/preset.ts +0 -13
package/.npmignore
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Source files (standalone manager.js used instead)
|
|
2
|
+
src/
|
|
3
|
+
|
|
4
|
+
# Exclude compiled manager files to avoid bundler conflicts in Storybook v10
|
|
5
|
+
dist/manager.js
|
|
6
|
+
dist/manager.d.ts
|
|
7
|
+
dist/components/
|
|
8
|
+
|
|
9
|
+
# Development files
|
|
10
|
+
scripts/
|
|
11
|
+
tsconfig.json
|
|
12
|
+
*.test.ts
|
|
13
|
+
*.test.tsx
|
|
14
|
+
*.spec.ts
|
|
15
|
+
*.spec.tsx
|
|
16
|
+
.DS_Store
|
|
17
|
+
node_modules/
|
|
18
|
+
coverage/
|
|
19
|
+
.nyc_output/
|
|
20
|
+
*.tgz
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@principal-ai/storybook-addon-otel",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Storybook addon for capturing and visualizing OpenTelemetry data from story interactions",
|
|
5
5
|
"main": "dist/preset.js",
|
|
6
6
|
"types": "dist/preset.d.ts",
|
|
7
7
|
"files": [
|
|
8
8
|
"dist/**/*",
|
|
9
|
-
"src/**/*",
|
|
10
9
|
"preset.js",
|
|
11
10
|
"preview.js",
|
|
12
11
|
"manager.js",
|
|
13
|
-
"README.md"
|
|
12
|
+
"README.md",
|
|
13
|
+
".npmignore"
|
|
14
14
|
],
|
|
15
15
|
"exports": {
|
|
16
16
|
".": {
|
package/src/collector.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Telemetry Collector
|
|
3
|
-
*
|
|
4
|
-
* Captures spans and events during story interactions
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { addons, types } from '@storybook/manager-api';
|
|
8
|
-
import { ADDON_ID, EVENTS } from './constants';
|
|
9
|
-
|
|
10
|
-
export interface SpanEvent {
|
|
11
|
-
time: number;
|
|
12
|
-
name: string;
|
|
13
|
-
attributes: Record<string, string | number | boolean>;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface TelemetrySpan {
|
|
17
|
-
id: string;
|
|
18
|
-
name: string;
|
|
19
|
-
parentId?: string;
|
|
20
|
-
startTime: number;
|
|
21
|
-
endTime?: number;
|
|
22
|
-
duration?: number;
|
|
23
|
-
attributes: Record<string, string | number | boolean>;
|
|
24
|
-
events: SpanEvent[];
|
|
25
|
-
status: 'OK' | 'ERROR';
|
|
26
|
-
errorMessage?: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
class TelemetryCollectorImpl {
|
|
30
|
-
private spans: TelemetrySpan[] = [];
|
|
31
|
-
private spanIdCounter = 0;
|
|
32
|
-
private activeSpans = new Map<string, TelemetrySpan>();
|
|
33
|
-
private channel: any;
|
|
34
|
-
|
|
35
|
-
constructor() {
|
|
36
|
-
// Get communication channel with manager
|
|
37
|
-
if (typeof addons !== 'undefined') {
|
|
38
|
-
this.channel = addons.getChannel();
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
startSpan(
|
|
43
|
-
name: string,
|
|
44
|
-
attributes?: Record<string, string | number | boolean>,
|
|
45
|
-
parentId?: string
|
|
46
|
-
): TelemetrySpan {
|
|
47
|
-
const span: TelemetrySpan = {
|
|
48
|
-
id: `span-${++this.spanIdCounter}`,
|
|
49
|
-
name,
|
|
50
|
-
parentId,
|
|
51
|
-
startTime: Date.now(),
|
|
52
|
-
attributes: {
|
|
53
|
-
'component.name': name,
|
|
54
|
-
...attributes,
|
|
55
|
-
},
|
|
56
|
-
events: [],
|
|
57
|
-
status: 'OK',
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
this.spans.push(span);
|
|
61
|
-
this.activeSpans.set(span.id, span);
|
|
62
|
-
console.log('[TelemetryCollector] Started span:', name, 'Total spans:', this.spans.length);
|
|
63
|
-
this.emit();
|
|
64
|
-
|
|
65
|
-
return span;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
addEvent(
|
|
69
|
-
span: TelemetrySpan,
|
|
70
|
-
eventName: string,
|
|
71
|
-
attributes?: Record<string, string | number | boolean>
|
|
72
|
-
): void {
|
|
73
|
-
span.events.push({
|
|
74
|
-
time: Date.now(),
|
|
75
|
-
name: eventName,
|
|
76
|
-
attributes: attributes || {},
|
|
77
|
-
});
|
|
78
|
-
this.emit();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
endSpan(span: TelemetrySpan): void {
|
|
82
|
-
span.endTime = Date.now();
|
|
83
|
-
span.duration = span.endTime - span.startTime;
|
|
84
|
-
this.activeSpans.delete(span.id);
|
|
85
|
-
this.emit();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
markError(span: TelemetrySpan, error: Error | string): void {
|
|
89
|
-
span.status = 'ERROR';
|
|
90
|
-
span.errorMessage = typeof error === 'string' ? error : error.message;
|
|
91
|
-
span.attributes['error'] = true;
|
|
92
|
-
span.attributes['error.message'] = span.errorMessage;
|
|
93
|
-
this.emit();
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
getSpans(): TelemetrySpan[] {
|
|
97
|
-
return [...this.spans];
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
clear(): void {
|
|
101
|
-
this.spans = [];
|
|
102
|
-
this.spanIdCounter = 0;
|
|
103
|
-
this.activeSpans.clear();
|
|
104
|
-
this.emit();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
private emit(): void {
|
|
108
|
-
console.log('[TelemetryCollector] Emitting update, spans:', this.spans.length, 'has channel:', !!this.channel);
|
|
109
|
-
if (this.channel) {
|
|
110
|
-
this.channel.emit(EVENTS.TELEMETRY_UPDATE, this.getSpans());
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
exportJson(): string {
|
|
115
|
-
// End any active spans
|
|
116
|
-
this.activeSpans.forEach(span => this.endSpan(span));
|
|
117
|
-
return JSON.stringify(this.spans, null, 2);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Singleton instance
|
|
122
|
-
export const TelemetryCollector = new TelemetryCollectorImpl();
|
|
@@ -1,279 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Telemetry Panel Component
|
|
3
|
-
*
|
|
4
|
-
* Displays captured telemetry in Storybook panel
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import React, { useEffect, useState } from 'react';
|
|
8
|
-
import { useChannel } from '@storybook/manager-api';
|
|
9
|
-
import { AddonPanel, TabButton } from '@storybook/components';
|
|
10
|
-
import { styled } from '@storybook/theming';
|
|
11
|
-
import type { TelemetrySpan } from '../collector';
|
|
12
|
-
import { EVENTS } from '../constants';
|
|
13
|
-
|
|
14
|
-
const PanelContainer = styled.div`
|
|
15
|
-
padding: 16px;
|
|
16
|
-
font-family: monospace;
|
|
17
|
-
font-size: 12px;
|
|
18
|
-
height: 100%;
|
|
19
|
-
overflow: auto;
|
|
20
|
-
background: #1a1a1a;
|
|
21
|
-
color: #e6e6e6;
|
|
22
|
-
`;
|
|
23
|
-
|
|
24
|
-
const Header = styled.div`
|
|
25
|
-
display: flex;
|
|
26
|
-
justify-content: space-between;
|
|
27
|
-
align-items: center;
|
|
28
|
-
margin-bottom: 16px;
|
|
29
|
-
padding-bottom: 12px;
|
|
30
|
-
border-bottom: 1px solid #333;
|
|
31
|
-
`;
|
|
32
|
-
|
|
33
|
-
const Button = styled.button`
|
|
34
|
-
background: #3b82f6;
|
|
35
|
-
color: white;
|
|
36
|
-
border: none;
|
|
37
|
-
border-radius: 4px;
|
|
38
|
-
padding: 6px 12px;
|
|
39
|
-
cursor: pointer;
|
|
40
|
-
font-size: 12px;
|
|
41
|
-
margin-left: 8px;
|
|
42
|
-
|
|
43
|
-
&:hover {
|
|
44
|
-
background: #2563eb;
|
|
45
|
-
}
|
|
46
|
-
`;
|
|
47
|
-
|
|
48
|
-
const SpanCard = styled.div<{ status: 'OK' | 'ERROR' }>`
|
|
49
|
-
background: #242424;
|
|
50
|
-
border: 1px solid ${props => props.status === 'ERROR' ? '#ef4444' : '#333'};
|
|
51
|
-
border-radius: 6px;
|
|
52
|
-
padding: 12px;
|
|
53
|
-
margin-bottom: 12px;
|
|
54
|
-
`;
|
|
55
|
-
|
|
56
|
-
const SpanHeader = styled.div`
|
|
57
|
-
display: flex;
|
|
58
|
-
justify-content: space-between;
|
|
59
|
-
align-items: flex-start;
|
|
60
|
-
margin-bottom: 8px;
|
|
61
|
-
`;
|
|
62
|
-
|
|
63
|
-
const SpanName = styled.div`
|
|
64
|
-
font-weight: 600;
|
|
65
|
-
color: #3b82f6;
|
|
66
|
-
font-size: 13px;
|
|
67
|
-
`;
|
|
68
|
-
|
|
69
|
-
const SpanDuration = styled.div`
|
|
70
|
-
color: #10b981;
|
|
71
|
-
font-size: 11px;
|
|
72
|
-
`;
|
|
73
|
-
|
|
74
|
-
const Attributes = styled.div`
|
|
75
|
-
margin-top: 8px;
|
|
76
|
-
padding: 8px;
|
|
77
|
-
background: #1a1a1a;
|
|
78
|
-
border-radius: 4px;
|
|
79
|
-
font-size: 11px;
|
|
80
|
-
`;
|
|
81
|
-
|
|
82
|
-
const AttributeRow = styled.div`
|
|
83
|
-
padding: 2px 0;
|
|
84
|
-
color: #a0a0a0;
|
|
85
|
-
|
|
86
|
-
span:first-child {
|
|
87
|
-
color: #8b5cf6;
|
|
88
|
-
}
|
|
89
|
-
`;
|
|
90
|
-
|
|
91
|
-
const EventList = styled.div`
|
|
92
|
-
margin-top: 8px;
|
|
93
|
-
`;
|
|
94
|
-
|
|
95
|
-
const Event = styled.div`
|
|
96
|
-
padding: 6px 8px;
|
|
97
|
-
background: #1a1a1a;
|
|
98
|
-
border-left: 2px solid #f59e0b;
|
|
99
|
-
margin-bottom: 4px;
|
|
100
|
-
font-size: 11px;
|
|
101
|
-
`;
|
|
102
|
-
|
|
103
|
-
const EventName = styled.div`
|
|
104
|
-
color: #f59e0b;
|
|
105
|
-
font-weight: 600;
|
|
106
|
-
margin-bottom: 4px;
|
|
107
|
-
`;
|
|
108
|
-
|
|
109
|
-
const ViewToggle = styled.div`
|
|
110
|
-
display: flex;
|
|
111
|
-
gap: 8px;
|
|
112
|
-
margin-bottom: 16px;
|
|
113
|
-
`;
|
|
114
|
-
|
|
115
|
-
const ToggleButton = styled.button<{ active: boolean }>`
|
|
116
|
-
background: ${props => props.active ? '#3b82f6' : '#333'};
|
|
117
|
-
color: ${props => props.active ? 'white' : '#a0a0a0'};
|
|
118
|
-
border: none;
|
|
119
|
-
border-radius: 4px;
|
|
120
|
-
padding: 6px 16px;
|
|
121
|
-
cursor: pointer;
|
|
122
|
-
font-size: 12px;
|
|
123
|
-
|
|
124
|
-
&:hover {
|
|
125
|
-
background: ${props => props.active ? '#2563eb' : '#404040'};
|
|
126
|
-
}
|
|
127
|
-
`;
|
|
128
|
-
|
|
129
|
-
const CanvasPlaceholder = styled.div`
|
|
130
|
-
padding: 40px;
|
|
131
|
-
text-align: center;
|
|
132
|
-
color: #666;
|
|
133
|
-
border: 2px dashed #333;
|
|
134
|
-
border-radius: 8px;
|
|
135
|
-
|
|
136
|
-
h3 {
|
|
137
|
-
margin: 0 0 8px 0;
|
|
138
|
-
color: #999;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
p {
|
|
142
|
-
margin: 0;
|
|
143
|
-
font-size: 12px;
|
|
144
|
-
}
|
|
145
|
-
`;
|
|
146
|
-
|
|
147
|
-
interface PanelProps {
|
|
148
|
-
active: boolean;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export const TelemetryPanel: React.FC<PanelProps> = ({ active }) => {
|
|
152
|
-
const [spans, setSpans] = useState<TelemetrySpan[]>([]);
|
|
153
|
-
const [view, setView] = useState<'timeline' | 'canvas'>('timeline');
|
|
154
|
-
|
|
155
|
-
const emit = useChannel({
|
|
156
|
-
[EVENTS.TELEMETRY_UPDATE]: (newSpans: TelemetrySpan[]) => {
|
|
157
|
-
console.log('[TelemetryPanel] Received update, spans:', newSpans.length, newSpans);
|
|
158
|
-
setSpans(newSpans);
|
|
159
|
-
},
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
console.log('[TelemetryPanel] Render, active:', active, 'spans:', spans.length);
|
|
163
|
-
|
|
164
|
-
const handleClear = () => {
|
|
165
|
-
emit(EVENTS.CLEAR_TELEMETRY);
|
|
166
|
-
setSpans([]);
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
const handleExport = () => {
|
|
170
|
-
const json = JSON.stringify(spans, null, 2);
|
|
171
|
-
const blob = new Blob([json], { type: 'application/json' });
|
|
172
|
-
const url = URL.createObjectURL(blob);
|
|
173
|
-
const a = document.createElement('a');
|
|
174
|
-
a.href = url;
|
|
175
|
-
a.download = 'storybook-telemetry.json';
|
|
176
|
-
a.click();
|
|
177
|
-
URL.revokeObjectURL(url);
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
if (!active) {
|
|
181
|
-
return null;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return (
|
|
185
|
-
<AddonPanel active={active}>
|
|
186
|
-
<PanelContainer>
|
|
187
|
-
<Header>
|
|
188
|
-
<div>
|
|
189
|
-
<strong>OTEL Telemetry</strong>
|
|
190
|
-
<div style={{ fontSize: '11px', color: '#666', marginTop: '4px' }}>
|
|
191
|
-
{spans.length} span{spans.length !== 1 ? 's' : ''} collected
|
|
192
|
-
</div>
|
|
193
|
-
</div>
|
|
194
|
-
<div>
|
|
195
|
-
<Button onClick={handleClear}>Clear</Button>
|
|
196
|
-
<Button onClick={handleExport}>Export JSON</Button>
|
|
197
|
-
</div>
|
|
198
|
-
</Header>
|
|
199
|
-
|
|
200
|
-
<ViewToggle>
|
|
201
|
-
<ToggleButton
|
|
202
|
-
active={view === 'timeline'}
|
|
203
|
-
onClick={() => setView('timeline')}
|
|
204
|
-
>
|
|
205
|
-
Timeline
|
|
206
|
-
</ToggleButton>
|
|
207
|
-
<ToggleButton
|
|
208
|
-
active={view === 'canvas'}
|
|
209
|
-
onClick={() => setView('canvas')}
|
|
210
|
-
>
|
|
211
|
-
Canvas
|
|
212
|
-
</ToggleButton>
|
|
213
|
-
</ViewToggle>
|
|
214
|
-
|
|
215
|
-
{view === 'timeline' ? (
|
|
216
|
-
<>
|
|
217
|
-
{spans.length === 0 ? (
|
|
218
|
-
<div style={{ textAlign: 'center', padding: '40px', color: '#666' }}>
|
|
219
|
-
No telemetry data yet. Interact with the story to capture spans.
|
|
220
|
-
</div>
|
|
221
|
-
) : (
|
|
222
|
-
spans.map(span => (
|
|
223
|
-
<SpanCard key={span.id} status={span.status}>
|
|
224
|
-
<SpanHeader>
|
|
225
|
-
<SpanName>{span.name}</SpanName>
|
|
226
|
-
{span.duration && (
|
|
227
|
-
<SpanDuration>{span.duration}ms</SpanDuration>
|
|
228
|
-
)}
|
|
229
|
-
</SpanHeader>
|
|
230
|
-
|
|
231
|
-
{Object.keys(span.attributes).length > 0 && (
|
|
232
|
-
<Attributes>
|
|
233
|
-
{Object.entries(span.attributes).map(([key, value]) => (
|
|
234
|
-
<AttributeRow key={key}>
|
|
235
|
-
<span>{key}:</span> {String(value)}
|
|
236
|
-
</AttributeRow>
|
|
237
|
-
))}
|
|
238
|
-
</Attributes>
|
|
239
|
-
)}
|
|
240
|
-
|
|
241
|
-
{span.events.length > 0 && (
|
|
242
|
-
<EventList>
|
|
243
|
-
{span.events.map((event, idx) => (
|
|
244
|
-
<Event key={idx}>
|
|
245
|
-
<EventName>{event.name}</EventName>
|
|
246
|
-
{Object.entries(event.attributes).map(([key, value]) => (
|
|
247
|
-
<AttributeRow key={key}>
|
|
248
|
-
<span>{key}:</span> {String(value)}
|
|
249
|
-
</AttributeRow>
|
|
250
|
-
))}
|
|
251
|
-
</Event>
|
|
252
|
-
))}
|
|
253
|
-
</EventList>
|
|
254
|
-
)}
|
|
255
|
-
|
|
256
|
-
{span.errorMessage && (
|
|
257
|
-
<div style={{ marginTop: '8px', color: '#ef4444', fontSize: '11px' }}>
|
|
258
|
-
Error: {span.errorMessage}
|
|
259
|
-
</div>
|
|
260
|
-
)}
|
|
261
|
-
</SpanCard>
|
|
262
|
-
))
|
|
263
|
-
)}
|
|
264
|
-
</>
|
|
265
|
-
) : (
|
|
266
|
-
<CanvasPlaceholder>
|
|
267
|
-
<h3>Canvas Visualization</h3>
|
|
268
|
-
<p>
|
|
269
|
-
Canvas view will visualize the telemetry flow using your GraphRenderer.
|
|
270
|
-
</p>
|
|
271
|
-
<p style={{ marginTop: '8px', fontSize: '11px' }}>
|
|
272
|
-
To implement: Convert spans to canvas format and render with GraphRenderer
|
|
273
|
-
</p>
|
|
274
|
-
</CanvasPlaceholder>
|
|
275
|
-
)}
|
|
276
|
-
</PanelContainer>
|
|
277
|
-
</AddonPanel>
|
|
278
|
-
);
|
|
279
|
-
};
|
package/src/constants.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export const ADDON_ID = 'storybook-addon-otel-telemetry';
|
|
2
|
-
export const PANEL_ID = `${ADDON_ID}/panel`;
|
|
3
|
-
export const PARAM_KEY = 'otelTelemetry';
|
|
4
|
-
|
|
5
|
-
export const EVENTS = {
|
|
6
|
-
TELEMETRY_UPDATE: `${ADDON_ID}/telemetry-update`,
|
|
7
|
-
CLEAR_TELEMETRY: `${ADDON_ID}/clear-telemetry`,
|
|
8
|
-
EXPORT_TELEMETRY: `${ADDON_ID}/export-telemetry`,
|
|
9
|
-
};
|
package/src/decorator.tsx
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Story Decorator
|
|
3
|
-
*
|
|
4
|
-
* Wraps stories to automatically capture telemetry
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import React, { useEffect, useState } from 'react';
|
|
8
|
-
import { TelemetryCollector } from './collector';
|
|
9
|
-
import { PARAM_KEY } from './constants';
|
|
10
|
-
|
|
11
|
-
export const withTelemetry = (Story: any, context: any) => {
|
|
12
|
-
const [rootSpan, setRootSpan] = useState<any>(null);
|
|
13
|
-
|
|
14
|
-
// Check if telemetry is enabled for this story
|
|
15
|
-
const enabled = context.parameters[PARAM_KEY]?.enabled !== false;
|
|
16
|
-
|
|
17
|
-
console.log('[Telemetry Decorator] Story:', context.title + '/' + context.name, 'Enabled:', enabled);
|
|
18
|
-
|
|
19
|
-
useEffect(() => {
|
|
20
|
-
if (!enabled) return;
|
|
21
|
-
|
|
22
|
-
// Start root span for story
|
|
23
|
-
const span = TelemetryCollector.startSpan(`Story: ${context.title}/${context.name}`, {
|
|
24
|
-
'story.id': context.id,
|
|
25
|
-
'story.title': context.title,
|
|
26
|
-
'story.name': context.name,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
setRootSpan(span);
|
|
30
|
-
|
|
31
|
-
TelemetryCollector.addEvent(span, 'story.mounted', {
|
|
32
|
-
'story.name': `${context.title}/${context.name}`,
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
return () => {
|
|
36
|
-
TelemetryCollector.addEvent(span, 'story.unmounted', {});
|
|
37
|
-
TelemetryCollector.endSpan(span);
|
|
38
|
-
};
|
|
39
|
-
}, [context.id, enabled]);
|
|
40
|
-
|
|
41
|
-
if (!enabled) {
|
|
42
|
-
return <Story />;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<TelemetryProvider rootSpan={rootSpan}>
|
|
47
|
-
<Story />
|
|
48
|
-
</TelemetryProvider>
|
|
49
|
-
);
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
// Context to provide telemetry to story components
|
|
53
|
-
const TelemetryContext = React.createContext<{
|
|
54
|
-
rootSpan: any;
|
|
55
|
-
collector: typeof TelemetryCollector;
|
|
56
|
-
} | null>(null);
|
|
57
|
-
|
|
58
|
-
function TelemetryProvider({
|
|
59
|
-
children,
|
|
60
|
-
rootSpan
|
|
61
|
-
}: {
|
|
62
|
-
children: React.ReactNode;
|
|
63
|
-
rootSpan: any;
|
|
64
|
-
}) {
|
|
65
|
-
return (
|
|
66
|
-
<TelemetryContext.Provider value={{ rootSpan, collector: TelemetryCollector }}>
|
|
67
|
-
{children}
|
|
68
|
-
</TelemetryContext.Provider>
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Hook for stories to access telemetry
|
|
73
|
-
export function useTelemetry() {
|
|
74
|
-
const context = React.useContext(TelemetryContext);
|
|
75
|
-
if (!context) {
|
|
76
|
-
throw new Error('useTelemetry must be used within a story with telemetry enabled');
|
|
77
|
-
}
|
|
78
|
-
return context;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Hook for automatic span lifecycle
|
|
82
|
-
export function useTelemetrySpan(
|
|
83
|
-
name: string,
|
|
84
|
-
attributes?: Record<string, string | number | boolean>,
|
|
85
|
-
deps: unknown[] = []
|
|
86
|
-
): any {
|
|
87
|
-
const { rootSpan } = useTelemetry();
|
|
88
|
-
const [span, setSpan] = useState<any>(null);
|
|
89
|
-
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
const newSpan = TelemetryCollector.startSpan(name, attributes, rootSpan?.id);
|
|
92
|
-
setSpan(newSpan);
|
|
93
|
-
|
|
94
|
-
return () => {
|
|
95
|
-
TelemetryCollector.endSpan(newSpan);
|
|
96
|
-
};
|
|
97
|
-
}, deps);
|
|
98
|
-
|
|
99
|
-
return span;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Export decorators array for Storybook
|
|
103
|
-
export const decorators = [withTelemetry];
|
package/src/index.ts
DELETED
package/src/manager.tsx
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Addon Manager Registration
|
|
3
|
-
*
|
|
4
|
-
* Registers the telemetry panel in Storybook UI
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import React from 'react';
|
|
8
|
-
import { addons, types } from '@storybook/manager-api';
|
|
9
|
-
import { TelemetryPanel } from './components/TelemetryPanel';
|
|
10
|
-
import { ADDON_ID, PANEL_ID } from './constants';
|
|
11
|
-
|
|
12
|
-
addons.register(ADDON_ID, (api) => {
|
|
13
|
-
addons.add(PANEL_ID, {
|
|
14
|
-
type: types.PANEL,
|
|
15
|
-
title: 'OTEL Telemetry',
|
|
16
|
-
match: ({ viewMode }) => viewMode === 'story',
|
|
17
|
-
render: ({ active }) => <TelemetryPanel active={active || false} />,
|
|
18
|
-
});
|
|
19
|
-
});
|
package/src/preset.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Addon Preset
|
|
3
|
-
*
|
|
4
|
-
* Configures the addon for Storybook
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export function managerEntries(entry: any[] = []) {
|
|
8
|
-
return [...entry, require.resolve('./manager')];
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function previewAnnotations(entry: any[] = []) {
|
|
12
|
-
return [...entry, require.resolve('./decorator')];
|
|
13
|
-
}
|