Skip to content

Commit

Permalink
feat(replay): Add event to capture options on checkouts (#8011)
Browse files Browse the repository at this point in the history
Add a custom event that captures configuration options on checkout + segment 0.
  • Loading branch information
billyvg authored May 5, 2023
1 parent 49837ac commit d400d69
Show file tree
Hide file tree
Showing 22 changed files with 258 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ sentryTest(

expect(event0).toEqual(
getExpectedReplayEvent({
contexts: { replay: { error_sample_rate: 0, session_sample_rate: 0 } },
error_ids: [errorEventId!],
replay_type: 'buffer',
}),
Expand Down Expand Up @@ -150,7 +149,6 @@ sentryTest(

expect(event1).toEqual(
getExpectedReplayEvent({
contexts: { replay: { error_sample_rate: 0, session_sample_rate: 0 } },
replay_type: 'buffer', // although we're in session mode, we still send 'buffer' as replay_type
segment_id: 1,
urls: [],
Expand All @@ -162,7 +160,6 @@ sentryTest(

expect(event2).toEqual(
getExpectedReplayEvent({
contexts: { replay: { error_sample_rate: 0, session_sample_rate: 0 } },
replay_type: 'buffer', // although we're in session mode, we still send 'buffer' as replay_type
segment_id: 2,
urls: [],
Expand Down Expand Up @@ -266,7 +263,6 @@ sentryTest(

expect(event0).toEqual(
getExpectedReplayEvent({
contexts: { replay: { error_sample_rate: 0, session_sample_rate: 0 } },
error_ids: [errorEventId!],
replay_type: 'buffer',
}),
Expand Down Expand Up @@ -372,7 +368,6 @@ sentryTest('[buffer-mode] can sample on each error event', async ({ getLocalTest

expect(event0).toEqual(
getExpectedReplayEvent({
contexts: { replay: { error_sample_rate: 1, session_sample_rate: 0 } },
error_ids: errorEventIds,
replay_type: 'buffer',
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalT
},
},
platform: 'javascript',
contexts: { replay: { session_sample_rate: 1, error_sample_rate: 0 } },
});

expect(replayEvent1).toBeDefined();
Expand Down Expand Up @@ -103,6 +102,5 @@ sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalT
},
},
platform: 'javascript',
contexts: { replay: { session_sample_rate: 1, error_sample_rate: 0 } },
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ sentryTest('should capture replays (@sentry/replay export)', async ({ getLocalTe
},
},
platform: 'javascript',
contexts: { replay: { session_sample_rate: 1, error_sample_rate: 0 } },
});

expect(replayEvent1).toBeDefined();
Expand Down Expand Up @@ -103,6 +102,5 @@ sentryTest('should capture replays (@sentry/replay export)', async ({ getLocalTe
},
},
platform: 'javascript',
contexts: { replay: { session_sample_rate: 1, error_sample_rate: 0 } },
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,60 @@ sentryTest(
);
},
);

sentryTest(
'replay recording should contain an "options" breadcrumb for Replay SDK configuration',
async ({ forceFlushReplay, getLocalTestPath, page, browserName }) => {
// TODO(replay): This is flakey on firefox and webkit where clicks are flakey
if (shouldSkipReplayTest() || ['firefox', 'webkit'].includes(browserName)) {
sentryTest.skip();
}

const reqPromise0 = waitForReplayRequest(page, 0);
const reqPromise1 = waitForReplayRequest(page, 1);

await page.route('https://dsn.ingest.sentry.io/**/*', route => {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ id: 'test-id' }),
});
});

const url = await getLocalTestPath({ testDir: __dirname });

await page.goto(url);
await forceFlushReplay();

await page.click('#error');
await forceFlushReplay();

const req0 = await reqPromise0;
const content0 = getReplayRecordingContent(req0);

expect(content0.optionsEvents).toEqual([
{
tag: 'options',
payload: {
sessionSampleRate: 1,
errorSampleRate: 0,
useCompressionOption: false,
blockAllMedia: false,
maskAllText: true,
maskAllInputs: true,
useCompression: false,
networkDetailHasUrls: false,
networkCaptureBodies: true,
networkRequestHasHeaders: true,
networkResponseHasHeaders: true,
},
},
]);

const req1 = await reqPromise1;
const content1 = getReplayRecordingContent(req1);

// Should only be on first segment
expect(content1.optionsEvents).toEqual([]);
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ sentryTest(

expect(event0).toEqual(
getExpectedReplayEvent({
contexts: { replay: { error_sample_rate: 1, session_sample_rate: 0 } },
error_ids: [errorEventId!],
replay_type: 'buffer',
}),
Expand Down Expand Up @@ -119,7 +118,6 @@ sentryTest(

expect(event1).toEqual(
getExpectedReplayEvent({
contexts: { replay: { error_sample_rate: 1, session_sample_rate: 0 } },
replay_type: 'buffer', // although we're in session mode, we still send 'error' as replay_type
segment_id: 1,
urls: [],
Expand All @@ -134,7 +132,6 @@ sentryTest(
// we continue recording everything
expect(event2).toEqual(
getExpectedReplayEvent({
contexts: { replay: { error_sample_rate: 1, session_sample_rate: 0 } },
replay_type: 'buffer',
segment_id: 2,
urls: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ const DEFAULT_REPLAY_EVENT = {
},
},
platform: 'javascript',
contexts: { replay: { session_sample_rate: 1, error_sample_rate: 0 } },
};

/**
Expand Down
9 changes: 8 additions & 1 deletion packages/browser-integration-tests/utils/replayHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ type CustomRecordingContent = {
type RecordingContent = {
fullSnapshots: RecordingSnapshot[];
incrementalSnapshots: RecordingSnapshot[];
optionsEvents: CustomRecordingEvent[];
} & CustomRecordingContent;

/**
Expand Down Expand Up @@ -207,6 +208,11 @@ export function getIncrementalRecordingSnapshots(resOrReq: Request | Response):
return events.filter(isIncrementalSnapshot);
}

function getOptionsEvents(replayRequest: Request): CustomRecordingEvent[] {
const events = getDecompressedRecordingEvents(replayRequest);
return getAllCustomRrwebRecordingEvents(events).filter(data => data.tag === 'options');
}

function getDecompressedRecordingEvents(resOrReq: Request | Response): RecordingSnapshot[] {
const replayRequest = getRequest(resOrReq);
return (
Expand All @@ -227,8 +233,9 @@ export function getReplayRecordingContent(resOrReq: Request | Response): Recordi
const fullSnapshots = getFullRecordingSnapshots(replayRequest);
const incrementalSnapshots = getIncrementalRecordingSnapshots(replayRequest);
const customEvents = getCustomRecordingEvents(replayRequest);
const optionsEvents = getOptionsEvents(replayRequest);

return { fullSnapshots, incrementalSnapshots, ...customEvents };
return { fullSnapshots, incrementalSnapshots, optionsEvents, ...customEvents };
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,26 @@ export const ReplayRecordingData = [
data: { href: expect.stringMatching(/http:\/\/localhost:\d+\//), width: 1280, height: 720 },
timestamp: expect.any(Number),
},
{
data: {
payload: {
blockAllMedia: true,
errorSampleRate: 0,
maskAllInputs: true,
maskAllText: true,
networkCaptureBodies: true,
networkDetailHasUrls: false,
networkRequestHasHeaders: true,
networkResponseHasHeaders: true,
sessionSampleRate: 1,
useCompression: false,
useCompressionOption: true,
},
tag: 'options',
},
timestamp: expect.any(Number),
type: 5,
},
{
type: 2,
data: {
Expand Down
7 changes: 6 additions & 1 deletion packages/replay/src/eventBuffer/EventBufferArray.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AddEventResult, EventBuffer, RecordingEvent } from '../types';
import type { AddEventResult, EventBuffer, EventBufferType, RecordingEvent } from '../types';
import { timestampToMs } from '../util/timestampToMs';

/**
Expand All @@ -18,6 +18,11 @@ export class EventBufferArray implements EventBuffer {
return this.events.length > 0;
}

/** @inheritdoc */
public get type(): EventBufferType {
return 'sync';
}

/** @inheritdoc */
public destroy(): void {
this.events = [];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ReplayRecordingData } from '@sentry/types';

import type { AddEventResult, EventBuffer, RecordingEvent } from '../types';
import type { AddEventResult, EventBuffer, EventBufferType, RecordingEvent } from '../types';
import { timestampToMs } from '../util/timestampToMs';
import { WorkerHandler } from './WorkerHandler';

Expand All @@ -22,6 +22,11 @@ export class EventBufferCompressionWorker implements EventBuffer {
return !!this._earliestTimestamp;
}

/** @inheritdoc */
public get type(): EventBufferType {
return 'worker';
}

/**
* Ensure the worker is ready (or not).
* This will either resolve when the worker is ready, or reject if an error occured.
Expand Down
7 changes: 6 additions & 1 deletion packages/replay/src/eventBuffer/EventBufferProxy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ReplayRecordingData } from '@sentry/types';
import { logger } from '@sentry/utils';

import type { AddEventResult, EventBuffer, RecordingEvent } from '../types';
import type { AddEventResult, EventBuffer, EventBufferType, RecordingEvent } from '../types';
import { EventBufferArray } from './EventBufferArray';
import { EventBufferCompressionWorker } from './EventBufferCompressionWorker';

Expand All @@ -24,6 +24,11 @@ export class EventBufferProxy implements EventBuffer {
this._ensureWorkerIsLoadedPromise = this._ensureWorkerIsLoaded();
}

/** @inheritdoc */
public get type(): EventBufferType {
return this._used.type;
}

/** @inheritDoc */
public get hasEvents(): boolean {
return this._used.hasEvents;
Expand Down
2 changes: 2 additions & 0 deletions packages/replay/src/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ export class Replay implements Integration {
errorSampleRate,
useCompression,
blockAllMedia,
maskAllInputs,
maskAllText,
networkDetailAllowUrls,
networkCaptureBodies,
networkRequestHeaders: _getMergedNetworkHeaders(networkRequestHeaders),
Expand Down
17 changes: 17 additions & 0 deletions packages/replay/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,16 @@ export interface ReplayPluginOptions extends ReplayNetworkOptions {
*/
blockAllMedia: boolean;

/**
* Mask all inputs in recordings
*/
maskAllInputs: boolean;

/**
* Mask all text in recordings
*/
maskAllText: boolean;

/**
* _experiments allows users to enable experimental or internal features.
* We don't consider such features as part of the public API and hence we don't guarantee semver for them.
Expand Down Expand Up @@ -435,12 +445,19 @@ export interface Session {
shouldRefresh: boolean;
}

export type EventBufferType = 'sync' | 'worker';

export interface EventBuffer {
/**
* If any events have been added to the buffer.
*/
readonly hasEvents: boolean;

/**
* The buffer type
*/
readonly type: EventBufferType;

/**
* Destroy the event buffer.
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/replay/src/types/rrweb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
type blockClass = string | RegExp;
type maskTextClass = string | RegExp;

enum EventType {
export enum EventType {
DomContentLoaded = 0,
Load = 1,
FullSnapshot = 2,
Expand Down
Loading

0 comments on commit d400d69

Please sign in to comment.