Browse Source

ref(replay): Refactor the url walker components to use the new *Frame types (#53099)

Ryan Albrecht 1 year ago
parent
commit
e493817ef7

+ 13 - 0
fixtures/js-stubs/replay/replayBreadcrumbFrameData.ts

@@ -112,3 +112,16 @@ export function MutationFrame(
     type: '',
   };
 }
+
+export function NavFrame(fields: TestableFrame<'navigation'>): MockFrame<'navigation'> {
+  return {
+    category: 'navigation',
+    data: fields.data ?? {
+      from: '',
+      to: '',
+    },
+    message: fields.message ?? '',
+    timestamp: fields.timestamp.getTime() / 1000,
+    type: '',
+  };
+}

+ 55 - 0
static/app/components/replays/walker/frameWalker.spec.tsx

@@ -0,0 +1,55 @@
+import {render, screen} from 'sentry-test/reactTestingLibrary';
+
+import FrameWalker from 'sentry/components/replays/walker/frameWalker';
+import hydrateBreadcrumbs, {
+  replayInitBreadcrumb,
+} from 'sentry/utils/replays/hydrateBreadcrumbs';
+
+describe('FrameWalker', () => {
+  const replayRecord = TestStubs.ReplayRecord({});
+
+  it('should accept a list of crumbs and render a <ChevronDividedList />', () => {
+    const init = replayInitBreadcrumb(replayRecord);
+    const breadcrumbs = hydrateBreadcrumbs(replayRecord, [
+      TestStubs.Replay.NavFrame({
+        timestamp: new Date(),
+        data: {
+          from: '/',
+          to: '/report/1655300817078_https%3A%2F%2Fmaxcdn.bootstrapcdn.com%2Fbootstrap%2F3.3.7%2Fjs%2Fbootstrap.min.js',
+        },
+      }),
+      TestStubs.Replay.NavFrame({
+        timestamp: new Date(),
+        data: {
+          from: '/',
+          to: '/report/1655300817078_https%3A%2F%2Fmaxcdn.bootstrapcdn.com%2Fbootstrap%2F3.3.7%2Fjs%2Fbootstrap.min.js',
+        },
+      }),
+      TestStubs.Replay.NavFrame({
+        timestamp: new Date(),
+        data: {
+          from: '/',
+          to: '/report/1655300817078_https%3A%2F%2Fmaxcdn.bootstrapcdn.com%2Fbootstrap%2F3.3.7%2Fjs%2Fbootstrap.min.js',
+        },
+      }),
+      TestStubs.Replay.NavFrame({
+        timestamp: new Date(),
+        data: {
+          from: '/report/1655300817078_https%3A%2F%2Fmaxcdn.bootstrapcdn.com%2Fbootstrap%2F3.3.7%2Fjs%2Fbootstrap.min.js',
+          to: '/report/1669088273097_http%3A%2F%2Funderscorejs.org%2Funderscore-min.js',
+        },
+      }),
+    ]);
+    const frames = [init, ...breadcrumbs];
+
+    render(<FrameWalker frames={frames} replayRecord={replayRecord} />);
+
+    expect(screen.getByText('/')).toBeInTheDocument();
+    expect(screen.getByText('3 Pages')).toBeInTheDocument();
+    expect(
+      screen.getByText(
+        '/report/1669088273097_http%3A%2F%2Funderscorejs.org%2Funderscore-min.js'
+      )
+    ).toBeInTheDocument();
+  });
+});

+ 29 - 0
static/app/components/replays/walker/frameWalker.tsx

@@ -0,0 +1,29 @@
+import {memo} from 'react';
+
+import ChevronDividedList from 'sentry/components/replays/walker/chevronDividedList';
+import splitCrumbs from 'sentry/components/replays/walker/splitCrumbs';
+import useCrumbHandlers from 'sentry/utils/replays/hooks/useCrumbHandlers';
+import type {ReplayFrame} from 'sentry/utils/replays/types';
+import type {ReplayRecord} from 'sentry/views/replays/types';
+
+type Props = {
+  frames: ReplayFrame[];
+  replayRecord: ReplayRecord;
+};
+
+const FrameWalker = memo(function FrameWalker({frames, replayRecord}: Props) {
+  const startTimestampMs = replayRecord.started_at.getTime();
+  const {handleClick} = useCrumbHandlers(startTimestampMs);
+
+  return (
+    <ChevronDividedList
+      items={splitCrumbs({
+        frames,
+        startTimestampMs,
+        onClick: handleClick,
+      })}
+    />
+  );
+});
+
+export default FrameWalker;

