@atlaskit/react-ufo 2.14.3 → 2.16.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 (73) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/cjs/assets/index.js +96 -0
  3. package/dist/cjs/assets/utils.js +32 -0
  4. package/dist/cjs/common/assets/types.js +5 -0
  5. package/dist/cjs/create-payload/index.js +42 -5
  6. package/dist/cjs/experience-trace-id-context/index.js +5 -1
  7. package/dist/cjs/segment/segment.js +7 -1
  8. package/dist/cjs/vc/vc-observer/heatmap/heatmap.js +264 -0
  9. package/dist/cjs/vc/vc-observer/index.js +119 -26
  10. package/dist/cjs/vc/vc-observer/revisions/ViewportUpdateClassifier.js +60 -0
  11. package/dist/cjs/vc/vc-observer/revisions/fy24_01.js +71 -0
  12. package/dist/cjs/vc/vc-observer/revisions/fy25_01.js +39 -0
  13. package/dist/cjs/vc/vc-observer/revisions/revisions.js +23 -0
  14. package/dist/cjs/vc/vc-observer/revisions/types.js +5 -0
  15. package/dist/es2019/assets/index.js +82 -0
  16. package/dist/es2019/assets/utils.js +26 -0
  17. package/dist/es2019/common/assets/types.js +1 -0
  18. package/dist/es2019/create-payload/index.js +43 -3
  19. package/dist/es2019/experience-trace-id-context/index.js +4 -0
  20. package/dist/es2019/segment/segment.js +11 -3
  21. package/dist/es2019/vc/vc-observer/heatmap/heatmap.js +230 -0
  22. package/dist/es2019/vc/vc-observer/index.js +97 -3
  23. package/dist/es2019/vc/vc-observer/revisions/ViewportUpdateClassifier.js +41 -0
  24. package/dist/es2019/vc/vc-observer/revisions/fy24_01.js +50 -0
  25. package/dist/es2019/vc/vc-observer/revisions/fy25_01.js +21 -0
  26. package/dist/es2019/vc/vc-observer/revisions/revisions.js +19 -0
  27. package/dist/es2019/vc/vc-observer/revisions/types.js +1 -0
  28. package/dist/esm/assets/index.js +89 -0
  29. package/dist/esm/assets/utils.js +26 -0
  30. package/dist/esm/common/assets/types.js +1 -0
  31. package/dist/esm/create-payload/index.js +42 -5
  32. package/dist/esm/experience-trace-id-context/index.js +4 -0
  33. package/dist/esm/segment/segment.js +7 -1
  34. package/dist/esm/vc/vc-observer/heatmap/heatmap.js +257 -0
  35. package/dist/esm/vc/vc-observer/index.js +119 -26
  36. package/dist/esm/vc/vc-observer/revisions/ViewportUpdateClassifier.js +53 -0
  37. package/dist/esm/vc/vc-observer/revisions/fy24_01.js +64 -0
  38. package/dist/esm/vc/vc-observer/revisions/fy25_01.js +32 -0
  39. package/dist/esm/vc/vc-observer/revisions/revisions.js +17 -0
  40. package/dist/esm/vc/vc-observer/revisions/types.js +1 -0
  41. package/dist/types/assets/index.d.ts +25 -0
  42. package/dist/types/assets/utils.d.ts +6 -0
  43. package/dist/types/common/assets/types.d.ts +18 -0
  44. package/dist/types/common/index.d.ts +1 -0
  45. package/dist/types/common/vc/types.d.ts +27 -8
  46. package/dist/types/config/index.d.ts +2 -1
  47. package/dist/types/create-payload/index.d.ts +11616 -0
  48. package/dist/types/experience-trace-id-context/index.d.ts +1 -0
  49. package/dist/types/resource-timing/index.d.ts +1 -1
  50. package/dist/types/vc/vc-observer/heatmap/heatmap.d.ts +72 -0
  51. package/dist/types/vc/vc-observer/index.d.ts +5 -1
  52. package/dist/types/vc/vc-observer/revisions/ViewportUpdateClassifier.d.ts +30 -0
  53. package/dist/types/vc/vc-observer/revisions/fy24_01.d.ts +13 -0
  54. package/dist/types/vc/vc-observer/revisions/fy25_01.d.ts +13 -0
  55. package/dist/types/vc/vc-observer/revisions/revisions.d.ts +2 -0
  56. package/dist/types/vc/vc-observer/revisions/types.d.ts +23 -0
  57. package/dist/types-ts4.5/assets/index.d.ts +25 -0
  58. package/dist/types-ts4.5/assets/utils.d.ts +6 -0
  59. package/dist/types-ts4.5/common/assets/types.d.ts +18 -0
  60. package/dist/types-ts4.5/common/index.d.ts +1 -0
  61. package/dist/types-ts4.5/common/vc/types.d.ts +27 -8
  62. package/dist/types-ts4.5/config/index.d.ts +2 -1
  63. package/dist/types-ts4.5/create-payload/index.d.ts +11616 -0
  64. package/dist/types-ts4.5/experience-trace-id-context/index.d.ts +1 -0
  65. package/dist/types-ts4.5/resource-timing/index.d.ts +1 -1
  66. package/dist/types-ts4.5/vc/vc-observer/heatmap/heatmap.d.ts +72 -0
  67. package/dist/types-ts4.5/vc/vc-observer/index.d.ts +15 -1
  68. package/dist/types-ts4.5/vc/vc-observer/revisions/ViewportUpdateClassifier.d.ts +30 -0
  69. package/dist/types-ts4.5/vc/vc-observer/revisions/fy24_01.d.ts +13 -0
  70. package/dist/types-ts4.5/vc/vc-observer/revisions/fy25_01.d.ts +13 -0
  71. package/dist/types-ts4.5/vc/vc-observer/revisions/revisions.d.ts +2 -0
  72. package/dist/types-ts4.5/vc/vc-observer/revisions/types.d.ts +23 -0
  73. package/package.json +11 -2
