@atlaskit/react-ufo 5.1.3 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +426 -0
- package/CHANGELOG.md +22 -0
- package/dist/cjs/create-payload/index.js +3 -3
- package/dist/cjs/hidden-timing/index.js +135 -0
- package/dist/cjs/interaction-metrics/index.js +11 -3
- package/dist/cjs/interaction-metrics-init/index.js +3 -0
- 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/percentile-calc/canvas-heatmap/index.js +22 -7
- package/dist/es2019/create-payload/index.js +3 -3
- package/dist/es2019/hidden-timing/index.js +128 -0
- package/dist/es2019/interaction-metrics/index.js +10 -2
- package/dist/es2019/interaction-metrics-init/index.js +4 -1
- 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/percentile-calc/canvas-heatmap/index.js +22 -7
- package/dist/esm/create-payload/index.js +4 -4
- package/dist/esm/hidden-timing/index.js +130 -0
- package/dist/esm/interaction-metrics/index.js +10 -2
- package/dist/esm/interaction-metrics-init/index.js +4 -1
- 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/percentile-calc/canvas-heatmap/index.js +22 -7
- package/dist/types/common/react-ufo-payload-schema.d.ts +1 -0
- 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/hidden-timing/index.d.ts +42 -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/react-ufo-payload-schema.d.ts +1 -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/hidden-timing/index.d.ts +42 -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 +10 -1
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,27 @@
|
|
|
1
1
|
# @atlaskit/ufo-interaction-ignore
|
|
2
2
|
|
|
3
|
+
## 5.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`b7f9d9f2e93dc`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/b7f9d9f2e93dc) -
|
|
8
|
+
Detect browser throttling in UFO client
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- [`03e2c7f2a7b38`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/03e2c7f2a7b38) -
|
|
13
|
+
Remove `featureFlags` field in the list of fields trimmed in the event of payload size exceeding
|
|
14
|
+
240KB
|
|
15
|
+
|
|
16
|
+
## 5.1.4
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- [`aa7b28d013b4f`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/aa7b28d013b4f) -
|
|
21
|
+
Add Speed Index metric using TTVC v4 ruleset
|
|
22
|
+
- [`e565e9abbe8fd`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/e565e9abbe8fd) -
|
|
23
|
+
Added Previous Interaction information to terminal error metric
|
|
24
|
+
|
|
3
25
|
## 5.1.3
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
|
@@ -9,13 +9,13 @@ try{return acc+item.cssRules.length;}catch(_unused3){return acc;}},0);var styleE
|
|
|
9
9
|
getInitialPageLoadSSRMetrics=function getInitialPageLoadSSRMetrics(){var _config$ssr;if(!isPageLoad){return{};}var config=(0,_config.getConfig)();var SSRDoneTimeValue=(0,_getSsrDoneTimeValue.default)(config);var SSRDoneTime=SSRDoneTimeValue!==undefined?{SSRDoneTime:Math.round(SSRDoneTimeValue)}:{};var isBM3ConfigSSRDoneAsFmp=interaction.metaData.__legacy__bm3ConfigSSRDoneAsFmp;var isUFOConfigSSRDoneAsFmp=interaction.metaData.__legacy__bm3ConfigSSRDoneAsFmp||!!(config!==null&&config!==void 0&&(_config$ssr=config.ssr)!==null&&_config$ssr!==void 0&&_config$ssr.getSSRDoneTime);if(!experimental&&(isBM3ConfigSSRDoneAsFmp||isUFOConfigSSRDoneAsFmp)&&SSRDoneTimeValue!==undefined){try{performance.mark("FMP",{startTime:SSRDoneTimeValue,detail:{devtools:{dataType:'marker'}}});}catch(_unused6){}}return _objectSpread(_objectSpread({},SSRDoneTime),{},{isBM3ConfigSSRDoneAsFmp:isBM3ConfigSSRDoneAsFmp,isUFOConfigSSRDoneAsFmp:isUFOConfigSSRDoneAsFmp});};pageLoadInteractionMetrics=getInitialPageLoadSSRMetrics();// Detailed payload. Page visibility = visible
|
|
10
10
|
getDetailedInteractionMetrics=function getDetailedInteractionMetrics(resourceTimings){if(experimental||window.__UFO_COMPACT_PAYLOAD__||!isDetailedPayload){return{};}var spans=[].concat((0,_toConsumableArray2.default)(interaction.spans),(0,_toConsumableArray2.default)(_interactionMetrics.interactionSpans));_interactionMetrics.interactionSpans.length=0;var shouldInclude3pHolds=(0,_config.shouldUseRawDataThirdPartyBehavior)(ufoName,type);var basePayload={errors:interaction.errors.map(function(_ref2){var labelStack=_ref2.labelStack,others=(0,_objectWithoutProperties2.default)(_ref2,_excluded);return _objectSpread(_objectSpread({},others),{},{labelStack:labelStack&&(0,_utils.optimizeLabelStack)(labelStack,(0,_getReactUfoPayloadVersion.getReactUFOPayloadVersion)(interaction.type))});}),holdActive:(0,_toConsumableArray2.default)(interaction.holdActive.values()),redirects:optimizeRedirects(interaction.redirects,start),holdInfo:(0,_optimizeHoldInfo.optimizeHoldInfo)(experimental?interaction.holdExpInfo:interaction.holdInfo,start,(0,_getReactUfoPayloadVersion.getReactUFOPayloadVersion)(interaction.type)),spans:(0,_optimizeSpans.optimizeSpans)(spans,start,(0,_getReactUfoPayloadVersion.getReactUFOPayloadVersion)(interaction.type)),requestInfo:(0,_optimizeRequestInfo.optimizeRequestInfo)(interaction.requestInfo,start,(0,_getReactUfoPayloadVersion.getReactUFOPayloadVersion)(interaction.type)),customTimings:(0,_optimizeCustomTimings.optimizeCustomTimings)(interaction.customTimings,start),bundleEvalTimings:objectToArray(getBundleEvalTimings(start)),resourceTimings:objectToArray(resourceTimings)};// Include third-party holds when feature flag is active
|
|
11
11
|
if(shouldInclude3pHolds){var _interaction$hold3pIn;return _objectSpread(_objectSpread({},basePayload),{},{hold3pActive:interaction.hold3pActive?(0,_toConsumableArray2.default)(interaction.hold3pActive.values()):[],hold3pInfo:(0,_optimizeHoldInfo.optimizeHoldInfo)((_interaction$hold3pIn=interaction.hold3pInfo)!==null&&_interaction$hold3pIn!==void 0?_interaction$hold3pIn:[],start,(0,_getReactUfoPayloadVersion.getReactUFOPayloadVersion)(interaction.type))});}return basePayload;};// Page load & detailed payload
|
|
12
|
-
getPageLoadDetailedInteractionMetrics=function getPageLoadDetailedInteractionMetrics(){var _config$ssr2,_config$ssr2$getSSRTi;if(!isPageLoad||!isDetailedPayload){return{};}var initialPageLoadExtraTimings=objectToArray(initialPageLoadExtraTiming.getTimings());var config=(0,_config.getConfig)();var defaultSSRTimings=objectToArray(ssr.getSSRTimings());var ssrTimingsFromConfig=config===null||config===void 0||(_config$ssr2=config.ssr)===null||_config$ssr2===void 0||(_config$ssr2$getSSRTi=_config$ssr2.getSSRTimings)===null||_config$ssr2$getSSRTi===void 0?void 0:_config$ssr2$getSSRTi.call(_config$ssr2);return{initialPageLoadExtraTimings:initialPageLoadExtraTimings,SSRTimings:ssrTimingsFromConfig?[].concat((0,_toConsumableArray2.default)(ssrTimingsFromConfig),(0,_toConsumableArray2.default)(defaultSSRTimings)):defaultSSRTimings};};if(experimental){expTTAI=(0,_getTtai.default)(interaction);}else{regularTTAI=(0,_getTtai.default)(interaction);}newUFOName=(0,_utils.sanitizeUfoName)(ufoName);resourceTimings=getResourceTimings(start,end);_context.t0=Promise;_context.t1=vcMetrics;if(_context.t1){_context.next=29;break;}_context.next=28;return(0,_getVcMetrics.default)(interaction);case 28:_context.t1=_context.sent;case 29:_context.t2=_context.t1;_context.t3=experimental?(0,_createExperimentalInteractionMetricsPayload.getExperimentalVCMetrics)(interaction):Promise.resolve(undefined);_context.t4=(0,_getPaintMetrics.getPaintMetricsToLegacyFormat)(type,end);_context.t5=(0,_getBatteryInfo.getBatteryInfoToLegacyFormat)();_context.t6=[_context.t2,_context.t3,_context.t4,_context.t5];_context.next=36;return _context.t0.all.call(_context.t0,_context.t6);case 36:_yield$Promise$all=_context.sent;_yield$Promise$all2=(0,_slicedToArray2.default)(_yield$Promise$all,4);finalVCMetrics=_yield$Promise$all2[0];experimentalMetrics=_yield$Promise$all2[1];paintMetrics=_yield$Promise$all2[2];batteryInfo=_yield$Promise$all2[3];if(!experimental){(0,_addPerformanceMeasures.addPerformanceMeasures)(interaction.start,(0,_toConsumableArray2.default)((finalVCMetrics===null||finalVCMetrics===void 0?void 0:finalVCMetrics['ufo:vc:rev'])||[]));}getReactHydrationStats=function getReactHydrationStats(){if(!hydration){return{};}return{hydration:hydration};};payload={actionSubject:'experience',action:'measured',eventType:'operational',source:'measured',tags:['observability'],attributes:{properties:_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread({// basic
|
|
12
|
+
getPageLoadDetailedInteractionMetrics=function getPageLoadDetailedInteractionMetrics(){var _config$ssr2,_config$ssr2$getSSRTi;if(!isPageLoad||!isDetailedPayload){return{};}var initialPageLoadExtraTimings=objectToArray(initialPageLoadExtraTiming.getTimings());var config=(0,_config.getConfig)();var defaultSSRTimings=objectToArray(ssr.getSSRTimings());var ssrTimingsFromConfig=config===null||config===void 0||(_config$ssr2=config.ssr)===null||_config$ssr2===void 0||(_config$ssr2$getSSRTi=_config$ssr2.getSSRTimings)===null||_config$ssr2$getSSRTi===void 0?void 0:_config$ssr2$getSSRTi.call(_config$ssr2);return{initialPageLoadExtraTimings:initialPageLoadExtraTimings,SSRTimings:ssrTimingsFromConfig?[].concat((0,_toConsumableArray2.default)(ssrTimingsFromConfig),(0,_toConsumableArray2.default)(defaultSSRTimings)):defaultSSRTimings};};if(experimental){expTTAI=(0,_getTtai.default)(interaction);}else{regularTTAI=(0,_getTtai.default)(interaction);}newUFOName=(0,_utils.sanitizeUfoName)(ufoName);resourceTimings=getResourceTimings(start,end);_context.t0=Promise;_context.t1=vcMetrics;if(_context.t1){_context.next=29;break;}_context.next=28;return(0,_getVcMetrics.default)(interaction);case 28:_context.t1=_context.sent;case 29:_context.t2=_context.t1;_context.t3=experimental?(0,_createExperimentalInteractionMetricsPayload.getExperimentalVCMetrics)(interaction):Promise.resolve(undefined);_context.t4=(0,_getPaintMetrics.getPaintMetricsToLegacyFormat)(type,end);_context.t5=(0,_getBatteryInfo.getBatteryInfoToLegacyFormat)();_context.t6=[_context.t2,_context.t3,_context.t4,_context.t5];_context.next=36;return _context.t0.all.call(_context.t0,_context.t6);case 36:_yield$Promise$all=_context.sent;_yield$Promise$all2=(0,_slicedToArray2.default)(_yield$Promise$all,4);finalVCMetrics=_yield$Promise$all2[0];experimentalMetrics=_yield$Promise$all2[1];paintMetrics=_yield$Promise$all2[2];batteryInfo=_yield$Promise$all2[3];if(!experimental){(0,_addPerformanceMeasures.addPerformanceMeasures)(interaction.start,(0,_toConsumableArray2.default)((finalVCMetrics===null||finalVCMetrics===void 0?void 0:finalVCMetrics['ufo:vc:rev'])||[]));}getReactHydrationStats=function getReactHydrationStats(){if(!hydration){return{};}return{hydration:hydration};};payload={actionSubject:'experience',action:'measured',eventType:'operational',source:'measured',tags:['observability'],attributes:{properties:_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread({// basic
|
|
13
13
|
'event:hostname':((_window$location=window.location)===null||_window$location===void 0?void 0:_window$location.hostname)||'unknown','event:product':config.product,'event:population':config.population,'event:schema':'1.0.0','event:sizeInKb':0,'event:source':{name:'react-ufo/web',version:(0,_getReactUfoPayloadVersion.getReactUFOPayloadVersion)(interaction.type)},'event:region':config.region||'unknown','experience:key':experimental?'custom.experimental-interaction-metrics':'custom.interaction-metrics','experience:name':newUFOName,// Include CPU usage monitoring data
|
|
14
|
-
'event:cpu:usage':(0,_machineUtilisation.createPressureStateReport)(interaction.start,interaction.end),'event:memory:usage':(0,_machineUtilisation.createMemoryStateReport)(interaction.start,interaction.end)},criticalPayloadCount!==undefined?{'ufo:multipayload':true,'ufo:criticalPayloadCount':criticalPayloadCount}:{}),(0,_platformFeatureFlags.fg)('platform_ufo_browser_backgrounded_abort_timestamp')?{'ufo:pageVisibilityHiddenTimestamp':(0,_hiddenTiming.getEarliestHiddenTiming)(interaction.start,interaction.end)}:{}),{},{'ufo:wasPageHiddenBeforeInit':(0,_hiddenTiming.getHasHiddenTimingBeforeSetup)(),'ufo:isOpenedInBackground':(0,_hiddenTiming.isOpenedInBackground)(interaction.type)},(0,_getBrowserMetadata.getBrowserMetadataToLegacyFormat)()),batteryInfo),getSSRProperties(type)),getAssetsMetrics(interaction,pageLoadInteractionMetrics===null||pageLoadInteractionMetrics===void 0?void 0:pageLoadInteractionMetrics.SSRDoneTime)),getPPSMetrics(interaction)),paintMetrics),(0,_getNavigationMetrics.getNavigationMetricsToLegacyFormat)(type)),finalVCMetrics),experimentalMetrics),(_config$additionalPay=config.additionalPayloadData)===null||_config$additionalPay===void 0?void 0:_config$additionalPay.call(config,interaction)),getTracingContextData(interaction)),getStylesheetMetrics()),getErrorCounts(interaction)),getReactHydrationStats()),{},{interactionMetrics:_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread({namePrefix:config.namePrefix||'',segmentPrefix:config.segmentPrefix||'',interactionId:interactionId,pageVisibilityAtTTI:pageVisibilityAtTTI,pageVisibilityAtTTAI:pageVisibilityAtTTAI,experimental__pageVisibilityAtTTI:moreAccuratePageVisibilityAtTTI,experimental__pageVisibilityAtTTAI:moreAccuratePageVisibilityAtTTAI,// raw interaction metrics
|
|
14
|
+
'event:cpu:usage':(0,_machineUtilisation.createPressureStateReport)(interaction.start,interaction.end),'event:memory:usage':(0,_machineUtilisation.createMemoryStateReport)(interaction.start,interaction.end)},criticalPayloadCount!==undefined?{'ufo:multipayload':true,'ufo:criticalPayloadCount':criticalPayloadCount}:{}),(0,_platformFeatureFlags.fg)('platform_ufo_browser_backgrounded_abort_timestamp')?{'ufo:pageVisibilityHiddenTimestamp':(0,_hiddenTiming.getEarliestHiddenTiming)(interaction.start,interaction.end)}:{}),{},{'ufo:wasPageHiddenBeforeInit':(0,_hiddenTiming.getHasHiddenTimingBeforeSetup)(),'ufo:isOpenedInBackground':(0,_hiddenTiming.isOpenedInBackground)(interaction.type)},(0,_platformFeatureFlags.fg)('platform_ufo_is_tab_throttled')?{'ufo:isTabThrottled':(0,_hiddenTiming.isTabThrottled)(start,end)}:{}),(0,_getBrowserMetadata.getBrowserMetadataToLegacyFormat)()),batteryInfo),getSSRProperties(type)),getAssetsMetrics(interaction,pageLoadInteractionMetrics===null||pageLoadInteractionMetrics===void 0?void 0:pageLoadInteractionMetrics.SSRDoneTime)),getPPSMetrics(interaction)),paintMetrics),(0,_getNavigationMetrics.getNavigationMetricsToLegacyFormat)(type)),finalVCMetrics),experimentalMetrics),(_config$additionalPay=config.additionalPayloadData)===null||_config$additionalPay===void 0?void 0:_config$additionalPay.call(config,interaction)),getTracingContextData(interaction)),getStylesheetMetrics()),getErrorCounts(interaction)),getReactHydrationStats()),{},{interactionMetrics:_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread({namePrefix:config.namePrefix||'',segmentPrefix:config.segmentPrefix||'',interactionId:interactionId,pageVisibilityAtTTI:pageVisibilityAtTTI,pageVisibilityAtTTAI:pageVisibilityAtTTAI,experimental__pageVisibilityAtTTI:moreAccuratePageVisibilityAtTTI,experimental__pageVisibilityAtTTAI:moreAccuratePageVisibilityAtTTAI,// raw interaction metrics
|
|
15
15
|
rate:rate,routeName:routeName,type:type,abortReason:abortReason,featureFlags:featureFlags,previousInteractionName:previousInteractionName,isPreviousInteractionAborted:isPreviousInteractionAborted,abortedByInteractionName:abortedByInteractionName,// performance
|
|
16
16
|
apdex:(0,_optimizeApdex.optimizeApdex)(interaction.apdex,(0,_getReactUfoPayloadVersion.getReactUFOPayloadVersion)(interaction.type)),end:Math.round(end)},interaction.end3p?{end3p:Math.round(interaction.end3p)}:{}),{},{start:Math.round(start),segments:(0,_getReactUfoPayloadVersion.getReactUFOPayloadVersion)(interaction.type)==='2.0.0'?segmentTree:(0,_utils.getOldSegmentsLabelStack)(segments,interaction.type),marks:(0,_optimizeMarks.optimizeMarks)(interaction.marks,(0,_getReactUfoPayloadVersion.getReactUFOPayloadVersion)(interaction.type)),customData:optimizeCustomData(interaction),reactProfilerTimings:(0,_optimizeReactProfilerTimings.optimizeReactProfilerTimings)(interaction.reactProfilerTimings,start,(0,_getReactUfoPayloadVersion.getReactUFOPayloadVersion)(interaction.type)),minorInteractions:interaction.minorInteractions},responsiveness?{responsiveness:responsiveness}:{}),labelStack),pageLoadInteractionMetrics),getDetailedInteractionMetrics(resourceTimings)),getPageLoadDetailedInteractionMetrics()),getBm3TrackerTimings(interaction)),{},{'metric:ttai':experimental?regularTTAI||expTTAI:undefined,'metric:experimental:ttai':expTTAI},unknownElementName?{unknownElementName:unknownElementName}:{}),unknownElementHierarchy?{unknownElementHierarchy:unknownElementHierarchy}:{}),'ufo:payloadTime':(0,_roundNumber.roundEpsilon)(performance.now()-interactionPayloadStart)})}};if(experimental){regularTTAI=undefined;expTTAI=undefined;}if((0,_platformFeatureFlags.fg)('platform_ufo_enable_vc_raw_data')){size=(0,_getPayloadSize.default)(payload.attributes.properties);vcRev=payload.attributes.properties['ufo:vc:rev'];rawData=vcRev.find(function(item){return item.revision==='raw-handler';});if(rawData){rawDataSize=(0,_getPayloadSize.default)(rawData);payload.attributes.properties['ufo:vc:raw:size']=rawDataSize;if(size>MAX_PAYLOAD_SIZE&&Array.isArray(vcRev)&&vcRev.length>0){payload.attributes.properties['ufo:vc:rev']=vcRev.filter(function(item){return item.revision!=='raw-handler';});payload.attributes.properties['ufo:vc:raw:removed']=true;}}payload.attributes.properties['event:sizeInKb']=(0,_getPayloadSize.default)(payload.attributes.properties);}else{payload.attributes.properties['event:sizeInKb']=(0,_getPayloadSize.default)(payload.attributes.properties);}// in order of importance, first one being least important
|
|
17
17
|
// we can add more fields as necessary
|
|
18
|
-
interactionMetricsFieldsToTrim=['requestInfo','featureFlags','resourceTimings'];properties=payload.attributes.properties;interactionMetrics=properties.interactionMetrics;if(!interactionMetrics){_context.next=74;break;}_iterator=_createForOfIteratorHelper(interactionMetricsFieldsToTrim);_context.prev=52;_iterator.s();case 54:if((_step=_iterator.n()).done){_context.next=66;break;}field=_step.value;if(!((0,_getPayloadSize.default)(properties)<=MAX_PAYLOAD_SIZE)){_context.next=58;break;}return _context.abrupt("continue",64);case 58:interactionMetrics[field]=undefined;properties['event:isTrimmed']=true;trimmedFields=properties['event:trimmedFields'];if(!Array.isArray(trimmedFields)){trimmedFields=[];}trimmedFields.push("interactionMetrics.".concat(field));properties['event:trimmedFields']=trimmedFields;case 64:_context.next=54;break;case 66:_context.next=71;break;case 68:_context.prev=68;_context.t7=_context["catch"](52);_iterator.e(_context.t7);case 71:_context.prev=71;_iterator.f();return _context.finish(71);case 74:return _context.abrupt("return",payload);case 75:case"end":return _context.stop();}},_callee,null,[[52,68,71,74]]);}));return _createInteractionMetricsPayload.apply(this,arguments);}function createPayloads(_x6,_x7){return _createPayloads.apply(this,arguments);}function _createPayloads(){_createPayloads=(0,_asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee2(interactionId,interaction){var ufoNameOverride,modifiedInteraction,payloads,isCriticalMetricsEnabled,vcMetrics,criticalMetricsPayloads,criticalPayloadCount,interactionMetricsPayload;return _regenerator.default.wrap(function _callee2$(_context2){while(1)switch(_context2.prev=_context2.next){case 0:ufoNameOverride=getUfoNameOverride(interaction);modifiedInteraction=_objectSpread(_objectSpread({},interaction),{},{ufoName:ufoNameOverride});payloads=[];isCriticalMetricsEnabled=(0,_platformFeatureFlags.fg)('platform_ufo_critical_metrics_payload');// Calculate VC metrics once to avoid duplicate expensive calculations
|
|
18
|
+
interactionMetricsFieldsToTrim=(0,_platformFeatureFlags.fg)('ufo_remove_featureflags_from_trimmed_fields')?['requestInfo','resourceTimings']:['requestInfo','featureFlags','resourceTimings'];properties=payload.attributes.properties;interactionMetrics=properties.interactionMetrics;if(!interactionMetrics){_context.next=74;break;}_iterator=_createForOfIteratorHelper(interactionMetricsFieldsToTrim);_context.prev=52;_iterator.s();case 54:if((_step=_iterator.n()).done){_context.next=66;break;}field=_step.value;if(!((0,_getPayloadSize.default)(properties)<=MAX_PAYLOAD_SIZE)){_context.next=58;break;}return _context.abrupt("continue",64);case 58:interactionMetrics[field]=undefined;properties['event:isTrimmed']=true;trimmedFields=properties['event:trimmedFields'];if(!Array.isArray(trimmedFields)){trimmedFields=[];}trimmedFields.push("interactionMetrics.".concat(field));properties['event:trimmedFields']=trimmedFields;case 64:_context.next=54;break;case 66:_context.next=71;break;case 68:_context.prev=68;_context.t7=_context["catch"](52);_iterator.e(_context.t7);case 71:_context.prev=71;_iterator.f();return _context.finish(71);case 74:return _context.abrupt("return",payload);case 75:case"end":return _context.stop();}},_callee,null,[[52,68,71,74]]);}));return _createInteractionMetricsPayload.apply(this,arguments);}function createPayloads(_x6,_x7){return _createPayloads.apply(this,arguments);}function _createPayloads(){_createPayloads=(0,_asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee2(interactionId,interaction){var ufoNameOverride,modifiedInteraction,payloads,isCriticalMetricsEnabled,vcMetrics,criticalMetricsPayloads,criticalPayloadCount,interactionMetricsPayload;return _regenerator.default.wrap(function _callee2$(_context2){while(1)switch(_context2.prev=_context2.next){case 0:ufoNameOverride=getUfoNameOverride(interaction);modifiedInteraction=_objectSpread(_objectSpread({},interaction),{},{ufoName:ufoNameOverride});payloads=[];isCriticalMetricsEnabled=(0,_platformFeatureFlags.fg)('platform_ufo_critical_metrics_payload');// Calculate VC metrics once to avoid duplicate expensive calculations
|
|
19
19
|
_context2.next=6;return(0,_getVcMetrics.default)(interaction);case 6:vcMetrics=_context2.sent;if(!isCriticalMetricsEnabled){_context2.next=13;break;}_context2.next=10;return(0,_criticalMetricsPayload.createCriticalMetricsPayloads)(interactionId,interaction,vcMetrics);case 10:_context2.t0=_context2.sent;_context2.next=14;break;case 13:_context2.t0=[];case 14:criticalMetricsPayloads=_context2.t0;payloads.push.apply(payloads,(0,_toConsumableArray2.default)(criticalMetricsPayloads));criticalPayloadCount=isCriticalMetricsEnabled?criticalMetricsPayloads.length:undefined;_context2.next=19;return createInteractionMetricsPayload(modifiedInteraction,interactionId,undefined,criticalPayloadCount,vcMetrics);case 19:interactionMetricsPayload=_context2.sent;payloads.push(interactionMetricsPayload);return _context2.abrupt("return",payloads.filter(Boolean));case 22:case"end":return _context2.stop();}},_callee2);}));return _createPayloads.apply(this,arguments);}function createExperimentalMetricsPayload(_x8,_x9){return _createExperimentalMetricsPayload.apply(this,arguments);}function _createExperimentalMetricsPayload(){_createExperimentalMetricsPayload=(0,_asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee3(interactionId,interaction){var config,ufoName,rate,pageVisibilityState,result;return _regenerator.default.wrap(function _callee3$(_context3){while(1)switch(_context3.prev=_context3.next){case 0:config=(0,_config.getConfig)();if(config){_context3.next=3;break;}throw Error('UFO Configuration not provided');case 3:ufoName=(0,_utils.sanitizeUfoName)(interaction.ufoName);rate=(0,_config.getExperimentalInteractionRate)(ufoName,interaction.type);if((0,_coinflip.default)(rate)){_context3.next=7;break;}return _context3.abrupt("return",null);case 7:pageVisibilityState=(0,_hiddenTiming.getPageVisibilityState)(interaction.start,interaction.end);if(!(pageVisibilityState!=='visible')){_context3.next=10;break;}return _context3.abrupt("return",null);case 10:_context3.next=12;return createInteractionMetricsPayload(interaction,interactionId,true);case 12:result=_context3.sent;return _context3.abrupt("return",result);case 14:case"end":return _context3.stop();}},_callee3);}));return _createExperimentalMetricsPayload.apply(this,arguments);}function createExtraSearchPageInteractionPayload(_x0,_x1){return _createExtraSearchPageInteractionPayload.apply(this,arguments);}function _createExtraSearchPageInteractionPayload(){_createExtraSearchPageInteractionPayload=(0,_asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee4(interactionId,interaction){var SAIN_HOLD_NAMES,NAME_OVERRIDE,SEARCH_PAGE_SMART_ANSWERS_SEGMENT_LABEL,newInteractionId,newEnd,holdInfo,reactProfilerTimings,lastHold,isLastHoldSAIN,lastFilteredTime,filteredReactProfilerTimings,lastTiming,modifiedInteraction,payloads,vcMetrics,interactionMetricsPayload;return _regenerator.default.wrap(function _callee4$(_context4){while(1)switch(_context4.prev=_context4.next){case 0:SAIN_HOLD_NAMES=['search-ai-dialog-visible-text-loading','search-ai-dialog-all-text-loading'];NAME_OVERRIDE='search-page-ignoring-smart-answers';SEARCH_PAGE_SMART_ANSWERS_SEGMENT_LABEL='search-page-smart-answers';newInteractionId="".concat(interactionId,"-ignoring-smart-answers");// Calculate a new end time which excludes SAIN holds
|
|
20
20
|
holdInfo=interaction.holdInfo,reactProfilerTimings=interaction.reactProfilerTimings;lastHold=holdInfo.at(-1);isLastHoldSAIN=Boolean(lastHold&&SAIN_HOLD_NAMES.includes(lastHold.name));// A new end time is only calculated if the last hold is a SAIN hold
|
|
21
21
|
if(isLastHoldSAIN){lastFilteredTime=null;filteredReactProfilerTimings=reactProfilerTimings.filter(function(timing){if(timing.commitTime===lastFilteredTime){return false;}var isTimingSmartAnswersInSearch=timing.labelStack.some(function(label){return label.name===SEARCH_PAGE_SMART_ANSWERS_SEGMENT_LABEL;});if(isTimingSmartAnswersInSearch){lastFilteredTime=timing.commitTime;return false;}return true;});lastTiming=filteredReactProfilerTimings.at(-1);if(lastTiming){newEnd=lastTiming.commitTime;}}modifiedInteraction=_objectSpread(_objectSpread({},interaction),{},{end:newEnd!==null&&newEnd!==void 0?newEnd:interaction.end,holdInfo:[],knownSegments:[],reactProfilerTimings:[],ufoName:NAME_OVERRIDE});payloads=[];_context4.next=12;return(0,_getVcMetrics.default)(interaction,false,true);case 12:vcMetrics=_context4.sent;_context4.next=15;return createInteractionMetricsPayload(modifiedInteraction,newInteractionId,undefined,undefined,vcMetrics);case 15:interactionMetricsPayload=_context4.sent;payloads.push(interactionMetricsPayload);return _context4.abrupt("return",payloads.filter(Boolean));case 18:case"end":return _context4.stop();}},_callee4);}));return _createExtraSearchPageInteractionPayload.apply(this,arguments);}
|
|
@@ -3,11 +3,16 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
+
exports.__injectThrottleMeasurementForTesting = __injectThrottleMeasurementForTesting;
|
|
6
7
|
exports.getEarliestHiddenTiming = getEarliestHiddenTiming;
|
|
7
8
|
exports.getHasHiddenTimingBeforeSetup = getHasHiddenTimingBeforeSetup;
|
|
8
9
|
exports.getPageVisibilityState = getPageVisibilityState;
|
|
10
|
+
exports.getThrottleMeasurements = getThrottleMeasurements;
|
|
9
11
|
exports.isOpenedInBackground = isOpenedInBackground;
|
|
12
|
+
exports.isTabThrottled = isTabThrottled;
|
|
10
13
|
exports.setupHiddenTimingCapture = setupHiddenTimingCapture;
|
|
14
|
+
exports.setupThrottleDetection = setupThrottleDetection;
|
|
15
|
+
exports.stopThrottleDetection = stopThrottleDetection;
|
|
11
16
|
var _bindEventListener = require("bind-event-listener");
|
|
12
17
|
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
13
18
|
var timings = [];
|
|
@@ -161,4 +166,134 @@ function getPageVisibilityState(start, end) {
|
|
|
161
166
|
hiddenState = timings[startIdx].hidden ? 'hidden' : 'visible';
|
|
162
167
|
}
|
|
163
168
|
return hiddenState;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Throttle detection configuration
|
|
172
|
+
// Expected interval for timer checks (in milliseconds)
|
|
173
|
+
var THROTTLE_CHECK_INTERVAL_MS = 1000;
|
|
174
|
+
// Threshold for considering a timer as throttled (50% drift tolerance)
|
|
175
|
+
var THROTTLE_DRIFT_THRESHOLD = 1.5;
|
|
176
|
+
// Maximum number of throttle measurements to store (circular buffer)
|
|
177
|
+
var THROTTLE_BUFFER_SIZE = 120;
|
|
178
|
+
// Circular buffer to store throttle measurements
|
|
179
|
+
var throttleMeasurements = [];
|
|
180
|
+
var throttleInsertIndex = 0;
|
|
181
|
+
var throttleIntervalId = null;
|
|
182
|
+
var lastThrottleCheckTime = null;
|
|
183
|
+
var throttleSetupDone = false;
|
|
184
|
+
function recordThrottleMeasurement(expectedElapsed, actualElapsed) {
|
|
185
|
+
var isThrottled = actualElapsed > expectedElapsed * THROTTLE_DRIFT_THRESHOLD;
|
|
186
|
+
throttleMeasurements[throttleInsertIndex] = {
|
|
187
|
+
time: performance.now(),
|
|
188
|
+
expectedElapsed: expectedElapsed,
|
|
189
|
+
actualElapsed: actualElapsed,
|
|
190
|
+
isThrottled: isThrottled
|
|
191
|
+
};
|
|
192
|
+
throttleInsertIndex = (throttleInsertIndex + 1) % THROTTLE_BUFFER_SIZE;
|
|
193
|
+
}
|
|
194
|
+
function throttleCheckCallback() {
|
|
195
|
+
var currentTime = performance.now();
|
|
196
|
+
if (lastThrottleCheckTime !== null) {
|
|
197
|
+
var actualElapsed = currentTime - lastThrottleCheckTime;
|
|
198
|
+
recordThrottleMeasurement(THROTTLE_CHECK_INTERVAL_MS, actualElapsed);
|
|
199
|
+
}
|
|
200
|
+
lastThrottleCheckTime = currentTime;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Sets up the throttle detection mechanism.
|
|
205
|
+
* This should be called early in the page lifecycle.
|
|
206
|
+
* Uses a periodic timer to detect browser throttling by measuring timer drift.
|
|
207
|
+
*/
|
|
208
|
+
function setupThrottleDetection() {
|
|
209
|
+
if (throttleSetupDone) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
throttleSetupDone = true;
|
|
213
|
+
|
|
214
|
+
// Record the initial timestamp
|
|
215
|
+
lastThrottleCheckTime = performance.now();
|
|
216
|
+
|
|
217
|
+
// Start the periodic timer for throttle detection
|
|
218
|
+
throttleIntervalId = setInterval(throttleCheckCallback, THROTTLE_CHECK_INTERVAL_MS);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Stops the throttle detection mechanism.
|
|
223
|
+
* Useful for cleanup in tests or when the feature is no longer needed.
|
|
224
|
+
*/
|
|
225
|
+
function stopThrottleDetection() {
|
|
226
|
+
if (throttleIntervalId !== null) {
|
|
227
|
+
clearInterval(throttleIntervalId);
|
|
228
|
+
throttleIntervalId = null;
|
|
229
|
+
}
|
|
230
|
+
lastThrottleCheckTime = null;
|
|
231
|
+
throttleSetupDone = false;
|
|
232
|
+
throttleMeasurements.length = 0;
|
|
233
|
+
throttleInsertIndex = 0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Checks if the tab was throttled at any point during the specified time window.
|
|
238
|
+
* Returns true if any timer measurement showed significant drift (throttling).
|
|
239
|
+
*
|
|
240
|
+
* @param startTime - The start timestamp of the window to check (DOMHighResTimeStamp)
|
|
241
|
+
* @param endTime - The end timestamp of the window to check (DOMHighResTimeStamp)
|
|
242
|
+
* @returns boolean - true if throttling was detected during the time window, false otherwise
|
|
243
|
+
*/
|
|
244
|
+
function isTabThrottled(startTime, endTime) {
|
|
245
|
+
// Input validation
|
|
246
|
+
if (!Number.isFinite(startTime) || !Number.isFinite(endTime) || startTime >= endTime) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// No measurements available
|
|
251
|
+
if (throttleMeasurements.length === 0) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Check if any measurement within the time window indicates throttling
|
|
256
|
+
for (var i = 0; i < throttleMeasurements.length; i++) {
|
|
257
|
+
var measurement = throttleMeasurements[i];
|
|
258
|
+
if (measurement && measurement.time >= startTime && measurement.time <= endTime && measurement.isThrottled) {
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Gets detailed throttle information for debugging purposes.
|
|
267
|
+
* Returns all throttle measurements within the specified time window.
|
|
268
|
+
*
|
|
269
|
+
* @param startTime - The start timestamp of the window to check
|
|
270
|
+
* @param endTime - The end timestamp of the window to check
|
|
271
|
+
* @returns Array of throttle measurements within the time window
|
|
272
|
+
*/
|
|
273
|
+
function getThrottleMeasurements(startTime, endTime) {
|
|
274
|
+
// Input validation
|
|
275
|
+
if (!Number.isFinite(startTime) || !Number.isFinite(endTime) || startTime >= endTime) {
|
|
276
|
+
return [];
|
|
277
|
+
}
|
|
278
|
+
return throttleMeasurements.filter(function (measurement) {
|
|
279
|
+
return measurement && measurement.time >= startTime && measurement.time <= endTime;
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Injects a fake throttle measurement for testing purposes.
|
|
285
|
+
* This allows integration tests to simulate throttling scenarios.
|
|
286
|
+
*
|
|
287
|
+
* @param measurement - The throttle measurement to inject
|
|
288
|
+
*/
|
|
289
|
+
function __injectThrottleMeasurementForTesting(measurement) {
|
|
290
|
+
throttleMeasurements[throttleInsertIndex] = measurement;
|
|
291
|
+
throttleInsertIndex = (throttleInsertIndex + 1) % THROTTLE_BUFFER_SIZE;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Expose testing API on window for integration tests
|
|
295
|
+
if (typeof window !== 'undefined') {
|
|
296
|
+
window.__reactUfoHiddenTiming = {
|
|
297
|
+
__injectThrottleMeasurementForTesting: __injectThrottleMeasurementForTesting
|
|
298
|
+
};
|
|
164
299
|
}
|