Skip to content

Commit 50fdd0e

Browse files
authored
feat(replay): Calculate hydration diff timestamps based on related hydration breadcrumbs (#73095)
This is the 2nd try at #72561 The original was reverted in 986296c because of some errors that popped up: - https://sentry.sentry.io/issues/5505399172/?project=11276&referrer=github-pr-bot - https://sentry.sentry.io/issues/5505401256/?project=11276&referrer=github-pr-bot - https://sentry.sentry.io/issues/5505436186/?project=11276&referrer=github-pr-bot - https://sentry.sentry.io/issues/5505439753/?project=11276&referrer=github-pr-bot - https://sentry.sentry.io/issues/5505459069/?project=11276&referrer=github-pr-bot The difference now is that I've improved the types to include `data.mutations.next`. The real but though was that before, in `breadcrumbItem.tsx`, we were doing the left/right timestamp math only for crumbs that have that mutations.next field... That problem is fixed now because we're checking the crumb type first, then defer to the new `<CrumbHydrationButton>` which will get the offsets and render all at once. Without that fix, we were basically trying to get the left/right offsets for any breadcrumb type, which would easily explode. Related to #70199
1 parent 8a16854 commit 50fdd0e

File tree

13 files changed

+394
-78
lines changed

13 files changed

+394
-78
lines changed

static/app/components/events/eventHydrationDiff/replayDiffContent.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {ReplayGroupContextProvider} from 'sentry/components/replays/replayGroupC
77
import {t} from 'sentry/locale';
88
import type {Event} from 'sentry/types/event';
99
import type {Group} from 'sentry/types/group';
10+
import {getReplayDiffOffsetsFromEvent} from 'sentry/utils/replays/getDiffTimestamps';
1011
import useReplayReader from 'sentry/utils/replays/hooks/useReplayReader';
1112

1213
interface Props {
@@ -31,11 +32,7 @@ export default function ReplayDiffContent({event, group, orgSlug, replaySlug}: P
3132
return null;
3233
}
3334

34-
// TODO: base the event timestamp off the replay data itself.
35-
const startTimestampMS =
36-
'startTimestamp' in event ? event.startTimestamp * 1000 : undefined;
37-
const timeOfEvent = event.dateCreated ?? startTimestampMS ?? event.dateReceived;
38-
const eventTimestampMs = timeOfEvent ? Math.floor(new Date(timeOfEvent).getTime()) : 0;
35+
const {leftOffsetMs, rightOffsetMs} = getReplayDiffOffsetsFromEvent(replay, event);
3936

4037
return (
4138
<EventDataSection
@@ -44,9 +41,9 @@ export default function ReplayDiffContent({event, group, orgSlug, replaySlug}: P
4441
actions={
4542
<OpenReplayComparisonButton
4643
key="open-modal-button"
47-
leftTimestamp={0}
44+
leftOffsetMs={leftOffsetMs}
4845
replay={replay}
49-
rightTimestamp={eventTimestampMs}
46+
rightOffsetMs={rightOffsetMs}
5047
size="xs"
5148
>
5249
{t('Open Diff Viewer')}
@@ -57,9 +54,9 @@ export default function ReplayDiffContent({event, group, orgSlug, replaySlug}: P
5754
<ReplayGroupContextProvider groupId={group?.id} eventId={event.id}>
5855
<ReplayDiff
5956
defaultTab={DiffType.VISUAL}
60-
leftTimestamp={0}
57+
leftOffsetMs={leftOffsetMs}
6158
replay={replay}
62-
rightTimestamp={eventTimestampMs}
59+
rightOffsetMs={rightOffsetMs}
6360
/>
6461
</ReplayGroupContextProvider>
6562
</ErrorBoundary>

static/app/components/replays/breadcrumbs/breadcrumbItem.tsx

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,21 @@ import {Tooltip} from 'sentry/components/tooltip';
2020
import {t} from 'sentry/locale';
2121
import {space} from 'sentry/styles/space';
2222
import type {Extraction} from 'sentry/utils/replays/extractDomNodes';
23+
import {getReplayDiffOffsetsFromFrame} from 'sentry/utils/replays/getDiffTimestamps';
2324
import getFrameDetails from 'sentry/utils/replays/getFrameDetails';
24-
import type {ErrorFrame, FeedbackFrame, ReplayFrame} from 'sentry/utils/replays/types';
25-
import {isErrorFrame, isFeedbackFrame} from 'sentry/utils/replays/types';
25+
import type ReplayReader from 'sentry/utils/replays/replayReader';
26+
import type {
27+
ErrorFrame,
28+
FeedbackFrame,
29+
HydrationErrorFrame,
30+
ReplayFrame,
31+
} from 'sentry/utils/replays/types';
32+
import {
33+
isBreadcrumbFrame,
34+
isErrorFrame,
35+
isFeedbackFrame,
36+
isHydrationErrorFrame,
37+
} from 'sentry/utils/replays/types';
2638
import useOrganization from 'sentry/utils/useOrganization';
2739
import useProjectFromSlug from 'sentry/utils/useProjectFromSlug';
2840
import IconWrapper from 'sentry/views/replays/detail/iconWrapper';
@@ -88,22 +100,10 @@ function BreadcrumbItem({
88100
}, [description, expandPaths, onInspectorExpanded]);
89101

90102
const renderComparisonButton = useCallback(() => {
91-
return frame?.data && 'mutations' in frame.data ? (
92-
<div>
93-
<OpenReplayComparisonButton
94-
replay={replay}
95-
leftTimestamp={frame.offsetMs}
96-
rightTimestamp={
97-
(frame.data.mutations.next?.timestamp ?? 0) -
98-
(replay?.getReplay().started_at.getTime() ?? 0)
99-
}
100-
size="xs"
101-
>
102-
{t('Open Hydration Diff')}
103-
</OpenReplayComparisonButton>
104-
</div>
103+
return isBreadcrumbFrame(frame) && isHydrationErrorFrame(frame) ? (
104+
<CrumbHydrationButton replay={replay} frame={frame} />
105105
) : null;
106-
}, [frame?.data, frame.offsetMs, replay]);
106+
}, [frame, replay]);
107107

108108
const renderCodeSnippet = useCallback(() => {
109109
return extraction?.html ? (
@@ -187,6 +187,29 @@ function BreadcrumbItem({
187187
);
188188
}
189189

190+
function CrumbHydrationButton({
191+
replay,
192+
frame,
193+
}: {
194+
frame: HydrationErrorFrame;
195+
replay: ReplayReader | null;
196+
}) {
197+
const {leftOffsetMs, rightOffsetMs} = getReplayDiffOffsetsFromFrame(replay, frame);
198+
199+
return (
200+
<div>
201+
<OpenReplayComparisonButton
202+
replay={replay}
203+
leftOffsetMs={leftOffsetMs}
204+
rightOffsetMs={rightOffsetMs}
205+
size="xs"
206+
>
207+
{t('Open Hydration Diff')}
208+
</OpenReplayComparisonButton>
209+
</div>
210+
);
211+
}
212+
190213
function CrumbErrorIssue({frame}: {frame: FeedbackFrame | ErrorFrame}) {
191214
const organization = useOrganization();
192215
const project = useProjectFromSlug({organization, projectSlug: frame.data.projectSlug});

static/app/components/replays/breadcrumbs/openReplayComparisonButton.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,17 @@ const LazyComparisonModal = lazy(
1414

1515
interface Props {
1616
children: ReactNode;
17-
leftTimestamp: number;
17+
leftOffsetMs: number;
1818
replay: null | ReplayReader;
19-
rightTimestamp: number;
19+
rightOffsetMs: number;
2020
size?: ButtonProps['size'];
2121
}
2222

2323
export function OpenReplayComparisonButton({
2424
children,
25-
leftTimestamp,
25+
leftOffsetMs,
2626
replay,
27-
rightTimestamp,
27+
rightOffsetMs,
2828
size,
2929
}: Props) {
3030
const organization = useOrganization();
@@ -59,8 +59,8 @@ export function OpenReplayComparisonButton({
5959
<LazyComparisonModal
6060
replay={replay}
6161
organization={organization}
62-
leftTimestamp={leftTimestamp}
63-
rightTimestamp={rightTimestamp}
62+
leftOffsetMs={leftOffsetMs}
63+
rightOffsetMs={rightOffsetMs}
6464
{...deps}
6565
/>
6666
</Suspense>

static/app/components/replays/breadcrumbs/replayComparisonModal.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,19 @@ import type ReplayReader from 'sentry/utils/replays/replayReader';
1111
import {OrganizationContext} from 'sentry/views/organizationContext';
1212

1313
interface Props extends ModalRenderProps {
14-
leftTimestamp: number;
14+
leftOffsetMs: number;
1515
organization: Organization;
1616
replay: null | ReplayReader;
17-
rightTimestamp: number;
17+
rightOffsetMs: number;
1818
}
1919

2020
export default function ReplayComparisonModal({
2121
Body,
2222
Header,
23-
leftTimestamp,
23+
leftOffsetMs,
2424
organization,
2525
replay,
26-
rightTimestamp,
26+
rightOffsetMs,
2727
}: Props) {
2828
return (
2929
<OrganizationContext.Provider value={organization}>
@@ -55,8 +55,8 @@ export default function ReplayComparisonModal({
5555
</StyledParagraph>
5656
<ReplayDiff
5757
replay={replay}
58-
leftTimestamp={leftTimestamp}
59-
rightTimestamp={rightTimestamp}
58+
leftOffsetMs={leftOffsetMs}
59+
rightOffsetMs={rightOffsetMs}
6060
/>
6161
</Body>
6262
</OrganizationContext.Provider>

static/app/components/replays/replayDiff.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ import type ReplayReader from 'sentry/utils/replays/replayReader';
2020
const MAX_CLAMP_TO_START = 2000;
2121

2222
interface Props {
23-
leftTimestamp: number;
23+
leftOffsetMs: number;
2424
replay: null | ReplayReader;
25-
rightTimestamp: number;
25+
rightOffsetMs: number;
2626
defaultTab?: DiffType;
2727
}
2828

@@ -33,16 +33,16 @@ export enum DiffType {
3333

3434
export default function ReplayDiff({
3535
defaultTab = DiffType.VISUAL,
36-
leftTimestamp,
36+
leftOffsetMs,
3737
replay,
38-
rightTimestamp,
38+
rightOffsetMs,
3939
}: Props) {
4040
const fetching = false;
4141

4242
const [leftBody, setLeftBody] = useState(null);
4343
const [rightBody, setRightBody] = useState(null);
4444

45-
let startOffset = leftTimestamp - 1;
45+
let startOffset = leftOffsetMs - 1;
4646
// If the error occurs close to the start of the replay, clamp the start offset to 1
4747
// to help compare with the html provided by the server, This helps with some errors on localhost.
4848
if (startOffset < MAX_CLAMP_TO_START) {
@@ -96,16 +96,16 @@ export default function ReplayDiff({
9696
</ReplayContextProvider>
9797
<ReplayContextProvider
9898
analyticsContext="replay_comparison_modal_right"
99-
initialTimeOffsetMs={{offsetMs: rightTimestamp + 1}}
99+
initialTimeOffsetMs={{offsetMs: rightOffsetMs + 1}}
100100
isFetching={fetching}
101101
prefsStrategy={StaticReplayPreferences}
102102
replay={replay}
103103
>
104104
<ComparisonSideWrapper id="rightSide">
105-
{rightTimestamp > 0 ? (
105+
{rightOffsetMs > 0 ? (
106106
<ReplaySide
107107
selector="#rightSide iframe"
108-
expectedTime={rightTimestamp + 1}
108+
expectedTime={rightOffsetMs + 1}
109109
onLoad={setRightBody}
110110
/>
111111
) : (

0 commit comments

Comments
 (0)