@atlaskit/react-ufo 3.12.4 → 3.12.5

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 (92) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/cjs/create-experimental-interaction-metrics-payload/index.js +2 -1
  3. package/dist/cjs/create-payload/utils/get-vc-metrics.js +2 -1
  4. package/dist/cjs/vc/index.js +4 -2
  5. package/dist/cjs/vc/vc-observer/getVCRevisionDebugDetails.js +41 -0
  6. package/dist/cjs/vc/vc-observer/index.js +59 -33
  7. package/dist/cjs/vc/vc-observer-new/index.js +13 -7
  8. package/dist/cjs/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.js +211 -36
  9. package/dist/cjs/vc/vc-observer-new/metric-calculator/fy25_03/index.js +4 -4
  10. package/dist/cjs/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/index.js +94 -4
  11. package/dist/cjs/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/test-with-debug-info.js +108 -0
  12. package/dist/cjs/vc/vc-observer-new/metric-calculator/percentile-calc/index.js +16 -57
  13. package/dist/cjs/vc/vc-observer-new/viewport-observer/index.js +15 -5
  14. package/dist/cjs/vc/vc-observer-new/viewport-observer/mutation-observer/index.js +3 -1
  15. package/dist/es2019/create-experimental-interaction-metrics-payload/index.js +2 -1
  16. package/dist/es2019/create-payload/utils/get-vc-metrics.js +1 -0
  17. package/dist/es2019/vc/index.js +4 -2
  18. package/dist/es2019/vc/vc-observer/getVCRevisionDebugDetails.js +32 -0
  19. package/dist/es2019/vc/vc-observer/index.js +30 -1
  20. package/dist/es2019/vc/vc-observer-new/index.js +12 -6
  21. package/dist/es2019/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.js +115 -17
  22. package/dist/es2019/vc/vc-observer-new/metric-calculator/fy25_03/index.js +4 -4
  23. package/dist/es2019/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/index.js +44 -1
  24. package/dist/es2019/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/test-with-debug-info.js +75 -0
  25. package/dist/es2019/vc/vc-observer-new/metric-calculator/percentile-calc/index.js +2 -20
  26. package/dist/es2019/vc/vc-observer-new/viewport-observer/index.js +15 -5
  27. package/dist/es2019/vc/vc-observer-new/viewport-observer/mutation-observer/index.js +3 -1
  28. package/dist/esm/create-experimental-interaction-metrics-payload/index.js +2 -1
  29. package/dist/esm/create-payload/utils/get-vc-metrics.js +2 -1
  30. package/dist/esm/vc/index.js +4 -2
  31. package/dist/esm/vc/vc-observer/getVCRevisionDebugDetails.js +35 -0
  32. package/dist/esm/vc/vc-observer/index.js +59 -33
  33. package/dist/esm/vc/vc-observer-new/index.js +13 -7
  34. package/dist/esm/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.js +211 -36
  35. package/dist/esm/vc/vc-observer-new/metric-calculator/fy25_03/index.js +4 -4
  36. package/dist/esm/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/index.js +94 -5
  37. package/dist/esm/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/test-with-debug-info.js +106 -0
  38. package/dist/esm/vc/vc-observer-new/metric-calculator/percentile-calc/index.js +2 -55
  39. package/dist/esm/vc/vc-observer-new/viewport-observer/index.js +15 -5
  40. package/dist/esm/vc/vc-observer-new/viewport-observer/mutation-observer/index.js +3 -1
  41. package/dist/types/vc/types.d.ts +1 -0
  42. package/dist/types/vc/vc-observer/getVCRevisionDebugDetails.d.ts +30 -0
  43. package/dist/types/vc/vc-observer/index.d.ts +1 -1
  44. package/dist/types/vc/vc-observer-new/index.d.ts +2 -0
  45. package/dist/types/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.d.ts +4 -1
  46. package/dist/types/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/index.d.ts +5 -1
  47. package/dist/types/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/test-with-debug-info.d.ts +1 -0
  48. package/dist/types/vc/vc-observer-new/metric-calculator/percentile-calc/index.d.ts +2 -4
  49. package/dist/types/vc/vc-observer-new/metric-calculator/percentile-calc/types.d.ts +20 -2
  50. package/dist/types/vc/vc-observer-new/metric-calculator/types.d.ts +2 -0
  51. package/dist/types/vc/vc-observer-new/types.d.ts +5 -1
  52. package/dist/types/vc/vc-observer-new/viewport-observer/mutation-observer/index.d.ts +2 -0
  53. package/dist/types/vc/vc-observer-new/viewport-observer/types.d.ts +2 -0
  54. package/dist/types-ts4.5/vc/types.d.ts +1 -0
  55. package/dist/types-ts4.5/vc/vc-observer/getVCRevisionDebugDetails.d.ts +30 -0
  56. package/dist/types-ts4.5/vc/vc-observer/index.d.ts +1 -1
  57. package/dist/types-ts4.5/vc/vc-observer-new/index.d.ts +2 -0
  58. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.d.ts +4 -1
  59. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/index.d.ts +5 -1
  60. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/test-with-debug-info.d.ts +1 -0
  61. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/percentile-calc/index.d.ts +2 -4
  62. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/percentile-calc/types.d.ts +20 -2
  63. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/types.d.ts +2 -0
  64. package/dist/types-ts4.5/vc/vc-observer-new/types.d.ts +5 -1
  65. package/dist/types-ts4.5/vc/vc-observer-new/viewport-observer/mutation-observer/index.d.ts +2 -0
  66. package/dist/types-ts4.5/vc/vc-observer-new/viewport-observer/types.d.ts +2 -0
  67. package/package.json +4 -1
  68. package/dist/cjs/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/heatmap.js +0 -367
  69. package/dist/cjs/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/index.js +0 -398
  70. package/dist/cjs/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/types.js +0 -5
  71. package/dist/cjs/vc/vc-observer-new/metric-calculator/percentile-calc/rect-sweeping-line/calc-union-area.js +0 -152
  72. package/dist/cjs/vc/vc-observer-new/metric-calculator/percentile-calc/rect-sweeping-line/index.js +0 -108
  73. package/dist/es2019/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/heatmap.js +0 -248
  74. package/dist/es2019/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/index.js +0 -263
  75. package/dist/es2019/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/types.js +0 -1
  76. package/dist/es2019/vc/vc-observer-new/metric-calculator/percentile-calc/rect-sweeping-line/calc-union-area.js +0 -99
  77. package/dist/es2019/vc/vc-observer-new/metric-calculator/percentile-calc/rect-sweeping-line/index.js +0 -60
  78. package/dist/esm/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/heatmap.js +0 -361
  79. package/dist/esm/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/index.js +0 -391
  80. package/dist/esm/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/types.js +0 -1
  81. package/dist/esm/vc/vc-observer-new/metric-calculator/percentile-calc/rect-sweeping-line/calc-union-area.js +0 -145
  82. package/dist/esm/vc/vc-observer-new/metric-calculator/percentile-calc/rect-sweeping-line/index.js +0 -101
  83. package/dist/types/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/heatmap.d.ts +0 -39
  84. package/dist/types/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/index.d.ts +0 -10
  85. package/dist/types/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/types.d.ts +0 -43
  86. package/dist/types/vc/vc-observer-new/metric-calculator/percentile-calc/rect-sweeping-line/calc-union-area.d.ts +0 -12
  87. package/dist/types/vc/vc-observer-new/metric-calculator/percentile-calc/rect-sweeping-line/index.d.ts +0 -25
  88. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/heatmap.d.ts +0 -39
  89. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/index.d.ts +0 -10
  90. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/types.d.ts +0 -43
  91. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/percentile-calc/rect-sweeping-line/calc-union-area.d.ts +0 -12
  92. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/percentile-calc/rect-sweeping-line/index.d.ts +0 -25
