diff --git a/static/app/components/events/eventReplay/index.spec.tsx b/static/app/components/events/eventReplay/index.spec.tsx index f1cf04e486e83c..04d41f4c960207 100644 --- a/static/app/components/events/eventReplay/index.spec.tsx +++ b/static/app/components/events/eventReplay/index.spec.tsx @@ -1,8 +1,8 @@ import {EventFixture} from 'sentry-fixture/event'; import {OrganizationFixture} from 'sentry-fixture/organization'; import {ProjectFixture} from 'sentry-fixture/project'; +import {RawReplayErrorFixture} from 'sentry-fixture/replay/error'; import {RRWebInitFrameEventsFixture} from 'sentry-fixture/replay/rrweb'; -import {ReplayErrorFixture} from 'sentry-fixture/replayError'; import {ReplayRecordFixture} from 'sentry-fixture/replayRecord'; import {render, screen} from 'sentry-test/reactTestingLibrary'; @@ -15,7 +15,7 @@ import { useReplayOnboardingSidebarPanel, } from 'sentry/utils/replays/hooks/useReplayOnboarding'; import ReplayReader from 'sentry/utils/replays/replayReader'; -import type {ReplayError} from 'sentry/views/replays/types'; +import type {RawReplayError} from 'sentry/utils/replays/types'; jest.mock('sentry/utils/replays/hooks/useReplayOnboarding'); jest.mock('sentry/utils/replays/hooks/useLoadReplayReader'); @@ -32,25 +32,23 @@ jest.mock( const mockEventTimestamp = new Date('2022-09-22T16:59:41Z'); const mockReplayId = '761104e184c64d439ee1014b72b4d83b'; -const mockErrors: ReplayError[] = [ - ReplayErrorFixture({ +const mockErrors: RawReplayError[] = [ + RawReplayErrorFixture({ id: '1', issue: 'JAVASCRIPT-101', 'issue.id': 101, - 'error.value': ['Something bad happened.'], 'error.type': ['error'], 'project.name': 'javascript', - timestamp: mockEventTimestamp.toISOString(), + timestamp: mockEventTimestamp, title: 'Something bad happened.', }), - ReplayErrorFixture({ + RawReplayErrorFixture({ id: '2', issue: 'JAVASCRIPT-102', 'issue.id': 102, - 'error.value': ['Something bad happened 2.'], 'error.type': ['error'], 'project.name': 'javascript', - timestamp: mockEventTimestamp.toISOString(), + timestamp: mockEventTimestamp, title: 'Something bad happened 2.', }), ]; diff --git a/static/app/components/replays/breadcrumbs/errorTitle.tsx b/static/app/components/replays/breadcrumbs/errorTitle.tsx index 7e170545207823..7991cdb120339f 100644 --- a/static/app/components/replays/breadcrumbs/errorTitle.tsx +++ b/static/app/components/replays/breadcrumbs/errorTitle.tsx @@ -16,7 +16,7 @@ export default function CrumbErrorTitle({frame}: {frame: ErrorFrame}) { return ( - Error:{' '} + {frame.data.level || 'Error'}:{' '} diff --git a/static/app/components/replays/header/errorCounts.spec.tsx b/static/app/components/replays/header/errorCounts.spec.tsx index 9631d2d28264c5..2e39023155cac8 100644 --- a/static/app/components/replays/header/errorCounts.spec.tsx +++ b/static/app/components/replays/header/errorCounts.spec.tsx @@ -1,6 +1,6 @@ import {OrganizationFixture} from 'sentry-fixture/organization'; import {ProjectFixture} from 'sentry-fixture/project'; -import {ReplayErrorFixture} from 'sentry-fixture/replayError'; +import {RawReplayErrorFixture} from 'sentry-fixture/replay/error'; import {ReplayRecordFixture} from 'sentry-fixture/replayRecord'; import {render, screen} from 'sentry-test/reactTestingLibrary'; @@ -11,7 +11,7 @@ import ProjectsStore from 'sentry/stores/projectsStore'; const replayRecord = ReplayRecordFixture(); const organization = OrganizationFixture(); -const baseErrorProps = {id: '1', issue: '', timestamp: new Date().toISOString()}; +const baseErrorProps = {id: '1', issue: '', timestamp: new Date()}; describe('ErrorCounts', () => { beforeEach(() => { @@ -43,7 +43,9 @@ describe('ErrorCounts', () => { }); it('should render an icon & count when all errors come from a single project', async () => { - const errors = [ReplayErrorFixture({...baseErrorProps, 'project.name': 'my-js-app'})]; + const errors = [ + RawReplayErrorFixture({...baseErrorProps, 'project.name': 'my-js-app'}), + ]; render(, { organization, @@ -63,9 +65,9 @@ describe('ErrorCounts', () => { it('should render an icon & count with links when there are errors in two unique projects', async () => { const errors = [ - ReplayErrorFixture({...baseErrorProps, 'project.name': 'my-js-app'}), - ReplayErrorFixture({...baseErrorProps, 'project.name': 'my-py-backend'}), - ReplayErrorFixture({...baseErrorProps, 'project.name': 'my-py-backend'}), + RawReplayErrorFixture({...baseErrorProps, 'project.name': 'my-js-app'}), + RawReplayErrorFixture({...baseErrorProps, 'project.name': 'my-py-backend'}), + RawReplayErrorFixture({...baseErrorProps, 'project.name': 'my-py-backend'}), ]; render(, { @@ -93,12 +95,12 @@ describe('ErrorCounts', () => { it('should render multiple icons, but a single count and link, when there are errors in three or more projects', async () => { const errors = [ - ReplayErrorFixture({...baseErrorProps, 'project.name': 'my-js-app'}), - ReplayErrorFixture({...baseErrorProps, 'project.name': 'my-py-backend'}), - ReplayErrorFixture({...baseErrorProps, 'project.name': 'my-py-backend'}), - ReplayErrorFixture({...baseErrorProps, 'project.name': 'my-node-service'}), - ReplayErrorFixture({...baseErrorProps, 'project.name': 'my-node-service'}), - ReplayErrorFixture({...baseErrorProps, 'project.name': 'my-node-service'}), + RawReplayErrorFixture({...baseErrorProps, 'project.name': 'my-js-app'}), + RawReplayErrorFixture({...baseErrorProps, 'project.name': 'my-py-backend'}), + RawReplayErrorFixture({...baseErrorProps, 'project.name': 'my-py-backend'}), + RawReplayErrorFixture({...baseErrorProps, 'project.name': 'my-node-service'}), + RawReplayErrorFixture({...baseErrorProps, 'project.name': 'my-node-service'}), + RawReplayErrorFixture({...baseErrorProps, 'project.name': 'my-node-service'}), ]; render(, { diff --git a/static/app/components/replays/header/errorCounts.tsx b/static/app/components/replays/header/errorCounts.tsx index 9c12e9e8c32e32..d696cc8b07ef0f 100644 --- a/static/app/components/replays/header/errorCounts.tsx +++ b/static/app/components/replays/header/errorCounts.tsx @@ -12,11 +12,12 @@ import {t, tn} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Project} from 'sentry/types/project'; import {TabKey} from 'sentry/utils/replays/hooks/useActiveReplayTab'; +import type {RawReplayError} from 'sentry/utils/replays/types'; import {useLocation} from 'sentry/utils/useLocation'; -import type {HydratedReplayRecord, ReplayError} from 'sentry/views/replays/types'; +import type {HydratedReplayRecord} from 'sentry/views/replays/types'; type Props = { - replayErrors: ReplayError[]; + replayErrors: RawReplayError[]; replayRecord: HydratedReplayRecord; }; diff --git a/static/app/components/replays/header/replayMetaData.tsx b/static/app/components/replays/header/replayMetaData.tsx index 2c99f26878e57c..1659b2f9e0f452 100644 --- a/static/app/components/replays/header/replayMetaData.tsx +++ b/static/app/components/replays/header/replayMetaData.tsx @@ -11,12 +11,13 @@ import {space} from 'sentry/styles/space'; import EventView from 'sentry/utils/discover/eventView'; import getRouteStringFromRoutes from 'sentry/utils/getRouteStringFromRoutes'; import {TabKey} from 'sentry/utils/replays/hooks/useActiveReplayTab'; +import type {RawReplayError} from 'sentry/utils/replays/types'; import {useLocation} from 'sentry/utils/useLocation'; import {useRoutes} from 'sentry/utils/useRoutes'; -import type {ReplayError, ReplayRecord} from 'sentry/views/replays/types'; +import type {ReplayRecord} from 'sentry/views/replays/types'; interface Props { - replayErrors: ReplayError[]; + replayErrors: RawReplayError[]; replayRecord: ReplayRecord; showDeadRageClicks?: boolean; } diff --git a/static/app/components/replays/header/useErrorCountPerProject.tsx b/static/app/components/replays/header/useErrorCountPerProject.tsx index 2f8e1d4f965abb..695834bb6f5a64 100644 --- a/static/app/components/replays/header/useErrorCountPerProject.tsx +++ b/static/app/components/replays/header/useErrorCountPerProject.tsx @@ -1,11 +1,12 @@ import {useMemo} from 'react'; import countBy from 'lodash/countBy'; +import type {RawReplayError} from 'sentry/utils/replays/types'; import useProjects from 'sentry/utils/useProjects'; -import type {ReplayError, ReplayRecord} from 'sentry/views/replays/types'; +import type {ReplayRecord} from 'sentry/views/replays/types'; type Props = { - replayErrors: ReplayError[]; + replayErrors: RawReplayError[]; replayRecord: ReplayRecord; }; diff --git a/static/app/utils/replays/getDiffTimestamps.spec.tsx b/static/app/utils/replays/getDiffTimestamps.spec.tsx index 85b8c8d5f782a8..d66d974d6de708 100644 --- a/static/app/utils/replays/getDiffTimestamps.spec.tsx +++ b/static/app/utils/replays/getDiffTimestamps.spec.tsx @@ -20,8 +20,8 @@ import { IncrementalSource, isHydrationErrorFrame, type RawBreadcrumbFrame, + type RawReplayError, } from 'sentry/utils/replays/types'; -import type {ReplayError} from 'sentry/views/replays/types'; const START_DATE = new Date('2022-06-15T00:40:00.000Z'); const INIT_DATE = new Date('2022-06-15T00:40:00.100Z'); @@ -62,7 +62,7 @@ const RRWEB_EVENTS = [ }), ]; -function getMockReplay(rrwebEvents: any[], errors: ReplayError[]) { +function getMockReplay(rrwebEvents: any[], errors: RawReplayError[]) { const attachments = [...rrwebEvents]; const replay = ReplayReader.factory({ replayRecord, @@ -77,7 +77,7 @@ function getMockReplay(rrwebEvents: any[], errors: ReplayError[]) { function getMockReplayWithCrumbFrame( rrwebEvents: any[], crumbFrame: RawBreadcrumbFrame, - errors: ReplayError[] + errors: RawReplayError[] ) { const attachments = [...rrwebEvents]; @@ -155,7 +155,7 @@ describe('getReplayDiffOffsetsFromEvent', () => { }); const errorEvent = EventFixture({dateCreated: ERROR_DATE.toISOString()}); const {replay} = getMockReplayWithCrumbFrame(RRWEB_EVENTS, rawHydrationCrumbFrame, [ - errorEvent as any as ReplayError, + errorEvent as any as RawReplayError, ]); const [hydratedHydrationCrumbFrame] = hydrateBreadcrumbs(replayRecord, [ @@ -170,7 +170,7 @@ describe('getReplayDiffOffsetsFromEvent', () => { it('should get offsets when no hydration breadcrumb exists', () => { const errorEvent = EventFixture({dateCreated: ERROR_DATE.toISOString()}); - const {replay} = getMockReplay(RRWEB_EVENTS, [errorEvent as any as ReplayError]); + const {replay} = getMockReplay(RRWEB_EVENTS, [errorEvent as any as RawReplayError]); expect(getReplayDiffOffsetsFromEvent(replay!, errorEvent)).toEqual({ frameOrEvent: errorEvent, diff --git a/static/app/utils/replays/hooks/useReplayData.spec.tsx b/static/app/utils/replays/hooks/useReplayData.spec.tsx index 67775effae2d3e..b05bff25ab1075 100644 --- a/static/app/utils/replays/hooks/useReplayData.spec.tsx +++ b/static/app/utils/replays/hooks/useReplayData.spec.tsx @@ -1,11 +1,11 @@ import type {ReactNode} from 'react'; import {duration} from 'moment-timezone'; +import {RawReplayErrorFixture} from 'sentry-fixture/replay/error'; import { ReplayConsoleEventFixture, ReplayNavigateEventFixture, } from 'sentry-fixture/replay/helpers'; import {RRWebInitFrameEventsFixture} from 'sentry-fixture/replay/rrweb'; -import {ReplayErrorFixture} from 'sentry-fixture/replayError'; import {ReplayRecordFixture} from 'sentry-fixture/replayRecord'; import {initializeOrg} from 'sentry-test/initializeOrg'; @@ -278,31 +278,31 @@ describe('useReplayData', () => { }); const mockErrorResponse1 = [ - ReplayErrorFixture({ + RawReplayErrorFixture({ id: ERROR_IDS[0]!, issue: 'JAVASCRIPT-123E', - timestamp: startedAt.toISOString(), + timestamp: startedAt, }), ]; const mockErrorResponse2 = [ - ReplayErrorFixture({ + RawReplayErrorFixture({ id: ERROR_IDS[1]!, issue: 'JAVASCRIPT-789Z', - timestamp: startedAt.toISOString(), + timestamp: startedAt, }), ]; const mockErrorResponse3 = [ - ReplayErrorFixture({ + RawReplayErrorFixture({ id: ERROR_IDS[0]!, issue: 'JAVASCRIPT-123E', - timestamp: startedAt.toISOString(), + timestamp: startedAt, }), ]; const mockErrorResponse4 = [ - ReplayErrorFixture({ + RawReplayErrorFixture({ id: ERROR_IDS[1]!, issue: 'JAVASCRIPT-789Z', - timestamp: startedAt.toISOString(), + timestamp: startedAt, }), ]; @@ -418,10 +418,10 @@ describe('useReplayData', () => { timestamp: startedAt, }); const mockErrorResponse = [ - ReplayErrorFixture({ + RawReplayErrorFixture({ id: ERROR_IDS[0]!, issue: 'JAVASCRIPT-123E', - timestamp: startedAt.toISOString(), + timestamp: startedAt, }), ]; diff --git a/static/app/utils/replays/hooks/useReplayData.tsx b/static/app/utils/replays/hooks/useReplayData.tsx index 23f4aeca0ed5d9..e9e403793aa67c 100644 --- a/static/app/utils/replays/hooks/useReplayData.tsx +++ b/static/app/utils/replays/hooks/useReplayData.tsx @@ -11,8 +11,9 @@ import {useApiQuery, useQueryClient} from 'sentry/utils/queryClient'; import useFeedbackEvents from 'sentry/utils/replays/hooks/useFeedbackEvents'; import {useReplayProjectSlug} from 'sentry/utils/replays/hooks/useReplayProjectSlug'; import {mapResponseToReplayRecord} from 'sentry/utils/replays/replayDataUtils'; +import type {RawReplayError} from 'sentry/utils/replays/types'; import type RequestError from 'sentry/utils/requestError/requestError'; -import type {ReplayError, ReplayRecord} from 'sentry/views/replays/types'; +import type {ReplayRecord} from 'sentry/views/replays/types'; type Options = { /** @@ -41,7 +42,7 @@ type Options = { interface Result { attachmentError: undefined | RequestError[]; attachments: unknown[]; - errors: ReplayError[]; + errors: RawReplayError[]; fetchError: undefined | RequestError; isError: boolean; isPending: boolean; @@ -200,7 +201,7 @@ function useReplayData({ pages: errorPages, status: fetchErrorsStatus, getLastResponseHeader: lastErrorsResponseHeader, - } = useFetchParallelPages<{data: ReplayError[]}>({ + } = useFetchParallelPages<{data: RawReplayError[]}>({ enabled: enableErrors, hits: replayRecord?.count_errors ?? 0, getQueryKey: getErrorsQueryKey, @@ -214,7 +215,7 @@ function useReplayData({ (!replayRecord?.count_errors || Boolean(links.next?.results)) && fetchErrorsStatus === 'success'; const {pages: extraErrorPages, status: fetchExtraErrorsStatus} = - useFetchSequentialPages<{data: ReplayError[]}>({ + useFetchSequentialPages<{data: RawReplayError[]}>({ enabled: enableExtraErrors, initialCursor: links.next?.cursor, getQueryKey: getErrorsQueryKey, @@ -222,7 +223,7 @@ function useReplayData({ }); const {pages: platformErrorPages, status: fetchPlatformErrorsStatus} = - useFetchSequentialPages<{data: ReplayError[]}>({ + useFetchSequentialPages<{data: RawReplayError[]}>({ enabled: true, getQueryKey: getPlatformErrorsQueryKey, perPage: errorsPerPage, diff --git a/static/app/utils/replays/hydrateErrors.spec.tsx b/static/app/utils/replays/hydrateErrors.spec.tsx index 6f281b34ab8cd8..394490cbcbb4e0 100644 --- a/static/app/utils/replays/hydrateErrors.spec.tsx +++ b/static/app/utils/replays/hydrateErrors.spec.tsx @@ -25,6 +25,7 @@ describe('hydrateErrors', () => { groupShortId: 'JS-374', label: '', labels: [], + level: 'Error', projectSlug: 'javascript', }, message: 'A Redirect with :orgId param on customer domain', @@ -41,6 +42,7 @@ describe('hydrateErrors', () => { groupShortId: 'JS-374', label: '', labels: [], + level: 'Error', projectSlug: 'javascript', }, message: 'A Redirect with :orgId param on customer domain', @@ -57,6 +59,7 @@ describe('hydrateErrors', () => { groupShortId: 'JS-374', label: '', labels: [], + level: 'Error', projectSlug: 'javascript', }, message: 'A Redirect with :orgId param on customer domain', diff --git a/static/app/utils/replays/hydrateErrors.tsx b/static/app/utils/replays/hydrateErrors.tsx index 5b40c973a7c2f0..51eca4c8777405 100644 --- a/static/app/utils/replays/hydrateErrors.tsx +++ b/static/app/utils/replays/hydrateErrors.tsx @@ -39,6 +39,7 @@ export default function hydrateErrors( (Array.isArray(e['error.type']) ? e['error.type'][0] : e['error.type']) ?? '', labels: toArray(e['error.type']).filter(Boolean), + level: e.level, projectSlug: e['project.name'], }, message: @@ -64,6 +65,7 @@ export default function hydrateErrors( label: (Array.isArray(e['error.type']) ? e['error.type'][0] : e['error.type']) ?? '', labels: toArray(e['error.type']).filter(defined), + level: e.level, projectSlug: e['project.name'], }, message: e.title, diff --git a/static/app/utils/replays/replayReader.spec.tsx b/static/app/utils/replays/replayReader.spec.tsx index 333c00b472fc1a..ec802da4a5c386 100644 --- a/static/app/utils/replays/replayReader.spec.tsx +++ b/static/app/utils/replays/replayReader.spec.tsx @@ -1,3 +1,4 @@ +import {RawReplayErrorFixture} from 'sentry-fixture/replay/error'; import { ReplayClickEventFixture, ReplayConsoleEventFixture, @@ -18,7 +19,6 @@ import { RRWebFullSnapshotFrameEventFixture, RRWebIncrementalSnapshotFrameEventFixture, } from 'sentry-fixture/replay/rrweb'; -import {ReplayErrorFixture} from 'sentry-fixture/replayError'; import {ReplayRecordFixture} from 'sentry-fixture/replayRecord'; import {BreadcrumbType} from 'sentry/types/breadcrumbs'; @@ -411,20 +411,20 @@ describe('ReplayReader', () => { }, }); - const error1 = ReplayErrorFixture({ + const error1 = RawReplayErrorFixture({ id: '1', issue: '100', - timestamp: '2024-01-01T00:02:30', + timestamp: new Date('2024-01-01T00:02:30'), }); - const error2 = ReplayErrorFixture({ + const error2 = RawReplayErrorFixture({ id: '2', issue: '200', - timestamp: '2024-01-01T00:03:06', + timestamp: new Date('2024-01-01T00:03:06'), }); - const error3 = ReplayErrorFixture({ + const error3 = RawReplayErrorFixture({ id: '1', issue: '100', - timestamp: '2024-01-01T00:03:30', + timestamp: new Date('2024-01-01T00:03:30'), }); const replay = ReplayReader.factory({ diff --git a/static/app/utils/replays/replayReader.tsx b/static/app/utils/replays/replayReader.tsx index f674a9b1acda8b..3e65f7d47b8f8e 100644 --- a/static/app/utils/replays/replayReader.tsx +++ b/static/app/utils/replays/replayReader.tsx @@ -30,6 +30,7 @@ import type { incrementalSnapshotEvent, MemoryFrame, OptionFrame, + RawReplayError, RecordingFrame, ReplayFrame, serializedNodeWithId, @@ -54,7 +55,7 @@ import { isWebVitalFrame, NodeType, } from 'sentry/utils/replays/types'; -import type {HydratedReplayRecord, ReplayError} from 'sentry/views/replays/types'; +import type {HydratedReplayRecord} from 'sentry/views/replays/types'; interface ReplayReaderParams { /** @@ -71,7 +72,7 @@ interface ReplayReaderParams { * Error instances could be frontend, backend, or come from the error platform * like performance-errors or replay-errors */ - errors: ReplayError[] | undefined; + errors: RawReplayError[] | undefined; /** * Is replay data still fetching? diff --git a/static/app/utils/replays/types.tsx b/static/app/utils/replays/types.tsx index 4ff608d814590e..bd7b5d17eb8441 100644 --- a/static/app/utils/replays/types.tsx +++ b/static/app/utils/replays/types.tsx @@ -442,6 +442,7 @@ export type RawReplayError = { id: string; issue: string; ['issue.id']: number; + level: string; ['project.name']: string; timestamp: string; title: string; @@ -457,6 +458,7 @@ export type ErrorFrame = Overwrite< groupShortId: string; label: string; labels: string[]; + level: string; projectSlug: string; }; message: string; diff --git a/static/app/views/replays/types.tsx b/static/app/views/replays/types.tsx index e39022bf17e55d..eb08575d4b58b5 100644 --- a/static/app/views/replays/types.tsx +++ b/static/app/views/replays/types.tsx @@ -299,20 +299,6 @@ export type ReplayListRecord = Pick< | 'warning_ids' >; -/** - * This is a result of a custom discover query - */ -export interface ReplayError { - ['error.type']: string[]; - ['error.value']: string[]; // deprecated, use title instead. See organization_replay_events_meta.py - id: string; - issue: string; - ['issue.id']: number; - ['project.name']: string; - timestamp: string; - title: string; -} - export type DeadRageSelectorItem = { aria_label: string; dom_element: { diff --git a/tests/js/fixtures/replay/error.ts b/tests/js/fixtures/replay/error.ts index daf75c64090948..6a9108c405742f 100644 --- a/tests/js/fixtures/replay/error.ts +++ b/tests/js/fixtures/replay/error.ts @@ -12,6 +12,7 @@ export function RawReplayErrorFixture( 'issue.id': error['issue.id'] ?? 3740335939, 'project.name': error['project.name'] ?? 'javascript', timestamp: error.timestamp.toISOString(), + level: error.level ?? 'Error', title: error.title ?? 'A Redirect with :orgId param on customer domain', }; } diff --git a/tests/js/fixtures/replayError.ts b/tests/js/fixtures/replayError.ts deleted file mode 100644 index 5161036ba2eb43..00000000000000 --- a/tests/js/fixtures/replayError.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type {ReplayError} from 'sentry/views/replays/types'; - -export function ReplayErrorFixture( - error: Partial & Pick -): ReplayError { - return { - 'error.type': error['error.type'] ?? ([] as string[]), - 'error.value': error['error.value'] ?? ([] as string[]), - id: error.id, - issue: error.issue, - 'issue.id': error['issue.id'] ?? 3740335939, - 'project.name': error['project.name'] ?? 'javascript', - timestamp: error.timestamp, - title: error.title ?? 'A Redirect with :orgId param on customer domain', - }; -}