@@ -1,6 +1,7 @@
1
1
  import Bowser from 'bowser-ultralight';
2
2
  import { fg } from '@atlaskit/platform-feature-flags';
3
3
  import { getLighthouseMetrics } from '../additional-payload';
4
+ import { CHRReporter } from '../assets';
4
5
  import * as bundleEvalTiming from '../bundle-eval-timing';
5
6
  import coinflip from '../coinflip';
6
7
  import { REACT_UFO_VERSION } from '../common/constants';
@@ -201,6 +202,16 @@ const getVCMetrics = interaction => {
201
202
  if (interactionStatus.originalInteractionStatus !== 'SUCCEEDED' || pageVisibilityUpToTTAI !== 'visible') {
202
203
  return result;
203
204
  }
205
+ if (fg('ufo_vc_multiheatmap')) {
206
+ var _result;
207
+ (_result = result[`${prefix}:vc:rev`]) === null || _result === void 0 ? void 0 : _result.forEach(element => {
208
+ var _element$vcDetails, _element$vcDetails$;
209
+ if ((_element$vcDetails = element.vcDetails) !== null && _element$vcDetails !== void 0 && (_element$vcDetails$ = _element$vcDetails['90']) !== null && _element$vcDetails$ !== void 0 && _element$vcDetails$.t) {
210
+ var _element$vcDetails$2;
211
+ element['metric:vc90'] = (_element$vcDetails$2 = element.vcDetails['90']) === null || _element$vcDetails$2 === void 0 ? void 0 : _element$vcDetails$2.t;
212
+ }
213
+ });
214
+ }
204
215
  return {
205
216
  ...result,
206
217
  'metric:vc90': VC['90']
@@ -288,6 +299,33 @@ function getSSRProperties(type) {
288
299
  'ssr:featureFlags': getSSRFeatureFlags(type)
289
300
  };
290
301
  }
302
+ const getAssetsMetrics = (interaction, resourceTimings) => {
303
+ try {
304
+ const config = getConfig();
305
+ const {
306
+ type
307
+ } = interaction;
308
+ const isCHREnabled = fg('ufo_chr_config');
309
+ const allowedTypes = ['page_load', 'transition'];
310
+ const assetsClassification = config === null || config === void 0 ? void 0 : config.assetsClassification;
311
+ if (!isCHREnabled || !allowedTypes.includes(type) || !assetsClassification) {
312
+ // Skip if: FG disabled, not allowed type or assetsClassification isn't configured
313
+ return {};
314
+ }
315
+ const reporter = new CHRReporter();
316
+ const assets = reporter.get(resourceTimings, assetsClassification);
317
+ if (assets) {
318
+ // Only add assets in case it exists
319
+ return {
320
+ 'event:assets': assets
321
+ };
322
+ }
323
+ return {};
324
+ } catch (error) {
325
+ // Skip CHR in case of error
326
+ return {};
327
+ }
328
+ };
291
329
  const getBrowserMetadata = () => {
292
330
  const data = {};
293
331
  const now = new Date();
@@ -713,7 +751,7 @@ function createInteractionMetricsPayload(interaction, interactionId, experimenta
713
751
  };
714
752
  };
715
753
  // Detailed payload. Page visibility = visible
716
- const getDetailedInteractionMetrics = () => {
754
+ const getDetailedInteractionMetrics = resourceTimings => {
717
755
  if (experimental || window.__UFO_COMPACT_PAYLOAD__ || !isDetailedPayload) {
718
756
  return {};
719
757
  }
@@ -734,7 +772,7 @@ function createInteractionMetricsPayload(interaction, interactionId, experimenta
734
772
  requestInfo: optimizeRequestInfo(interaction.requestInfo, start),
735
773
  customTimings: optimizeCustomTimings(interaction.customTimings, start),
736
774
  bundleEvalTimings: objectToArray(getBundleEvalTimings(start)),
737
- resourceTimings: objectToArray(getResourceTimings(start, end))
775
+ resourceTimings: objectToArray(resourceTimings)
738
776
  };
739
777
  };
740
778
  // Page load & detailed payload
@@ -755,6 +793,7 @@ function createInteractionMetricsPayload(interaction, interactionId, experimenta
755
793
  regularTTAI = getTTAI(interaction);
756
794
  }
757
795
  const newUFOName = sanitizeUfoName(ufoName);
796
+ const resourceTimings = getResourceTimings(start, end);
758
797
  const payload = {
759
798
  actionSubject: 'experience',
760
799
  action: 'measured',
@@ -779,6 +818,7 @@ function createInteractionMetricsPayload(interaction, interactionId, experimenta
779
818
  // root
780
819
  ...getBrowserMetadata(),
781
820
  ...getSSRProperties(type),
821
+ ...getAssetsMetrics(interaction, resourceTimings),
782
822
  ...getPPSMetrics(interaction),
783
823
  ...getPaintMetrics(type),
784
824
  ...getNavigationMetrics(type),
@@ -821,7 +861,7 @@ function createInteractionMetricsPayload(interaction, interactionId, experimenta
821
861
  reactProfilerTimings: optimizeReactProfilerTimings(interaction.reactProfilerTimings, start),
822
862
  ...labelStack,
823
863
  ...getPageLoadInteractionMetrics(),
824
- ...getDetailedInteractionMetrics(),
864
+ ...getDetailedInteractionMetrics(resourceTimings),
825
865
  ...getPageLoadDetailedInteractionMetrics(),
826
866
  ...getBm3TrackerTimings(interaction),
827
867
  'metric:ttai': experimental ? regularTTAI || expTTAI : undefined,
@@ -28,4 +28,8 @@ export const getActiveTraceHttpRequestHeaders = _url => {
28
28
  spanId
29
29
  } = state.context;
30
30
  return makeTraceHttpRequestHeaders(traceId, spanId);
31
+ };
32
+ export const getActiveTraceAsQueryParams = _url => {
33
+ const traceHeaders = getActiveTraceHttpRequestHeaders(_url);
34
+ return traceHeaders ? new URLSearchParams(traceHeaders).toString().toLowerCase() : null;
31
35
  };
@@ -1,6 +1,7 @@
1
1
  import React, { lazy, Profiler, Suspense, useCallback, useContext, useEffect, useMemo, useRef } from 'react';
2
2
  import { unstable_NormalPriority as NormalPriority, unstable_scheduleCallback as scheduleCallback } from 'scheduler';
3
3
  import { v4 as createUUID } from 'uuid';
4
+ import { fg } from '@atlaskit/platform-feature-flags';
4
5
  import coinflip from '../coinflip';
5
6
  import { getConfig, getInteractionRate } from '../config';
6
7
  import { getActiveTrace, setInteractionActiveTrace } from '../experience-trace-id-context';
@@ -31,7 +32,9 @@ export default function UFOSegment({
31
32
  }], [parentContext, segmentName, segmentId]);
32
33
  const interactionId = useContext(UFOInteractionIDContext);
33
34
  const interactionContext = useMemo(() => {
34
- addSegment(labelStack);
35
+ if (!fg('platform-ufo-add-segment-use-effect')) {
36
+ addSegment(labelStack);
37
+ }
35
38
  let lastCompleteEndTime = 0;
36
39
  function complete(endTime = performance.now()) {
37
40
  if (interactionId.current) {
@@ -205,8 +208,13 @@ export default function UFOSegment({
205
208
  interactionContext.onRender(phase, actualDuration, baseDuration, startTime, commitTime);
206
209
  }
207
210
  }, [interactionContext]);
208
- useEffect(() => () => {
209
- removeSegment(labelStack);
211
+ useEffect(() => {
212
+ if (fg('platform-ufo-add-segment-use-effect')) {
213
+ addSegment(labelStack);
214
+ }
215
+ return () => {
216
+ removeSegment(labelStack);
217
+ };
210
218
  }, [interactionId, parentContext, interactionContext, labelStack]);
211
219
  const reactProfilerId = useMemo(() => labelStack.map(l => l.name).join('/'), [labelStack]);
212
220
  const enableSegmentHighlighting = (_getConfig2 = getConfig()) === null || _getConfig2 === void 0 ? void 0 : _getConfig2.enableSegmentHighlighting;
@@ -0,0 +1,230 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ const UNUSED_SECTOR = 0;
3
+ export class MultiRevisionHeatmap {
4
+ constructor({
5
+ viewport,
6
+ revisions,
7
+ arraySize,
8
+ devToolsEnabled
9
+ }) {
10
+ _defineProperty(this, "arraySize", {
11
+ w: 200,
12
+ h: 200
13
+ });
14
+ _defineProperty(this, "mapPixelsToHeatmap", (left, top, width, height) => {
15
+ const {
16
+ w,
17
+ h
18
+ } = this.viewport;
19
+ const l = Math.floor(left / w * this.arraySize.w);
20
+ const t = Math.floor(top / h * this.arraySize.h);
21
+ const r = Math.ceil((left + width) / w * this.arraySize.w);
22
+ const b = Math.ceil((top + height) / h * this.arraySize.h);
23
+
24
+ // correct values to min - 0, max - arraySize
25
+ const result = {
26
+ l: Math.max(0, l),
27
+ t: Math.max(0, t),
28
+ r: Math.min(this.arraySize.w, r),
29
+ b: Math.min(this.arraySize.h, b)
30
+ };
31
+ return result;
32
+ });
33
+ _defineProperty(this, "getElementRatio", mappedValues => {
34
+ const {
35
+ r,
36
+ l,
37
+ b,
38
+ t
39
+ } = mappedValues;
40
+ return (r - l) * (b - t) / (this.arraySize.w * this.arraySize.h);
41
+ });
42
+ this.viewport = viewport;
43
+ this.revisions = revisions;
44
+ if (arraySize) {
45
+ this.arraySize = arraySize;
46
+ }
47
+ this.heatmaps = new Array(revisions.length);
48
+ this.componentsLogs = new Array(revisions.length);
49
+ this.vcRatios = new Array(revisions.length);
50
+ this.devToolsEnabled = devToolsEnabled || false;
51
+ revisions.forEach(({}, i) => {
52
+ this.heatmaps[i] = this.getCleanHeatmap();
53
+ this.componentsLogs[i] = {};
54
+ this.vcRatios[i] = {};
55
+ });
56
+ }
57
+ handleUpdate({
58
+ time,
59
+ type,
60
+ classification,
61
+ intersectionRect,
62
+ element,
63
+ targetName,
64
+ ignoreReason,
65
+ onError
66
+ }) {
67
+ const mappedValues = this.mapPixelsToHeatmap(intersectionRect.left, intersectionRect.top, intersectionRect.width, intersectionRect.height);
68
+ const result = this.applyChangesToHeatMap(mappedValues, time, classification);
69
+ if (result !== true) {
70
+ onError(result);
71
+ }
72
+ const componentRatio = this.getElementRatio(mappedValues);
73
+ this.revisions.forEach((_, i) => {
74
+ if (classification[i]) {
75
+ this.vcRatios[i][targetName] = componentRatio;
76
+ }
77
+ if (!this.componentsLogs[i][time]) {
78
+ this.componentsLogs[i][time] = [];
79
+ }
80
+ this.componentsLogs[i][time].push({
81
+ __debug__element: this.devToolsEnabled ? new WeakRef(element) : null,
82
+ intersectionRect,
83
+ targetName,
84
+ ignoreReason
85
+ });
86
+ });
87
+ }
88
+ getData() {
89
+ return {
90
+ heatmaps: this.heatmaps
91
+ };
92
+ }
93
+ getPayloadShapedData(args) {
94
+ const result = this.processData(args);
95
+ const payload = this.revisions.map((rev, i) => {
96
+ const vcDetails = {};
97
+ args.VCParts.forEach(VCPart => {
98
+ vcDetails[VCPart] = {
99
+ t: result[i].VC[VCPart] || 0,
100
+ e: Array.from(result[i].VCBox[VCPart] || [])
101
+ };
102
+ });
103
+ return {
104
+ revision: rev.name,
105
+ vcDetails,
106
+ clean: args.clean,
107
+ 'metric:vc90': null // will be set or not in the payload generator
108
+ };
109
+ });
110
+ return payload;
111
+ }
112
+ processData({
113
+ VCParts,
114
+ VCCalculationMethods,
115
+ ssr = UNUSED_SECTOR
116
+ }) {
117
+ return this.heatmaps.map((heatmap, i) => {
118
+ const lastUpdate = {};
119
+ let totalPainted = 0;
120
+ let componentsLog = this.componentsLogs[i];
121
+ if (ssr !== UNUSED_SECTOR) {
122
+ var _window$document;
123
+ const element = {
124
+ __debug__element: new WeakRef((_window$document = window.document) === null || _window$document === void 0 ? void 0 : _window$document.body),
125
+ intersectionRect: {
126
+ top: 0,
127
+ left: 0,
128
+ right: 0,
129
+ bottom: 0,
130
+ x: 0,
131
+ y: 0,
132
+ width: this.viewport.w,
133
+ height: this.viewport.h,
134
+ toJSON() {
135
+ return {};
136
+ }
137
+ },
138
+ targetName: 'SSR'
139
+ };
140
+ if (!componentsLog[ssr]) {
141
+ componentsLog[ssr] = [];
142
+ }
143
+ componentsLog[ssr].push(element);
144
+ }
145
+ for (let i = 0; i < heatmap.length; i++) {
146
+ const rounded = Math.floor(heatmap[i] === UNUSED_SECTOR && ssr !== UNUSED_SECTOR ? ssr : heatmap[i]);
147
+ totalPainted += rounded !== UNUSED_SECTOR ? 1 : 0;
148
+ if (rounded !== UNUSED_SECTOR) {
149
+ lastUpdate[rounded] = lastUpdate[rounded] ? lastUpdate[rounded] + 1 : 1;
150
+ }
151
+ }
152
+ const entries = Object.entries(lastUpdate).map(a => [parseInt(a[0], 10), a[1]]).sort((a, b) => a[0] > b[0] ? 1 : -1);
153
+ const {
154
+ VC,
155
+ VCBox
156
+ } = VCCalculationMethods[i]({
157
+ VCParts,
158
+ componentsLog,
159
+ entries,
160
+ totalPainted
161
+ });
162
+ const VCEntries = entries.reduce((acc, [timestamp, entryPainted], i) => {
163
+ var _acc$abs, _componentsLog$timest;
164
+ const currentlyPainted = entryPainted + (((_acc$abs = acc.abs[i - 1]) === null || _acc$abs === void 0 ? void 0 : _acc$abs[1]) || 0);
165
+ const currentlyPaintedRatio = Math.round(currentlyPainted / totalPainted * 1000) / 10;
166
+ const logEntry = (_componentsLog$timest = componentsLog[timestamp]) === null || _componentsLog$timest === void 0 ? void 0 : _componentsLog$timest.map(v => v.targetName);
167
+ acc.abs.push([timestamp, currentlyPainted]);
168
+ acc.rel.push({
169
+ time: timestamp,
170
+ vc: currentlyPaintedRatio,
171
+ elements: logEntry
172
+ });
173
+ return acc;
174
+ }, {
175
+ abs: [],
176
+ rel: []
177
+ });
178
+ return {
179
+ VC,
180
+ VCBox,
181
+ VCEntries,
182
+ totalPainted
183
+ };
184
+ });
185
+ }
186
+ applyChangesToHeatMap(a, time, classification) {
187
+ const {
188
+ l,
189
+ t,
190
+ r,
191
+ b
192
+ } = a;
193
+ const size = classification.length;
194
+ for (let row = t; row < b; row++) {
195
+ if (this.heatmaps[0][row] === undefined) {
196
+ try {
197
+ return {
198
+ error: `index - ${row}`,
199
+ time
200
+ };
201
+ } catch (e) {
202
+ return {
203
+ error: 'row error',
204
+ time
205
+ };
206
+ }
207
+ } else {
208
+ for (let heatmapIndex = 0; heatmapIndex < size; heatmapIndex++) {
209
+ if (classification[heatmapIndex]) {
210
+ this.heatmaps[heatmapIndex].fill(time, this.getIndex(l, row), this.getIndex(r, row));
211
+ }
212
+ }
213
+ }
214
+ }
215
+ return true;
216
+ }
217
+ getIndex(x, y) {
218
+ return x + this.arraySize.w * y;
219
+ }
220
+ getCleanHeatmap() {
221
+ return new Uint32Array(this.arraySize.w * this.arraySize.h);
222
+ }
223
+ static makeVCReturnObj(VCParts) {
224
+ const vc = {};
225
+ VCParts.forEach(v => {
226
+ vc[v] = null;
227
+ });
228
+ return vc;
229
+ }
230
+ }
@@ -2,7 +2,9 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import { fg } from '@atlaskit/platform-feature-flags';
3
3
  import { attachAbortListeners } from './attachAbortListeners';
4
4
  import { getViewportHeight, getViewportWidth } from './getViewport';
5
+ import { MultiRevisionHeatmap } from './heatmap/heatmap';
5
6
  import { Observers } from './observers';
7
+ import { getRevisions } from './revisions/revisions';
6
8
  const abortReason = {
7
9
  scroll: 'scroll',
8
10
  keypress: 'keypress',
@@ -35,6 +37,7 @@ export class VCObserver {
35
37
  });
36
38
  /* heatmap */
37
39
  _defineProperty(this, "arraySize", 0);
40
+ _defineProperty(this, "multiHeatmap", null);
38
41
  _defineProperty(this, "componentsLog", {});
39
42
  _defineProperty(this, "vcRatios", {});
40
43
  _defineProperty(this, "active", false);
@@ -68,6 +71,7 @@ export class VCObserver {
68
71
  },
69
72
  heatmap: this.heatmap,
70
73
  heatmapNext: this.heatmapNext,
74
+ multiHeatmap: this.multiHeatmap,
71
75
  outOfBoundaryInfo: this.outOfBoundaryInfo,
72
76
  totalTime: Math.round(this.totalTime + this.observers.getTotalTime()),
73
77
  componentsLog: {
@@ -106,7 +110,8 @@ export class VCObserver {
106
110
  componentsLog,
107
111
  viewport,
108
112
  devToolsEnabled,
109
- ratios
113
+ ratios,
114
+ multiHeatmap
110
115
  } = rawData;
111
116
  if (abortReasonInfo !== null && abortReason.blocking) {
112
117
  // exposing data to devtools
@@ -150,6 +155,7 @@ export class VCObserver {
150
155
  /* empty */
151
156
  }
152
157
  let _componentsLog = {};
158
+ // eslint-disable-next-line @atlaskit/platform/ensure-feature-flag-prefix
153
159
  if (fg('ufo-remove-vc-component-observations-after-ttai')) {
154
160
  Object.entries(this.componentsLog).forEach(([_timestamp, value]) => {
155
161
  const timestamp = Number(_timestamp);
@@ -207,9 +213,30 @@ export class VCObserver {
207
213
  tti,
208
214
  ttai: stop - start
209
215
  },
216
+ start,
217
+ stop,
210
218
  heatmap,
211
219
  ratios
212
220
  };
221
+ window.__vcNext = {
222
+ entries: vcNext.VCEntries.rel,
223
+ log: componentsLog,
224
+ metrics: {
225
+ '75': vcNext.VC['75'],
226
+ '80': vcNext.VC['80'],
227
+ '85': vcNext.VC['85'],
228
+ '90': vcNext.VC['90'],
229
+ '95': vcNext.VC['95'],
230
+ '98': vcNext.VC['98'],
231
+ '99': vcNext.VC['99'],
232
+ tti,
233
+ ttai: stop - start
234
+ },
235
+ start,
236
+ stop,
237
+ heatmap: heatmapNext,
238
+ ratios
239
+ };
213
240
 
214
241
  // Emitting a custom event to make it available in the Chrome extension
215
242
  window.dispatchEvent(new CustomEvent('vcReady', {
@@ -222,6 +249,18 @@ export class VCObserver {
222
249
  } catch (e) {
223
250
  /* do nothing */
224
251
  }
252
+ const isMultiHeatmapEnabled = fg('ufo_vc_multiheatmap');
253
+ const revisionsData = isMultiHeatmapEnabled && multiHeatmap !== null ? {
254
+ [`${fullPrefix}vc:rev`]: multiHeatmap.getPayloadShapedData({
255
+ VCParts: VCObserver.VCParts.map(v => parseInt(v)),
256
+ VCCalculationMethods: getRevisions().map(({
257
+ classifier
258
+ }) => classifier.VCCalculationMethod),
259
+ ssr,
260
+ clean: !abortReasonInfo
261
+ })
262
+ } : null;
263
+ // eslint-disable-next-line @atlaskit/platform/ensure-feature-flag-prefix
225
264
  const isCalcSpeedIndexEnabled = fg('ufo-calc-speed-index');
226
265
  return {
227
266
  'metrics:vc': VC,
@@ -240,12 +279,27 @@ export class VCObserver {
240
279
  [`${fullPrefix}vc:next:dom`]: vcNext.VCBox,
241
280
  //...oldDomUpdates,
242
281
  [`${fullPrefix}vc:ignored`]: this.getIgnoredElements(componentsLog),
282
+ ...revisionsData,
243
283
  [`ufo:speedIndex`]: isCalcSpeedIndexEnabled ? VCEntries.speedIndex : undefined,
244
284
  [`ufo:next:speedIndex`]: isCalcSpeedIndexEnabled ? vcNext.VCEntries.speedIndex : undefined
245
285
  };
246
286
  });
247
287
  _defineProperty(this, "handleUpdate", (rawTime, intersectionRect, targetName, element, type, ignoreReason) => {
248
288
  this.measureStart();
289
+ this.legacyHandleUpdate(rawTime, intersectionRect, targetName, element, type, ignoreReason);
290
+ if (fg('ufo_vc_multiheatmap')) {
291
+ this.onViewportChangeDetected({
292
+ timestamp: rawTime,
293
+ intersectionRect,
294
+ targetName,
295
+ element,
296
+ type,
297
+ ignoreReason
298
+ });
299
+ }
300
+ this.measureStop();
301
+ });
302
+ _defineProperty(this, "legacyHandleUpdate", (rawTime, intersectionRect, targetName, element, type, ignoreReason) => {
249
303
  if (this.abortReason.reason === null || this.abortReason.blocking === false) {
250
304
  const time = Math.round(rawTime - this.startTime);
251
305
  const mappedValues = this.mapPixelsToHeatmap(intersectionRect.left, intersectionRect.top, intersectionRect.width, intersectionRect.height);
@@ -266,8 +320,39 @@ export class VCObserver {
266
320
  ignoreReason
267
321
  });
268
322
  }
269
- // devtools export
270
- this.measureStop();
323
+ });
324
+ _defineProperty(this, "onViewportChangeDetected", ({
325
+ element,
326
+ type,
327
+ ignoreReason,
328
+ timestamp,
329
+ targetName,
330
+ intersectionRect
331
+ }) => {
332
+ if (this.multiHeatmap === null) {
333
+ return;
334
+ }
335
+ // @todo add abort reason handling
336
+ const time = Math.round(timestamp - this.startTime);
337
+ const revisions = getRevisions();
338
+ const revisionsClassification = revisions.map(revision => {
339
+ return revision.classifier.classifyUpdate({
340
+ element,
341
+ type,
342
+ ignoreReason
343
+ });
344
+ }, []);
345
+ this.multiHeatmap.handleUpdate({
346
+ time,
347
+ targetName,
348
+ intersectionRect,
349
+ type,
350
+ element,
351
+ classification: revisionsClassification,
352
+ onError: error => {
353
+ this.setAbortReason(abortReason.error, error.time, error.error);
354
+ }
355
+ });
271
356
  });
272
357
  _defineProperty(this, "mapPixelsToHeatmap", (left, top, width, height) => {
273
358
  const {
@@ -352,6 +437,13 @@ export class VCObserver {
352
437
  });
353
438
  this.heatmap = this.getCleanHeatmap();
354
439
  this.heatmapNext = this.getCleanHeatmap();
440
+ if (fg('ufo_vc_multiheatmap')) {
441
+ this.multiHeatmap = new MultiRevisionHeatmap({
442
+ viewport: this.viewport,
443
+ revisions: getRevisions(),
444
+ devToolsEnabled: this.devToolsEnabled
445
+ });
446
+ }
355
447
  this.isPostInteraction = options.isPostInteraction || false;
356
448
  }
357
449
  start({
@@ -428,6 +520,8 @@ export class VCObserver {
428
520
  const entries = Object.entries(lastUpdate).map(a => [parseInt(a[0], 10), a[1]]).sort((a, b) => a[0] > b[0] ? 1 : -1);
429
521
  const VC = VCObserver.makeVCReturnObj();
430
522
  const VCBox = VCObserver.makeVCReturnObj();
523
+
524
+ // eslint-disable-next-line @atlaskit/platform/ensure-feature-flag-prefix
431
525
  const isCalcSpeedIndexEnabled = fg('ufo-calc-speed-index');
432
526
  entries.reduce((acc = 0, v) => {
433
527
  const currRatio = v[1] / totalPainted;
@@ -0,0 +1,41 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ export class ViewportUpdateClassifier {
3
+ constructor() {
4
+ _defineProperty(this, "types", []);
5
+ _defineProperty(this, "filters", []);
6
+ _defineProperty(this, "removedFilters", []);
7
+ _defineProperty(this, "__combinedTypes", []);
8
+ _defineProperty(this, "__combinedFilters", []);
9
+ }
10
+ VCCalculationMethod(_) {
11
+ return {
12
+ VC: {},
13
+ VCBox: {}
14
+ };
15
+ }
16
+ mergeConfig() {
17
+ this.__combinedTypes = [...this.types, ...((this === null || this === void 0 ? void 0 : this.__combinedTypes) || [])];
18
+ const previousFilters = this.removedFilters.length === 0 ? this.__combinedFilters : this.__combinedFilters.filter(filter => !this.removedFilters.includes(filter.name));
19
+ this.__combinedFilters = [...this.filters, ...previousFilters];
20
+ }
21
+ classifyUpdate({
22
+ element,
23
+ type,
24
+ tags,
25
+ ignoreReason
26
+ }) {
27
+ if (!this.__combinedTypes.includes(type)) {
28
+ return false;
29
+ }
30
+ return this.__combinedFilters.every(({
31
+ filter,
32
+ name
33
+ }) => {
34
+ return filter({
35
+ type,
36
+ tags,
37
+ ignoreReason
38
+ });
39
+ });
40
+ }
41
+ }
@@ -0,0 +1,50 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import { ViewportUpdateClassifier } from './ViewportUpdateClassifier';
3
+ const legacyIgnoreReasons = ['image', 'ssr-hydration', 'editor-lazy-node-view', 'editor-container-mutation'];
4
+ export class FY24_01Classifier extends ViewportUpdateClassifier {
5
+ VCCalculationMethod({
6
+ VCParts,
7
+ entries,
8
+ totalPainted,
9
+ componentsLog
10
+ }) {
11
+ const VC = {};
12
+ const VCBox = {};
13
+ entries.reduce((acc = 0, v) => {
14
+ const VCRatio = v[1] / totalPainted + acc;
15
+ const time = v[0];
16
+ VCParts.forEach(value => {
17
+ if ((VC[value] === null || VC[value] === undefined) && VCRatio >= value / 100) {
18
+ var _componentsLog$time;
19
+ VC[value] = time;
20
+ VCBox[value] = new Set();
21
+ (_componentsLog$time = componentsLog[time]) === null || _componentsLog$time === void 0 ? void 0 : _componentsLog$time.forEach(v => {
22
+ var _VCBox$value;
23
+ return (_VCBox$value = VCBox[value]) === null || _VCBox$value === void 0 ? void 0 : _VCBox$value.add(v.targetName);
24
+ });
25
+ }
26
+ });
27
+ return VCRatio;
28
+ }, 0);
29
+ return {
30
+ VC,
31
+ VCBox
32
+ };
33
+ }
34
+ constructor() {
35
+ super();
36
+ _defineProperty(this, "revision", 'fy24.01');
37
+ _defineProperty(this, "types", ['html', 'text']);
38
+ _defineProperty(this, "filters", [{
39
+ name: 'default-ignore-reasons',
40
+ filter: ({
41
+ type,
42
+ ignoreReason
43
+ }) => {
44
+ return !ignoreReason || !legacyIgnoreReasons.includes(ignoreReason);
45
+ }
46
+ }]);
47
+ this.mergeConfig();
48
+ }
49
+ }
50
+ export const revFY24_01Classifier = new FY24_01Classifier();
@@ -0,0 +1,21 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import { FY24_01Classifier } from './fy24_01';
3
+ export class FY25_01Classifier extends FY24_01Classifier {
4
+ constructor() {
5
+ super();
6
+ _defineProperty(this, "revision", 'fy25.01');
7
+ _defineProperty(this, "types", ['attr']);
8
+ _defineProperty(this, "filters", [{
9
+ name: 'not-visible',
10
+ filter: ({
11
+ type,
12
+ ignoreReason
13
+ }) => {
14
+ return !(ignoreReason !== null && ignoreReason !== void 0 && ignoreReason.includes('not-visible'));
15
+ }
16
+ }]);
17
+ _defineProperty(this, "removedFilters", []);
18
+ this.mergeConfig();
19
+ }
20
+ }
21
+ export const revFY25_01Classifier = new FY25_01Classifier();
@@ -0,0 +1,19 @@
1
+ import { revFY24_01Classifier } from './fy24_01';
2
+ import { revFY25_01Classifier } from './fy25_01';
3
+ const Revisions = [{
4
+ name: 'fy24.01',
5
+ classifier: revFY24_01Classifier
6
+ }, {
7
+ name: 'fy25.01',
8
+ classifier: revFY25_01Classifier
9
+ }];
10
+ let revisionResultCache = null;
11
+ export const getRevisions = () => {
12
+ if (revisionResultCache !== null) {
13
+ return revisionResultCache;
14
+ }
15
+ revisionResultCache = [...Revisions
16
+ //...(fg('next_available') ? [{name: 'next', classifier:NEXT}] : [])
17
+ ];
18
+ return revisionResultCache;
19
+ };
@@ -0,0 +1 @@
1
+ export {};