From 68a45a839b81f5aaf9f3c58a5305b6517672e6f9 Mon Sep 17 00:00:00 2001 From: Ryan Albrecht Date: Fri, 1 Dec 2023 09:22:07 -0800 Subject: [PATCH] feat(replay): Add a debug view to look at mutations near replay.hydrate 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) --- static/app/utils/replays/getFrameDetails.tsx | 7 + .../app/utils/replays/getReplayEvent.spec.tsx | 5 +- .../utils/replays/hydrateBreadcrumbs.spec.tsx | 4 +- .../app/utils/replays/hydrateBreadcrumbs.tsx | 29 +- static/app/utils/replays/replayReader.tsx | 5 +- .../detail/console/messageFormatter.spec.tsx | 266 ++++++++++-------- .../detail/console/useConsoleFilters.spec.tsx | 162 ++++++----- .../detail/perfTable/usePerfFilters.spec.tsx | 15 +- 8 files changed, 291 insertions(+), 202 deletions(-) diff --git a/static/app/utils/replays/getFrameDetails.tsx b/static/app/utils/replays/getFrameDetails.tsx index d77ba6ec8d653c..1080ea9e33e0c3 100644 --- a/static/app/utils/replays/getFrameDetails.tsx +++ b/static/app/utils/replays/getFrameDetails.tsx @@ -151,6 +151,13 @@ const MAPPER_FOR_FRAME: Record Details> = { title: 'Replay', icon: , }), + 'replay.hydrate': frame => ({ + color: 'red300', + description: frame.data.mutations, + tabKey: TabKey.BREADCRUMBS, + title: 'Hydration Error', + icon: , + }), 'ui.click': frame => ({ color: 'purple300', description: frame.message ?? '', diff --git a/static/app/utils/replays/getReplayEvent.spec.tsx b/static/app/utils/replays/getReplayEvent.spec.tsx index 382eabff35c628..fe4d7529bbc3bf 100644 --- a/static/app/utils/replays/getReplayEvent.spec.tsx +++ b/static/app/utils/replays/getReplayEvent.spec.tsx @@ -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'), @@ -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; diff --git a/static/app/utils/replays/hydrateBreadcrumbs.spec.tsx b/static/app/utils/replays/hydrateBreadcrumbs.spec.tsx index 588ed45696ffa9..e0e3838ce8911a 100644 --- a/static/app/utils/replays/hydrateBreadcrumbs.spec.tsx +++ b/static/app/utils/replays/hydrateBreadcrumbs.spec.tsx @@ -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')}); @@ -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'}, diff --git a/static/app/utils/replays/hydrateBreadcrumbs.tsx b/static/app/utils/replays/hydrateBreadcrumbs.tsx index 42c2359bd4a929..6e731852ef9316 100644 --- a/static/app/utils/replays/hydrateBreadcrumbs.tsx +++ b/static/app/utils/replays/hydrateBreadcrumbs.tsx @@ -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(); @@ -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), diff --git a/static/app/utils/replays/replayReader.tsx b/static/app/utils/replays/replayReader.tsx index d8d273515ae784..850862c0e3d152 100644 --- a/static/app/utils/replays/replayReader.tsx +++ b/static/app/utils/replays/replayReader.tsx @@ -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. @@ -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) diff --git a/static/app/views/replays/detail/console/messageFormatter.spec.tsx b/static/app/views/replays/detail/console/messageFormatter.spec.tsx index f08eacc12340d9..2eda5fbe8bb960 100644 --- a/static/app/views/replays/detail/console/messageFormatter.spec.tsx +++ b/static/app/views/replays/detail/console/messageFormatter.spec.tsx @@ -7,19 +7,25 @@ import {BreadcrumbLevelType} from 'sentry/types/breadcrumbs'; import hydrateBreadcrumbs from 'sentry/utils/replays/hydrateBreadcrumbs'; import MessageFormatter from 'sentry/views/replays/detail/console/messageFormatter'; +const mockRRWebFrames = []; // This is only needed for replay.hydrate breadcrumbs. + describe('MessageFormatter', () => { it('Should print console message with placeholders correctly', () => { - const [frame] = hydrateBreadcrumbs(ReplayRecordFixture(), [ - ReplayConsoleFrameFixture({ - data: { - arguments: ['This is a %s', 'test'], - logger: 'console', - }, - level: BreadcrumbLevelType.LOG, - message: 'This is a %s test', - timestamp: new Date('2022-06-22T20:00:39.959Z'), - }), - ]); + const [frame] = hydrateBreadcrumbs( + ReplayRecordFixture(), + [ + ReplayConsoleFrameFixture({ + data: { + arguments: ['This is a %s', 'test'], + logger: 'console', + }, + level: BreadcrumbLevelType.LOG, + message: 'This is a %s test', + timestamp: new Date('2022-06-22T20:00:39.959Z'), + }), + ], + mockRRWebFrames + ); render(); @@ -27,13 +33,17 @@ describe('MessageFormatter', () => { }); it('Should print console message without data', () => { - const [frame] = hydrateBreadcrumbs(ReplayRecordFixture(), [ - ReplayConsoleFrameFixture({ - level: BreadcrumbLevelType.LOG, - message: 'This is only a test', - timestamp: new Date('2022-06-22T20:00:39.959Z'), - }), - ]); + const [frame] = hydrateBreadcrumbs( + ReplayRecordFixture(), + [ + ReplayConsoleFrameFixture({ + level: BreadcrumbLevelType.LOG, + message: 'This is only a test', + timestamp: new Date('2022-06-22T20:00:39.959Z'), + }), + ], + mockRRWebFrames + ); // Manually delete `data` from the mock. // This is reasonable because the type, at this point, `frame` is of type @@ -47,17 +57,21 @@ describe('MessageFormatter', () => { }); it('Should print console message with objects correctly', () => { - const [frame] = hydrateBreadcrumbs(ReplayRecordFixture(), [ - ReplayConsoleFrameFixture({ - data: { - arguments: ['test', 1, false, {}], - logger: 'console', - }, - level: BreadcrumbLevelType.LOG, - message: 'test 1 false [object Object]', - timestamp: new Date('2022-06-22T16:49:11.198Z'), - }), - ]); + const [frame] = hydrateBreadcrumbs( + ReplayRecordFixture(), + [ + ReplayConsoleFrameFixture({ + data: { + arguments: ['test', 1, false, {}], + logger: 'console', + }, + level: BreadcrumbLevelType.LOG, + message: 'test 1 false [object Object]', + timestamp: new Date('2022-06-22T16:49:11.198Z'), + }), + ], + mockRRWebFrames + ); render(); @@ -66,17 +80,21 @@ describe('MessageFormatter', () => { }); it('Should print console message correctly when it is an Error object', () => { - const [frame] = hydrateBreadcrumbs(ReplayRecordFixture(), [ - ReplayConsoleFrameFixture({ - data: { - arguments: [{}], - logger: 'console', - }, - level: BreadcrumbLevelType.ERROR, - message: 'Error: this is my error message', - timestamp: new Date('2022-06-22T20:00:39.958Z'), - }), - ]); + const [frame] = hydrateBreadcrumbs( + ReplayRecordFixture(), + [ + ReplayConsoleFrameFixture({ + data: { + arguments: [{}], + logger: 'console', + }, + level: BreadcrumbLevelType.ERROR, + message: 'Error: this is my error message', + timestamp: new Date('2022-06-22T20:00:39.958Z'), + }), + ], + mockRRWebFrames + ); render(); @@ -84,16 +102,20 @@ describe('MessageFormatter', () => { }); it('Should print empty object in case there is no message prop', () => { - const [frame] = hydrateBreadcrumbs(ReplayRecordFixture(), [ - ReplayConsoleFrameFixture({ - data: { - arguments: [{}], - logger: 'console', - }, - level: BreadcrumbLevelType.ERROR, - timestamp: new Date('2022-06-22T20:00:39.958Z'), - }), - ]); + const [frame] = hydrateBreadcrumbs( + ReplayRecordFixture(), + [ + ReplayConsoleFrameFixture({ + data: { + arguments: [{}], + logger: 'console', + }, + level: BreadcrumbLevelType.ERROR, + timestamp: new Date('2022-06-22T20:00:39.958Z'), + }), + ], + mockRRWebFrames + ); render(); @@ -101,24 +123,28 @@ describe('MessageFormatter', () => { }); it('Should style "%c" placeholder and print the console message correctly', () => { - const [frame] = hydrateBreadcrumbs(ReplayRecordFixture(), [ - ReplayConsoleFrameFixture({ - data: { - arguments: [ - '%c prev state', - 'color: #9E9E9E; font-weight: bold; background-image: url(foo);', - { - cart: [], - }, - ], - logger: 'console', - }, - level: BreadcrumbLevelType.LOG, - message: - '%c prev state color: #9E9E9E; font-weight: bold; background-image: url(foo); [object Object]', - timestamp: new Date('2022-06-09T00:50:25.273Z'), - }), - ]); + const [frame] = hydrateBreadcrumbs( + ReplayRecordFixture(), + [ + ReplayConsoleFrameFixture({ + data: { + arguments: [ + '%c prev state', + 'color: #9E9E9E; font-weight: bold; background-image: url(foo);', + { + cart: [], + }, + ], + logger: 'console', + }, + level: BreadcrumbLevelType.LOG, + message: + '%c prev state color: #9E9E9E; font-weight: bold; background-image: url(foo); [object Object]', + timestamp: new Date('2022-06-09T00:50:25.273Z'), + }), + ], + mockRRWebFrames + ); render(); @@ -132,17 +158,21 @@ describe('MessageFormatter', () => { }); it('Should print arrays correctly', () => { - const [frame] = hydrateBreadcrumbs(ReplayRecordFixture(), [ - ReplayConsoleFrameFixture({ - data: { - arguments: ['test', ['foo', 'bar']], - logger: 'console', - }, - level: BreadcrumbLevelType.LOG, - message: 'test foo,bar', - timestamp: new Date('2022-06-23T17:09:31.158Z'), - }), - ]); + const [frame] = hydrateBreadcrumbs( + ReplayRecordFixture(), + [ + ReplayConsoleFrameFixture({ + data: { + arguments: ['test', ['foo', 'bar']], + logger: 'console', + }, + level: BreadcrumbLevelType.LOG, + message: 'test foo,bar', + timestamp: new Date('2022-06-23T17:09:31.158Z'), + }), + ], + mockRRWebFrames + ); render(); @@ -155,17 +185,21 @@ describe('MessageFormatter', () => { }); it('Should print literal %', () => { - const [frame] = hydrateBreadcrumbs(ReplayRecordFixture(), [ - ReplayConsoleFrameFixture({ - data: { - arguments: ['This is a literal 100%'], - logger: 'console', - }, - level: BreadcrumbLevelType.LOG, - message: 'This is a literal 100%', - timestamp: new Date('2022-06-22T20:00:39.959Z'), - }), - ]); + const [frame] = hydrateBreadcrumbs( + ReplayRecordFixture(), + [ + ReplayConsoleFrameFixture({ + data: { + arguments: ['This is a literal 100%'], + logger: 'console', + }, + level: BreadcrumbLevelType.LOG, + message: 'This is a literal 100%', + timestamp: new Date('2022-06-22T20:00:39.959Z'), + }), + ], + mockRRWebFrames + ); render(); @@ -173,17 +207,21 @@ describe('MessageFormatter', () => { }); it('Should print unbound %s placeholder', () => { - const [frame] = hydrateBreadcrumbs(ReplayRecordFixture(), [ - ReplayConsoleFrameFixture({ - data: { - arguments: ['Unbound placeholder %s'], - logger: 'console', - }, - level: BreadcrumbLevelType.LOG, - message: 'Unbound placeholder %s', - timestamp: new Date('2022-06-22T20:00:39.959Z'), - }), - ]); + const [frame] = hydrateBreadcrumbs( + ReplayRecordFixture(), + [ + ReplayConsoleFrameFixture({ + data: { + arguments: ['Unbound placeholder %s'], + logger: 'console', + }, + level: BreadcrumbLevelType.LOG, + message: 'Unbound placeholder %s', + timestamp: new Date('2022-06-22T20:00:39.959Z'), + }), + ], + mockRRWebFrames + ); render(); @@ -191,17 +229,21 @@ describe('MessageFormatter', () => { }); it('Should print placeholder with literal %', () => { - const [frame] = hydrateBreadcrumbs(ReplayRecordFixture(), [ - ReplayConsoleFrameFixture({ - data: { - arguments: ['Placeholder %s with 100%', 'myPlaceholder'], - logger: 'console', - }, - level: BreadcrumbLevelType.LOG, - message: 'Placeholder %s with 100%', - timestamp: new Date('2022-06-22T20:00:39.959Z'), - }), - ]); + const [frame] = hydrateBreadcrumbs( + ReplayRecordFixture(), + [ + ReplayConsoleFrameFixture({ + data: { + arguments: ['Placeholder %s with 100%', 'myPlaceholder'], + logger: 'console', + }, + level: BreadcrumbLevelType.LOG, + message: 'Placeholder %s with 100%', + timestamp: new Date('2022-06-22T20:00:39.959Z'), + }), + ], + mockRRWebFrames + ); render(); diff --git a/static/app/views/replays/detail/console/useConsoleFilters.spec.tsx b/static/app/views/replays/detail/console/useConsoleFilters.spec.tsx index 62f2134e59a26c..2623ecd42aab14 100644 --- a/static/app/views/replays/detail/console/useConsoleFilters.spec.tsx +++ b/static/app/views/replays/detail/console/useConsoleFilters.spec.tsx @@ -16,83 +16,88 @@ jest.mock('react-router'); jest.mock('sentry/utils/useLocation'); const mockUseLocation = jest.mocked(useLocation); - -const frames = hydrateBreadcrumbs(ReplayRecordFixture(), [ - ReplayConsoleFrameFixture({ - type: BreadcrumbType.DEFAULT, - timestamp: new Date('2022-05-11T23:00:45.094000Z'), - level: BreadcrumbLevelType.INFO, - message: 'longtask - does not exist [object PerformanceLongTaskTiming]', - data: { - arguments: [ - 'longtask - does not exist', - { - attribution: ['[object TaskAttributionTiming]'], - duration: 76, - entryType: 'longtask', - name: 'self', - startTime: 3741, - }, - ], - logger: 'console', - }, - }), - ReplayConsoleFrameFixture({ - type: BreadcrumbType.DEFAULT, - timestamp: new Date('2022-05-11T23:00:45.094000Z'), - level: BreadcrumbLevelType.INFO, - message: 'longtask - does not exist [object PerformanceLongTaskTiming]', - }), - ReplayConsoleFrameFixture({ - type: BreadcrumbType.DEFAULT, - timestamp: new Date('2022-05-11T23:00:45.093000Z'), - level: BreadcrumbLevelType.INFO, - message: 'event - does not exist [object PerformanceEventTiming]', - data: { - arguments: [ - 'event - does not exist', - { - cancelable: true, - duration: 160, - entryType: 'event', - name: 'keyup', - processingEnd: 505, - processingStart: 505, - startTime: 347.90000009536743, - }, - ], - logger: 'console', - }, - }), - ReplayConsoleFrameFixture({ - type: BreadcrumbType.DEFAULT, - timestamp: new Date('2022-05-11T23:04:27.576000Z'), - level: BreadcrumbLevelType.ERROR, - message: - 'The above error occurred in the component:\n\n at TestButton (webpack-internal:///./app/views/userFeedback/index.tsx:224:76)\n at div\n at eval (webpack-internal:///../node_modules/@emotion/react/dist/emotion-element-a8309070.browser.esm.js:45:66)\n at ButtonBar (webpack-internal:///./app/components/buttonBar.tsx:30:5)\n at div\n at eval (webpack-internal:///../node_modules/@emotion/react/dist/emotion-element-a8309070.browser.esm.js:45:66)\n at ErrorBoundary (webpack-internal:///./app/components/errorBoundary.tsx:45:5)\n at div\n at NoProjectMessage (webpack-internal:///./app/components/noProjectMessage.tsx:45:5)\n at div\n at eval (webpack-internal:///../node_modules/@emotion/react/dist/emotion-element-a8309070.browser.esm.js:45:66)\n at Container (webpack-internal:///./app/components/organizations/pageFilters/container.tsx:66:5)\n at eval (webpack-internal:///../node_modules/create-react-class/factory.js:821:37)\n at _class (webpack-internal:///./app/utils/withOrganization.tsx:24:19)\n at DocumentTitle\n at SideEffect (webpack-internal:///../node_modules/react-side-effect/lib/index.js:74:27)\n at SentryDocumentTitle (webpack-internal:///./app/components/sentryDocumentTitle.tsx:22:5)\n at OrganizationUserFeedback (webpack-internal:///./app/views/userFeedback/index.tsx:73:1)\n at Profiler (webpack-internal:///../node_modules/@sentry/react/esm/profiler.js:83:28)\n at profiler(OrganizationUserFeedback)\n at _class (webpack-internal:///./app/utils/withOrganization.tsx:24:19)\n at LazyLoad (webpack-internal:///./app/components/lazyLoad.tsx:39:5)\n at ErrorHandler (webpack-internal:///./app/utils/errorHandler.tsx:24:7)\n at ErrorBoundary (webpack-internal:///./app/components/errorBoundary.tsx:45:5)\n at OrganizationDetailsBody (webpack-internal:///./app/views/organizationDetails/body.tsx:131:5)\n at _class (webpack-internal:///./app/utils/withOrganization.tsx:24:19)\n at div\n at DocumentTitle\n at SideEffect (webpack-internal:///../node_modules/react-side-effect/lib/index.js:74:27)\n at SentryDocumentTitle (webpack-internal:///./app/components/sentryDocumentTitle.tsx:22:5)\n at OrganizationContextContainer (webpack-internal:///./app/views/organizationContextContainer.tsx:157:5)\n at Profiler (webpack-internal:///../node_modules/@sentry/react/esm/profiler.js:83:28)\n at profiler(OrganizationContextContainer)\n at WithOrganizations (webpack-internal:///./app/utils/withOrganizations.tsx:27:7)\n at WithApi (webpack-internal:///./app/utils/withApi.tsx:35:12)\n at OrganizationDetails (webpack-internal:///./app/views/organizationDetails/index.tsx:27:5)\n at ErrorHandler (webpack-internal:///./app/utils/errorHandler.tsx:24:7)\n at ErrorBoundary (webpack-internal:///./app/components/errorBoundary.tsx:45:5)\n at div\n at eval (webpack-internal:///../node_modules/@emotion/react/dist/emotion-element-a8309070.browser.esm.js:45:66)\n at App (webpack-internal:///./app/views/app/index.tsx:72:5)\n at ErrorHandler (webpack-internal:///./app/utils/errorHandler.tsx:24:7)\n at eval (webpack-internal:///../node_modules/create-react-class/factory.js:821:37)\n at eval (webpack-internal:///../node_modules/create-react-class/factory.js:821:37)\n at PersistedStoreProvider (webpack-internal:///./app/stores/persistedStore.tsx:59:76)\n at ThemeProvider (webpack-internal:///../node_modules/@emotion/react/dist/emotion-element-a8309070.browser.esm.js:79:64)\n at ThemeAndStyleProvider (webpack-internal:///./app/components/themeAndStyleProvider.tsx:45:5)\n at Main\n\nReact will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary.', - data: { - arguments: [ - 'The above error occurred in the component:\n\n at TestButton (webpack-internal:///./app/views/userFeedback/index.tsx:224:76)\n at div\n at eval (webpack-internal:///../node_modules/@emotion/react/dist/emotion-element-a8309070.browser.esm.js:45:66)\n at ButtonBar (webpack-internal:///./app/components/buttonBar.tsx:30:5)\n at div\n at eval (webpack-internal:///../node_modules/@emotion/react/dist/emotion-element-a8309070.browser.esm.js:45:66)\n at ErrorBoundary (webpack-internal:///./app/components/errorBoundary.tsx:45:5)\n at div\n at NoProjectMessage (webpack-internal:///./app/components/noProjectMessage.tsx:45:5)\n at div\n at eval (webpack-internal:///../node_modules/@emotion/react/dist/emotion-element-a8309070.browser.esm.js:45:66)\n at Container (webpack-internal:///./app/components/organizations/pageFilters/container.tsx:66:5)\n at eval (webpack-internal:///../node_modules/create-react-class/factory.js:821:37)\n at _class (webpack-internal:///./app/utils/withOrganization.tsx:24:19)\n at DocumentTitle\n at SideEffect (webpack-internal:///../node_modules/react-side-effect/lib/index.js:74:27)\n at SentryDocumentTitle (webpack-internal:///./app/components/sentryDocumentTitle.tsx:22:5)\n at OrganizationUserFeedback (webpack-internal:///./app/views/userFeedback/index.tsx:73:1)\n at Profiler (webpack-internal:///../node_modules/@sentry/react/esm/profiler.js:83:28)\n at profiler(OrganizationUserFeedback)\n at _class (webpack-internal:///./app/utils/withOrganization.tsx:24:19)\n at LazyLoad (webpack-internal:///./app/components/lazyLoad.tsx:39:5)\n at ErrorHandler (webpack-internal:///./app/utils/errorHandler.tsx:24:7)\n at ErrorBoundary (webpack-internal:///./app/components/errorBoundary.tsx:45:5)\n at OrganizationDetailsBody (webpack-internal:///./app/views/organizationDetails/body.tsx:131:5)\n at _class (webpack-internal:///./app/utils/withOrganization.tsx:24:19)\n at div\n at DocumentTitle\n at SideEffect (webpack-internal:///../node_modules/...', - ], - logger: '', - }, - }), - ReplayConsoleFrameFixture({ - type: BreadcrumbType.DEFAULT, - timestamp: new Date('2022-05-11T23:05:51.531000Z'), - level: BreadcrumbLevelType.WARNING, - message: - 'Warning: componentWillMount has been renamed, and is not recommended for use. See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n* Move code with side effects to componentDidMount, and set initial state in the constructor.\n* Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\nPlease update the following components: %s Router, RouterContext', - data: { - arguments: [ - 'Warning: componentWillMount has been renamed, and is not recommended for use. See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n* Move code with side effects to componentDidMount, and set initial state in the constructor.\n* Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\nPlease update the following components: %s', - 'Router, RouterContext', - 'find me', - ], - logger: 'console', - }, - }), -]); +const mockRRWebFrames = []; // This is only needed for replay.hydrate breadcrumbs. + +const frames = hydrateBreadcrumbs( + ReplayRecordFixture(), + [ + ReplayConsoleFrameFixture({ + type: BreadcrumbType.DEFAULT, + timestamp: new Date('2022-05-11T23:00:45.094000Z'), + level: BreadcrumbLevelType.INFO, + message: 'longtask - does not exist [object PerformanceLongTaskTiming]', + data: { + arguments: [ + 'longtask - does not exist', + { + attribution: ['[object TaskAttributionTiming]'], + duration: 76, + entryType: 'longtask', + name: 'self', + startTime: 3741, + }, + ], + logger: 'console', + }, + }), + ReplayConsoleFrameFixture({ + type: BreadcrumbType.DEFAULT, + timestamp: new Date('2022-05-11T23:00:45.094000Z'), + level: BreadcrumbLevelType.INFO, + message: 'longtask - does not exist [object PerformanceLongTaskTiming]', + }), + ReplayConsoleFrameFixture({ + type: BreadcrumbType.DEFAULT, + timestamp: new Date('2022-05-11T23:00:45.093000Z'), + level: BreadcrumbLevelType.INFO, + message: 'event - does not exist [object PerformanceEventTiming]', + data: { + arguments: [ + 'event - does not exist', + { + cancelable: true, + duration: 160, + entryType: 'event', + name: 'keyup', + processingEnd: 505, + processingStart: 505, + startTime: 347.90000009536743, + }, + ], + logger: 'console', + }, + }), + ReplayConsoleFrameFixture({ + type: BreadcrumbType.DEFAULT, + timestamp: new Date('2022-05-11T23:04:27.576000Z'), + level: BreadcrumbLevelType.ERROR, + message: + 'The above error occurred in the component:\n\n at TestButton (webpack-internal:///./app/views/userFeedback/index.tsx:224:76)\n at div\n at eval (webpack-internal:///../node_modules/@emotion/react/dist/emotion-element-a8309070.browser.esm.js:45:66)\n at ButtonBar (webpack-internal:///./app/components/buttonBar.tsx:30:5)\n at div\n at eval (webpack-internal:///../node_modules/@emotion/react/dist/emotion-element-a8309070.browser.esm.js:45:66)\n at ErrorBoundary (webpack-internal:///./app/components/errorBoundary.tsx:45:5)\n at div\n at NoProjectMessage (webpack-internal:///./app/components/noProjectMessage.tsx:45:5)\n at div\n at eval (webpack-internal:///../node_modules/@emotion/react/dist/emotion-element-a8309070.browser.esm.js:45:66)\n at Container (webpack-internal:///./app/components/organizations/pageFilters/container.tsx:66:5)\n at eval (webpack-internal:///../node_modules/create-react-class/factory.js:821:37)\n at _class (webpack-internal:///./app/utils/withOrganization.tsx:24:19)\n at DocumentTitle\n at SideEffect (webpack-internal:///../node_modules/react-side-effect/lib/index.js:74:27)\n at SentryDocumentTitle (webpack-internal:///./app/components/sentryDocumentTitle.tsx:22:5)\n at OrganizationUserFeedback (webpack-internal:///./app/views/userFeedback/index.tsx:73:1)\n at Profiler (webpack-internal:///../node_modules/@sentry/react/esm/profiler.js:83:28)\n at profiler(OrganizationUserFeedback)\n at _class (webpack-internal:///./app/utils/withOrganization.tsx:24:19)\n at LazyLoad (webpack-internal:///./app/components/lazyLoad.tsx:39:5)\n at ErrorHandler (webpack-internal:///./app/utils/errorHandler.tsx:24:7)\n at ErrorBoundary (webpack-internal:///./app/components/errorBoundary.tsx:45:5)\n at OrganizationDetailsBody (webpack-internal:///./app/views/organizationDetails/body.tsx:131:5)\n at _class (webpack-internal:///./app/utils/withOrganization.tsx:24:19)\n at div\n at DocumentTitle\n at SideEffect (webpack-internal:///../node_modules/react-side-effect/lib/index.js:74:27)\n at SentryDocumentTitle (webpack-internal:///./app/components/sentryDocumentTitle.tsx:22:5)\n at OrganizationContextContainer (webpack-internal:///./app/views/organizationContextContainer.tsx:157:5)\n at Profiler (webpack-internal:///../node_modules/@sentry/react/esm/profiler.js:83:28)\n at profiler(OrganizationContextContainer)\n at WithOrganizations (webpack-internal:///./app/utils/withOrganizations.tsx:27:7)\n at WithApi (webpack-internal:///./app/utils/withApi.tsx:35:12)\n at OrganizationDetails (webpack-internal:///./app/views/organizationDetails/index.tsx:27:5)\n at ErrorHandler (webpack-internal:///./app/utils/errorHandler.tsx:24:7)\n at ErrorBoundary (webpack-internal:///./app/components/errorBoundary.tsx:45:5)\n at div\n at eval (webpack-internal:///../node_modules/@emotion/react/dist/emotion-element-a8309070.browser.esm.js:45:66)\n at App (webpack-internal:///./app/views/app/index.tsx:72:5)\n at ErrorHandler (webpack-internal:///./app/utils/errorHandler.tsx:24:7)\n at eval (webpack-internal:///../node_modules/create-react-class/factory.js:821:37)\n at eval (webpack-internal:///../node_modules/create-react-class/factory.js:821:37)\n at PersistedStoreProvider (webpack-internal:///./app/stores/persistedStore.tsx:59:76)\n at ThemeProvider (webpack-internal:///../node_modules/@emotion/react/dist/emotion-element-a8309070.browser.esm.js:79:64)\n at ThemeAndStyleProvider (webpack-internal:///./app/components/themeAndStyleProvider.tsx:45:5)\n at Main\n\nReact will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary.', + data: { + arguments: [ + 'The above error occurred in the component:\n\n at TestButton (webpack-internal:///./app/views/userFeedback/index.tsx:224:76)\n at div\n at eval (webpack-internal:///../node_modules/@emotion/react/dist/emotion-element-a8309070.browser.esm.js:45:66)\n at ButtonBar (webpack-internal:///./app/components/buttonBar.tsx:30:5)\n at div\n at eval (webpack-internal:///../node_modules/@emotion/react/dist/emotion-element-a8309070.browser.esm.js:45:66)\n at ErrorBoundary (webpack-internal:///./app/components/errorBoundary.tsx:45:5)\n at div\n at NoProjectMessage (webpack-internal:///./app/components/noProjectMessage.tsx:45:5)\n at div\n at eval (webpack-internal:///../node_modules/@emotion/react/dist/emotion-element-a8309070.browser.esm.js:45:66)\n at Container (webpack-internal:///./app/components/organizations/pageFilters/container.tsx:66:5)\n at eval (webpack-internal:///../node_modules/create-react-class/factory.js:821:37)\n at _class (webpack-internal:///./app/utils/withOrganization.tsx:24:19)\n at DocumentTitle\n at SideEffect (webpack-internal:///../node_modules/react-side-effect/lib/index.js:74:27)\n at SentryDocumentTitle (webpack-internal:///./app/components/sentryDocumentTitle.tsx:22:5)\n at OrganizationUserFeedback (webpack-internal:///./app/views/userFeedback/index.tsx:73:1)\n at Profiler (webpack-internal:///../node_modules/@sentry/react/esm/profiler.js:83:28)\n at profiler(OrganizationUserFeedback)\n at _class (webpack-internal:///./app/utils/withOrganization.tsx:24:19)\n at LazyLoad (webpack-internal:///./app/components/lazyLoad.tsx:39:5)\n at ErrorHandler (webpack-internal:///./app/utils/errorHandler.tsx:24:7)\n at ErrorBoundary (webpack-internal:///./app/components/errorBoundary.tsx:45:5)\n at OrganizationDetailsBody (webpack-internal:///./app/views/organizationDetails/body.tsx:131:5)\n at _class (webpack-internal:///./app/utils/withOrganization.tsx:24:19)\n at div\n at DocumentTitle\n at SideEffect (webpack-internal:///../node_modules/...', + ], + logger: '', + }, + }), + ReplayConsoleFrameFixture({ + type: BreadcrumbType.DEFAULT, + timestamp: new Date('2022-05-11T23:05:51.531000Z'), + level: BreadcrumbLevelType.WARNING, + message: + 'Warning: componentWillMount has been renamed, and is not recommended for use. See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n* Move code with side effects to componentDidMount, and set initial state in the constructor.\n* Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\nPlease update the following components: %s Router, RouterContext', + data: { + arguments: [ + 'Warning: componentWillMount has been renamed, and is not recommended for use. See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n* Move code with side effects to componentDidMount, and set initial state in the constructor.\n* Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\nPlease update the following components: %s', + 'Router, RouterContext', + 'find me', + ], + logger: 'console', + }, + }), + ], + mockRRWebFrames +); describe('useConsoleFilters', () => { beforeEach(() => { @@ -216,7 +221,8 @@ describe('useConsoleFilters', () => { level: BreadcrumbLevelType.ERROR, message: '', }), - ] + ], + mockRRWebFrames ); // const CRUMB_LOG_1 = {level: BreadcrumbLevelType.LOG, message: ''} as Crumb; diff --git a/static/app/views/replays/detail/perfTable/usePerfFilters.spec.tsx b/static/app/views/replays/detail/perfTable/usePerfFilters.spec.tsx index b10afef32788e4..b3b8998ddbbe9a 100644 --- a/static/app/views/replays/detail/perfTable/usePerfFilters.spec.tsx +++ b/static/app/views/replays/detail/perfTable/usePerfFilters.spec.tsx @@ -21,6 +21,7 @@ jest.mock('react-router'); jest.mock('sentry/utils/useLocation'); const mockUseLocation = jest.mocked(useLocation); +const mockRRWebFrames = []; // This is only needed for replay.hydrate breadcrumbs. const replayRecord = ReplayRecordFixture(); @@ -56,11 +57,15 @@ const CRUMB_2_CLICK: ReplayTraceRow = { lcpFrames: [], offsetMs: 100, paintFrames: [], - replayFrame: hydrateBreadcrumbs(replayRecord, [ - ReplayClickFrameFixture({ - timestamp: new Date(1663691559961), - }), - ])[0], + replayFrame: hydrateBreadcrumbs( + replayRecord, + [ + ReplayClickFrameFixture({ + timestamp: new Date(1663691559961), + }), + ], + mockRRWebFrames + )[0], timestampMs: 1663691560061, traces: [], };