Skip to content

Commit

Permalink
feat(replay): Add a debug view to look at mutations near replay.hydra…
Browse files Browse the repository at this point in the history
…te breadcrumbs (#60942)

This PR adds a render strategy for `replay.hydrate` breadcrumbs. 

The data we're interested in is the mutation that happened after this
breadcrumb was logged. We're showing that whole mutation for inspection.
If the data looks good after some manual checks, we can follow up and
use the data to render something nicer for folks.



![SCR-20231130-oaay](https://github.com/getsentry/sentry/assets/187460/bbba1a81-3b9c-4764-8173-e20a9a00dcb8)
  • Loading branch information
ryan953 committed Dec 1, 2023
1 parent ad1ea86 commit 68a45a8
Show file tree
Hide file tree
Showing 8 changed files with 291 additions and 202 deletions.
7 changes: 7 additions & 0 deletions static/app/utils/replays/getFrameDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,13 @@ const MAPPER_FOR_FRAME: Record<string, (frame) => Details> = {
title: 'Replay',
icon: <IconWarning size="xs" />,
}),
'replay.hydrate': frame => ({
color: 'red300',
description: frame.data.mutations,
tabKey: TabKey.BREADCRUMBS,
title: 'Hydration Error',
icon: <IconFire size="xs" />,
}),
'ui.click': frame => ({
color: 'purple300',
description: frame.message ?? '',
Expand Down
5 changes: 4 additions & 1 deletion static/app/utils/replays/getReplayEvent.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
} from 'sentry/utils/replays/getReplayEvent';
import hydrateBreadcrumbs from 'sentry/utils/replays/hydrateBreadcrumbs';

const mockRRWebFrames = []; // This is only needed for replay.hydrate breadcrumbs.

const frames = hydrateBreadcrumbs(
ReplayRecordFixture({
started_at: new Date('2022-05-04T19:41:30.00Z'),
Expand All @@ -32,7 +34,8 @@ const frames = hydrateBreadcrumbs(
timestamp: new Date('2022-05-04T19:47:59.915000Z'),
message: 'index 4',
}),
]
],
mockRRWebFrames
);

const CURRENT_OFFSET_MS = frames[0].offsetMs + 15000;
Expand Down
4 changes: 3 additions & 1 deletion static/app/utils/replays/hydrateBreadcrumbs.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {BreadcrumbFrame} from 'sentry/utils/replays/types';

const ONE_DAY_MS = 60 * 60 * 24 * 1000;

const mockRRWebFrames = []; // This is only needed for replay.hydrate breadcrumbs.

describe('hydrateBreadcrumbs', () => {
const replayRecord = ReplayRecordFixture({started_at: new Date('2023/12/23')});

Expand All @@ -18,7 +20,7 @@ describe('hydrateBreadcrumbs', () => {
ReplayConsoleFrameFixture({timestamp: new Date('2023/12/25')}),
];

expect(hydrateBreadcrumbs(replayRecord, breadcrumbs)).toStrictEqual([
expect(hydrateBreadcrumbs(replayRecord, breadcrumbs, mockRRWebFrames)).toStrictEqual([
{
category: 'console',
data: {logger: 'unknown'},
Expand Down
29 changes: 26 additions & 3 deletions static/app/utils/replays/hydrateBreadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,31 @@ import invariant from 'invariant';

import {BreadcrumbType} from 'sentry/types/breadcrumbs';
import isValidDate from 'sentry/utils/date/isValidDate';
import type {BreadcrumbFrame, RawBreadcrumbFrame} from 'sentry/utils/replays/types';
import {isBreadcrumbFrame} from 'sentry/utils/replays/types';
import type {
BreadcrumbFrame,
RawBreadcrumbFrame,
RecordingFrame,
} from 'sentry/utils/replays/types';
import {EventType, isBreadcrumbFrame} from 'sentry/utils/replays/types';
import type {ReplayRecord} from 'sentry/views/replays/types';

function findCloseMutations(date: Date, rrwebFrames: RecordingFrame[]) {
const timeMS = date.getTime();
const incrementalFrames = rrwebFrames.filter(
frame => frame.type === EventType.IncrementalSnapshot
);
const framesBefore = incrementalFrames.filter(frame => frame.timestamp <= timeMS);
const framesAfter = incrementalFrames.filter(frame => frame.timestamp > timeMS);
return {
prev: framesBefore.slice(-1)[0] ?? null,
next: framesAfter[0] ?? null,
};
}

export default function hydrateBreadcrumbs(
replayRecord: ReplayRecord,
breadcrumbFrames: RawBreadcrumbFrame[]
breadcrumbFrames: RawBreadcrumbFrame[],
rrwebFrames: RecordingFrame[]
): BreadcrumbFrame[] {
const startTimestampMs = replayRecord.started_at.getTime();

Expand All @@ -18,6 +36,11 @@ export default function hydrateBreadcrumbs(
const time = new Date(frame.timestamp * 1000);
invariant(isValidDate(time), 'breadcrumbFrame.timestamp is invalid');

if (frame.category === 'replay.hydrate') {
frame.data = {
mutations: findCloseMutations(time, rrwebFrames),
};
}
return {
...frame,
offsetMs: Math.abs(time.getTime() - startTimestampMs),
Expand Down
5 changes: 3 additions & 2 deletions static/app/utils/replays/replayReader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ export default class ReplayReader {
// few seconds later.
this._sortedBreadcrumbFrames = hydrateBreadcrumbs(
replayRecord,
breadcrumbFrames
breadcrumbFrames,
this._sortedRRWebEvents
).sort(sortFrames);
// Spans must be sorted so components like the Timeline and Network Chart
// can have an easier time to render.
Expand Down Expand Up @@ -265,7 +266,7 @@ export default class ReplayReader {
[
...this.getPerfFrames(),
...this._sortedBreadcrumbFrames.filter(frame =>
['replay.init', 'replay.mutations'].includes(frame.category)
['replay.init', 'replay.mutations', 'replay.hydrate'].includes(frame.category)
),
...this._errors,
].sort(sortFrames)
Expand Down
Loading

0 comments on commit 68a45a8

Please sign in to comment.