Skip to content

Commit

Permalink
feat(replay): Add layout shift to CLS replay data (#13386)
Browse files Browse the repository at this point in the history
We want to show the score for each layout shift as well as the all the
nodes that contributed to the score, so we're adding a new
`attributions` object to our web vitals data

Relates to getsentry/sentry#69881

---------

Co-authored-by: Billy Vong <[email protected]>
  • Loading branch information
c298lee and billyvg committed Aug 28, 2024
1 parent 8d9fe35 commit 918c011
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export const expectedCLSPerformanceSpan = {
data: {
value: expect.any(Number),
nodeIds: expect.any(Array),
attributions: expect.any(Array),
rating: expect.any(String),
size: expect.any(Number),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ export const ReplayRecordingData = [
data: {
value: expect.any(Number),
size: expect.any(Number),
nodeId: 16,
nodeIds: [16],
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ export const ReplayRecordingData = [
size: expect.any(Number),
rating: expect.any(String),
nodeIds: expect.any(Array),
attributions: expect.any(Array),
},
},
},
Expand Down
4 changes: 4 additions & 0 deletions packages/replay-internal/src/types/performance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ export interface WebVitalData {
* The recording id of the web vital nodes. -1 if not found
*/
nodeIds?: number[];
/**
* The layout shifts of a CLS metric
*/
attributions?: { value: number; sources?: number[] }[];
}

/**
Expand Down
47 changes: 37 additions & 10 deletions packages/replay-internal/src/util/createPerformanceEntries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,13 @@ export interface Metric {
* The array may also be empty if the metric value was not based on any
* entries (e.g. a CLS value of 0 given no layout shifts).
*/
entries: PerformanceEntry[] | PerformanceEventTiming[];
entries: PerformanceEntry[] | LayoutShift[];
}

interface LayoutShift extends PerformanceEntry {
value: number;
sources: LayoutShiftAttribution[];
hadRecentInput: boolean;
}

interface LayoutShiftAttribution {
Expand All @@ -52,6 +58,11 @@ interface LayoutShiftAttribution {
currentRect: DOMRectReadOnly;
}

interface Attribution {
value: number;
nodeIds?: number[];
}

/**
* Handler creater for web vitals
*/
Expand Down Expand Up @@ -187,22 +198,32 @@ export function getLargestContentfulPaint(metric: Metric): ReplayPerformanceEntr
return getWebVital(metric, 'largest-contentful-paint', node);
}

function isLayoutShift(entry: PerformanceEntry | LayoutShift): entry is LayoutShift {
return (entry as LayoutShift).sources !== undefined;
}

/**
* Add a CLS event to the replay based on a CLS metric.
*/
export function getCumulativeLayoutShift(metric: Metric): ReplayPerformanceEntry<WebVitalData> {
const lastEntry = metric.entries[metric.entries.length - 1] as
| (PerformanceEntry & { sources?: LayoutShiftAttribution[] })
| undefined;
const layoutShifts: Attribution[] = [];
const nodes: Node[] = [];
if (lastEntry && lastEntry.sources) {
for (const source of lastEntry.sources) {
if (source.node) {
nodes.push(source.node);
for (const entry of metric.entries) {
if (isLayoutShift(entry)) {
const nodeIds = [];
for (const source of entry.sources) {
if (source.node) {
nodes.push(source.node);
const nodeId = record.mirror.getId(source.node);
if (nodeId) {
nodeIds.push(nodeId);
}
}
}
layoutShifts.push({ value: entry.value, nodeIds });
}
}
return getWebVital(metric, 'cumulative-layout-shift', nodes);
return getWebVital(metric, 'cumulative-layout-shift', nodes, layoutShifts);
}

/**
Expand All @@ -226,7 +247,12 @@ export function getInteractionToNextPaint(metric: Metric): ReplayPerformanceEntr
/**
* Add an web vital event to the replay based on the web vital metric.
*/
function getWebVital(metric: Metric, name: string, nodes: Node[] | undefined): ReplayPerformanceEntry<WebVitalData> {
function getWebVital(
metric: Metric,
name: string,
nodes: Node[] | undefined,
attributions?: Attribution[],
): ReplayPerformanceEntry<WebVitalData> {
const value = metric.value;
const rating = metric.rating;

Expand All @@ -242,6 +268,7 @@ function getWebVital(metric: Metric, name: string, nodes: Node[] | undefined): R
size: value,
rating,
nodeIds: nodes ? nodes.map(node => record.mirror.getId(node)) : undefined,
attributions,
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe('Unit | util | createPerformanceEntries', () => {
name: 'largest-contentful-paint',
start: 1672531205.108299,
end: 1672531205.108299,
data: { value: 5108.299, rating: 'good', size: 5108.299, nodeIds: undefined },
data: { value: 5108.299, rating: 'good', size: 5108.299, nodeIds: undefined, attributions: undefined },
});
});
});
Expand All @@ -103,7 +103,7 @@ describe('Unit | util | createPerformanceEntries', () => {
name: 'cumulative-layout-shift',
start: 1672531205.108299,
end: 1672531205.108299,
data: { value: 5108.299, size: 5108.299, rating: 'good', nodeIds: [] },
data: { value: 5108.299, size: 5108.299, rating: 'good', nodeIds: [], attributions: [] },
});
});
});
Expand All @@ -123,7 +123,7 @@ describe('Unit | util | createPerformanceEntries', () => {
name: 'first-input-delay',
start: 1672531205.108299,
end: 1672531205.108299,
data: { value: 5108.299, size: 5108.299, rating: 'good', nodeIds: undefined },
data: { value: 5108.299, size: 5108.299, rating: 'good', nodeIds: undefined, attributions: undefined },
});
});
});
Expand All @@ -143,7 +143,7 @@ describe('Unit | util | createPerformanceEntries', () => {
name: 'interaction-to-next-paint',
start: 1672531205.108299,
end: 1672531205.108299,
data: { value: 5108.299, size: 5108.299, rating: 'good', nodeIds: undefined },
data: { value: 5108.299, size: 5108.299, rating: 'good', nodeIds: undefined, attributions: undefined },
});
});
});
Expand Down

0 comments on commit 918c011

Please sign in to comment.