@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.
Files changed (40) hide show
  1. package/AGENTS.md +426 -0
  2. package/CHANGELOG.md +22 -0
  3. package/dist/cjs/create-payload/index.js +3 -3
  4. package/dist/cjs/hidden-timing/index.js +135 -0
  5. package/dist/cjs/interaction-metrics/index.js +11 -3
  6. package/dist/cjs/interaction-metrics-init/index.js +3 -0
  7. package/dist/cjs/set-terminal-error/index.js +11 -3
  8. package/dist/cjs/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.js +39 -27
  9. package/dist/cjs/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/index.js +22 -7
  10. package/dist/es2019/create-payload/index.js +3 -3
  11. package/dist/es2019/hidden-timing/index.js +128 -0
  12. package/dist/es2019/interaction-metrics/index.js +10 -2
  13. package/dist/es2019/interaction-metrics-init/index.js +4 -1
  14. package/dist/es2019/set-terminal-error/index.js +12 -4
  15. package/dist/es2019/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.js +17 -4
  16. package/dist/es2019/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/index.js +22 -7
  17. package/dist/esm/create-payload/index.js +4 -4
  18. package/dist/esm/hidden-timing/index.js +130 -0
  19. package/dist/esm/interaction-metrics/index.js +10 -2
  20. package/dist/esm/interaction-metrics-init/index.js +4 -1
  21. package/dist/esm/set-terminal-error/index.js +12 -4
  22. package/dist/esm/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.js +39 -27
  23. package/dist/esm/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/index.js +22 -7
  24. package/dist/types/common/react-ufo-payload-schema.d.ts +1 -0
  25. package/dist/types/common/vc/types.d.ts +2 -0
  26. package/dist/types/create-terminal-error-payload/index.d.ts +4 -0
  27. package/dist/types/hidden-timing/index.d.ts +42 -0
  28. package/dist/types/interaction-metrics/index.d.ts +8 -0
  29. package/dist/types/set-terminal-error/index.d.ts +4 -0
  30. package/dist/types/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/index.d.ts +3 -3
  31. package/dist/types/vc/vc-observer-new/metric-calculator/percentile-calc/types.d.ts +12 -0
  32. package/dist/types-ts4.5/common/react-ufo-payload-schema.d.ts +1 -0
  33. package/dist/types-ts4.5/common/vc/types.d.ts +2 -0
  34. package/dist/types-ts4.5/create-terminal-error-payload/index.d.ts +4 -0
  35. package/dist/types-ts4.5/hidden-timing/index.d.ts +42 -0
  36. package/dist/types-ts4.5/interaction-metrics/index.d.ts +8 -0
  37. package/dist/types-ts4.5/set-terminal-error/index.d.ts +4 -0
  38. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/index.d.ts +3 -3
  39. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/percentile-calc/types.d.ts +12 -0
  40. 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
  }