Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions packages/replay-internal/src/util/createPerformanceEntries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,10 @@ export function getCumulativeLayoutShift(metric: Metric): ReplayPerformanceEntry
}
}

return getWebVital(metric, 'cumulative-layout-shift', nodes, layoutShifts);
// The CLS value is a score, not a time, so we place the event at the last layout shift. A CLS of 0 has no layout
// shift, so it goes at the time origin.
const lastEntry = metric.entries[metric.entries.length - 1];
return getWebVital(metric, 'cumulative-layout-shift', nodes, layoutShifts, lastEntry?.startTime ?? 0);
}

/**
Expand All @@ -230,7 +233,8 @@ export function getInteractionToNextPaint(metric: Metric): ReplayPerformanceEntr
// oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- rule false positive: the cast exposes the entry's `target` field; tsc errors without it
const lastEntry = metric.entries[metric.entries.length - 1] as (PerformanceEntry & { target?: Node }) | undefined;
const node = lastEntry?.target ? [lastEntry.target] : undefined;
return getWebVital(metric, 'interaction-to-next-paint', node);
// The INP value is a duration, not a time, so we place the event at the interaction.
return getWebVital(metric, 'interaction-to-next-paint', node, undefined, lastEntry?.startTime ?? 0);
}

/**
Expand All @@ -241,11 +245,12 @@ function getWebVital(
name: string,
nodes: Node[] | undefined,
attributions?: WebVitalData['attributions'],
time = metric.value,
): ReplayPerformanceEntry<WebVitalData> {
const value = metric.value;
const rating = metric.rating;

const end = getAbsoluteTime(value);
const end = getAbsoluteTime(time);

return {
type: 'web-vital',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,14 @@ describe('Unit | util | createPerformanceEntries', () => {
});

describe('getCumulativeLayoutShift', () => {
it('works with a CLS metric', async () => {
it('places the CLS event at the last layout shift', async () => {
const metric = {
value: 5108.299,
rating: 'good' as const,
entries: [],
value: 0.15,
rating: 'needs-improvement' as const,
entries: [
{ entryType: 'layout-shift', startTime: 1000, value: 0.05, sources: [], hadRecentInput: false },
{ entryType: 'layout-shift', startTime: 5108.299, value: 0.1, sources: [], hadRecentInput: false },
] as unknown as PerformanceEntry[],
};

const event = getCumulativeLayoutShift(metric);
Expand All @@ -157,17 +160,56 @@ describe('Unit | util | createPerformanceEntries', () => {
name: 'cumulative-layout-shift',
start: 1672531205.108299,
end: 1672531205.108299,
data: { value: 5108.299, size: 5108.299, rating: 'good', nodeIds: [], attributions: [] },
data: {
value: 0.15,
size: 0.15,
rating: 'needs-improvement',
nodeIds: [],
attributions: [
{ value: 0.05, nodeIds: undefined },
{ value: 0.1, nodeIds: undefined },
],
},
});
});

it('uses the time origin from when the last layout shift happened', () => {
const driftPointMs = 200_000;
const sleepDurationMs = 3_600_000;
vi.mocked(browserPerformanceTimeOrigin).mockImplementation((time = 0) =>
time < driftPointMs ? TIME_ORIGIN : TIME_ORIGIN + sleepDurationMs,
);

const event = getCumulativeLayoutShift({
value: 0.1,
rating: 'good',
entries: [
{ entryType: 'layout-shift', startTime: driftPointMs + 1000, value: 0.1, sources: [], hadRecentInput: false },
] as unknown as PerformanceEntry[],
});

expect(event.start).toBe((TIME_ORIGIN + sleepDurationMs + driftPointMs + 1000) / 1000);
});

it('places a CLS of 0 at the time origin', async () => {
const event = getCumulativeLayoutShift({ value: 0, rating: 'good', entries: [] });

expect(event).toEqual({
type: 'web-vital',
name: 'cumulative-layout-shift',
start: TIME_ORIGIN / 1000,
end: TIME_ORIGIN / 1000,
data: { value: 0, size: 0, rating: 'good', nodeIds: [], attributions: [] },
});
});
});

describe('getInteractionToNextPaint', () => {
it('works with an INP metric', async () => {
it('places the INP event at the interaction', async () => {
const metric = {
value: 5108.299,
value: 120,
rating: 'good' as const,
entries: [],
entries: [{ name: 'click', entryType: 'event', startTime: 5108.299, duration: 120 }] as PerformanceEntry[],
};

const event = getInteractionToNextPaint(metric);
Expand All @@ -177,8 +219,26 @@ 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, attributions: undefined },
data: { value: 120, size: 120, rating: 'good', nodeIds: undefined, attributions: undefined },
});
});

it('uses the time origin from when the interaction happened', () => {
const driftPointMs = 200_000;
const sleepDurationMs = 3_600_000;
vi.mocked(browserPerformanceTimeOrigin).mockImplementation((time = 0) =>
time < driftPointMs ? TIME_ORIGIN : TIME_ORIGIN + sleepDurationMs,
);

const event = getInteractionToNextPaint({
value: 120,
rating: 'good',
entries: [
{ name: 'click', entryType: 'event', startTime: driftPointMs + 1000, duration: 120 },
] as PerformanceEntry[],
});

expect(event.start).toBe((TIME_ORIGIN + sleepDurationMs + driftPointMs + 1000) / 1000);
});
});
});
Loading