diff --git a/static/app/components/replays/breadcrumbs/breadcrumbItem.tsx b/static/app/components/replays/breadcrumbs/breadcrumbItem.tsx index 5b0fb28f1b1916..ccbff2e2dcf86f 100644 --- a/static/app/components/replays/breadcrumbs/breadcrumbItem.tsx +++ b/static/app/components/replays/breadcrumbs/breadcrumbItem.tsx @@ -34,6 +34,7 @@ import type { } from 'sentry/utils/replays/types'; import { isBreadcrumbFrame, + isCLSFrame, isErrorFrame, isFeedbackFrame, isHydrationErrorFrame, @@ -237,11 +238,7 @@ function WebVitalData({ const selectors = frameToExtraction?.get(frame)?.selectors; const webVitalData = {value: frame.data.value}; - if ( - frame.description === 'cumulative-layout-shift' && - frame.data.attributions && - selectors - ) { + if (isCLSFrame(frame) && frame.data.attributions && selectors) { const layoutShifts: {[x: string]: ReactNode[]}[] = []; for (const attr of frame.data.attributions) { const elements: ReactNode[] = []; diff --git a/static/app/utils/replays/getFrameDetails.tsx b/static/app/utils/replays/getFrameDetails.tsx index 5720b198c218b3..79b5cd12a83e0d 100644 --- a/static/app/utils/replays/getFrameDetails.tsx +++ b/static/app/utils/replays/getFrameDetails.tsx @@ -44,6 +44,7 @@ import type { } from 'sentry/utils/replays/types'; import { getFrameOpOrCategory, + isCLSFrame, isDeadClick, isDeadRageClick, isRageClick, @@ -286,8 +287,9 @@ const MAPPER_FOR_FRAME: Record Details> = { case 'good': return { color: 'green300', - description: tct('[value]ms (Good)', { + description: tct('[value][unit] (Good)', { value: frame.data.value.toFixed(2), + unit: isCLSFrame(frame) ? '' : 'ms', }), tabKey: TabKey.NETWORK, title: 'Web Vital: ' + toTitleCase(explodeSlug(frame.description)), @@ -296,8 +298,9 @@ const MAPPER_FOR_FRAME: Record Details> = { case 'needs-improvement': return { color: 'yellow300', - description: tct('[value]ms (Meh)', { + description: tct('[value][unit] (Meh)', { value: frame.data.value.toFixed(2), + unit: isCLSFrame(frame) ? '' : 'ms', }), tabKey: TabKey.NETWORK, title: 'Web Vital: ' + toTitleCase(explodeSlug(frame.description)), @@ -306,8 +309,9 @@ const MAPPER_FOR_FRAME: Record Details> = { default: return { color: 'red300', - description: tct('[value]ms (Poor)', { + description: tct('[value][unit] (Poor)', { value: frame.data.value.toFixed(2), + unit: isCLSFrame(frame) ? '' : 'ms', }), tabKey: TabKey.NETWORK, title: 'Web Vital: ' + toTitleCase(explodeSlug(frame.description)), diff --git a/static/app/utils/replays/replayReader.tsx b/static/app/utils/replays/replayReader.tsx index 318c3f59c2fa9f..87c8bd7739a819 100644 --- a/static/app/utils/replays/replayReader.tsx +++ b/static/app/utils/replays/replayReader.tsx @@ -41,6 +41,7 @@ import { EventType, getNodeIds, IncrementalSource, + isCLSFrame, isDeadClick, isDeadRageClick, isPaintFrame, @@ -637,12 +638,12 @@ export default class ReplayReader { let lastTimestamp = 0; const groupedCls: WebVitalFrame[] = []; - for (const cls of allWebVitals) { - if (cls.description === 'cumulative-layout-shift') { - if (lastTimestamp === cls.timestampMs) { - groupedCls.push(cls); + for (const frame of allWebVitals) { + if (isCLSFrame(frame)) { + if (lastTimestamp === frame.timestampMs) { + groupedCls.push(frame); } else { - lastTimestamp = cls.timestampMs; + lastTimestamp = frame.timestampMs; } } } diff --git a/static/app/utils/replays/types.tsx b/static/app/utils/replays/types.tsx index d83cf02a2fff1c..cd7c710fdad964 100644 --- a/static/app/utils/replays/types.tsx +++ b/static/app/utils/replays/types.tsx @@ -202,6 +202,10 @@ export function isWebVitalFrame(frame: SpanFrame): frame is WebVitalFrame { return frame.op === 'web-vital'; } +export function isCLSFrame(frame: WebVitalFrame): frame is WebVitalFrame { + return frame.description === 'cumulative-layout-shift'; +} + export function isPaintFrame(frame: SpanFrame): frame is PaintFrame { return frame.op === 'paint'; }