@atlaskit/react-ufo 5.1.2 → 5.1.4
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/AGENTS.md +426 -0
- package/CHANGELOG.md +16 -0
- package/dist/cjs/interaction-metrics/index.js +11 -3
- package/dist/cjs/set-terminal-error/index.js +11 -3
- package/dist/cjs/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.js +39 -27
- package/dist/cjs/vc/vc-observer-new/metric-calculator/fy25_03/index.js +1 -3
- package/dist/cjs/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/index.js +22 -7
- package/dist/es2019/interaction-metrics/index.js +10 -2
- package/dist/es2019/set-terminal-error/index.js +12 -4
- package/dist/es2019/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.js +17 -4
- package/dist/es2019/vc/vc-observer-new/metric-calculator/fy25_03/index.js +1 -3
- package/dist/es2019/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/index.js +22 -7
- package/dist/esm/interaction-metrics/index.js +10 -2
- package/dist/esm/set-terminal-error/index.js +12 -4
- package/dist/esm/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.js +39 -27
- package/dist/esm/vc/vc-observer-new/metric-calculator/fy25_03/index.js +1 -3
- package/dist/esm/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/index.js +22 -7
- package/dist/types/common/vc/types.d.ts +2 -0
- package/dist/types/create-terminal-error-payload/index.d.ts +4 -0
- package/dist/types/interaction-metrics/index.d.ts +8 -0
- package/dist/types/set-terminal-error/index.d.ts +4 -0
- package/dist/types/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/index.d.ts +3 -3
- package/dist/types/vc/vc-observer-new/metric-calculator/percentile-calc/types.d.ts +12 -0
- package/dist/types-ts4.5/common/vc/types.d.ts +2 -0
- package/dist/types-ts4.5/create-terminal-error-payload/index.d.ts +4 -0
- package/dist/types-ts4.5/interaction-metrics/index.d.ts +8 -0
- package/dist/types-ts4.5/set-terminal-error/index.d.ts +4 -0
- package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/index.d.ts +3 -3
- package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/percentile-calc/types.d.ts +12 -0
- package/package.json +4 -4
package/AGENTS.md
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
# AGENTS.md - React UFO (@atlaskit/react-ufo)
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
React UFO (User-Facing Observability) is a performance instrumentation library that captures key user experience metrics across Atlassian products. It produces structured performance events that are consumed by downstream analytics systems, primarily the **Glance (Performance Portal)** system.
|
|
6
|
+
|
|
7
|
+
## Team & Product Context
|
|
8
|
+
|
|
9
|
+
- **Team**: Frontend Observability (FeObs) — provides end-to-end solutions for frontend observability (performance + reliability)
|
|
10
|
+
- **Product**: Glance (formerly Performance Portal) — the one-stop shop for frontend performance data at Atlassian
|
|
11
|
+
- **Mission**: Make performance data available, accountable, and actionable
|
|
12
|
+
- **Support channel**: `#help-devinfra-fe-observability` (Slack)
|
|
13
|
+
- **Jira project**: AFO on `product-fabric.atlassian.net`
|
|
14
|
+
- **Shortlinks**: `go/glance`, `go/feo`
|
|
15
|
+
- **Package**: `@atlaskit/react-ufo`
|
|
16
|
+
|
|
17
|
+
## Architecture
|
|
18
|
+
|
|
19
|
+
### Local Package Architecture
|
|
20
|
+
|
|
21
|
+
```mermaid
|
|
22
|
+
graph TD
|
|
23
|
+
subgraph "React Application"
|
|
24
|
+
A[traceUFOPageLoad / traceUFOTransition / traceUFOPress] --> B[InteractionMetrics]
|
|
25
|
+
C[UFOSegment] --> B
|
|
26
|
+
D[UFOLoadHold] --> B
|
|
27
|
+
E[UFOLabel] --> B
|
|
28
|
+
F[Custom Data/Timings] --> B
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
subgraph "React UFO Core"
|
|
32
|
+
B --> G[VCObserver - Visual Completion]
|
|
33
|
+
B --> H[HoldTracking]
|
|
34
|
+
B --> I[ReactProfiler]
|
|
35
|
+
G --> J[createPayload]
|
|
36
|
+
H --> J
|
|
37
|
+
I --> J
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
subgraph "Output"
|
|
41
|
+
J --> K[GASv3 Analytics Events]
|
|
42
|
+
K --> L[Performance Portal / Databricks]
|
|
43
|
+
end
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### End-to-End Data Pipeline
|
|
47
|
+
|
|
48
|
+
React UFO is the **client-side instrumentation** in a larger data pipeline:
|
|
49
|
+
|
|
50
|
+
```mermaid
|
|
51
|
+
graph LR
|
|
52
|
+
subgraph "Client Instrumentation"
|
|
53
|
+
A[React UFO in Products]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
subgraph "Transport Layer"
|
|
57
|
+
B[GASv3 analytics-service]
|
|
58
|
+
C[StreamHub / Kinesis]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
subgraph "Event Processing - perf-portal-monorepo"
|
|
62
|
+
D[UFO Service - splits events]
|
|
63
|
+
E[BM Data Pipeline]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
subgraph "Data Storage"
|
|
67
|
+
F[Socrates - data lake]
|
|
68
|
+
G[Databricks - aggregation]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
subgraph "Data Serving"
|
|
72
|
+
H[Perf Portal GQL]
|
|
73
|
+
I[Glance UI]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
A --> B --> C --> D --> E --> F --> G --> H --> I
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Key Pipeline Details:**
|
|
80
|
+
- **UFO Service** splits root interactions from page segments (e.g., `jira.fe.page-load.issue-view` generates multiple `jira.fe.page-segment-load.*` events)
|
|
81
|
+
- **Databricks** has streaming (high-resolution) and daily aggregation jobs
|
|
82
|
+
- **External dependencies**: Socrates (data lake), Observo (high-cardinality data), SignalFx (metrics/alerting)
|
|
83
|
+
|
|
84
|
+
## Key Concepts
|
|
85
|
+
|
|
86
|
+
### Interaction Types
|
|
87
|
+
- **`page_load`**: Initial page rendering measurement
|
|
88
|
+
- **`transition`**: SPA navigation between routes
|
|
89
|
+
- **`press`**: User click/tap interactions
|
|
90
|
+
- **`hover`**: Mouse hover interactions
|
|
91
|
+
- **`typing`**: Keyboard input performance
|
|
92
|
+
|
|
93
|
+
### Core Metrics
|
|
94
|
+
- **TTI (Time to Interactive)**: Legacy metric from BM3 (Browser Metrics 3)
|
|
95
|
+
- **TTAI (Time to All Interactive)**: When all holds are released
|
|
96
|
+
- **TTVC (Time to Visual Completion)**: When visual changes stabilize at various thresholds (VC50, VC80, VC90, VC99)
|
|
97
|
+
- **FMP (First Meaningful Paint)**: When primary content is visible
|
|
98
|
+
- **Speed Index**: Weighted average of visual progress over time
|
|
99
|
+
|
|
100
|
+
### TTVC Revisions
|
|
101
|
+
The package supports multiple TTVC calculation algorithms:
|
|
102
|
+
- `fy25.01`, `fy25.02`: Legacy revisions
|
|
103
|
+
- `fy25.03`: Current default revision (`DEFAULT_TTVC_REVISION`)
|
|
104
|
+
- `fy26.04`, `next`: Experimental revisions
|
|
105
|
+
|
|
106
|
+
## Directory Structure
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
src/
|
|
110
|
+
├── trace-pageload/ # Page load interaction initialization
|
|
111
|
+
├── trace-transition/ # SPA transition tracking
|
|
112
|
+
├── trace-press/ # Press interaction tracking
|
|
113
|
+
├── trace-interaction/ # Generic interaction tracing
|
|
114
|
+
├── trace-redirect/ # Route redirect tracking
|
|
115
|
+
├── trace-hover/ # Hover interaction tracking
|
|
116
|
+
├── interaction-metrics/ # Core interaction state management
|
|
117
|
+
├── create-payload/ # Analytics payload generation
|
|
118
|
+
├── vc/ # Visual Completion observer system
|
|
119
|
+
│ ├── vc-observer/ # Legacy VC observer (fy25.01, fy25.02)
|
|
120
|
+
│ └── vc-observer-new/ # New VC observer (fy25.03+)
|
|
121
|
+
├── segment/ # UFOSegment component
|
|
122
|
+
├── load-hold/ # UFOLoadHold component
|
|
123
|
+
├── label/ # UFOLabel component
|
|
124
|
+
├── config/ # Configuration management
|
|
125
|
+
├── custom-data/ # Custom data injection
|
|
126
|
+
├── custom-cohort-data/ # Cohort data for A/B testing
|
|
127
|
+
├── custom-timings/ # Custom timing tracking
|
|
128
|
+
├── custom-spans/ # Custom span tracking
|
|
129
|
+
├── feature-flags-accessed/ # Feature flag correlation
|
|
130
|
+
├── ssr/ # SSR timing integration
|
|
131
|
+
├── suspense/ # Instrumented React Suspense
|
|
132
|
+
├── placeholder/ # Lazy loading placeholder integration
|
|
133
|
+
├── report-error/ # Error reporting utilities
|
|
134
|
+
├── set-interaction-error/ # Interaction error handling
|
|
135
|
+
├── set-terminal-error/ # Terminal error handling
|
|
136
|
+
└── common/ # Shared types and utilities
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Key Entry Points
|
|
140
|
+
|
|
141
|
+
### Interaction Initialization
|
|
142
|
+
- [`traceUFOPageLoad`](./src/trace-pageload/index.ts) - Start page load measurement
|
|
143
|
+
- [`traceUFOTransition`](./src/trace-transition/index.ts) - Start transition measurement
|
|
144
|
+
- [`traceUFOPress`](./src/trace-press/index.ts) - Start press interaction measurement
|
|
145
|
+
- [`traceUFOInteraction`](./src/trace-interaction/index.ts) - Generic interaction from browser events
|
|
146
|
+
- [`traceUFORedirect`](./src/trace-redirect/index.ts) - Track route redirects
|
|
147
|
+
|
|
148
|
+
### React Components
|
|
149
|
+
- [`UFOSegment`](./src/segment/segment.tsx) - Define measurable page sections
|
|
150
|
+
- [`UFOLoadHold`](./src/load-hold/UFOLoadHold.tsx) - Hold interaction completion during loading
|
|
151
|
+
- [`UFOLabel`](./src/label/UFOLabel.tsx) - Annotate component tree for debugging
|
|
152
|
+
- [`Suspense`](./src/suspense/Suspense.tsx) - Instrumented React Suspense boundary
|
|
153
|
+
- [`UFOCustomData`](./src/custom-data/index.ts) - Add custom data to interactions
|
|
154
|
+
- [`UFOCustomCohortData`](./src/custom-cohort-data/index.ts) - Add cohort data for experiments
|
|
155
|
+
|
|
156
|
+
### Configuration
|
|
157
|
+
- [`setUFOConfig`](./src/config/index.ts) - Configure UFO behavior, sampling rates, and features
|
|
158
|
+
|
|
159
|
+
### Payload Generation
|
|
160
|
+
- [`createPayloads`](./src/create-payload/index.ts) - Generate analytics payloads from interaction data
|
|
161
|
+
|
|
162
|
+
## Payload Schema
|
|
163
|
+
|
|
164
|
+
Events are sent with `experience:key` of `custom.interaction-metrics` and follow the schema defined in [`react-ufo-payload-schema.ts`](./src/common/react-ufo-payload-schema.ts).
|
|
165
|
+
|
|
166
|
+
Key payload fields:
|
|
167
|
+
- `experience:name` - The UFO interaction name (e.g., `issue-view`)
|
|
168
|
+
- `interactionMetrics.type` - Interaction type (page_load, transition, press, etc.)
|
|
169
|
+
- `interactionMetrics.start/end` - Timing boundaries
|
|
170
|
+
- `interactionMetrics.segments` - Measured page sections
|
|
171
|
+
- `interactionMetrics.holdInfo` - Loading hold timings
|
|
172
|
+
- `interactionMetrics.errors` - Captured errors during interaction
|
|
173
|
+
- `ufo:vc:rev` - Visual completion revision data
|
|
174
|
+
- `ufo:vc:ratios` - VC percentage at various thresholds
|
|
175
|
+
- `metric:ttai` - Time to All Interactive
|
|
176
|
+
- `metric:fp`, `metric:fcp`, `metric:lcp` - Paint metrics
|
|
177
|
+
|
|
178
|
+
## Downstream Consumer: Glance (Performance Portal)
|
|
179
|
+
|
|
180
|
+
The events produced by this package are consumed by the **Glance** system (https://bitbucket.org/atlassian/perf-portal-monorepo):
|
|
181
|
+
|
|
182
|
+
### perf-portal-monorepo Apps
|
|
183
|
+
|
|
184
|
+
| App | Purpose | Role in Pipeline |
|
|
185
|
+
|-----|---------|------------------|
|
|
186
|
+
| `apps/ufo` | UFO event processing service | Event Ingestion & Splitting |
|
|
187
|
+
| `apps/bm-data-pipeline` | Browser Metrics data pipeline | Event Processing (BM3) |
|
|
188
|
+
| `apps/databricks-jobs` | Data aggregation notebooks | Data Aggregation |
|
|
189
|
+
| `apps/perf-portal-gql` | GraphQL API server | Data Serving + Alerts |
|
|
190
|
+
| `apps/performance-portal` | React frontend (Glance UI) | Data Visualization |
|
|
191
|
+
|
|
192
|
+
### Event Flow
|
|
193
|
+
|
|
194
|
+
```mermaid
|
|
195
|
+
sequenceDiagram
|
|
196
|
+
participant App as Atlassian Product
|
|
197
|
+
participant UFO as React UFO
|
|
198
|
+
participant GAS as GASv3 Analytics
|
|
199
|
+
participant Stream as StreamHub/Kinesis
|
|
200
|
+
participant UFOSvc as UFO Service
|
|
201
|
+
participant DB as Databricks
|
|
202
|
+
participant GQL as Perf Portal GQL
|
|
203
|
+
participant UI as Glance UI
|
|
204
|
+
|
|
205
|
+
App->>UFO: traceUFOPageLoad('issue-view')
|
|
206
|
+
UFO->>UFO: Measure TTAI, TTVC
|
|
207
|
+
UFO->>GAS: Send interaction-metrics event
|
|
208
|
+
GAS->>Stream: Forward to Kinesis
|
|
209
|
+
Stream->>UFOSvc: Process event
|
|
210
|
+
UFOSvc->>UFOSvc: Split root + segments
|
|
211
|
+
UFOSvc->>DB: Streaming aggregation
|
|
212
|
+
DB->>DB: Daily aggregation
|
|
213
|
+
DB->>GQL: Query aggregated data
|
|
214
|
+
GQL->>UI: GraphQL response
|
|
215
|
+
UI->>UI: Display dashboards
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### What Glance Provides
|
|
219
|
+
|
|
220
|
+
1. **Topline Metrics**: FMP, TTVC, TTAI dashboards
|
|
221
|
+
2. **Experience Breakdown**: Per-route performance analysis
|
|
222
|
+
3. **Segment Analysis**: Component-level timing breakdown
|
|
223
|
+
4. **Regression Detection**: Automated performance alerts
|
|
224
|
+
5. **P-BREACH Tickets**: Automatic Jira tickets for regressions
|
|
225
|
+
6. **AI Investigation**: LLM-powered regression analysis
|
|
226
|
+
|
|
227
|
+
## Feature Flags
|
|
228
|
+
|
|
229
|
+
The package uses platform feature flags for gradual rollouts. Key flags are defined in [`package.json`](./package.json):
|
|
230
|
+
|
|
231
|
+
| Flag | Purpose |
|
|
232
|
+
|------|---------|
|
|
233
|
+
| `platform_ufo_enable_ttai_with_3p` | Third-party segment timing |
|
|
234
|
+
| `platform_ufo_enable_vc_raw_data` | Raw VC data inclusion |
|
|
235
|
+
| `platform_ufo_segment_critical_metrics` | Segment-level metrics |
|
|
236
|
+
| `platform_ufo_enable_terminal_errors` | Terminal error tracking |
|
|
237
|
+
| `platform_ufo_raw_data_thirdparty` | Third-party raw data behavior |
|
|
238
|
+
| `platform_ufo_enable_finish_interaction_transition` | Finish interaction on transition |
|
|
239
|
+
|
|
240
|
+
## Testing
|
|
241
|
+
|
|
242
|
+
### Unit Tests
|
|
243
|
+
```bash
|
|
244
|
+
cd platform && afm test unit packages/react-ufo/atlaskit/src/<test-file>
|
|
245
|
+
# Or run all:
|
|
246
|
+
cd platform && yarn test packages/react-ufo
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Integration Tests
|
|
250
|
+
```bash
|
|
251
|
+
# Terminal 1: Start dev server
|
|
252
|
+
cd platform && yarn start:rspack react-ufo
|
|
253
|
+
|
|
254
|
+
# Terminal 2: Run integration tests
|
|
255
|
+
cd platform && yarn test:integration packages/react-ufo/atlaskit/__tests__/ \
|
|
256
|
+
--retries 0 --reporter list --reuse-dev-server \
|
|
257
|
+
--project=desktop-chromium --max-failures=0
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Verifying Non-Flaky Tests
|
|
261
|
+
To simulate CI-like environment:
|
|
262
|
+
```bash
|
|
263
|
+
yarn test:integration packages/react-ufo/atlaskit/__tests__/playwright/base.spec.ts \
|
|
264
|
+
--retries 0 --reporter list --reuse-dev-server \
|
|
265
|
+
--project=desktop-chromium --max-failures=0 \
|
|
266
|
+
--repeat-each 50 --workers 50
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Integration tests are located in `__tests__/playwright/` and use example pages from `examples/`.
|
|
270
|
+
|
|
271
|
+
## Common Tasks
|
|
272
|
+
|
|
273
|
+
### Adding a New Interaction Type
|
|
274
|
+
1. Create trace function in `src/trace-{type}/`
|
|
275
|
+
2. Update `InteractionType` in common types
|
|
276
|
+
3. Add configuration support in `src/config/`
|
|
277
|
+
4. Update payload generation in `src/create-payload/`
|
|
278
|
+
|
|
279
|
+
### Modifying VC Calculation
|
|
280
|
+
1. VC observers are in `src/vc/vc-observer-new/` (current) and `src/vc/vc-observer/` (legacy)
|
|
281
|
+
2. Revisions are controlled by `enabledVCRevisions` config
|
|
282
|
+
3. Test changes against existing integration tests
|
|
283
|
+
|
|
284
|
+
### Adding Custom Metrics
|
|
285
|
+
1. Use `addUFOCustomData` for custom attributes
|
|
286
|
+
2. Use `addCustomTiming` for custom timing spans
|
|
287
|
+
3. Custom data appears in payload under `interactionMetrics.customData`
|
|
288
|
+
|
|
289
|
+
### Adding Feature Flags
|
|
290
|
+
1. Import `fg` from `@atlaskit/platform-feature-flags`
|
|
291
|
+
2. Register flag in `package.json` under `platform-feature-flags`
|
|
292
|
+
3. Put fg check as **last condition** in if statements: `if (a === b && fg('my_fg'))`
|
|
293
|
+
|
|
294
|
+
## Important Patterns
|
|
295
|
+
|
|
296
|
+
### Singleton Pattern
|
|
297
|
+
The package uses a singleton pattern for global state:
|
|
298
|
+
```typescript
|
|
299
|
+
// VCObserver is global per interaction
|
|
300
|
+
globalThis.__vcObserver = new VCObserverWrapper(opts);
|
|
301
|
+
|
|
302
|
+
// Config is module-level singleton
|
|
303
|
+
let config: Config | undefined;
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Hold System
|
|
307
|
+
Holds prevent interaction completion until all loading states resolve:
|
|
308
|
+
```typescript
|
|
309
|
+
// Hold is added when loading starts
|
|
310
|
+
const release = addHold(interactionId, labelStack, 'my-loading');
|
|
311
|
+
|
|
312
|
+
// Hold is released when loading completes
|
|
313
|
+
release();
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Segment Tree
|
|
317
|
+
Segments are tracked hierarchically for nested measurement:
|
|
318
|
+
```typescript
|
|
319
|
+
// Parent segment
|
|
320
|
+
<UFOSegment name="page">
|
|
321
|
+
// Child segment
|
|
322
|
+
<UFOSegment name="sidebar">
|
|
323
|
+
<Content />
|
|
324
|
+
</UFOSegment>
|
|
325
|
+
</UFOSegment>
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Third-Party Segments
|
|
329
|
+
For external/plugin content:
|
|
330
|
+
```typescript
|
|
331
|
+
import { UFOThirdPartySegment } from '@atlaskit/react-ufo/segment';
|
|
332
|
+
|
|
333
|
+
<UFOThirdPartySegment name="external-widget">
|
|
334
|
+
<ExternalWidget />
|
|
335
|
+
</UFOThirdPartySegment>
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Debugging
|
|
339
|
+
|
|
340
|
+
### Performance Tracing
|
|
341
|
+
Enable Chrome DevTools performance tracing:
|
|
342
|
+
```typescript
|
|
343
|
+
window.__REACT_UFO_ENABLE_PERF_TRACING = true;
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Event Inspection
|
|
347
|
+
Listen for interaction completion:
|
|
348
|
+
```typescript
|
|
349
|
+
window.addEventListener('UFO_FINISH_INTERACTION', (event) => {
|
|
350
|
+
console.log('UFO interaction finished:', event.detail);
|
|
351
|
+
});
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Chrome Extension
|
|
355
|
+
Use the **UFO Chrome Extension** (`apps/ufo-chrome-extension` in perf-portal-monorepo) for DevTools integration.
|
|
356
|
+
|
|
357
|
+
## Related Packages
|
|
358
|
+
|
|
359
|
+
| Package | Purpose |
|
|
360
|
+
|---------|---------|
|
|
361
|
+
| `@atlaskit/react-ufo` | Core UFO instrumentation (this package) |
|
|
362
|
+
| `ufo-load-hold` | Standalone load hold component |
|
|
363
|
+
| `ufo-apollo-log` | Apollo Client integration |
|
|
364
|
+
| `ufo-resource-router-log` | react-resource-router integration |
|
|
365
|
+
| `ufo-use-layout-effect-safe` | SSR-safe useLayoutEffect |
|
|
366
|
+
|
|
367
|
+
## Key Confluence References
|
|
368
|
+
|
|
369
|
+
| Topic | Link |
|
|
370
|
+
|-------|------|
|
|
371
|
+
| Team Home (FeObs) | `go/feo` |
|
|
372
|
+
| Glance Product Page | `go/glance` |
|
|
373
|
+
| Data Architecture Diagram | `go/glance-data-architecture` |
|
|
374
|
+
| UFO Pipeline Architecture | [UFO Design - M1 Pipeline Architecture](https://hello.atlassian.net/wiki/spaces/UFO/pages/1050787866) |
|
|
375
|
+
| Team Onboarding | [Onboarding](https://hello.atlassian.net/wiki/spaces/APD/pages/1386514416) |
|
|
376
|
+
| React UFO Onboarding | [React UFO and UFO Onboarding](https://hello.atlassian.net/wiki/spaces/APD/pages/3615063023) |
|
|
377
|
+
| React UFO v2 Design | [react-ufo UFO v2](https://hello.atlassian.net/wiki/spaces/UFO/pages/2305847386) |
|
|
378
|
+
| Performance Blog | [react-UFO: A deeper understanding of performance](https://hello.atlassian.net/wiki/spaces/UFO/blog/2022/12/16/2280380649) |
|
|
379
|
+
|
|
380
|
+
## Configuration Reference
|
|
381
|
+
|
|
382
|
+
Key configuration options in `setUFOConfig`:
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
interface Config {
|
|
386
|
+
product: string; // Product identifier (e.g., 'jira', 'confluence')
|
|
387
|
+
region: string; // Deployment region
|
|
388
|
+
population?: string; // Event population for segmentation
|
|
389
|
+
rates?: Record<string, number>; // Sampling rates per experience
|
|
390
|
+
kind?: Record<InteractionKind, number>; // Rates by interaction type
|
|
391
|
+
vc?: {
|
|
392
|
+
enabled?: boolean;
|
|
393
|
+
enabledVCRevisions?: {
|
|
394
|
+
all: TTVCRevision[];
|
|
395
|
+
byExperience?: Record<string, TTVCRevision[]>;
|
|
396
|
+
};
|
|
397
|
+
heatmapSize?: number;
|
|
398
|
+
selectorConfig?: SelectorConfig;
|
|
399
|
+
};
|
|
400
|
+
ssr?: {
|
|
401
|
+
getSSRTimings?: () => SSRTiming[];
|
|
402
|
+
getSSRDoneTime?: () => number | undefined;
|
|
403
|
+
};
|
|
404
|
+
postInteractionLog?: { enabled?: boolean; rates?: Rates; };
|
|
405
|
+
experimentalInteractionMetrics?: { enabled?: boolean; rates?: Rates; };
|
|
406
|
+
terminalErrors?: { enabled?: boolean; };
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## Contributing
|
|
411
|
+
|
|
412
|
+
When making changes:
|
|
413
|
+
1. Run unit tests: `cd platform && yarn test packages/react-ufo`
|
|
414
|
+
2. Run integration tests to verify VC measurement accuracy
|
|
415
|
+
3. Consider impact on downstream Glance/Performance Portal processing
|
|
416
|
+
4. Update sampling rates carefully as they affect data volume
|
|
417
|
+
5. Feature flag new functionality for gradual rollout
|
|
418
|
+
6. Coordinate with the FeObs team (`#help-devinfra-fe-observability`) for major changes
|
|
419
|
+
|
|
420
|
+
## Dangerous Operations — Never Do These
|
|
421
|
+
|
|
422
|
+
1. **Never modify sampling rates without approval** — affects data volume and costs
|
|
423
|
+
2. **Never change payload schema without coordination** — breaks downstream processing
|
|
424
|
+
3. **Never remove TTVC revisions** — experiences may depend on specific revisions
|
|
425
|
+
4. **Never disable holds without understanding impact** — affects TTAI accuracy
|
|
426
|
+
5. **Never add blocking operations in hot paths** — affects user performance
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @atlaskit/ufo-interaction-ignore
|
|
2
2
|
|
|
3
|
+
## 5.1.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`aa7b28d013b4f`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/aa7b28d013b4f) -
|
|
8
|
+
Add Speed Index metric using TTVC v4 ruleset
|
|
9
|
+
- [`e565e9abbe8fd`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/e565e9abbe8fd) -
|
|
10
|
+
Added Previous Interaction information to terminal error metric
|
|
11
|
+
|
|
12
|
+
## 5.1.3
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- [`376606c3c8197`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/376606c3c8197) -
|
|
17
|
+
FG cleanup - platform_ufo_enable_media_for_ttvc_v3
|
|
18
|
+
|
|
3
19
|
## 5.1.2
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
|
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
|
-
exports.ModuleLoadingProfiler = void 0;
|
|
7
|
+
exports.PreviousInteractionLog = exports.ModuleLoadingProfiler = void 0;
|
|
8
8
|
exports.abort = abort;
|
|
9
9
|
exports.abortAll = abortAll;
|
|
10
10
|
exports.abortByNewInteraction = abortByNewInteraction;
|
|
@@ -71,9 +71,12 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
|
|
|
71
71
|
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
|
|
72
72
|
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
73
73
|
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
|
|
74
|
-
var PreviousInteractionLog = {
|
|
74
|
+
var PreviousInteractionLog = exports.PreviousInteractionLog = {
|
|
75
|
+
id: undefined,
|
|
75
76
|
name: undefined,
|
|
76
|
-
|
|
77
|
+
type: undefined,
|
|
78
|
+
isAborted: undefined,
|
|
79
|
+
timestamp: undefined
|
|
77
80
|
};
|
|
78
81
|
var postInteractionLog = exports.postInteractionLog = new _postInteractionLog.default();
|
|
79
82
|
var interactionExtraMetrics = exports.interactionExtraMetrics = new _interactionExtraMetrics.default();
|
|
@@ -751,6 +754,11 @@ function finishInteraction(id, data) {
|
|
|
751
754
|
remove(id);
|
|
752
755
|
}
|
|
753
756
|
}
|
|
757
|
+
if ((0, _platformFeatureFlags.fg)('platform_ufo_enable_terminal_errors')) {
|
|
758
|
+
PreviousInteractionLog.id = data.id;
|
|
759
|
+
PreviousInteractionLog.type = data.type;
|
|
760
|
+
PreviousInteractionLog.timestamp = data.end;
|
|
761
|
+
}
|
|
754
762
|
PreviousInteractionLog.name = data.ufoName || 'unknown';
|
|
755
763
|
PreviousInteractionLog.isAborted = data.abortReason != null;
|
|
756
764
|
if (data.ufoName) {
|
|
@@ -18,18 +18,26 @@ function sinkTerminalErrorHandler(fn) {
|
|
|
18
18
|
sinkHandlerFn = fn;
|
|
19
19
|
}
|
|
20
20
|
function setTerminalError(error, additionalAttributes, labelStack) {
|
|
21
|
-
var _activeInteraction$uf, _activeInteraction$id, _activeInteraction$ty;
|
|
21
|
+
var _activeInteraction$uf, _activeInteraction$id, _activeInteraction$ty, _PreviousInteractionL, _PreviousInteractionL2, _PreviousInteractionL3;
|
|
22
22
|
var activeInteraction = (0, _interactionMetrics.getActiveInteraction)();
|
|
23
|
+
var currentTime = performance.now();
|
|
23
24
|
var errorData = _objectSpread({
|
|
24
25
|
errorType: error.name || 'Error',
|
|
25
26
|
errorMessage: error.message.slice(0, 100),
|
|
26
|
-
timestamp:
|
|
27
|
+
timestamp: currentTime
|
|
27
28
|
}, additionalAttributes);
|
|
29
|
+
|
|
30
|
+
// Calculate time since previous interaction
|
|
31
|
+
var timeSincePreviousInteraction = _interactionMetrics.PreviousInteractionLog.timestamp != null ? currentTime - _interactionMetrics.PreviousInteractionLog.timestamp : null;
|
|
28
32
|
var context = {
|
|
29
33
|
labelStack: labelStack !== null && labelStack !== void 0 ? labelStack : null,
|
|
30
34
|
activeInteractionName: (_activeInteraction$uf = activeInteraction === null || activeInteraction === void 0 ? void 0 : activeInteraction.ufoName) !== null && _activeInteraction$uf !== void 0 ? _activeInteraction$uf : null,
|
|
31
35
|
activeInteractionId: (_activeInteraction$id = activeInteraction === null || activeInteraction === void 0 ? void 0 : activeInteraction.id) !== null && _activeInteraction$id !== void 0 ? _activeInteraction$id : null,
|
|
32
|
-
activeInteractionType: (_activeInteraction$ty = activeInteraction === null || activeInteraction === void 0 ? void 0 : activeInteraction.type) !== null && _activeInteraction$ty !== void 0 ? _activeInteraction$ty : null
|
|
36
|
+
activeInteractionType: (_activeInteraction$ty = activeInteraction === null || activeInteraction === void 0 ? void 0 : activeInteraction.type) !== null && _activeInteraction$ty !== void 0 ? _activeInteraction$ty : null,
|
|
37
|
+
previousInteractionId: (_PreviousInteractionL = _interactionMetrics.PreviousInteractionLog.id) !== null && _PreviousInteractionL !== void 0 ? _PreviousInteractionL : null,
|
|
38
|
+
previousInteractionName: (_PreviousInteractionL2 = _interactionMetrics.PreviousInteractionLog.name) !== null && _PreviousInteractionL2 !== void 0 ? _PreviousInteractionL2 : null,
|
|
39
|
+
previousInteractionType: (_PreviousInteractionL3 = _interactionMetrics.PreviousInteractionLog.type) !== null && _PreviousInteractionL3 !== void 0 ? _PreviousInteractionL3 : null,
|
|
40
|
+
timeSincePreviousInteraction: timeSincePreviousInteraction
|
|
33
41
|
};
|
|
34
42
|
sinkHandlerFn(errorData, context);
|
|
35
43
|
}
|
|
@@ -120,13 +120,14 @@ var AbstractVCCalculatorBase = exports.default = /*#__PURE__*/function () {
|
|
|
120
120
|
value: function () {
|
|
121
121
|
var _calculateWithDebugInfo = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(filteredEntries, startTime, stopTime, isPostInteraction, isVCClean, interactionType, isPageVisible, interactionId, dirtyReason, allEntries, include3p, excludeSmartAnswersInSearch, interactionAbortReason, includeSSRRatio) {
|
|
122
122
|
var _window, _window2, _window3, _window4, _window6;
|
|
123
|
-
var percentiles, viewportEntries, vcLogs, vcDetails, percentileIndex, entryDataBuffer, ssrRatio, _iterator4, _step4, _entry3, time, viewportPercentage, entries, elementNames, previousResult, i, percentile, enhancedVcLogs, shouldCalculate3p, shouldCalculateDebugDetails, sortedVcLogs, maxViewportPercentageAtTime, maxSoFar, _iterator5, _step5, log, getBiggestPreviousViewportPercentage, ignoredEntriesByTime, _iterator6, _step6, _entry4, _ignoredEntriesByTime, _viewportData$rect, _viewportData$previou, viewportData, timestamp, additionalVcLogs, _iterator7, _step7, _step7$value, _timestamp, ignoredEntries, _viewportPercentage, v3RevisionDebugDetails, _window5, _window5$__ufo_devtoo, _window7, _window7$__on_ufo_vc_;
|
|
123
|
+
var percentiles, viewportEntries, shouldCalculateSpeedIndex, _yield$calculateTTVCP, vcLogs, speedIndex, vcDetails, percentileIndex, entryDataBuffer, ssrRatio, _iterator4, _step4, _entry3, time, viewportPercentage, entries, elementNames, previousResult, i, percentile, enhancedVcLogs, shouldCalculate3p, shouldCalculateDebugDetails, sortedVcLogs, maxViewportPercentageAtTime, maxSoFar, _iterator5, _step5, log, getBiggestPreviousViewportPercentage, ignoredEntriesByTime, _iterator6, _step6, _entry4, _ignoredEntriesByTime, _viewportData$rect, _viewportData$previou, viewportData, timestamp, additionalVcLogs, _iterator7, _step7, _step7$value, _timestamp, ignoredEntries, _viewportPercentage, v3RevisionDebugDetails, _window5, _window5$__ufo_devtoo, _window7, _window7$__on_ufo_vc_;
|
|
124
124
|
return _regenerator.default.wrap(function _callee$(_context) {
|
|
125
125
|
while (1) switch (_context.prev = _context.next) {
|
|
126
126
|
case 0:
|
|
127
127
|
percentiles = [25, 50, 75, 80, 85, 90, 95, 98, 99, 100];
|
|
128
128
|
viewportEntries = this.filterViewportEntries(filteredEntries);
|
|
129
|
-
|
|
129
|
+
shouldCalculateSpeedIndex = (0, _platformFeatureFlags.fg)('platform_ufo_ttvc_v4_speed_index');
|
|
130
|
+
_context.next = 5;
|
|
130
131
|
return (0, _percentileCalc.calculateTTVCPercentilesWithDebugInfo)({
|
|
131
132
|
viewport: {
|
|
132
133
|
width: (0, _getViewportWidth.default)(),
|
|
@@ -134,24 +135,27 @@ var AbstractVCCalculatorBase = exports.default = /*#__PURE__*/function () {
|
|
|
134
135
|
},
|
|
135
136
|
startTime: startTime,
|
|
136
137
|
stopTime: stopTime,
|
|
137
|
-
orderedEntries: viewportEntries
|
|
138
|
+
orderedEntries: viewportEntries,
|
|
139
|
+
calculateSpeedIndex: shouldCalculateSpeedIndex
|
|
138
140
|
});
|
|
139
|
-
case
|
|
140
|
-
|
|
141
|
+
case 5:
|
|
142
|
+
_yield$calculateTTVCP = _context.sent;
|
|
143
|
+
vcLogs = _yield$calculateTTVCP.entries;
|
|
144
|
+
speedIndex = _yield$calculateTTVCP.speedIndex;
|
|
141
145
|
vcDetails = {};
|
|
142
146
|
percentileIndex = 0;
|
|
143
147
|
entryDataBuffer = new Set();
|
|
144
148
|
ssrRatio = -1;
|
|
145
149
|
if (!vcLogs) {
|
|
146
|
-
_context.next =
|
|
150
|
+
_context.next = 33;
|
|
147
151
|
break;
|
|
148
152
|
}
|
|
149
153
|
_iterator4 = _createForOfIteratorHelper(vcLogs);
|
|
150
|
-
_context.prev =
|
|
154
|
+
_context.prev = 14;
|
|
151
155
|
_iterator4.s();
|
|
152
|
-
case
|
|
156
|
+
case 16:
|
|
153
157
|
if ((_step4 = _iterator4.n()).done) {
|
|
154
|
-
_context.next =
|
|
158
|
+
_context.next = 25;
|
|
155
159
|
break;
|
|
156
160
|
}
|
|
157
161
|
_entry3 = _step4.value;
|
|
@@ -164,11 +168,11 @@ var AbstractVCCalculatorBase = exports.default = /*#__PURE__*/function () {
|
|
|
164
168
|
|
|
165
169
|
// Only process entries if we haven't reached all percentiles
|
|
166
170
|
if (!(percentileIndex >= percentiles.length)) {
|
|
167
|
-
_context.next =
|
|
171
|
+
_context.next = 22;
|
|
168
172
|
break;
|
|
169
173
|
}
|
|
170
|
-
return _context.abrupt("break",
|
|
171
|
-
case
|
|
174
|
+
return _context.abrupt("break", 25);
|
|
175
|
+
case 22:
|
|
172
176
|
// Check if this entry matches any checkpoint percentiles
|
|
173
177
|
if (viewportPercentage >= percentiles[percentileIndex]) {
|
|
174
178
|
elementNames = [];
|
|
@@ -199,21 +203,21 @@ var AbstractVCCalculatorBase = exports.default = /*#__PURE__*/function () {
|
|
|
199
203
|
return entryDataBuffer.add(e);
|
|
200
204
|
});
|
|
201
205
|
}
|
|
202
|
-
case
|
|
203
|
-
_context.next =
|
|
206
|
+
case 23:
|
|
207
|
+
_context.next = 16;
|
|
204
208
|
break;
|
|
205
|
-
case
|
|
206
|
-
_context.next =
|
|
209
|
+
case 25:
|
|
210
|
+
_context.next = 30;
|
|
207
211
|
break;
|
|
208
|
-
case 24:
|
|
209
|
-
_context.prev = 24;
|
|
210
|
-
_context.t0 = _context["catch"](11);
|
|
211
|
-
_iterator4.e(_context.t0);
|
|
212
212
|
case 27:
|
|
213
213
|
_context.prev = 27;
|
|
214
|
-
|
|
215
|
-
|
|
214
|
+
_context.t0 = _context["catch"](14);
|
|
215
|
+
_iterator4.e(_context.t0);
|
|
216
216
|
case 30:
|
|
217
|
+
_context.prev = 30;
|
|
218
|
+
_iterator4.f();
|
|
219
|
+
return _context.finish(30);
|
|
220
|
+
case 33:
|
|
217
221
|
// Fill in any missing percentiles with the last known values
|
|
218
222
|
previousResult = {
|
|
219
223
|
t: 0,
|
|
@@ -373,13 +377,14 @@ var AbstractVCCalculatorBase = exports.default = /*#__PURE__*/function () {
|
|
|
373
377
|
}
|
|
374
378
|
return _context.abrupt("return", {
|
|
375
379
|
vcDetails: vcDetails,
|
|
376
|
-
ssrRatio: ssrRatio
|
|
380
|
+
ssrRatio: ssrRatio,
|
|
381
|
+
speedIndex: speedIndex
|
|
377
382
|
});
|
|
378
|
-
case
|
|
383
|
+
case 45:
|
|
379
384
|
case "end":
|
|
380
385
|
return _context.stop();
|
|
381
386
|
}
|
|
382
|
-
}, _callee, this, [[
|
|
387
|
+
}, _callee, this, [[14, 27, 30, 33]]);
|
|
383
388
|
}));
|
|
384
389
|
function calculateWithDebugInfo(_x, _x2, _x3, _x4, _x5, _x6, _x7, _x8, _x9, _x0, _x1, _x10, _x11, _x12) {
|
|
385
390
|
return _calculateWithDebugInfo.apply(this, arguments);
|
|
@@ -393,7 +398,7 @@ var AbstractVCCalculatorBase = exports.default = /*#__PURE__*/function () {
|
|
|
393
398
|
var _this = this,
|
|
394
399
|
_vcDetails$90$t,
|
|
395
400
|
_vcDetails$;
|
|
396
|
-
var startTime, stopTime, orderedEntries, interactionId, isPostInteraction, include3p, excludeSmartAnswersInSearch, includeSSRRatio, interactionType, isPageVisible, interactionAbortReason, filteredEntries, isVCClean, dirtyReason, getVCCleanStatusResult, _yield$this$calculate, vcDetails, ssrRatio, result;
|
|
401
|
+
var startTime, stopTime, orderedEntries, interactionId, isPostInteraction, include3p, excludeSmartAnswersInSearch, includeSSRRatio, interactionType, isPageVisible, interactionAbortReason, filteredEntries, isVCClean, dirtyReason, getVCCleanStatusResult, _yield$this$calculate, vcDetails, ssrRatio, speedIndex, result;
|
|
397
402
|
return _regenerator.default.wrap(function _callee2$(_context2) {
|
|
398
403
|
while (1) switch (_context2.prev = _context2.next) {
|
|
399
404
|
case 0:
|
|
@@ -422,6 +427,7 @@ var AbstractVCCalculatorBase = exports.default = /*#__PURE__*/function () {
|
|
|
422
427
|
_yield$this$calculate = _context2.sent;
|
|
423
428
|
vcDetails = _yield$this$calculate.vcDetails;
|
|
424
429
|
ssrRatio = _yield$this$calculate.ssrRatio;
|
|
430
|
+
speedIndex = _yield$this$calculate.speedIndex;
|
|
425
431
|
result = {
|
|
426
432
|
revision: this.revisionNo,
|
|
427
433
|
clean: true,
|
|
@@ -432,9 +438,15 @@ var AbstractVCCalculatorBase = exports.default = /*#__PURE__*/function () {
|
|
|
432
438
|
if (ssrRatio !== -1) {
|
|
433
439
|
result.ssrRatio = ssrRatio;
|
|
434
440
|
}
|
|
441
|
+
|
|
442
|
+
// speedIndex is only calculated when platform_ufo_ttvc_v4_speed_index is enabled,
|
|
443
|
+
// so we only include it in the result when it has a meaningful value (> 0)
|
|
444
|
+
if (speedIndex > 0) {
|
|
445
|
+
result.speedIndex = speedIndex;
|
|
446
|
+
}
|
|
435
447
|
result.labelStacks = this.getLabelStacks(filteredEntries, isPostInteraction);
|
|
436
448
|
return _context2.abrupt("return", result);
|
|
437
|
-
case
|
|
449
|
+
case 19:
|
|
438
450
|
case "end":
|
|
439
451
|
return _context2.stop();
|
|
440
452
|
}
|
|
@@ -34,9 +34,7 @@ var getConsideredEntryTypes = function getConsideredEntryTypes(include3p, exclud
|
|
|
34
34
|
entryTypes.push('mutation:smart-answers-element');
|
|
35
35
|
entryTypes.push('mutation:smart-answers-attribute');
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
entryTypes.push('mutation:media');
|
|
39
|
-
}
|
|
37
|
+
entryTypes.push('mutation:media');
|
|
40
38
|
|
|
41
39
|
// Still included as part of TTVC v3
|
|
42
40
|
entryTypes.push('mutation:attribute:non-visual-input-name');
|