@atlaskit/react-ufo 4.15.9 → 4.15.11
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/CHANGELOG.md +14 -0
- package/api-reference.md +432 -327
- package/dist/cjs/create-payload/index.js +5 -5
- package/dist/cjs/global-error-handler/index.js +2 -2
- package/dist/cjs/ssr/index.js +2 -6
- package/dist/cjs/vc/vc-observer/index.js +3 -3
- package/dist/cjs/vc/vc-observer/observers/ssr-placeholders/index.js +22 -1
- package/dist/es2019/create-payload/index.js +5 -5
- package/dist/es2019/global-error-handler/index.js +2 -2
- package/dist/es2019/ssr/index.js +2 -6
- package/dist/es2019/vc/vc-observer/index.js +3 -3
- package/dist/es2019/vc/vc-observer/observers/ssr-placeholders/index.js +20 -1
- package/dist/esm/create-payload/index.js +5 -5
- package/dist/esm/global-error-handler/index.js +2 -2
- package/dist/esm/ssr/index.js +2 -6
- package/dist/esm/vc/vc-observer/index.js +3 -3
- package/dist/esm/vc/vc-observer/observers/ssr-placeholders/index.js +22 -1
- package/dist/types/vc/vc-observer/observers/ssr-placeholders/index.d.ts +1 -0
- package/dist/types-ts4.5/vc/vc-observer/observers/ssr-placeholders/index.d.ts +1 -0
- package/package.json +4 -4
package/api-reference.md
CHANGED
|
@@ -10,7 +10,9 @@ date: '2025-11-10'
|
|
|
10
10
|
|
|
11
11
|
# React UFO Instrumentation API Reference
|
|
12
12
|
|
|
13
|
-
React UFO provides performance instrumentation components and utilities to measure user experience
|
|
13
|
+
React UFO provides performance instrumentation components and utilities to measure user experience
|
|
14
|
+
metrics across Atlassian products. This library captures key performance indicators like Time to
|
|
15
|
+
Interactive (TTI), Time to Visual Complete (TTVC), and custom interaction metrics.
|
|
14
16
|
|
|
15
17
|
## Getting Started
|
|
16
18
|
|
|
@@ -68,134 +70,158 @@ yarn add @atlaskit/react-ufo
|
|
|
68
70
|
|
|
69
71
|
### traceUFOPageLoad
|
|
70
72
|
|
|
71
|
-
Initializes measurement of page load performance for a specific route or page. This function should
|
|
73
|
+
Initializes measurement of page load performance for a specific route or page. This function should
|
|
74
|
+
be called early in the page lifecycle to capture accurate timing data.
|
|
72
75
|
|
|
73
76
|
**Import:**
|
|
77
|
+
|
|
74
78
|
```typescript
|
|
75
79
|
import traceUFOPageLoad from '@atlaskit/react-ufo/trace-pageload';
|
|
76
80
|
```
|
|
77
81
|
|
|
78
82
|
**Usage:**
|
|
83
|
+
|
|
79
84
|
```typescript
|
|
80
85
|
traceUFOPageLoad('issue-view');
|
|
81
86
|
```
|
|
82
87
|
|
|
83
88
|
**Parameters:**
|
|
89
|
+
|
|
84
90
|
- `ufoName` (string): The UFO interaction name used for sampling and event identification
|
|
85
91
|
|
|
86
92
|
**Example:**
|
|
93
|
+
|
|
87
94
|
```typescript
|
|
88
95
|
// In your route component
|
|
89
96
|
import { useEffect } from 'react';
|
|
90
97
|
import traceUFOPageLoad from '@atlaskit/react-ufo/trace-pageload';
|
|
91
98
|
|
|
92
99
|
function IssueView() {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
traceUFOPageLoad('issue-view');
|
|
102
|
+
}, []);
|
|
96
103
|
|
|
97
|
-
|
|
104
|
+
return <div>Issue content...</div>;
|
|
98
105
|
}
|
|
99
106
|
```
|
|
100
107
|
|
|
101
|
-
**Note:** The function uses sampling rates configured in your UFO settings to control which page
|
|
108
|
+
**Note:** The function uses sampling rates configured in your UFO settings to control which page
|
|
109
|
+
loads are measured.
|
|
102
110
|
|
|
103
111
|
### traceUFOPress
|
|
104
112
|
|
|
105
|
-
Measures user interaction performance from a press/click event until all loading is complete. Use
|
|
113
|
+
Measures user interaction performance from a press/click event until all loading is complete. Use
|
|
114
|
+
this when `usePressTracing` hook or the `interactionName` property on components isn't suitable.
|
|
106
115
|
|
|
107
116
|
**Import:**
|
|
117
|
+
|
|
108
118
|
```typescript
|
|
109
119
|
import traceUFOPress from '@atlaskit/react-ufo/trace-press';
|
|
110
120
|
```
|
|
111
121
|
|
|
112
122
|
**Usage:**
|
|
123
|
+
|
|
113
124
|
```typescript
|
|
114
125
|
traceUFOPress('button-click');
|
|
115
126
|
traceUFOPress('menu-open', performance.now()); // with custom timestamp
|
|
116
127
|
```
|
|
117
128
|
|
|
118
129
|
**Parameters:**
|
|
130
|
+
|
|
119
131
|
- `name` (string): Unique identifier for the interaction
|
|
120
132
|
- `timestamp` (number, optional): Custom timestamp, defaults to `performance.now()`
|
|
121
133
|
|
|
122
134
|
**Example:**
|
|
135
|
+
|
|
123
136
|
```typescript
|
|
124
137
|
import traceUFOPress from '@atlaskit/react-ufo/trace-press';
|
|
125
138
|
|
|
126
139
|
function CustomButton() {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
140
|
+
const handleClick = () => {
|
|
141
|
+
traceUFOPress('custom-action');
|
|
142
|
+
// Trigger your action that may cause loading states
|
|
143
|
+
performAsyncAction();
|
|
144
|
+
};
|
|
132
145
|
|
|
133
|
-
|
|
146
|
+
return <button onClick={handleClick}>Custom Action</button>;
|
|
134
147
|
}
|
|
135
148
|
```
|
|
136
149
|
|
|
137
150
|
**When to use:**
|
|
151
|
+
|
|
138
152
|
- Custom components that don't support `interactionName`
|
|
139
153
|
- Complex interaction patterns
|
|
140
154
|
- Manual event handling scenarios
|
|
141
155
|
|
|
142
|
-
**Note:** Results are sent as `inline-result` type events. Prefer `usePressTracing` or
|
|
156
|
+
**Note:** Results are sent as `inline-result` type events. Prefer `usePressTracing` or
|
|
157
|
+
`interactionName` when possible.
|
|
143
158
|
|
|
144
159
|
### traceUFOTransition
|
|
145
160
|
|
|
146
|
-
Measures navigation transition performance between routes or major state changes within a
|
|
161
|
+
Measures navigation transition performance between routes or major state changes within a
|
|
162
|
+
single-page application. This function aborts active interactions and starts measuring the new
|
|
163
|
+
transition.
|
|
147
164
|
|
|
148
165
|
**Import:**
|
|
166
|
+
|
|
149
167
|
```typescript
|
|
150
168
|
import traceUFOTransition from '@atlaskit/react-ufo/trace-transition';
|
|
151
169
|
```
|
|
152
170
|
|
|
153
171
|
**Usage:**
|
|
172
|
+
|
|
154
173
|
```typescript
|
|
155
174
|
traceUFOTransition('board-to-backlog');
|
|
156
175
|
```
|
|
157
176
|
|
|
158
177
|
**Parameters:**
|
|
178
|
+
|
|
159
179
|
- `ufoName` (string): The UFO name for the transition
|
|
160
180
|
|
|
161
181
|
**Example:**
|
|
182
|
+
|
|
162
183
|
```typescript
|
|
163
184
|
import { useRouter } from '@atlassian/react-resource-router';
|
|
164
185
|
import traceUFOTransition from '@atlaskit/react-ufo/trace-transition';
|
|
165
186
|
|
|
166
187
|
function NavigationHandler() {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
188
|
+
const [routerState] = useRouter();
|
|
189
|
+
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
const routeName = getUFORouteName(routerState.route);
|
|
192
|
+
if (routeName) {
|
|
193
|
+
traceUFOTransition(routeName);
|
|
194
|
+
}
|
|
195
|
+
}, [routerState]);
|
|
196
|
+
|
|
197
|
+
return <div>Page content...</div>;
|
|
177
198
|
}
|
|
178
199
|
```
|
|
179
200
|
|
|
180
201
|
### traceUFOInteraction
|
|
181
202
|
|
|
182
|
-
Measures performance of user interactions based on browser events. This function automatically
|
|
203
|
+
Measures performance of user interactions based on browser events. This function automatically
|
|
204
|
+
detects the interaction type from the event and applies appropriate timing.
|
|
183
205
|
|
|
184
206
|
**Import:**
|
|
207
|
+
|
|
185
208
|
```typescript
|
|
186
209
|
import traceUFOInteraction from '@atlaskit/react-ufo/trace-interaction';
|
|
187
210
|
```
|
|
188
211
|
|
|
189
212
|
**Usage:**
|
|
213
|
+
|
|
190
214
|
```typescript
|
|
191
215
|
traceUFOInteraction('dropdown-open', event);
|
|
192
216
|
```
|
|
193
217
|
|
|
194
218
|
**Parameters:**
|
|
219
|
+
|
|
195
220
|
- `name` (string): Unique identifier for the interaction
|
|
196
221
|
- `event` (Event | UIEvent): Browser event object (must be trusted)
|
|
197
222
|
|
|
198
223
|
**Supported Event Types:**
|
|
224
|
+
|
|
199
225
|
- `click`
|
|
200
226
|
- `dblclick`
|
|
201
227
|
- `mousedown`
|
|
@@ -203,80 +229,79 @@ traceUFOInteraction('dropdown-open', event);
|
|
|
203
229
|
- `mouseover`
|
|
204
230
|
|
|
205
231
|
**Example:**
|
|
232
|
+
|
|
206
233
|
```typescript
|
|
207
234
|
import traceUFOInteraction from '@atlaskit/react-ufo/trace-interaction';
|
|
208
235
|
|
|
209
236
|
function InteractiveComponent() {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
<div onClick={handleClick}>
|
|
219
|
-
Click me
|
|
220
|
-
</div>
|
|
221
|
-
);
|
|
237
|
+
const handleClick = (event: React.MouseEvent) => {
|
|
238
|
+
traceUFOInteraction('component-action', event.nativeEvent);
|
|
239
|
+
|
|
240
|
+
// Your interaction logic
|
|
241
|
+
setShowModal(true);
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
return <div onClick={handleClick}>Click me</div>;
|
|
222
245
|
}
|
|
223
246
|
```
|
|
224
247
|
|
|
225
248
|
**Notes:**
|
|
249
|
+
|
|
226
250
|
- Only trusted events (user-initiated) are processed
|
|
227
251
|
- Unsupported event types are ignored
|
|
228
252
|
- Event timestamp is automatically extracted from the event object
|
|
229
253
|
|
|
230
254
|
### traceUFORedirect
|
|
231
255
|
|
|
232
|
-
Specialized function for tracking navigation between routes, providing detailed route transition
|
|
256
|
+
Specialized function for tracking navigation between routes, providing detailed route transition
|
|
257
|
+
information. This is typically used with routing libraries like `@atlassian/react-resource-router`.
|
|
233
258
|
|
|
234
259
|
**Import:**
|
|
260
|
+
|
|
235
261
|
```typescript
|
|
236
262
|
import traceUFORedirect from '@atlaskit/react-ufo/trace-redirect';
|
|
237
263
|
import getUFORouteName from '@atlaskit/react-ufo/route-name';
|
|
238
264
|
```
|
|
239
265
|
|
|
240
266
|
**Usage:**
|
|
267
|
+
|
|
241
268
|
```typescript
|
|
242
269
|
traceUFORedirect(fromUfoName, nextUfoName, nextRouteName, timestamp);
|
|
243
270
|
```
|
|
244
271
|
|
|
245
272
|
**Parameters:**
|
|
273
|
+
|
|
246
274
|
- `fromUfoName` (string): Current route UFO name
|
|
247
275
|
- `nextUfoName` (string): Target route UFO name from route config
|
|
248
276
|
- `nextRouteName` (string): Target route name from route definition
|
|
249
277
|
- `time` (number): High-precision timestamp from `performance.now()`
|
|
250
278
|
|
|
251
279
|
**Example with React Resource Router:**
|
|
280
|
+
|
|
252
281
|
```typescript
|
|
253
282
|
import { matchRoute, useRouter } from '@atlassian/react-resource-router';
|
|
254
283
|
import traceUFORedirect from '@atlaskit/react-ufo/trace-redirect';
|
|
255
284
|
import getUFORouteName from '@atlaskit/react-ufo/route-name';
|
|
256
285
|
|
|
257
286
|
function NavigationTracker() {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
performance.now()
|
|
272
|
-
);
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
return null; // This is typically a side-effect component
|
|
287
|
+
const [routerState] = useRouter();
|
|
288
|
+
|
|
289
|
+
const handleNavigation = (nextLocation) => {
|
|
290
|
+
const currentRouteName = getUFORouteName(routerState.route);
|
|
291
|
+
|
|
292
|
+
const nextMatchObject = matchRoute(routes, nextLocation.pathname, nextLocation.search);
|
|
293
|
+
const { route: nextRoute } = nextMatchObject;
|
|
294
|
+
const nextRouteUfoName = getUFORouteName(nextRoute);
|
|
295
|
+
|
|
296
|
+
traceUFORedirect(currentRouteName, nextRouteUfoName, nextRoute.name, performance.now());
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
return null; // This is typically a side-effect component
|
|
276
300
|
}
|
|
277
301
|
```
|
|
278
302
|
|
|
279
303
|
**Use Cases:**
|
|
304
|
+
|
|
280
305
|
- Single-page application route changes
|
|
281
306
|
- Cross-product navigation
|
|
282
307
|
- Complex navigation patterns requiring detailed tracking
|
|
@@ -287,57 +312,63 @@ function NavigationTracker() {
|
|
|
287
312
|
|
|
288
313
|
### UFOSegment
|
|
289
314
|
|
|
290
|
-
A React context provider that defines distinct, measurable sections of your application. Each
|
|
315
|
+
A React context provider that defines distinct, measurable sections of your application. Each
|
|
316
|
+
segment generates its own performance payload, allowing granular analysis of different page areas.
|
|
291
317
|
|
|
292
318
|
**Import:**
|
|
319
|
+
|
|
293
320
|
```typescript
|
|
294
321
|
import UFOSegment from '@atlaskit/react-ufo/segment';
|
|
295
322
|
```
|
|
296
323
|
|
|
297
324
|
**Basic Usage:**
|
|
325
|
+
|
|
298
326
|
```typescript
|
|
299
327
|
<UFOSegment name="sidebar">
|
|
300
|
-
|
|
328
|
+
<SidebarComponent />
|
|
301
329
|
</UFOSegment>
|
|
302
330
|
```
|
|
303
331
|
|
|
304
332
|
**Parameters:**
|
|
333
|
+
|
|
305
334
|
- `name` (string): Unique identifier for this page segment
|
|
306
335
|
- `children` (ReactNode): Components to be measured within this segment
|
|
307
336
|
|
|
308
337
|
**Real-world Example:**
|
|
338
|
+
|
|
309
339
|
```typescript
|
|
310
340
|
import UFOSegment from '@atlaskit/react-ufo/segment';
|
|
311
341
|
|
|
312
342
|
function IssuePage() {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
343
|
+
return (
|
|
344
|
+
<div>
|
|
345
|
+
<UFOSegment name="issue-header">
|
|
346
|
+
<IssueHeader />
|
|
347
|
+
</UFOSegment>
|
|
348
|
+
|
|
349
|
+
<UFOSegment name="issue-content">
|
|
350
|
+
<IssueDescription />
|
|
351
|
+
<IssueComments />
|
|
352
|
+
</UFOSegment>
|
|
353
|
+
|
|
354
|
+
<UFOSegment name="issue-sidebar">
|
|
355
|
+
<IssueSidebar />
|
|
356
|
+
</UFOSegment>
|
|
357
|
+
</div>
|
|
358
|
+
);
|
|
329
359
|
}
|
|
330
360
|
```
|
|
331
361
|
|
|
332
|
-
**Event Generation:**
|
|
333
|
-
|
|
334
|
-
- Main interaction: `jira.fe.page-load.issue-view`
|
|
335
|
-
- Segment events:
|
|
362
|
+
**Event Generation:** For a page load with UFO name `issue-view`, segments generate:
|
|
363
|
+
|
|
364
|
+
- Main interaction: `jira.fe.page-load.issue-view`
|
|
365
|
+
- Segment events:
|
|
336
366
|
- `jira.fe.page-segment-load.issue-header`
|
|
337
367
|
- `jira.fe.page-segment-load.issue-content`
|
|
338
368
|
- `jira.fe.page-segment-load.issue-sidebar`
|
|
339
369
|
|
|
340
370
|
**Best Practices:**
|
|
371
|
+
|
|
341
372
|
- Use distinct, meaningful names for each segment
|
|
342
373
|
- Represent logical page sections that could theoretically render independently
|
|
343
374
|
- Avoid generic names like "content" or "main" - be specific
|
|
@@ -345,52 +376,59 @@ For a page load with UFO name `issue-view`, segments generate:
|
|
|
345
376
|
- Nesting is supported but use judiciously
|
|
346
377
|
|
|
347
378
|
**Third-Party Extensions:**
|
|
379
|
+
|
|
348
380
|
```typescript
|
|
349
381
|
import { UFOThirdPartySegment } from '@atlaskit/react-ufo/segment';
|
|
350
382
|
|
|
351
383
|
// For external/plugin content that should be tracked separately
|
|
352
384
|
<UFOThirdPartySegment name="confluence-macro-calendar">
|
|
353
|
-
|
|
354
|
-
</UFOThirdPartySegment
|
|
385
|
+
<CalendarMacro />
|
|
386
|
+
</UFOThirdPartySegment>;
|
|
355
387
|
```
|
|
356
388
|
|
|
357
389
|
### UFOLabel
|
|
358
390
|
|
|
359
|
-
A React context provider used to annotate sections of your component tree with descriptive names.
|
|
391
|
+
A React context provider used to annotate sections of your component tree with descriptive names.
|
|
392
|
+
Unlike segments, labels don't generate separate events but provide context for debugging and
|
|
393
|
+
analysis.
|
|
360
394
|
|
|
361
395
|
**Import:**
|
|
396
|
+
|
|
362
397
|
```typescript
|
|
363
398
|
import UFOLabel from '@atlaskit/react-ufo/label';
|
|
364
399
|
```
|
|
365
400
|
|
|
366
401
|
**Usage:**
|
|
402
|
+
|
|
367
403
|
```typescript
|
|
368
404
|
<UFOLabel name="welcome_banner">
|
|
369
|
-
|
|
405
|
+
<Text>Hello folks</Text>
|
|
370
406
|
</UFOLabel>
|
|
371
407
|
```
|
|
372
408
|
|
|
373
409
|
**Example:**
|
|
410
|
+
|
|
374
411
|
```typescript
|
|
375
412
|
import UFOLabel from '@atlaskit/react-ufo/label';
|
|
376
413
|
|
|
377
414
|
function CommentThread() {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
415
|
+
return (
|
|
416
|
+
<UFOLabel name="comment_thread">
|
|
417
|
+
<div>
|
|
418
|
+
<UFOLabel name="comment_author">
|
|
419
|
+
<UserAvatar user={author} />
|
|
420
|
+
</UFOLabel>
|
|
421
|
+
<UFOLabel name="comment_content">
|
|
422
|
+
<CommentBody content={comment.body} />
|
|
423
|
+
</UFOLabel>
|
|
424
|
+
</div>
|
|
425
|
+
</UFOLabel>
|
|
426
|
+
);
|
|
390
427
|
}
|
|
391
428
|
```
|
|
392
429
|
|
|
393
430
|
**Guidelines:**
|
|
431
|
+
|
|
394
432
|
- Use static strings only - no dynamic values
|
|
395
433
|
- Focus on being distinct rather than semantically perfect
|
|
396
434
|
- No need to label every component - use judiciously
|
|
@@ -398,9 +436,11 @@ function CommentThread() {
|
|
|
398
436
|
|
|
399
437
|
### UFOLoadHold
|
|
400
438
|
|
|
401
|
-
Identifies loading elements and prevents interaction completion until they are no longer visible to
|
|
439
|
+
Identifies loading elements and prevents interaction completion until they are no longer visible to
|
|
440
|
+
the user. This component is crucial for accurate TTI (Time to Interactive) measurements.
|
|
402
441
|
|
|
403
442
|
**Import:**
|
|
443
|
+
|
|
404
444
|
```typescript
|
|
405
445
|
import UFOLoadHold from '@atlaskit/react-ufo/load-hold';
|
|
406
446
|
```
|
|
@@ -408,155 +448,169 @@ import UFOLoadHold from '@atlaskit/react-ufo/load-hold';
|
|
|
408
448
|
**Usage Patterns:**
|
|
409
449
|
|
|
410
450
|
**1. Wrapping loading elements:**
|
|
451
|
+
|
|
411
452
|
```typescript
|
|
412
453
|
if (isLoading) {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
454
|
+
return (
|
|
455
|
+
<UFOLoadHold name="card-loading">
|
|
456
|
+
<Skeleton />
|
|
457
|
+
</UFOLoadHold>
|
|
458
|
+
);
|
|
418
459
|
}
|
|
419
460
|
```
|
|
420
461
|
|
|
421
462
|
**2. As a sibling component:**
|
|
463
|
+
|
|
422
464
|
```typescript
|
|
423
465
|
return (
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
466
|
+
<>
|
|
467
|
+
<Skeleton />
|
|
468
|
+
<UFOLoadHold name="card-loading" />
|
|
469
|
+
</>
|
|
428
470
|
);
|
|
429
471
|
```
|
|
430
472
|
|
|
431
473
|
**3. Conditional wrapping:**
|
|
474
|
+
|
|
432
475
|
```typescript
|
|
433
476
|
<UFOLoadHold name="card" hold={isLoading}>
|
|
434
|
-
|
|
477
|
+
<Card />
|
|
435
478
|
</UFOLoadHold>
|
|
436
479
|
```
|
|
437
480
|
|
|
438
481
|
**4. Conditional sibling:**
|
|
482
|
+
|
|
439
483
|
```typescript
|
|
440
484
|
return (
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
485
|
+
<>
|
|
486
|
+
<Card />
|
|
487
|
+
<UFOLoadHold name="card" hold={isLoading} />
|
|
488
|
+
</>
|
|
445
489
|
);
|
|
446
490
|
```
|
|
447
491
|
|
|
448
492
|
**Parameters:**
|
|
493
|
+
|
|
449
494
|
- `name` (string): Unique identifier for this loading state
|
|
450
495
|
- `hold` (boolean, optional): Controls whether the hold is active
|
|
451
496
|
- `children` (ReactNode, optional): Components wrapped by the hold
|
|
452
497
|
|
|
453
498
|
**Real-world Examples:**
|
|
499
|
+
|
|
454
500
|
```typescript
|
|
455
501
|
import UFOLoadHold from '@atlaskit/react-ufo/load-hold';
|
|
456
502
|
|
|
457
503
|
// Loading skeleton
|
|
458
504
|
function IssueCard({ issue }) {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
505
|
+
if (!issue) {
|
|
506
|
+
return (
|
|
507
|
+
<UFOLoadHold name="issue-card-loading">
|
|
508
|
+
<IssueCardSkeleton />
|
|
509
|
+
</UFOLoadHold>
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return <IssueCardContent issue={issue} />;
|
|
468
514
|
}
|
|
469
515
|
|
|
470
516
|
// Conditional loading
|
|
471
517
|
function CommentsSection({ loading, comments }) {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
518
|
+
return (
|
|
519
|
+
<div>
|
|
520
|
+
<UFOLoadHold name="comments-loading" hold={loading} />
|
|
521
|
+
{loading ? <CommentsSkeleton /> : <CommentsList comments={comments} />}
|
|
522
|
+
</div>
|
|
523
|
+
);
|
|
478
524
|
}
|
|
479
525
|
```
|
|
480
526
|
|
|
481
527
|
**Notes:**
|
|
528
|
+
|
|
482
529
|
- Only instrument loading elements once if they're reused
|
|
483
530
|
- Multiple holds with the same name are fine
|
|
484
531
|
- Holds automatically release when component unmounts
|
|
485
532
|
|
|
486
533
|
### Suspense
|
|
487
534
|
|
|
488
|
-
An enhanced Suspense boundary that provides UFO instrumentation. Use this as a drop-in replacement
|
|
535
|
+
An enhanced Suspense boundary that provides UFO instrumentation. Use this as a drop-in replacement
|
|
536
|
+
for React's `Suspense` component.
|
|
489
537
|
|
|
490
538
|
**Import:**
|
|
539
|
+
|
|
491
540
|
```typescript
|
|
492
541
|
import Suspense from '@atlaskit/react-ufo/suspense';
|
|
493
542
|
```
|
|
494
543
|
|
|
495
544
|
**Usage:**
|
|
545
|
+
|
|
496
546
|
```typescript
|
|
497
547
|
<Suspense name="lazy-component" fallback={<Skeleton />}>
|
|
498
|
-
|
|
548
|
+
<LazyComponent />
|
|
499
549
|
</Suspense>
|
|
500
550
|
```
|
|
501
551
|
|
|
502
552
|
**Parameters:**
|
|
553
|
+
|
|
503
554
|
- `name` (string): Unique identifier for this suspense boundary
|
|
504
555
|
- `fallback` (ReactNode): Component to show while suspended
|
|
505
556
|
- `children` (ReactNode): Components that might suspend
|
|
506
557
|
|
|
507
558
|
**Example:**
|
|
559
|
+
|
|
508
560
|
```typescript
|
|
509
561
|
import Suspense from '@atlaskit/react-ufo/suspense';
|
|
510
562
|
|
|
511
563
|
const LazyIssueView = lazy(() => import('./IssueView'));
|
|
512
564
|
|
|
513
565
|
function App() {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
<LazyIssueView />
|
|
520
|
-
</Suspense>
|
|
521
|
-
);
|
|
566
|
+
return (
|
|
567
|
+
<Suspense name="issue-view-lazy" fallback={<IssueViewSkeleton />}>
|
|
568
|
+
<LazyIssueView />
|
|
569
|
+
</Suspense>
|
|
570
|
+
);
|
|
522
571
|
}
|
|
523
572
|
```
|
|
524
573
|
|
|
525
574
|
### UFOCustomData
|
|
526
575
|
|
|
527
|
-
Adds custom metadata to the current UFO interaction. This data appears in analytics events with a
|
|
576
|
+
Adds custom metadata to the current UFO interaction. This data appears in analytics events with a
|
|
577
|
+
"custom:" prefix.
|
|
528
578
|
|
|
529
579
|
**Import:**
|
|
580
|
+
|
|
530
581
|
```typescript
|
|
531
582
|
import UFOCustomData from '@atlaskit/react-ufo/custom-data';
|
|
532
583
|
```
|
|
533
584
|
|
|
534
585
|
**Usage:**
|
|
586
|
+
|
|
535
587
|
```typescript
|
|
536
588
|
<UFOCustomData data={{ viewType: 'list', itemCount: 42 }} />
|
|
537
589
|
```
|
|
538
590
|
|
|
539
591
|
**Example:**
|
|
592
|
+
|
|
540
593
|
```typescript
|
|
541
594
|
import UFOCustomData from '@atlaskit/react-ufo/custom-data';
|
|
542
595
|
|
|
543
596
|
function IssueList({ issues, viewType }) {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
597
|
+
const customData = {
|
|
598
|
+
viewType,
|
|
599
|
+
issueCount: issues.length,
|
|
600
|
+
hasFilters: filters.length > 0,
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
return (
|
|
604
|
+
<div>
|
|
605
|
+
<UFOCustomData data={customData} />
|
|
606
|
+
<IssueTable issues={issues} />
|
|
607
|
+
</div>
|
|
608
|
+
);
|
|
556
609
|
}
|
|
557
610
|
```
|
|
558
611
|
|
|
559
612
|
**Output in analytics:**
|
|
613
|
+
|
|
560
614
|
```javascript
|
|
561
615
|
{
|
|
562
616
|
'custom:viewType': 'list',
|
|
@@ -569,32 +623,36 @@ function IssueList({ issues, viewType }) {
|
|
|
569
623
|
|
|
570
624
|
**Note: This is an experimental feature that may change or be removed in future versions.**
|
|
571
625
|
|
|
572
|
-
Adds cohort-specific custom data to UFO interactions. This is used for A/B testing and feature flag
|
|
626
|
+
Adds cohort-specific custom data to UFO interactions. This is used for A/B testing and feature flag
|
|
627
|
+
correlation.
|
|
573
628
|
|
|
574
629
|
**Import:**
|
|
630
|
+
|
|
575
631
|
```typescript
|
|
576
632
|
import UFOCustomCohortData from '@atlaskit/react-ufo/custom-cohort-data';
|
|
577
633
|
```
|
|
578
634
|
|
|
579
635
|
**Usage:**
|
|
636
|
+
|
|
580
637
|
```typescript
|
|
581
638
|
<UFOCustomCohortData dataKey="experiment_variant" value="control" />
|
|
582
639
|
```
|
|
583
640
|
|
|
584
641
|
**Example:**
|
|
642
|
+
|
|
585
643
|
```typescript
|
|
586
644
|
import UFOCustomCohortData from '@atlaskit/react-ufo/custom-cohort-data';
|
|
587
645
|
import { fg } from '@atlassian/jira-feature-gating';
|
|
588
646
|
|
|
589
647
|
function ExperimentalFeature() {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
648
|
+
const variant = fg('new_issue_view') ? 'treatment' : 'control';
|
|
649
|
+
|
|
650
|
+
return (
|
|
651
|
+
<div>
|
|
652
|
+
<UFOCustomCohortData dataKey="new_issue_view_variant" value={variant} />
|
|
653
|
+
{variant === 'treatment' ? <NewIssueView /> : <OldIssueView />}
|
|
654
|
+
</div>
|
|
655
|
+
);
|
|
598
656
|
}
|
|
599
657
|
```
|
|
600
658
|
|
|
@@ -604,39 +662,48 @@ function ExperimentalFeature() {
|
|
|
604
662
|
|
|
605
663
|
### usePressTracing
|
|
606
664
|
|
|
607
|
-
A hook that provides a function to trace user press interactions. Use this for custom components
|
|
665
|
+
A hook that provides a function to trace user press interactions. Use this for custom components
|
|
666
|
+
that need interaction tracking.
|
|
608
667
|
|
|
609
668
|
**Import:**
|
|
669
|
+
|
|
610
670
|
```typescript
|
|
611
671
|
import usePressTracing from '@atlaskit/react-ufo/use-press-tracing';
|
|
612
672
|
```
|
|
613
673
|
|
|
614
674
|
**Usage:**
|
|
675
|
+
|
|
615
676
|
```typescript
|
|
616
677
|
const traceInteraction = usePressTracing('custom-button-click');
|
|
617
678
|
```
|
|
618
679
|
|
|
619
680
|
**Example:**
|
|
681
|
+
|
|
620
682
|
```typescript
|
|
621
683
|
import { useCallback } from 'react';
|
|
622
684
|
import usePressTracing from '@atlaskit/react-ufo/use-press-tracing';
|
|
623
685
|
|
|
624
686
|
function CustomButton({ onAction }) {
|
|
625
|
-
|
|
687
|
+
const traceInteraction = usePressTracing('custom-action');
|
|
626
688
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
689
|
+
const handleClick = useCallback(
|
|
690
|
+
(event) => {
|
|
691
|
+
traceInteraction(event.timeStamp);
|
|
692
|
+
onAction();
|
|
693
|
+
},
|
|
694
|
+
[traceInteraction, onAction],
|
|
695
|
+
);
|
|
631
696
|
|
|
632
|
-
|
|
697
|
+
return <button onClick={handleClick}>Custom Action</button>;
|
|
633
698
|
}
|
|
634
699
|
```
|
|
635
700
|
|
|
636
701
|
**Parameters:**
|
|
702
|
+
|
|
637
703
|
- `name` (string): The UFO interaction name
|
|
638
704
|
|
|
639
705
|
**Returns:**
|
|
706
|
+
|
|
640
707
|
- `traceInteraction` (function): Function to call when interaction starts
|
|
641
708
|
- `timeStamp` (number, optional): Custom timestamp
|
|
642
709
|
|
|
@@ -649,98 +716,105 @@ function CustomButton({ onAction }) {
|
|
|
649
716
|
Measures typing latency in input fields to track user experience during text input.
|
|
650
717
|
|
|
651
718
|
**Import:**
|
|
719
|
+
|
|
652
720
|
```typescript
|
|
653
721
|
import useUFOTypingPerformanceTracing from '@atlaskit/react-ufo/typing-performance-tracing';
|
|
654
722
|
```
|
|
655
723
|
|
|
656
724
|
**Usage:**
|
|
725
|
+
|
|
657
726
|
```typescript
|
|
658
727
|
const typingRef = useUFOTypingPerformanceTracing<HTMLInputElement>('text-input-typing');
|
|
659
728
|
```
|
|
660
729
|
|
|
661
730
|
**Example:**
|
|
731
|
+
|
|
662
732
|
```typescript
|
|
663
733
|
import useUFOTypingPerformanceTracing from '@atlaskit/react-ufo/typing-performance-tracing';
|
|
664
734
|
|
|
665
735
|
function CommentEditor() {
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
return (
|
|
671
|
-
<textarea
|
|
672
|
-
ref={typingRef}
|
|
673
|
-
placeholder="Write a comment..."
|
|
674
|
-
/>
|
|
675
|
-
);
|
|
736
|
+
const typingRef = useUFOTypingPerformanceTracing<HTMLTextAreaElement>('comment-editor-typing');
|
|
737
|
+
|
|
738
|
+
return <textarea ref={typingRef} placeholder="Write a comment..." />;
|
|
676
739
|
}
|
|
677
740
|
```
|
|
678
741
|
|
|
679
742
|
**Metrics Captured:**
|
|
743
|
+
|
|
680
744
|
- Minimum typing latency
|
|
681
|
-
- Maximum typing latency
|
|
745
|
+
- Maximum typing latency
|
|
682
746
|
- Average typing latency
|
|
683
747
|
- Key press count
|
|
684
748
|
- Count of key presses under 50ms
|
|
685
749
|
|
|
686
750
|
**Notes:**
|
|
751
|
+
|
|
687
752
|
- Starts measuring on first keypress
|
|
688
753
|
- Sends metrics after 2 seconds of inactivity
|
|
689
754
|
- Only character-producing keys are measured (not backspace, etc.)
|
|
690
755
|
|
|
691
756
|
### useUFOTransitionCompleter
|
|
692
757
|
|
|
693
|
-
Automatically completes UFO transitions when the component mounts. Use this in destination route
|
|
758
|
+
Automatically completes UFO transitions when the component mounts. Use this in destination route
|
|
759
|
+
components.
|
|
694
760
|
|
|
695
761
|
**Import:**
|
|
762
|
+
|
|
696
763
|
```typescript
|
|
697
764
|
import { useUFOTransitionCompleter } from '@atlaskit/react-ufo/trace-transition';
|
|
698
765
|
```
|
|
699
766
|
|
|
700
767
|
**Usage:**
|
|
768
|
+
|
|
701
769
|
```typescript
|
|
702
770
|
function MyPageComponent() {
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
771
|
+
useUFOTransitionCompleter();
|
|
772
|
+
|
|
773
|
+
return <div>Page content</div>;
|
|
706
774
|
}
|
|
707
775
|
```
|
|
708
776
|
|
|
709
777
|
**Example:**
|
|
778
|
+
|
|
710
779
|
```typescript
|
|
711
780
|
import { useUFOTransitionCompleter } from '@atlaskit/react-ufo/trace-transition';
|
|
712
781
|
|
|
713
782
|
function IssueViewPage() {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
783
|
+
// This will complete any active transition when the page renders
|
|
784
|
+
useUFOTransitionCompleter();
|
|
785
|
+
|
|
786
|
+
return (
|
|
787
|
+
<div>
|
|
788
|
+
<IssueHeader />
|
|
789
|
+
<IssueContent />
|
|
790
|
+
</div>
|
|
791
|
+
);
|
|
723
792
|
}
|
|
724
793
|
```
|
|
725
794
|
|
|
726
795
|
### useUFOReportError
|
|
727
796
|
|
|
728
|
-
A hook that provides a function to report errors within UFO interactions. This allows you to capture
|
|
797
|
+
A hook that provides a function to report errors within UFO interactions. This allows you to capture
|
|
798
|
+
and track errors that occur during performance-critical user flows.
|
|
729
799
|
|
|
730
800
|
**Import:**
|
|
801
|
+
|
|
731
802
|
```typescript
|
|
732
803
|
import { useUFOReportError } from '@atlaskit/react-ufo/report-error';
|
|
733
804
|
```
|
|
734
805
|
|
|
735
806
|
**Usage:**
|
|
807
|
+
|
|
736
808
|
```typescript
|
|
737
809
|
const reportError = useUFOReportError();
|
|
738
810
|
```
|
|
739
811
|
|
|
740
812
|
**Parameters:**
|
|
813
|
+
|
|
741
814
|
- None
|
|
742
815
|
|
|
743
816
|
**Returns:**
|
|
817
|
+
|
|
744
818
|
- `reportError` (function): Function to report errors to the current UFO interaction
|
|
745
819
|
- `error` (object): Error information with the following properties:
|
|
746
820
|
- `name` (string): Error name/type
|
|
@@ -751,58 +825,57 @@ const reportError = useUFOReportError();
|
|
|
751
825
|
- `errorStatusCode` (number, optional): HTTP status code if applicable
|
|
752
826
|
|
|
753
827
|
**Example:**
|
|
828
|
+
|
|
754
829
|
```typescript
|
|
755
830
|
import { useUFOReportError } from '@atlaskit/react-ufo/report-error';
|
|
756
831
|
|
|
757
832
|
function DataLoader({ issueId }) {
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
833
|
+
const reportError = useUFOReportError();
|
|
834
|
+
|
|
835
|
+
const loadIssueData = async () => {
|
|
836
|
+
try {
|
|
837
|
+
const issue = await fetchIssue(issueId);
|
|
838
|
+
return issue;
|
|
839
|
+
} catch (error) {
|
|
840
|
+
reportError({
|
|
841
|
+
name: 'IssueLoadError',
|
|
842
|
+
errorMessage: error.message,
|
|
843
|
+
errorStack: error.stack,
|
|
844
|
+
errorStatusCode: error.status,
|
|
845
|
+
});
|
|
846
|
+
throw error; // Re-throw for component error handling
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
// Component logic...
|
|
776
851
|
}
|
|
777
852
|
```
|
|
778
853
|
|
|
779
854
|
**Advanced Example with Context:**
|
|
855
|
+
|
|
780
856
|
```typescript
|
|
781
857
|
import { useUFOReportError } from '@atlaskit/react-ufo/report-error';
|
|
782
858
|
|
|
783
859
|
function SearchComponent() {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
<div>
|
|
799
|
-
{/* Search UI */}
|
|
800
|
-
</div>
|
|
801
|
-
);
|
|
860
|
+
const reportError = useUFOReportError();
|
|
861
|
+
|
|
862
|
+
const handleSearchError = (error, searchContext) => {
|
|
863
|
+
reportError({
|
|
864
|
+
name: 'SearchAPIError',
|
|
865
|
+
errorMessage: `Search failed: ${error.message}`,
|
|
866
|
+
errorStack: error.stack,
|
|
867
|
+
errorStatusCode: error.response?.status,
|
|
868
|
+
// Custom error hash for grouping similar errors
|
|
869
|
+
errorHash: `search-${searchContext.query}-${error.type}`,
|
|
870
|
+
});
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
return <div>{/* Search UI */}</div>;
|
|
802
874
|
}
|
|
803
875
|
```
|
|
804
876
|
|
|
805
877
|
**Notes:**
|
|
878
|
+
|
|
806
879
|
- Errors are automatically associated with the currently active UFO interaction
|
|
807
880
|
- If no interaction is active, errors are added to all active interactions
|
|
808
881
|
- Use meaningful error names for better categorization in analytics
|
|
@@ -817,27 +890,30 @@ function SearchComponent() {
|
|
|
817
890
|
Programmatically adds custom data to the current UFO interaction without using a component.
|
|
818
891
|
|
|
819
892
|
**Import:**
|
|
893
|
+
|
|
820
894
|
```typescript
|
|
821
895
|
import { addUFOCustomData } from '@atlaskit/react-ufo/custom-data';
|
|
822
896
|
```
|
|
823
897
|
|
|
824
898
|
**Usage:**
|
|
899
|
+
|
|
825
900
|
```typescript
|
|
826
901
|
addUFOCustomData({ key: 'value', count: 42 });
|
|
827
902
|
```
|
|
828
903
|
|
|
829
904
|
**Example:**
|
|
905
|
+
|
|
830
906
|
```typescript
|
|
831
907
|
import { addUFOCustomData } from '@atlaskit/react-ufo/custom-data';
|
|
832
908
|
|
|
833
909
|
function performSearch(query) {
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
910
|
+
addUFOCustomData({
|
|
911
|
+
searchQuery: query,
|
|
912
|
+
searchType: 'advanced',
|
|
913
|
+
hasFilters: filters.length > 0,
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
return searchAPI(query);
|
|
841
917
|
}
|
|
842
918
|
```
|
|
843
919
|
|
|
@@ -848,25 +924,28 @@ function performSearch(query) {
|
|
|
848
924
|
Programmatically adds cohort data for A/B testing and feature flag tracking.
|
|
849
925
|
|
|
850
926
|
**Import:**
|
|
927
|
+
|
|
851
928
|
```typescript
|
|
852
929
|
import { addUFOCustomCohortData } from '@atlaskit/react-ufo/custom-cohort-data';
|
|
853
930
|
```
|
|
854
931
|
|
|
855
932
|
**Usage:**
|
|
933
|
+
|
|
856
934
|
```typescript
|
|
857
935
|
addUFOCustomCohortData('feature_flag_key', true);
|
|
858
936
|
```
|
|
859
937
|
|
|
860
938
|
**Example:**
|
|
939
|
+
|
|
861
940
|
```typescript
|
|
862
941
|
import { addUFOCustomCohortData } from '@atlaskit/react-ufo/custom-cohort-data';
|
|
863
942
|
import { fg } from '@atlassian/jira-feature-gating';
|
|
864
943
|
|
|
865
944
|
function initializeExperiment() {
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
945
|
+
const isNewDesignEnabled = fg('new_design_system');
|
|
946
|
+
|
|
947
|
+
addUFOCustomCohortData('design_system_version', isNewDesignEnabled ? 'v2' : 'v1');
|
|
948
|
+
addUFOCustomCohortData('user_segment', getUserSegment());
|
|
870
949
|
}
|
|
871
950
|
```
|
|
872
951
|
|
|
@@ -875,29 +954,32 @@ function initializeExperiment() {
|
|
|
875
954
|
Records feature flag usage for correlation with performance metrics.
|
|
876
955
|
|
|
877
956
|
**Import:**
|
|
957
|
+
|
|
878
958
|
```typescript
|
|
879
959
|
import { addFeatureFlagAccessed } from '@atlaskit/react-ufo/feature-flags-accessed';
|
|
880
960
|
```
|
|
881
961
|
|
|
882
962
|
**Usage:**
|
|
963
|
+
|
|
883
964
|
```typescript
|
|
884
965
|
addFeatureFlagAccessed('feature_flag_name', flagValue);
|
|
885
966
|
```
|
|
886
967
|
|
|
887
968
|
**Example:**
|
|
969
|
+
|
|
888
970
|
```typescript
|
|
889
971
|
import { addFeatureFlagAccessed } from '@atlaskit/react-ufo/feature-flags-accessed';
|
|
890
972
|
import { fg } from '@atlassian/jira-feature-gating';
|
|
891
973
|
|
|
892
974
|
function checkFeatureFlag(flagKey) {
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
975
|
+
const flagValue = fg(flagKey);
|
|
976
|
+
|
|
977
|
+
// Record this flag access for performance correlation
|
|
978
|
+
if (shouldRecordFeatureFlagForUFO(flagKey)) {
|
|
979
|
+
addFeatureFlagAccessed(flagKey, flagValue);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
return flagValue;
|
|
901
983
|
}
|
|
902
984
|
```
|
|
903
985
|
|
|
@@ -908,32 +990,35 @@ function checkFeatureFlag(flagKey) {
|
|
|
908
990
|
Manually records an error for the current UFO interaction.
|
|
909
991
|
|
|
910
992
|
**Import:**
|
|
993
|
+
|
|
911
994
|
```typescript
|
|
912
995
|
import { setInteractionError } from '@atlaskit/react-ufo/set-interaction-error';
|
|
913
996
|
```
|
|
914
997
|
|
|
915
998
|
**Usage:**
|
|
999
|
+
|
|
916
1000
|
```typescript
|
|
917
|
-
setInteractionError('interaction-name', {
|
|
918
|
-
|
|
919
|
-
|
|
1001
|
+
setInteractionError('interaction-name', {
|
|
1002
|
+
errorMessage: 'Something went wrong',
|
|
1003
|
+
name: 'CustomError',
|
|
920
1004
|
});
|
|
921
1005
|
```
|
|
922
1006
|
|
|
923
1007
|
**Example:**
|
|
1008
|
+
|
|
924
1009
|
```typescript
|
|
925
1010
|
import { setInteractionError } from '@atlaskit/react-ufo/set-interaction-error';
|
|
926
1011
|
|
|
927
1012
|
function loadIssueData(issueId) {
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1013
|
+
try {
|
|
1014
|
+
return loadIssue(issueId);
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
setInteractionError('issue-view', {
|
|
1017
|
+
errorMessage: error.message,
|
|
1018
|
+
name: 'IssueLoadError',
|
|
1019
|
+
});
|
|
1020
|
+
throw error;
|
|
1021
|
+
}
|
|
937
1022
|
}
|
|
938
1023
|
```
|
|
939
1024
|
|
|
@@ -942,31 +1027,34 @@ function loadIssueData(issueId) {
|
|
|
942
1027
|
Extracts the UFO name from a route configuration object.
|
|
943
1028
|
|
|
944
1029
|
**Import:**
|
|
1030
|
+
|
|
945
1031
|
```typescript
|
|
946
1032
|
import getUFORouteName from '@atlaskit/react-ufo/route-name';
|
|
947
1033
|
```
|
|
948
1034
|
|
|
949
1035
|
**Usage:**
|
|
1036
|
+
|
|
950
1037
|
```typescript
|
|
951
1038
|
const ufoName = getUFORouteName(route);
|
|
952
1039
|
```
|
|
953
1040
|
|
|
954
1041
|
**Example:**
|
|
1042
|
+
|
|
955
1043
|
```typescript
|
|
956
1044
|
import getUFORouteName from '@atlaskit/react-ufo/route-name';
|
|
957
1045
|
import { useRouter } from '@atlassian/react-resource-router';
|
|
958
1046
|
|
|
959
1047
|
function RouteAnalytics() {
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1048
|
+
const [routerState] = useRouter();
|
|
1049
|
+
const ufoName = getUFORouteName(routerState.route);
|
|
1050
|
+
|
|
1051
|
+
useEffect(() => {
|
|
1052
|
+
if (ufoName) {
|
|
1053
|
+
console.log('Current UFO route:', ufoName);
|
|
1054
|
+
}
|
|
1055
|
+
}, [ufoName]);
|
|
1056
|
+
|
|
1057
|
+
return null;
|
|
970
1058
|
}
|
|
971
1059
|
```
|
|
972
1060
|
|
|
@@ -976,44 +1064,43 @@ function RouteAnalytics() {
|
|
|
976
1064
|
|
|
977
1065
|
### interactionName Property
|
|
978
1066
|
|
|
979
|
-
Many Atlassian Design System components support an `interactionName` prop that automatically adds
|
|
1067
|
+
Many Atlassian Design System components support an `interactionName` prop that automatically adds
|
|
1068
|
+
UFO tracking.
|
|
980
1069
|
|
|
981
1070
|
**Supported Components:**
|
|
982
1071
|
|
|
983
1072
|
**@atlaskit/button:**
|
|
1073
|
+
|
|
984
1074
|
```typescript
|
|
985
1075
|
import Button from '@atlaskit/button';
|
|
986
1076
|
|
|
987
|
-
<Button interactionName="save-issue">Save</Button
|
|
1077
|
+
<Button interactionName="save-issue">Save</Button>;
|
|
988
1078
|
```
|
|
989
1079
|
|
|
990
1080
|
**@atlaskit/spinner:**
|
|
1081
|
+
|
|
991
1082
|
```typescript
|
|
992
1083
|
import Spinner from '@atlaskit/spinner';
|
|
993
1084
|
|
|
994
|
-
<Spinner interactionName="loading-comments" size="medium"
|
|
1085
|
+
<Spinner interactionName="loading-comments" size="medium" />;
|
|
995
1086
|
```
|
|
996
1087
|
|
|
997
1088
|
**Examples:**
|
|
1089
|
+
|
|
998
1090
|
```typescript
|
|
999
1091
|
// Button with interaction tracking
|
|
1000
|
-
<Button
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
>
|
|
1004
|
-
Create Issue
|
|
1005
|
-
</Button>
|
|
1092
|
+
<Button interactionName="create-issue" onClick={handleCreateIssue}>
|
|
1093
|
+
Create Issue
|
|
1094
|
+
</Button>;
|
|
1006
1095
|
|
|
1007
1096
|
// Spinner that holds interaction until unmounted
|
|
1008
|
-
{
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
size="large"
|
|
1012
|
-
/>
|
|
1013
|
-
)}
|
|
1097
|
+
{
|
|
1098
|
+
isLoading && <Spinner interactionName="issue-loading" size="large" />;
|
|
1099
|
+
}
|
|
1014
1100
|
```
|
|
1015
1101
|
|
|
1016
1102
|
**Notes:**
|
|
1103
|
+
|
|
1017
1104
|
- Button: Triggers a UFO press interaction on click
|
|
1018
1105
|
- Spinner: Adds a UFO hold while the spinner is mounted
|
|
1019
1106
|
- Use descriptive, unique names for each interaction
|
|
@@ -1024,31 +1111,35 @@ import Spinner from '@atlaskit/spinner';
|
|
|
1024
1111
|
|
|
1025
1112
|
### SSR Configuration
|
|
1026
1113
|
|
|
1027
|
-
Configure UFO for Server-Side Rendering scenarios. This will produce a more accurate FMP Topline
|
|
1114
|
+
Configure UFO for Server-Side Rendering scenarios. This will produce a more accurate FMP Topline
|
|
1115
|
+
Metric in Glance.
|
|
1028
1116
|
|
|
1029
1117
|
**Import:**
|
|
1118
|
+
|
|
1030
1119
|
```typescript
|
|
1031
1120
|
import { configure } from '@atlaskit/react-ufo/ssr';
|
|
1032
1121
|
```
|
|
1033
1122
|
|
|
1034
1123
|
**Usage:**
|
|
1124
|
+
|
|
1035
1125
|
```typescript
|
|
1036
1126
|
configure({
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1127
|
+
getDoneMark: () => performance.now(),
|
|
1128
|
+
getFeatureFlags: () => ({ flag1: true, flag2: false }),
|
|
1129
|
+
getTimings: () => ({
|
|
1130
|
+
total: { startTime: 0, duration: 1200 },
|
|
1131
|
+
render: { startTime: 100, duration: 800 },
|
|
1132
|
+
}),
|
|
1133
|
+
getSsrPhaseSuccess: () => ({
|
|
1134
|
+
prefetch: true,
|
|
1135
|
+
earlyFlush: true,
|
|
1136
|
+
done: true,
|
|
1137
|
+
}),
|
|
1048
1138
|
});
|
|
1049
1139
|
```
|
|
1050
1140
|
|
|
1051
1141
|
**Configuration Options:**
|
|
1142
|
+
|
|
1052
1143
|
- `getDoneMark()`: Returns timestamp when SSR rendering completed
|
|
1053
1144
|
- `getFeatureFlags()`: Returns feature flags used during SSR
|
|
1054
1145
|
- `getTimings()`: Returns detailed SSR timing breakdown
|
|
@@ -1059,18 +1150,21 @@ configure({
|
|
|
1059
1150
|
Special segment type for tracking external or plugin content separately from first-party code.
|
|
1060
1151
|
|
|
1061
1152
|
**Import:**
|
|
1153
|
+
|
|
1062
1154
|
```typescript
|
|
1063
1155
|
import { UFOThirdPartySegment } from '@atlaskit/react-ufo/segment';
|
|
1064
1156
|
```
|
|
1065
1157
|
|
|
1066
1158
|
**Usage:**
|
|
1159
|
+
|
|
1067
1160
|
```typescript
|
|
1068
1161
|
<UFOThirdPartySegment name="external-calendar-widget">
|
|
1069
|
-
|
|
1162
|
+
<CalendarWidget />
|
|
1070
1163
|
</UFOThirdPartySegment>
|
|
1071
1164
|
```
|
|
1072
1165
|
|
|
1073
1166
|
**Use Cases:**
|
|
1167
|
+
|
|
1074
1168
|
- External service integrations
|
|
1075
1169
|
- Third-party plugins or widgets
|
|
1076
1170
|
- Partner-provided components
|
|
@@ -1081,12 +1175,14 @@ import { UFOThirdPartySegment } from '@atlaskit/react-ufo/segment';
|
|
|
1081
1175
|
## Best Practices
|
|
1082
1176
|
|
|
1083
1177
|
### Naming Conventions
|
|
1178
|
+
|
|
1084
1179
|
- Use descriptive, specific names: `issue-header` not `header`
|
|
1085
1180
|
- Use kebab-case for consistency
|
|
1086
1181
|
- Include context when helpful: `board-issue-card` vs `issue-card`
|
|
1087
1182
|
- Avoid dynamic values in names
|
|
1088
1183
|
|
|
1089
1184
|
### Performance Considerations
|
|
1185
|
+
|
|
1090
1186
|
- Don't over-instrument - focus on key user journeys
|
|
1091
1187
|
- Use segments for logical page sections (like header, content, sidebar)
|
|
1092
1188
|
- Be selective with custom data to avoid large payloads
|
|
@@ -1095,6 +1191,7 @@ import { UFOThirdPartySegment } from '@atlaskit/react-ufo/segment';
|
|
|
1095
1191
|
- Consider using UFOThirdPartySegment for external content that may impact performance
|
|
1096
1192
|
|
|
1097
1193
|
### Debugging
|
|
1194
|
+
|
|
1098
1195
|
- Use browser dev tools to inspect UFO events
|
|
1099
1196
|
- Check network tab for UFO payload requests (look for analytics events)
|
|
1100
1197
|
- Use UFO labels to identify problematic code areas
|
|
@@ -1105,6 +1202,7 @@ import { UFOThirdPartySegment } from '@atlaskit/react-ufo/segment';
|
|
|
1105
1202
|
- Pay attention to "holds" that never release - they prevent interactions from completing
|
|
1106
1203
|
|
|
1107
1204
|
### Migration Guide
|
|
1205
|
+
|
|
1108
1206
|
- Use `interactionName` props on ADS components when possible
|
|
1109
1207
|
- Migrate from manual event tracking to UFO components
|
|
1110
1208
|
- Consider SSR implications when adding new instrumentation
|
|
@@ -1114,52 +1212,55 @@ import { UFOThirdPartySegment } from '@atlaskit/react-ufo/segment';
|
|
|
1114
1212
|
## Common Patterns
|
|
1115
1213
|
|
|
1116
1214
|
### Page Load Tracking
|
|
1215
|
+
|
|
1117
1216
|
```typescript
|
|
1118
1217
|
function MyPage() {
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1218
|
+
useEffect(() => {
|
|
1219
|
+
traceUFOPageLoad('my-page');
|
|
1220
|
+
}, []);
|
|
1221
|
+
|
|
1222
|
+
return (
|
|
1223
|
+
<UFOSegment name="main-content">
|
|
1224
|
+
<PageContent />
|
|
1225
|
+
</UFOSegment>
|
|
1226
|
+
);
|
|
1128
1227
|
}
|
|
1129
1228
|
```
|
|
1130
1229
|
|
|
1131
1230
|
### Loading States
|
|
1231
|
+
|
|
1132
1232
|
```typescript
|
|
1133
1233
|
function DataComponent() {
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1234
|
+
const [loading, setLoading] = useState(true);
|
|
1235
|
+
|
|
1236
|
+
return (
|
|
1237
|
+
<UFOSegment name="data-section">
|
|
1238
|
+
{loading ? (
|
|
1239
|
+
<UFOLoadHold name="data-loading">
|
|
1240
|
+
<Skeleton />
|
|
1241
|
+
</UFOLoadHold>
|
|
1242
|
+
) : (
|
|
1243
|
+
<DataTable />
|
|
1244
|
+
)}
|
|
1245
|
+
</UFOSegment>
|
|
1246
|
+
);
|
|
1147
1247
|
}
|
|
1148
1248
|
```
|
|
1149
1249
|
|
|
1150
1250
|
### Navigation Tracking
|
|
1251
|
+
|
|
1151
1252
|
```typescript
|
|
1152
1253
|
function NavigationWrapper() {
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1254
|
+
const [routerState] = useRouter();
|
|
1255
|
+
|
|
1256
|
+
useEffect(() => {
|
|
1257
|
+
const ufoName = getUFORouteName(routerState.route);
|
|
1258
|
+
if (ufoName) {
|
|
1259
|
+
traceUFOTransition(ufoName);
|
|
1260
|
+
}
|
|
1261
|
+
}, [routerState]);
|
|
1262
|
+
|
|
1263
|
+
return <Router />;
|
|
1163
1264
|
}
|
|
1164
1265
|
```
|
|
1165
1266
|
|
|
@@ -1168,21 +1269,25 @@ function NavigationWrapper() {
|
|
|
1168
1269
|
### Common Issues
|
|
1169
1270
|
|
|
1170
1271
|
**Interaction Never Completes**
|
|
1272
|
+
|
|
1171
1273
|
- Check for holds that are never released (components that mount `UFOLoadHold` but never unmount)
|
|
1172
1274
|
- Verify all async operations complete properly
|
|
1173
1275
|
- Ensure error boundaries don't prevent holds from releasing
|
|
1174
1276
|
|
|
1175
1277
|
**Missing Analytics Events**
|
|
1278
|
+
|
|
1176
1279
|
- Verify UFO configuration is properly set up in your application
|
|
1177
1280
|
- Check that sampling rates are configured for your interaction names
|
|
1178
1281
|
- Ensure you're calling trace functions early in the component lifecycle
|
|
1179
1282
|
|
|
1180
1283
|
**Performance Impact**
|
|
1284
|
+
|
|
1181
1285
|
- Avoid over-instrumentation - focus on critical user journeys
|
|
1182
1286
|
- Use feature flags to control UFO instrumentation in production
|
|
1183
1287
|
- Monitor payload sizes if adding extensive custom data
|
|
1184
1288
|
|
|
1185
1289
|
**SSR Compatibility**
|
|
1290
|
+
|
|
1186
1291
|
- Use the SSR configuration APIs for server-side rendered applications
|
|
1187
1292
|
- Ensure UFO components handle server-side rendering gracefully
|
|
1188
1293
|
- Test SSR placeholder behavior with your specific setup
|