+ 48 - 49
static/app/components/replays/walker/splitCrumbs.spec.tsx

@@ -1,41 +1,40 @@
 import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
 
-import type {Crumb} from 'sentry/types/breadcrumbs';
-
-import splitCrumbs from './splitCrumbs';
-
-const PAGELOAD_CRUMB = TestStubs.Breadcrumb({
-  id: 4,
-  data: {
-    to: 'https://sourcemaps.io/',
-  },
-}) as Crumb;
-
-const NAV_CRUMB_BOOTSTRAP = TestStubs.Breadcrumb({
-  id: 5,
-  data: {
-    from: '/',
-    to: '/report/1655300817078_https%3A%2F%2Fmaxcdn.bootstrapcdn.com%2Fbootstrap%2F3.3.7%2Fjs%2Fbootstrap.min.js',
-  },
-}) as Crumb;
-
-const NAV_CRUMB_UNDERSCORE = TestStubs.Breadcrumb({
-  id: 6,
-  data: {
-    from: '/report/1655300817078_https%3A%2F%2Fmaxcdn.bootstrapcdn.com%2Fbootstrap%2F3.3.7%2Fjs%2Fbootstrap.min.js',
-    to: '/report/1669088273097_http%3A%2F%2Funderscorejs.org%2Funderscore-min.js',
-  },
-}) as Crumb;
+import splitCrumbs from 'sentry/components/replays/walker/splitCrumbs';
+import hydrateBreadcrumbs, {
+  replayInitBreadcrumb,
+} from 'sentry/utils/replays/hydrateBreadcrumbs';
+
+const replayRecord = TestStubs.ReplayRecord({});
+
+const INIT_FRAME = replayInitBreadcrumb(replayRecord);
+
+const [BOOTSTRAP_FRAME, UNDERSCORE_FRAME] = hydrateBreadcrumbs(replayRecord, [
+  TestStubs.Replay.NavFrame({
+    timestamp: new Date(),
+    data: {
+      from: '/',
+      to: '/report/1655300817078_https%3A%2F%2Fmaxcdn.bootstrapcdn.com%2Fbootstrap%2F3.3.7%2Fjs%2Fbootstrap.min.js',
+    },
+  }),
+  TestStubs.Replay.NavFrame({
+    timestamp: new Date(),
+    data: {
+      from: '/report/1655300817078_https%3A%2F%2Fmaxcdn.bootstrapcdn.com%2Fbootstrap%2F3.3.7%2Fjs%2Fbootstrap.min.js',
+      to: '/report/1669088273097_http%3A%2F%2Funderscorejs.org%2Funderscore-min.js',
+    },
+  }),
+]);
 
 describe('splitCrumbs', () => {
   const onClick = null;
   const startTimestampMs = 0;
 
   it('should accept an empty list, and print that there are zero pages', () => {
-    const crumbs = [];
+    const frames = [];
 
     const results = splitCrumbs({
-      crumbs,
+      frames,
       onClick,
       startTimestampMs,
     });
@@ -46,31 +45,31 @@ describe('splitCrumbs', () => {
   });
 
   it('should accept one crumb and return that single segment', () => {
-    const crumbs = [PAGELOAD_CRUMB];
+    const frames = [INIT_FRAME];
 
     const results = splitCrumbs({
-      crumbs,
+      frames,
       onClick,
       startTimestampMs,
     });
     expect(results).toHaveLength(1);
 
     render(results[0]);
-    expect(screen.getByText('https://sourcemaps.io/')).toBeInTheDocument();
+    expect(screen.getByText('/')).toBeInTheDocument();
   });
 
   it('should accept three crumbs and return them all as individual segments', () => {
-    const crumbs = [PAGELOAD_CRUMB, NAV_CRUMB_BOOTSTRAP, NAV_CRUMB_UNDERSCORE];
+    const frames = [INIT_FRAME, BOOTSTRAP_FRAME, UNDERSCORE_FRAME];
 
     const results = splitCrumbs({
-      crumbs,
+      frames,
       onClick,
       startTimestampMs,
     });
     expect(results).toHaveLength(3);
 
     render(results[0]);
-    expect(screen.getByText('https://sourcemaps.io/')).toBeInTheDocument();
+    expect(screen.getByText('/')).toBeInTheDocument();
 
     render(results[1]);
     expect(
@@ -88,23 +87,23 @@ describe('splitCrumbs', () => {
   });
 
   it('should accept more than three crumbs and summarize the middle ones', () => {
-    const crumbs = [
-      PAGELOAD_CRUMB,
-      NAV_CRUMB_BOOTSTRAP,
-      NAV_CRUMB_BOOTSTRAP,
-      NAV_CRUMB_BOOTSTRAP,
-      NAV_CRUMB_UNDERSCORE,
+    const frames = [
+      INIT_FRAME,
+      BOOTSTRAP_FRAME,
+      BOOTSTRAP_FRAME,
+      BOOTSTRAP_FRAME,
+      UNDERSCORE_FRAME,
     ];
 
     const results = splitCrumbs({
-      crumbs,
+      frames,
       onClick,
       startTimestampMs,
     });
     expect(results).toHaveLength(3);
 
     render(results[0]);
-    expect(screen.getByText('https://sourcemaps.io/')).toBeInTheDocument();
+    expect(screen.getByText('/')).toBeInTheDocument();
 
     render(results[1]);
     expect(screen.getByText('3 Pages')).toBeInTheDocument();
@@ -118,16 +117,16 @@ describe('splitCrumbs', () => {
   });
 
   it('should show the summarized items on hover', async () => {
-    const crumbs = [
-      PAGELOAD_CRUMB,
-      {...NAV_CRUMB_BOOTSTRAP, id: 1},
-      {...NAV_CRUMB_BOOTSTRAP, id: 2},
-      {...NAV_CRUMB_BOOTSTRAP, id: 3},
-      NAV_CRUMB_UNDERSCORE,
+    const frames = [
+      INIT_FRAME,
+      BOOTSTRAP_FRAME,
+      BOOTSTRAP_FRAME,
+      BOOTSTRAP_FRAME,
+      UNDERSCORE_FRAME,
     ];
 
     const results = splitCrumbs({
-      crumbs,
+      frames,
       onClick,
       startTimestampMs,
     });

+ 24 - 31
static/app/components/replays/walker/splitCrumbs.tsx

@@ -5,37 +5,28 @@ import BreadcrumbItem from 'sentry/components/replays/breadcrumbs/breadcrumbItem
 import TextOverflow from 'sentry/components/textOverflow';
 import {tn} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
-import {BreadcrumbType, Crumb} from 'sentry/types/breadcrumbs';
+import {Crumb} from 'sentry/types/breadcrumbs';
+import {getDescription} from 'sentry/utils/replays/frame';
 import useCrumbHandlers from 'sentry/utils/replays/hooks/useCrumbHandlers';
 import type {ReplayFrame} from 'sentry/utils/replays/types';
 
-type MaybeOnClickHandler = null | ((crumb: Crumb | ReplayFrame) => void);
-
-function getUrl(crumb: undefined | Crumb) {
-  if (crumb?.type === BreadcrumbType.NAVIGATION) {
-    return crumb.data?.to?.split('?')?.[0];
-  }
-  if (crumb?.type === BreadcrumbType.INIT) {
-    return crumb.data?.url;
-  }
-  return undefined;
-}
+type MaybeOnClickHandler = null | ((frame: Crumb | ReplayFrame) => void);
 
 function splitCrumbs({
-  crumbs,
+  frames,
   onClick,
   startTimestampMs,
 }: {
-  crumbs: Crumb[];
+  frames: ReplayFrame[];
   onClick: MaybeOnClickHandler;
   startTimestampMs: number;
 }) {
-  const firstCrumb = crumbs.slice(0, 1) as Crumb[];
-  const summarizedCrumbs = crumbs.slice(1, -1) as Crumb[];
-  const lastCrumb = crumbs.slice(-1) as Crumb[];
+  const firstFrame = frames.slice(0, 1);
+  const summarizedFrames = frames.slice(1, -1);
+  const lastFrame = frames.slice(-1);
 
-  if (crumbs.length === 0) {
-    // This one shouldn't overflow, but by including the component css stays
+  if (frames.length === 0) {
+    // This one shouldn't overflow, but by including <TextOverflow> css stays
     // consistent with the other Segment types
     return [
       <Span key="summary">
@@ -44,33 +35,33 @@ function splitCrumbs({
     ];
   }
 
-  if (crumbs.length > 3) {
+  if (frames.length > 3) {
     return [
       <SummarySegment
         key="first"
-        crumbs={firstCrumb}
+        frames={firstFrame}
         startTimestampMs={startTimestampMs}
         handleOnClick={onClick}
       />,
       <SummarySegment
         key="summary"
-        crumbs={summarizedCrumbs}
+        frames={summarizedFrames}
         startTimestampMs={startTimestampMs}
         handleOnClick={onClick}
       />,
       <SummarySegment
         key="last"
-        crumbs={lastCrumb}
+        frames={lastFrame}
         startTimestampMs={startTimestampMs}
         handleOnClick={onClick}
       />,
     ];
   }
 
-  return crumbs.map((crumb, i) => (
+  return frames.map((frame, i) => (
     <SummarySegment
       key={i}
-      crumbs={[crumb] as Crumb[]}
+      frames={[frame]}
       startTimestampMs={startTimestampMs}
       handleOnClick={onClick}
     />
@@ -78,11 +69,11 @@ function splitCrumbs({
 }
 
 function SummarySegment({
-  crumbs,
+  frames,
   handleOnClick,
   startTimestampMs,
 }: {
-  crumbs: Crumb[];
+  frames: ReplayFrame[];
   handleOnClick: MaybeOnClickHandler;
   startTimestampMs: number;
 }) {
@@ -90,10 +81,10 @@ function SummarySegment({
 
   const summaryItems = (
     <ScrollingList>
-      {crumbs.map((crumb, i) => (
-        <li key={crumb.id || i}>
+      {frames.map((frame, i) => (
+        <li key={i}>
           <BreadcrumbItem
-            crumb={crumb}
+            crumb={frame}
             onClick={handleOnClick}
             onMouseEnter={handleMouseEnter}
             onMouseLeave={handleMouseLeave}
@@ -105,7 +96,9 @@ function SummarySegment({
   );
 
   const label =
-    crumbs.length === 1 ? getUrl(crumbs[0]) : tn('%s Page', '%s Pages', crumbs.length);
+    frames.length === 1
+      ? getDescription(frames[0])
+      : tn('%s Page', '%s Pages', frames.length);
   return (
     <Span>
       <HalfPaddingHovercard body={summaryItems} position="right">

+ 24 - 0
static/app/components/replays/walker/stringWalker.spec.tsx

@@ -0,0 +1,24 @@
+import {render, screen} from 'sentry-test/reactTestingLibrary';
+
+import StringWalker from 'sentry/components/replays/walker/stringWalker';
+
+describe('StringWalker', () => {
+  it('should accept a list of strings and render a <ChevronDividedList />', () => {
+    const urls = [
+      'https://sourcemaps.io/',
+      '/report/1655300817078_https%3A%2F%2Fmaxcdn.bootstrapcdn.com%2Fbootstrap%2F3.3.7%2Fjs%2Fbootstrap.min.js',
+      '/report/1669088273097_http%3A%2F%2Funderscorejs.org%2Funderscore-min.js',
+      '/report/1669088971516_https%3A%2F%2Fcdn.ravenjs.com%2F3.17.0%2Fraven.min.js',
+    ];
+
+    render(<StringWalker urls={urls} />);
+
+    expect(screen.getByText('/')).toBeInTheDocument();
+    expect(screen.getByText('2 Pages')).toBeInTheDocument();
+    expect(
+      screen.getByText(
+        '/report/1669088971516_https%3A%2F%2Fcdn.ravenjs.com%2F3.17.0%2Fraven.min.js'
+      )
+    ).toBeInTheDocument();
+  });
+});

+ 40 - 0
static/app/components/replays/walker/stringWalker.tsx

@@ -0,0 +1,40 @@
+import {memo} from 'react';
+
+import ChevronDividedList from 'sentry/components/replays/walker/chevronDividedList';
+import splitCrumbs from 'sentry/components/replays/walker/splitCrumbs';
+import {BreadcrumbType} from 'sentry/types/breadcrumbs';
+import type {ReplayFrame} from 'sentry/utils/replays/types';
+
+type StringProps = {
+  urls: string[];
+};
+
+const StringWalker = memo(function StringWalker({urls}: StringProps) {
+  return (
+    <ChevronDividedList
+      items={splitCrumbs({
+        frames: urls.map(urlToFrame),
+        startTimestampMs: 0,
+        onClick: null,
+      })}
+    />
+  );
+});
+
+export default StringWalker;
+
+function urlToFrame(url: string): ReplayFrame {
+  const now = new Date();
+  return {
+    category: 'navigation',
+    data: {
+      from: '',
+      to: url,
+    },
+    message: url,
+    timestamp: now,
+    type: BreadcrumbType.NAVIGATION,
+    offsetMs: 0,
+    timestampMs: now.getTime() / 1000,
+  };
+}

+ 0 - 75
static/app/components/replays/walker/urlWalker.spec.tsx

@@ -1,75 +0,0 @@
-import {render, screen} from 'sentry-test/reactTestingLibrary';
-
-import type {Crumb} from 'sentry/types/breadcrumbs';
-
-import {CrumbWalker, StringWalker} from './urlWalker';
-
-describe('UrlWalker', () => {
-  describe('StringWalker', () => {
-    it('should accept a list of strings and render a <ChevronDividedList />', () => {
-      const urls = [
-        'https://sourcemaps.io/',
-        '/report/1655300817078_https%3A%2F%2Fmaxcdn.bootstrapcdn.com%2Fbootstrap%2F3.3.7%2Fjs%2Fbootstrap.min.js',
-        '/report/1669088273097_http%3A%2F%2Funderscorejs.org%2Funderscore-min.js',
-        '/report/1669088971516_https%3A%2F%2Fcdn.ravenjs.com%2F3.17.0%2Fraven.min.js',
-      ];
-
-      render(<StringWalker urls={urls} />);
-
-      expect(screen.getByText('https://sourcemaps.io/')).toBeInTheDocument();
-      expect(screen.getByText('2 Pages')).toBeInTheDocument();
-      expect(
-        screen.getByText(
-          '/report/1669088971516_https%3A%2F%2Fcdn.ravenjs.com%2F3.17.0%2Fraven.min.js'
-        )
-      ).toBeInTheDocument();
-    });
-  });
-
-  describe('CrumbWalker', () => {
-    const replayRecord = TestStubs.ReplayRecord({});
-
-    const PAGELOAD_CRUMB = TestStubs.Breadcrumb({
-      id: 4,
-      data: {
-        to: 'https://sourcemaps.io/',
-      },
-    }) as Crumb;
-
-    const NAV_CRUMB_BOOTSTRAP = TestStubs.Breadcrumb({
-      id: 5,
-      data: {
-        from: '/',
-        to: '/report/1655300817078_https%3A%2F%2Fmaxcdn.bootstrapcdn.com%2Fbootstrap%2F3.3.7%2Fjs%2Fbootstrap.min.js',
-      },
-    }) as Crumb;
-
-    const NAV_CRUMB_UNDERSCORE = TestStubs.Breadcrumb({
-      id: 6,
-      data: {
-        from: '/report/1655300817078_https%3A%2F%2Fmaxcdn.bootstrapcdn.com%2Fbootstrap%2F3.3.7%2Fjs%2Fbootstrap.min.js',
-        to: '/report/1669088273097_http%3A%2F%2Funderscorejs.org%2Funderscore-min.js',
-      },
-    }) as Crumb;
-
-    it('should accept a list of crumbs and render a <ChevronDividedList />', () => {
-      const crumbs = [
-        PAGELOAD_CRUMB,
-        NAV_CRUMB_BOOTSTRAP,
-        NAV_CRUMB_BOOTSTRAP,
-        NAV_CRUMB_BOOTSTRAP,
-        NAV_CRUMB_UNDERSCORE,
-      ];
-
-      render(<CrumbWalker crumbs={crumbs} replayRecord={replayRecord} />);
-
-      expect(screen.getByText('https://sourcemaps.io/')).toBeInTheDocument();
-      expect(screen.getByText('3 Pages')).toBeInTheDocument();
-      expect(
-        screen.getByText(
-          '/report/1669088273097_http%3A%2F%2Funderscorejs.org%2Funderscore-min.js'
-        )
-      ).toBeInTheDocument();
-    });
-  });
-});

+ 0 - 57
static/app/components/replays/walker/urlWalker.tsx

@@ -1,57 +0,0 @@
-import {memo} from 'react';
-
-import ChevronDividedList from 'sentry/components/replays/walker/chevronDividedList';
-import splitCrumbs from 'sentry/components/replays/walker/splitCrumbs';
-import {BreadcrumbLevelType, BreadcrumbType, Crumb} from 'sentry/types/breadcrumbs';
-import useCrumbHandlers from 'sentry/utils/replays/hooks/useCrumbHandlers';
-import type {ReplayRecord} from 'sentry/views/replays/types';
-
-type CrumbProps = {
-  crumbs: Crumb[];
-  replayRecord: ReplayRecord;
-};
-
-type StringProps = {
-  urls: string[];
-};
-
-export const CrumbWalker = memo(function CrumbWalker({crumbs, replayRecord}: CrumbProps) {
-  const startTimestampMs = replayRecord.started_at.getTime();
-  const {handleClick} = useCrumbHandlers(startTimestampMs);
-
-  return (
-    <ChevronDividedList
-      items={splitCrumbs({
-        crumbs,
-        startTimestampMs,
-        onClick: handleClick,
-      })}
-    />
-  );
-});
-
-export const StringWalker = memo(function StringWalker({urls}: StringProps) {
-  return (
-    <ChevronDividedList
-      items={splitCrumbs({
-        crumbs: urls.map(urlToCrumb),
-        startTimestampMs: 0,
-        onClick: null,
-      })}
-    />
-  );
-});
-
-function urlToCrumb(url: string): Crumb {
-  return {
-    type: BreadcrumbType.NAVIGATION,
-    category: BreadcrumbType.NAVIGATION,
-    level: BreadcrumbLevelType.INFO,
-    description: 'Navigation',
-
-    id: 0,
-    color: 'green300',
-    timestamp: undefined,
-    data: {to: url},
-  };
-}

+ 13 - 2
static/app/utils/replays/frame.tsx

@@ -16,10 +16,13 @@ import {
   SpanFrame,
 } from 'sentry/utils/replays/types';
 import type {Color} from 'sentry/utils/theme';
+import stripOrigin from 'sentry/utils/url/stripOrigin';
 
 export function getColor(frame: ReplayFrame): Color {
   if ('category' in frame) {
     switch (frame.category) {
+      case 'replay.init':
+        return 'gray300';
       case 'navigation':
         return 'green300';
       case 'issue':
@@ -69,6 +72,8 @@ export function getColor(frame: ReplayFrame): Color {
 export function getBreadcrumbType(frame: ReplayFrame): BreadcrumbType {
   if ('category' in frame) {
     switch (frame.category) {
+      case 'replay.init':
+        return BreadcrumbType.DEFAULT;
       case 'navigation':
         return BreadcrumbType.NAVIGATION;
       case 'issue':
@@ -125,6 +130,8 @@ export function getTitle(frame: ReplayFrame): ReactNode {
   if ('category' in frame) {
     const [type, action] = frame.category.split('.');
     switch (frame.category) {
+      case 'replay.init':
+        return 'Replay Init';
       case 'navigation':
         return 'Navigation';
       case 'ui.slowClickDetected':
@@ -177,9 +184,11 @@ function stringifyNodeAttributes(node: SlowClickFrame['data']['node']) {
 export function getDescription(frame: ReplayFrame): ReactNode {
   if ('category' in frame) {
     switch (frame.category) {
+      case 'replay.init':
+        return stripOrigin(frame.message ?? '');
       case 'navigation':
         const navFrame = frame as NavFrame;
-        return navFrame.data.to;
+        return stripOrigin(navFrame.data.to);
       case 'issue':
       case 'ui.slowClickDetected': {
         const slowClickFrame = frame as SlowClickFrame;
@@ -233,7 +242,7 @@ export function getDescription(frame: ReplayFrame): ReactNode {
     case 'navigation.reload':
     case 'navigation.back_forward':
     case 'navigation.push':
-      return frame.description;
+      return stripOrigin(frame.description);
     case 'largest-contentful-paint': {
       const lcpFrame = frame as LargestContentfulPaintFrame;
       if (typeof lcpFrame.data.value === 'number') {
@@ -258,6 +267,8 @@ export function getDescription(frame: ReplayFrame): ReactNode {
 export function getTabKeyForFrame(frame: BreadcrumbFrame | SpanFrame): TabKey {
   if ('category' in frame) {
     switch (frame.category) {
+      case 'replay.init':
+        return TabKey.CONSOLE;
       case 'navigation':
         return TabKey.NETWORK;
       case 'issue':

Some files were not shown because too many files changed in this diff