@@ -1,25 +1,131 @@
1
1
  import { fg } from '@atlaskit/platform-feature-flags';
2
- import calculateTTVCPercentiles from './percentile-calc';
2
+ import { calculateTTVCPercentiles, calculateTTVCPercentilesWithDebugInfo } from './percentile-calc';
3
3
  import getViewportHeight from './utils/get-viewport-height';
4
4
  import getViewportWidth from './utils/get-viewport-width';
5
5
  export default class AbstractVCCalculatorBase {
6
6
  constructor(revisionNo) {
7
7
  this.revisionNo = revisionNo;
8
8
  }
9
+ filterViewportEntries(entries) {
10
+ return entries.filter(entry => {
11
+ return 'rect' in entry.data;
12
+ });
13
+ }
14
+ async calculateBasic(filteredEntries, startTime, stopTime) {
15
+ const percentiles = [25, 50, 75, 80, 85, 90, 95, 98, 99];
16
+ const viewportEntries = this.filterViewportEntries(filteredEntries);
17
+ const vcLogs = await calculateTTVCPercentiles({
18
+ viewport: {
19
+ width: getViewportWidth(),
20
+ height: getViewportHeight()
21
+ },
22
+ startTime,
23
+ stopTime,
24
+ orderedEntries: viewportEntries,
25
+ percentiles
26
+ });
27
+ return vcLogs;
28
+ }
29
+ async calculateWithDebugInfo(filteredEntries, startTime, stopTime, isPostInteraction, isVCClean, interactionId, dirtyReason) {
30
+ const percentiles = [25, 50, 75, 80, 85, 90, 95, 98, 99];
31
+ const viewportEntries = this.filterViewportEntries(filteredEntries);
32
+ const vcLogs = await calculateTTVCPercentilesWithDebugInfo({
33
+ viewport: {
34
+ width: getViewportWidth(),
35
+ height: getViewportHeight()
36
+ },
37
+ startTime,
38
+ stopTime,
39
+ orderedEntries: viewportEntries
40
+ });
41
+ const vcDetails = {};
42
+ let percentileIndex = 0;
43
+ const entryDataBuffer = new Set();
44
+ if (vcLogs) {
45
+ for (const entry of vcLogs) {
46
+ const {
47
+ time,
48
+ viewportPercentage,
49
+ entries
50
+ } = entry;
51
+
52
+ // Only process entries if we haven't reached all percentiles
53
+ if (percentileIndex >= percentiles.length) {
54
+ break;
55
+ }
56
+
57
+ // Check if this entry matches any checkpoint percentiles
58
+ if (viewportPercentage >= percentiles[percentileIndex]) {
59
+ const elementNames = entries.map(e => e.elementName);
60
+
61
+ // Process all matching percentiles in one go
62
+ while (percentileIndex < percentiles.length && viewportPercentage >= percentiles[percentileIndex]) {
63
+ vcDetails[`${percentiles[percentileIndex]}`] = {
64
+ t: Math.round(time),
65
+ e: elementNames
66
+ };
67
+ percentileIndex++;
68
+ }
69
+
70
+ // Clear buffer after processing all matching percentiles
71
+ entryDataBuffer.clear();
72
+ } else {
73
+ // Only add to buffer if we haven't reached all percentiles
74
+ entries.forEach(e => entryDataBuffer.add(e));
75
+ }
76
+ }
77
+ }
78
+
79
+ // Fill in any missing percentiles with the last known values
80
+ let previousResult = {
81
+ t: 0,
82
+ e: []
83
+ };
84
+ for (let i = 0; i < percentiles.length; i++) {
85
+ const percentile = percentiles[i];
86
+ if (!(percentile in vcDetails)) {
87
+ vcDetails[`${percentile}`] = previousResult;
88
+ } else {
89
+ previousResult = vcDetails[`${percentile}`];
90
+ }
91
+ }
92
+
93
+ // Handle devtool callback
94
+ if (!isPostInteraction && typeof window !== 'undefined' && typeof window.__ufo_devtool_onVCRevisionReady__ === 'function' && fg('platform_ufo_ttvc_v3_devtool')) {
95
+ try {
96
+ var _ufo_devtool_onVCRev, _ref;
97
+ (_ufo_devtool_onVCRev = (_ref = window).__ufo_devtool_onVCRevisionReady__) === null || _ufo_devtool_onVCRev === void 0 ? void 0 : _ufo_devtool_onVCRev.call(_ref, {
98
+ revision: this.revisionNo,
99
+ isClean: isVCClean,
100
+ abortReason: dirtyReason,
101
+ vcLogs,
102
+ interactionId
103
+ });
104
+ } catch (e) {
105
+ // if any error communicating with devtool, we don't want to break the app
106
+ // eslint-disable-next-line no-console
107
+ console.error('Error in onVCRevisionReady', e);
108
+ }
109
+ }
110
+ return vcDetails;
111
+ }
9
112
  async calculate({
10
113
  startTime,
11
114
  stopTime,
12
- orderedEntries
115
+ orderedEntries,
116
+ interactionId,
117
+ isPostInteraction
13
118
  }) {
14
119
  var _vcDetails$90$t, _vcDetails$;
15
120
  const filteredEntries = orderedEntries.filter(entry => {
16
121
  return this.isEntryIncluded(entry);
17
122
  });
123
+ let isVCClean;
124
+ let dirtyReason;
18
125
  if (fg('platform_ufo_add_vc_abort_reason_by_revisions')) {
19
- const {
20
- isVCClean,
21
- dirtyReason
22
- } = this.getVCCleanStatus(filteredEntries);
126
+ const getVCCleanStatusResult = this.getVCCleanStatus(filteredEntries);
127
+ isVCClean = getVCCleanStatusResult.isVCClean;
128
+ dirtyReason = getVCCleanStatusResult.dirtyReason;
23
129
  if (!isVCClean) {
24
130
  return {
25
131
  revision: this.revisionNo,
@@ -29,7 +135,7 @@ export default class AbstractVCCalculatorBase {
29
135
  };
30
136
  }
31
137
  } else {
32
- const isVCClean = this.isVCClean(filteredEntries);
138
+ isVCClean = this.isVCClean(filteredEntries);
33
139
  if (!isVCClean) {
34
140
  return {
35
141
  revision: this.revisionNo,
@@ -38,16 +144,8 @@ export default class AbstractVCCalculatorBase {
38
144
  };
39
145
  }
40
146
  }
41
- const vcDetails = await calculateTTVCPercentiles({
42
- viewport: {
43
- width: getViewportWidth(),
44
- height: getViewportHeight()
45
- },
46
- startTime,
47
- stopTime,
48
- orderedEntries: filteredEntries,
49
- percentiles: [25, 50, 75, 80, 85, 90, 95, 98, 99]
50
- });
147
+ const useDebugInfo = fg('platform_ufo_ttvc_v3_devtool');
148
+ const vcDetails = useDebugInfo ? await this.calculateWithDebugInfo(filteredEntries, startTime, stopTime, isPostInteraction, isVCClean, interactionId, dirtyReason) : await this.calculateBasic(filteredEntries, startTime, stopTime);
51
149
  return {
52
150
  revision: this.revisionNo,
53
151
  clean: true,
@@ -13,10 +13,10 @@ export default class VCCalculator_FY25_03 extends AbstractVCCalculatorBase {
13
13
  super(REVISION_NO);
14
14
  }
15
15
  isEntryIncluded(entry) {
16
- if (!CONSIDERED_ENTRY_TYPE.includes(entry.type)) {
16
+ if (!CONSIDERED_ENTRY_TYPE.includes(entry.data.type)) {
17
17
  return false;
18
18
  }
19
- if (entry.type === 'mutation:attribute') {
19
+ if (entry.data.type === 'mutation:attribute') {
20
20
  const entryData = entry.data;
21
21
  const attributeName = entryData.attributeName;
22
22
  if (!attributeName || KNOWN_ATTRIBUTES_THAT_DOES_NOT_CAUSE_LAYOUT_SHIFTS.includes(attributeName)) {
@@ -31,7 +31,7 @@ export default class VCCalculator_FY25_03 extends AbstractVCCalculatorBase {
31
31
  }
32
32
  isVCClean(filteredEntries) {
33
33
  const hasAbortEvent = filteredEntries.some(entry => {
34
- if (entry.type === 'window:event') {
34
+ if (entry.data.type === 'window:event') {
35
35
  const data = entry.data;
36
36
  if (ABORTING_WINDOW_EVENT.includes(data.eventType)) {
37
37
  return true;
@@ -44,7 +44,7 @@ export default class VCCalculator_FY25_03 extends AbstractVCCalculatorBase {
44
44
  getVCCleanStatus(filteredEntries) {
45
45
  let dirtyReason = '';
46
46
  const hasAbortEvent = filteredEntries.some(entry => {
47
- if (entry.type === 'window:event') {
47
+ if (entry.data.type === 'window:event') {
48
48
  const data = entry.data;
49
49
  if (ABORTING_WINDOW_EVENT.includes(data.eventType)) {
50
50
  dirtyReason = data.eventType === 'keydown' ? 'keypress' : data.eventType;
@@ -27,6 +27,31 @@ async function calculateTTVCPercentiles({
27
27
  const totalPixels = canvasDimenstions.width * canvasDimenstions.height;
28
28
  return calculatePercentiles(timePixelCounts, elementMap, percentiles, totalPixels, startTime);
29
29
  }
30
+ async function calculateTTVCPercentilesWithDebugInfo({
31
+ viewport,
32
+ orderedEntries,
33
+ startTime
34
+ }) {
35
+ const canvas = new ViewportCanvas(viewport, fg('platform_ufo_canvas_heatmap_full_precision') ? 1 : 0.25);
36
+ const elementMap = new Map();
37
+ for (const entry of orderedEntries) {
38
+ if (!('rect' in entry.data)) {
39
+ continue;
40
+ }
41
+ const rect = entry.data.rect;
42
+ canvas.drawRect(rect, entry.time);
43
+ if (!elementMap.has(entry.time)) {
44
+ elementMap.set(entry.time, []);
45
+ }
46
+ elementMap.get(entry.time).push(entry.data);
47
+ }
48
+
49
+ // Get pixel counts
50
+ const timePixelCounts = await canvas.getPixelCounts();
51
+ const canvasDimensions = canvas.getScaledDimensions();
52
+ const totalPixels = canvasDimensions.width * canvasDimensions.height;
53
+ return calculatePercentilesWithDebugInfo(timePixelCounts, elementMap, totalPixels, startTime);
54
+ }
30
55
  export default calculateTTVCPercentiles;
31
56
  export function calculatePercentiles(timePixelCounts, elementMap, unorderedPercentiles, totalPixels, startTime) {
32
57
  const results = {};
@@ -70,4 +95,22 @@ export function calculatePercentiles(timePixelCounts, elementMap, unorderedPerce
70
95
  previousResult = results[`${percentile}`];
71
96
  }
72
97
  return results;
73
- }
98
+ }
99
+ export function calculatePercentilesWithDebugInfo(timePixelCounts, elementMap, totalPixels, startTime) {
100
+ const results = new Array(elementMap.size);
101
+ let cumulativePixels = 0;
102
+ const sortedEntries = Array.from(timePixelCounts.entries()).sort(([timeA], [timeB]) => Number(timeA) - Number(timeB));
103
+ for (let i = 0; i < sortedEntries.length; i++) {
104
+ const [time, pixelCount] = sortedEntries[i];
105
+ cumulativePixels += pixelCount;
106
+ const percentCovered = cumulativePixels / totalPixels * 100;
107
+ const entryDatas = elementMap.get(time) || [];
108
+ results[i] = {
109
+ time: Math.round(Number(time - startTime)),
110
+ viewportPercentage: percentCovered,
111
+ entries: Array.from(entryDatas)
112
+ };
113
+ }
114
+ return results;
115
+ }
116
+ export { calculateTTVCPercentilesWithDebugInfo };
@@ -0,0 +1,75 @@
1
+ import { calculatePercentilesWithDebugInfo } from './index';
2
+
3
+ // Test utilities
4
+ const createMockRect = (x = 0, y = 0, width = 100, height = 100) => new MockDOMRect(x, y, width, height);
5
+ const createViewportEntry = (elementName, rect = createMockRect(), visible = true, type = 'mutation:element') => ({
6
+ elementName,
7
+ rect,
8
+ visible,
9
+ type
10
+ });
11
+ const createTimePixelCounts = counts => new Map(counts);
12
+ const createElementMap = entries => new Map(entries);
13
+ const createExpectedResult = (time, viewportPercentage, entries) => ({
14
+ time,
15
+ viewportPercentage,
16
+ entries
17
+ });
18
+ class MockDOMRect {
19
+ constructor(x, y, width, height) {
20
+ this.x = x;
21
+ this.y = y;
22
+ this.width = width;
23
+ this.height = height;
24
+ }
25
+ get bottom() {
26
+ return this.y + this.height;
27
+ }
28
+ get left() {
29
+ return this.x;
30
+ }
31
+ get right() {
32
+ return this.x + this.width;
33
+ }
34
+ get top() {
35
+ return this.y;
36
+ }
37
+ toJSON() {
38
+ return {
39
+ x: this.x,
40
+ y: this.y,
41
+ width: this.width,
42
+ height: this.height
43
+ };
44
+ }
45
+ }
46
+ describe('calculatePercentilesWithDebugInfo', () => {
47
+ it('should correctly calculate percentiles with accumulated elements from timestamps', () => {
48
+ const timePixelCounts = createTimePixelCounts([[100, 10], [200, 20], [300, 10], [400, 10]]);
49
+ const elementMap = createElementMap([[100, [createViewportEntry('div'), createViewportEntry('span')]], [200, [createViewportEntry('img')]], [300, [createViewportEntry('p'), createViewportEntry('a')]], [400, [createViewportEntry('img')]]]);
50
+ const expected = [createExpectedResult(100, 20, [createViewportEntry('div'), createViewportEntry('span')]), createExpectedResult(200, 60, [createViewportEntry('img')]), createExpectedResult(300, 80, [createViewportEntry('p'), createViewportEntry('a')]), createExpectedResult(400, 100, [createViewportEntry('img')])];
51
+ const result = calculatePercentilesWithDebugInfo(timePixelCounts, elementMap, 50, 0);
52
+ expect(result).toEqual(expected);
53
+ });
54
+ it('should handle empty entries gracefully', () => {
55
+ const timePixelCounts = new Map();
56
+ const elementMap = new Map();
57
+ const expected = [];
58
+ const result = calculatePercentilesWithDebugInfo(timePixelCounts, elementMap, 100, 0);
59
+ expect(result).toEqual(expected);
60
+ });
61
+ it('should handle non-sequential timestamps', () => {
62
+ const timePixelCounts = createTimePixelCounts([[300, 70], [100, 30]]);
63
+ const elementMap = createElementMap([[300, [createViewportEntry('p'), createViewportEntry('a')]], [100, [createViewportEntry('div')]]]);
64
+ const expected = [createExpectedResult(100, 30, [createViewportEntry('div')]), createExpectedResult(300, 100, [createViewportEntry('p'), createViewportEntry('a')])];
65
+ const result = calculatePercentilesWithDebugInfo(timePixelCounts, elementMap, 100, 0);
66
+ expect(result).toEqual(expected);
67
+ });
68
+ it('should correctly calculate percentiles with startTime offset', () => {
69
+ const timePixelCounts = createTimePixelCounts([[100, 10], [200, 20], [300, 10], [400, 10]]);
70
+ const elementMap = createElementMap([[100, [createViewportEntry('div'), createViewportEntry('span')]], [200, [createViewportEntry('img')]], [300, [createViewportEntry('p'), createViewportEntry('a')]], [400, [createViewportEntry('img')]]]);
71
+ const expected = [createExpectedResult(50, 20, [createViewportEntry('div'), createViewportEntry('span')]), createExpectedResult(150, 60, [createViewportEntry('img')]), createExpectedResult(250, 80, [createViewportEntry('p'), createViewportEntry('a')]), createExpectedResult(350, 100, [createViewportEntry('img')])];
72
+ const result = calculatePercentilesWithDebugInfo(timePixelCounts, elementMap, 50, 50);
73
+ expect(result).toEqual(expected);
74
+ });
75
+ });
@@ -1,20 +1,2 @@
1
- import calcUsingCanvas from './canvas-heatmap';
2
- import calcUsingOldHeatmap from './heatmap';
3
- import calcUsingRectSweepingLine from './rect-sweeping-line';
4
- async function calculateTTVCPercentiles(arg) {
5
- const algo = 'canvas_heatmap';
6
- if (algo === 'canvas_heatmap') {
7
- const vcDetails = await calcUsingCanvas(arg);
8
- return vcDetails;
9
- }
10
- if (algo === 'rect_sweeping') {
11
- const vcDetails = await calcUsingRectSweepingLine(arg);
12
- return vcDetails;
13
- }
14
- if (algo === 'old_heatmap') {
15
- const vcDetails = await calcUsingOldHeatmap(arg);
16
- return vcDetails;
17
- }
18
- throw new Error('unexpected Error algo not chosen correctly');
19
- }
20
- export default calculateTTVCPercentiles;
1
+ export { default as calculateTTVCPercentiles } from './canvas-heatmap';
2
+ export { calculateTTVCPercentilesWithDebugInfo } from './canvas-heatmap';
@@ -108,7 +108,9 @@ export default class ViewportObserver {
108
108
  });
109
109
  _defineProperty(this, "handleAttributeMutation", ({
110
110
  target,
111
- attributeName
111
+ attributeName,
112
+ oldValue,
113
+ newValue
112
114
  }) => {
113
115
  var _this$intersectionObs4;
114
116
  (_this$intersectionObs4 = this.intersectionObserver) === null || _this$intersectionObs4 === void 0 ? void 0 : _this$intersectionObs4.watchAndTag(target, ({
@@ -119,7 +121,9 @@ export default class ViewportObserver {
119
121
  return {
120
122
  type: 'mutation:media',
121
123
  mutationData: {
122
- attributeName
124
+ attributeName,
125
+ oldValue,
126
+ newValue
123
127
  }
124
128
  };
125
129
  }
@@ -131,7 +135,9 @@ export default class ViewportObserver {
131
135
  return {
132
136
  type: 'mutation:attribute:non-visual-style',
133
137
  mutationData: {
134
- attributeName
138
+ attributeName,
139
+ oldValue,
140
+ newValue
135
141
  }
136
142
  };
137
143
  }
@@ -140,14 +146,18 @@ export default class ViewportObserver {
140
146
  return {
141
147
  type: 'mutation:attribute:no-layout-shift',
142
148
  mutationData: {
143
- attributeName
149
+ attributeName,
150
+ oldValue,
151
+ newValue
144
152
  }
145
153
  };
146
154
  }
147
155
  return {
148
156
  type: 'mutation:attribute',
149
157
  mutationData: {
150
- attributeName
158
+ attributeName,
159
+ oldValue,
160
+ newValue
151
161
  }
152
162
  };
153
163
  });
@@ -30,7 +30,9 @@ function createMutationObserver({
30
30
  var _mut$attributeName;
31
31
  onAttributeMutation({
32
32
  target: mut.target,
33
- attributeName: (_mut$attributeName = mut.attributeName) !== null && _mut$attributeName !== void 0 ? _mut$attributeName : 'unknown'
33
+ attributeName: (_mut$attributeName = mut.attributeName) !== null && _mut$attributeName !== void 0 ? _mut$attributeName : 'unknown',
34
+ oldValue,
35
+ newValue
34
36
  });
35
37
  }
36
38
  continue;
@@ -101,7 +101,8 @@ function _getExperimentalVCMetrics() {
101
101
  isEventAborted: !!interaction.abortReason,
102
102
  prefix: prefix,
103
103
  vc: interaction.vc,
104
- experienceKey: interaction.ufoName
104
+ experienceKey: interaction.ufoName,
105
+ interactionId: interaction.id
105
106
  });
106
107
  case 4:
107
108
  result = _context.sent;
@@ -58,7 +58,8 @@ function _getVCMetrics() {
58
58
  prefix: prefix,
59
59
  vc: interaction.vc,
60
60
  isEventAborted: interactionStatus.originalInteractionStatus !== 'SUCCEEDED',
61
- experienceKey: interaction.ufoName
61
+ experienceKey: interaction.ufoName,
62
+ interactionId: interaction.id
62
63
  }, ssr));
63
64
  case 18:
64
65
  result = _context.sent;
@@ -122,7 +122,8 @@ export var VCObserverWrapper = /*#__PURE__*/function () {
122
122
  _context.next = 13;
123
123
  return (_this$newVCObserver5 = this.newVCObserver) === null || _this$newVCObserver5 === void 0 ? void 0 : _this$newVCObserver5.getVCResult({
124
124
  start: param.start,
125
- stop: param.stop
125
+ stop: param.stop,
126
+ interactionId: param.interactionId
126
127
  });
127
128
  case 13:
128
129
  _context.t1 = _context.sent;
@@ -149,7 +150,8 @@ export var VCObserverWrapper = /*#__PURE__*/function () {
149
150
  _context.next = 28;
150
151
  return (_this$newVCObserver6 = this.newVCObserver) === null || _this$newVCObserver6 === void 0 ? void 0 : _this$newVCObserver6.getVCResult({
151
152
  start: param.start,
152
- stop: param.stop
153
+ stop: param.stop,
154
+ interactionId: param.interactionId
153
155
  });
154
156
  case 28:
155
157
  newResult = _context.sent;
@@ -0,0 +1,35 @@
1
+ export function getVCRevisionDebugDetails(_ref) {
2
+ var revision = _ref.revision,
3
+ isClean = _ref.isClean,
4
+ abortReason = _ref.abortReason,
5
+ VCEntries = _ref.VCEntries,
6
+ componentsLog = _ref.componentsLog,
7
+ interactionId = _ref.interactionId;
8
+ return {
9
+ revision: revision,
10
+ isClean: isClean,
11
+ abortReason: abortReason,
12
+ vcLogs: VCEntries.map(function (entry) {
13
+ return {
14
+ time: entry.time,
15
+ viewportPercentage: entry.vc,
16
+ entries: entry.elements.map(function (element) {
17
+ var _componentsLog$entry$;
18
+ var logEntry = (_componentsLog$entry$ = componentsLog[entry.time]) === null || _componentsLog$entry$ === void 0 ? void 0 : _componentsLog$entry$.find(function (log) {
19
+ return log.targetName === element;
20
+ });
21
+ return {
22
+ elementName: element,
23
+ type: logEntry === null || logEntry === void 0 ? void 0 : logEntry.type,
24
+ rect: logEntry === null || logEntry === void 0 ? void 0 : logEntry.intersectionRect,
25
+ visible: true,
26
+ attributeName: logEntry === null || logEntry === void 0 ? void 0 : logEntry.attributeName,
27
+ oldValue: logEntry === null || logEntry === void 0 ? void 0 : logEntry.oldValue,
28
+ newValue: logEntry === null || logEntry === void 0 ? void 0 : logEntry.newValue
29
+ };
30
+ })
31
+ };
32
+ }),
33
+ interactionId: interactionId
34
+ };
35
+ }
@@ -13,6 +13,7 @@ import { fg } from '@atlaskit/platform-feature-flags';
13
13
  import { isVCRevisionEnabled } from '../../config';
14
14
  import { getActiveInteraction } from '../../interaction-metrics';
15
15
  import { attachAbortListeners } from './attachAbortListeners';
16
+ import { getVCRevisionDebugDetails } from './getVCRevisionDebugDetails';
16
17
  import { getVCRevisionsData } from './getVCRevisionsData';
17
18
  import { getViewportHeight, getViewportWidth } from './getViewport';
18
19
  import { MultiRevisionHeatmap } from './heatmap/heatmap';
@@ -99,11 +100,11 @@ export var VCObserver = /*#__PURE__*/function () {
99
100
  });
100
101
  _defineProperty(this, "getVCResult", /*#__PURE__*/function () {
101
102
  var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(_ref3) {
102
- var start, stop, tti, prefix, ssr, vc, isEventAborted, experienceKey, startTime, fullPrefix, rawData, abortReason, abortReasonInfo, heatmap, heatmapNext, outOfBoundaryInfo, totalTime, componentsLog, viewport, devToolsEnabled, ratios, multiHeatmap, isTTVCv1Disabled, vcAbortedResultWithRevisions, ttvcV1Result, VC, VCBox, VCEntries, totalPainted, _componentsLog, vcNext, outOfBoundary, stopTime, ttvcV1DevToolInfo, ttvcV2DevToolInfo, isVCClean, revisionsData, speedIndex, isTTVCv3Enabled;
103
+ var start, stop, tti, prefix, ssr, vc, isEventAborted, experienceKey, interactionId, startTime, fullPrefix, rawData, abortReason, abortReasonInfo, heatmap, heatmapNext, outOfBoundaryInfo, totalTime, componentsLog, viewport, devToolsEnabled, ratios, multiHeatmap, isTTVCv1Disabled, vcAbortedResultWithRevisions, ttvcV1Result, VC, VCBox, VCEntries, totalPainted, _componentsLog, vcNext, outOfBoundary, stopTime, ttvcV1DevToolInfo, ttvcV2DevToolInfo, _ufo_devtool_onVCRev2, _ref10, _ufo_devtool_onVCRev, _ref9, isVCClean, revisionsData, speedIndex, isTTVCv3Enabled;
103
104
  return _regeneratorRuntime.wrap(function _callee$(_context) {
104
105
  while (1) switch (_context.prev = _context.next) {
105
106
  case 0:
106
- start = _ref3.start, stop = _ref3.stop, tti = _ref3.tti, prefix = _ref3.prefix, ssr = _ref3.ssr, vc = _ref3.vc, isEventAborted = _ref3.isEventAborted, experienceKey = _ref3.experienceKey;
107
+ start = _ref3.start, stop = _ref3.stop, tti = _ref3.tti, prefix = _ref3.prefix, ssr = _ref3.ssr, vc = _ref3.vc, isEventAborted = _ref3.isEventAborted, experienceKey = _ref3.experienceKey, interactionId = _ref3.interactionId;
107
108
  startTime = performance.now(); // add local measurement
108
109
  fullPrefix = prefix !== undefined && prefix !== '' ? "".concat(prefix, ":") : '';
109
110
  rawData = vc !== undefined ? vc : _this.getVCRawData();
@@ -280,6 +281,31 @@ export var VCObserver = /*#__PURE__*/function () {
280
281
  entries: isTTVCv1Disabled ? vcNext.VCEntries.rel : VCEntries.rel
281
282
  }
282
283
  }));
284
+
285
+ // Add devtool callback for both v1 and v2
286
+ if (typeof window.__ufo_devtool_onVCRevisionReady__ === 'function' && fg('platform_ufo_ttvc_v3_devtool')) {
287
+ // Handle v1 if not disabled
288
+ if (!isTTVCv1Disabled) {
289
+ (_ufo_devtool_onVCRev = (_ref9 = window).__ufo_devtool_onVCRevisionReady__) === null || _ufo_devtool_onVCRev === void 0 || _ufo_devtool_onVCRev.call(_ref9, getVCRevisionDebugDetails({
290
+ revision: 'fy25.01',
291
+ isClean: !abortReasonInfo,
292
+ abortReason: abortReason.reason,
293
+ VCEntries: VCEntries.rel,
294
+ componentsLog: componentsLog,
295
+ interactionId: interactionId
296
+ }));
297
+ }
298
+
299
+ // Handle v2
300
+ (_ufo_devtool_onVCRev2 = (_ref10 = window).__ufo_devtool_onVCRevisionReady__) === null || _ufo_devtool_onVCRev2 === void 0 || _ufo_devtool_onVCRev2.call(_ref10, getVCRevisionDebugDetails({
301
+ revision: 'fy25.02',
302
+ isClean: !abortReasonInfo,
303
+ abortReason: abortReason.reason,
304
+ VCEntries: vcNext.VCEntries.rel,
305
+ componentsLog: componentsLog,
306
+ interactionId: interactionId
307
+ }));
308
+ }
283
309
  }
284
310
  } catch (e) {
285
311
  /* do nothing */
@@ -380,16 +406,16 @@ export var VCObserver = /*#__PURE__*/function () {
380
406
  });
381
407
  }
382
408
  });
383
- _defineProperty(this, "onViewportChangeDetected", function (_ref9) {
384
- var element = _ref9.element,
385
- type = _ref9.type,
386
- ignoreReason = _ref9.ignoreReason,
387
- timestamp = _ref9.timestamp,
388
- targetName = _ref9.targetName,
389
- intersectionRect = _ref9.intersectionRect,
390
- attributeName = _ref9.attributeName,
391
- oldValue = _ref9.oldValue,
392
- newValue = _ref9.newValue;
409
+ _defineProperty(this, "onViewportChangeDetected", function (_ref11) {
410
+ var element = _ref11.element,
411
+ type = _ref11.type,
412
+ ignoreReason = _ref11.ignoreReason,
413
+ timestamp = _ref11.timestamp,
414
+ targetName = _ref11.targetName,
415
+ intersectionRect = _ref11.intersectionRect,
416
+ attributeName = _ref11.attributeName,
417
+ oldValue = _ref11.oldValue,
418
+ newValue = _ref11.newValue;
393
419
  if (_this.multiHeatmap === null) {
394
420
  return;
395
421
  }
@@ -474,10 +500,10 @@ export var VCObserver = /*#__PURE__*/function () {
474
500
  var unbinds = attachAbortListeners(window, _this.viewport, _this.abortReasonCallback);
475
501
  if ((_window = window) !== null && _window !== void 0 && _window.__SSR_ABORT_LISTENERS__) {
476
502
  var _window2;
477
- Object.entries(window.__SSR_ABORT_LISTENERS__.aborts).forEach(function (_ref10) {
478
- var _ref11 = _slicedToArray(_ref10, 2),
479
- key = _ref11[0],
480
- time = _ref11[1];
503
+ Object.entries(window.__SSR_ABORT_LISTENERS__.aborts).forEach(function (_ref12) {
504
+ var _ref13 = _slicedToArray(_ref12, 2),
505
+ key = _ref13[0],
506
+ time = _ref13[1];
481
507
  if (time) {
482
508
  _this.abortReasonCallback(key, time);
483
509
  }
@@ -510,8 +536,8 @@ export var VCObserver = /*#__PURE__*/function () {
510
536
  }
511
537
  return _createClass(VCObserver, [{
512
538
  key: "start",
513
- value: function start(_ref12) {
514
- var startTime = _ref12.startTime;
539
+ value: function start(_ref14) {
540
+ var startTime = _ref14.startTime;
515
541
  this.active = true;
516
542
  if (this.observers.isBrowserSupported()) {
517
543
  this.setViewportSize();
@@ -533,12 +559,12 @@ export var VCObserver = /*#__PURE__*/function () {
533
559
  }, {
534
560
  key: "getIgnoredElements",
535
561
  value: function getIgnoredElements(componentsLog) {
536
- return Object.values(componentsLog).flat().filter(function (_ref13) {
537
- var ignoreReason = _ref13.ignoreReason;
562
+ return Object.values(componentsLog).flat().filter(function (_ref15) {
563
+ var ignoreReason = _ref15.ignoreReason;
538
564
  return Boolean(ignoreReason);
539
- }).map(function (_ref14) {
540
- var targetName = _ref14.targetName,
541
- ignoreReason = _ref14.ignoreReason;
565
+ }).map(function (_ref16) {
566
+ var targetName = _ref16.targetName,
567
+ ignoreReason = _ref16.ignoreReason;
542
568
  return {
543
569
  targetName: targetName,
544
570
  ignoreReason: ignoreReason
@@ -661,12 +687,12 @@ export var VCObserver = /*#__PURE__*/function () {
661
687
  }
662
688
  }], [{
663
689
  key: "calculateVC",
664
- value: function calculateVC(_ref15) {
665
- var heatmap = _ref15.heatmap,
666
- _ref15$ssr = _ref15.ssr,
667
- ssr = _ref15$ssr === void 0 ? UNUSED_SECTOR : _ref15$ssr,
668
- componentsLog = _ref15.componentsLog,
669
- viewport = _ref15.viewport;
690
+ value: function calculateVC(_ref17) {
691
+ var heatmap = _ref17.heatmap,
692
+ _ref17$ssr = _ref17.ssr,
693
+ ssr = _ref17$ssr === void 0 ? UNUSED_SECTOR : _ref17$ssr,
694
+ componentsLog = _ref17.componentsLog,
695
+ viewport = _ref17.viewport;
670
696
  var lastUpdate = {};
671
697
  var totalPainted = 0;
672
698
  if (ssr !== UNUSED_SECTOR) {
@@ -732,11 +758,11 @@ export var VCObserver = /*#__PURE__*/function () {
732
758
  });
733
759
  return VCRatio;
734
760
  }, 0);
735
- var VCEntries = entries.reduce(function (acc, _ref16, i) {
761
+ var VCEntries = entries.reduce(function (acc, _ref18, i) {
736
762
  var _acc$abs, _componentsLog$timest, _acc$rel$vc, _acc$rel;
737
- var _ref17 = _slicedToArray(_ref16, 2),
738
- timestamp = _ref17[0],
739
- entryPainted = _ref17[1];
763
+ var _ref19 = _slicedToArray(_ref18, 2),
764
+ timestamp = _ref19[0],
765
+ entryPainted = _ref19[1];
740
766
  var currentlyPainted = entryPainted + (((_acc$abs = acc.abs[i - 1]) === null || _acc$abs === void 0 ? void 0 : _acc$abs[1]) || 0);
741
767
  var currentlyPaintedRatio = Math.round(currentlyPainted / totalPainted * 1000) / 10;
742
768
  var logEntry = _toConsumableArray(new Set((_componentsLog$timest = componentsLog[timestamp]) === null || _componentsLog$timest === void 0 ? void 0 : _componentsLog$timest.filter(function (v) {