From 67a067a567f70a737343132a6fd61be907570119 Mon Sep 17 00:00:00 2001 From: Catherine Lee <55311782+c298lee@users.noreply.github.com> Date: Tue, 30 Jul 2024 13:13:40 -0400 Subject: [PATCH] ref(replay): Update CLS web vitals to align with browserTracing (#13058) NodeId has been replaced with nodeIds which now accepts several nodes since CLS scores are cumulative. Also updated CLS metrics to match CLS captured from browserTracing. Relates to https://github.com/getsentry/sentry/issues/69881 --- .size-limit.js | 2 +- .../utils/replayEventTemplates.ts | 7 ++-- .../tests/fixtures/ReplayRecordingData.ts | 5 +-- .../replay-internal/src/types/performance.ts | 4 +-- .../src/util/createPerformanceEntries.ts | 34 +++++++++---------- .../unit/util/createPerformanceEntry.test.ts | 10 +++--- 6 files changed, 32 insertions(+), 30 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index dc85fffe40af..72050f7225f3 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -149,7 +149,7 @@ module.exports = [ name: 'CDN Bundle (incl. Tracing, Replay)', path: createCDNPath('bundle.tracing.replay.min.js'), gzip: true, - limit: '72 KB', + limit: '73 KB', }, { name: 'CDN Bundle (incl. Tracing, Replay, Feedback)', diff --git a/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts b/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts index 257c47fbfa9b..f4defc27182c 100644 --- a/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts +++ b/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts @@ -127,7 +127,7 @@ export const expectedLCPPerformanceSpan = { endTimestamp: expect.any(Number), data: { value: expect.any(Number), - nodeId: expect.any(Number), + nodeIds: expect.any(Array), rating: expect.any(String), size: expect.any(Number), }, @@ -140,6 +140,7 @@ export const expectedCLSPerformanceSpan = { endTimestamp: expect.any(Number), data: { value: expect.any(Number), + nodeIds: expect.any(Array), rating: expect.any(String), size: expect.any(Number), }, @@ -154,7 +155,7 @@ export const expectedFIDPerformanceSpan = { value: expect.any(Number), rating: expect.any(String), size: expect.any(Number), - nodeId: expect.any(Number), + nodeIds: expect.any(Array), }, }; @@ -167,7 +168,7 @@ export const expectedINPPerformanceSpan = { value: expect.any(Number), rating: expect.any(String), size: expect.any(Number), - nodeId: expect.any(Number), + nodeIds: expect.any(Array), }, }; diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/fixtures/ReplayRecordingData.ts b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/fixtures/ReplayRecordingData.ts index 156c2775f5ff..1b054c099b3d 100644 --- a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/fixtures/ReplayRecordingData.ts +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/fixtures/ReplayRecordingData.ts @@ -220,7 +220,7 @@ export const ReplayRecordingData = [ value: expect.any(Number), size: expect.any(Number), rating: expect.any(String), - nodeId: 16, + nodeIds: [16], }, }, }, @@ -239,6 +239,7 @@ export const ReplayRecordingData = [ value: expect.any(Number), size: expect.any(Number), rating: expect.any(String), + nodeIds: expect.any(Array), }, }, }, @@ -257,7 +258,7 @@ export const ReplayRecordingData = [ value: expect.any(Number), size: expect.any(Number), rating: expect.any(String), - nodeId: 10, + nodeIds: [10], }, }, }, diff --git a/packages/replay-internal/src/types/performance.ts b/packages/replay-internal/src/types/performance.ts index 5241c12d847a..6b264a44ee9c 100644 --- a/packages/replay-internal/src/types/performance.ts +++ b/packages/replay-internal/src/types/performance.ts @@ -108,9 +108,9 @@ export interface WebVitalData { */ rating: 'good' | 'needs-improvement' | 'poor'; /** - * The recording id of the LCP node. -1 if not found + * The recording id of the web vital nodes. -1 if not found */ - nodeId?: number; + nodeIds?: number[]; } /** diff --git a/packages/replay-internal/src/util/createPerformanceEntries.ts b/packages/replay-internal/src/util/createPerformanceEntries.ts index 28ccf60280e8..d55c2269d0f4 100644 --- a/packages/replay-internal/src/util/createPerformanceEntries.ts +++ b/packages/replay-internal/src/util/createPerformanceEntries.ts @@ -183,7 +183,7 @@ function createResourceEntry( */ export function getLargestContentfulPaint(metric: Metric): ReplayPerformanceEntry { const lastEntry = metric.entries[metric.entries.length - 1] as (PerformanceEntry & { element?: Node }) | undefined; - const node = lastEntry ? lastEntry.element : undefined; + const node = lastEntry && lastEntry.element ? [lastEntry.element] : undefined; return getWebVital(metric, 'largest-contentful-paint', node); } @@ -191,14 +191,18 @@ export function getLargestContentfulPaint(metric: Metric): ReplayPerformanceEntr * Add a CLS event to the replay based on a CLS metric. */ export function getCumulativeLayoutShift(metric: Metric): ReplayPerformanceEntry { - // get first node that shifts - const firstEntry = metric.entries[0] as (PerformanceEntry & { sources?: LayoutShiftAttribution[] }) | undefined; - const node = firstEntry - ? firstEntry.sources && firstEntry.sources[0] - ? firstEntry.sources[0].node - : undefined - : undefined; - return getWebVital(metric, 'cumulative-layout-shift', node); + const lastEntry = metric.entries[metric.entries.length - 1] as + | (PerformanceEntry & { sources?: LayoutShiftAttribution[] }) + | undefined; + const nodes: Node[] = []; + if (lastEntry && lastEntry.sources) { + for (const source of lastEntry.sources) { + if (source.node) { + nodes.push(source.node); + } + } + } + return getWebVital(metric, 'cumulative-layout-shift', nodes); } /** @@ -206,7 +210,7 @@ export function getCumulativeLayoutShift(metric: Metric): ReplayPerformanceEntry */ export function getFirstInputDelay(metric: Metric): ReplayPerformanceEntry { const lastEntry = metric.entries[metric.entries.length - 1] as (PerformanceEntry & { target?: Node }) | undefined; - const node = lastEntry ? lastEntry.target : undefined; + const node = lastEntry && lastEntry.target ? [lastEntry.target] : undefined; return getWebVital(metric, 'first-input-delay', node); } @@ -215,18 +219,14 @@ export function getFirstInputDelay(metric: Metric): ReplayPerformanceEntry { const lastEntry = metric.entries[metric.entries.length - 1] as (PerformanceEntry & { target?: Node }) | undefined; - const node = lastEntry ? lastEntry.target : undefined; + const node = lastEntry && lastEntry.target ? [lastEntry.target] : undefined; return getWebVital(metric, 'interaction-to-next-paint', node); } /** * Add an web vital event to the replay based on the web vital metric. */ -export function getWebVital( - metric: Metric, - name: string, - node: Node | undefined, -): ReplayPerformanceEntry { +function getWebVital(metric: Metric, name: string, nodes: Node[] | undefined): ReplayPerformanceEntry { const value = metric.value; const rating = metric.rating; @@ -241,7 +241,7 @@ export function getWebVital( value, size: value, rating, - nodeId: node ? record.mirror.getId(node) : undefined, + nodeIds: nodes ? nodes.map(node => record.mirror.getId(node)) : undefined, }, }; diff --git a/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts b/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts index f1f9f71bc85c..d85698d1be1d 100644 --- a/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts +++ b/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts @@ -83,13 +83,13 @@ describe('Unit | util | createPerformanceEntries', () => { name: 'largest-contentful-paint', start: 1672531205.108299, end: 1672531205.108299, - data: { value: 5108.299, rating: 'good', size: 5108.299, nodeId: undefined }, + data: { value: 5108.299, rating: 'good', size: 5108.299, nodeIds: undefined }, }); }); }); describe('getCumulativeLayoutShift', () => { - it('works with an CLS metric', async () => { + it('works with a CLS metric', async () => { const metric = { value: 5108.299, rating: 'good' as const, @@ -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', nodeId: undefined }, + data: { value: 5108.299, size: 5108.299, rating: 'good', nodeIds: [] }, }); }); }); @@ -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', nodeId: undefined }, + data: { value: 5108.299, size: 5108.299, rating: 'good', nodeIds: undefined }, }); }); }); @@ -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', nodeId: undefined }, + data: { value: 5108.299, size: 5108.299, rating: 'good', nodeIds: undefined }, }); }); });