Browse Source

ref(replay): Use the Replay class to feed data into the details components (#34216)

This uses the new Replay class to make all the data available within the <FocusArea> tabbed section.

The tabbed sections are now each responsible for doing any specific data munging or filtering that they want to do. In this case we've got two tabs using the Span data: one shows the network waterfall without indicating when a memory snapshot was taken, and the other a chart of memory snapshots over time.

I like having these two filters at the same layer like this: "only-memory" and "not-memory". Having them at the same layer could make it easier to see whether we have all the different types of data covered.

Related to #34172
Ryan Albrecht 2 years ago
parent
commit
8a1db47428

+ 0 - 0
static/app/views/replays/utils/createHighlightEvents.tsx → static/app/utils/replays/createHighlightEvents.tsx


+ 0 - 0
static/app/views/replays/utils/mergeAndSortEvents.tsx → static/app/utils/replays/mergeAndSortEvents.tsx


+ 27 - 10
static/app/utils/replays/replayReader.tsx

@@ -1,3 +1,4 @@
+import last from 'lodash/last';
 import memoize from 'lodash/memoize';
 import type {eventWithTime} from 'rrweb/typings/types';
 
@@ -5,13 +6,11 @@ import type {RawSpanType} from 'sentry/components/events/interfaces/spans/types'
 import type {RawCrumb} from 'sentry/types/breadcrumbs';
 import type {Event, EventTransaction} from 'sentry/types/event';
 import {EntryType} from 'sentry/types/event';
+import createHighlightEvents from 'sentry/utils/replays/createHighlightEvents';
+import mergeAndSortEvents from 'sentry/utils/replays/mergeAndSortEvents';
 import mergeBreadcrumbEntries from 'sentry/utils/replays/mergeBreadcrumbEntries';
 import mergeSpanEntries from 'sentry/utils/replays/mergeSpanEntries';
 
-function last<T>(arr: T[]): T {
-  return arr[arr.length - 1];
-}
-
 export default class ReplayReader {
   static factory(
     event: EventTransaction | undefined,
@@ -56,9 +55,9 @@ export default class ReplayReader {
     // So we need to figure out the real end time (in seconds).
     const endTimestamp =
       Math.max(
-        lastRRweb.timestamp,
-        +new Date(lastBreadcrumb.timestamp || 0),
-        lastSpan.timestamp * 1000
+        lastRRweb?.timestamp || 0,
+        +new Date(lastBreadcrumb?.timestamp || 0),
+        (lastSpan?.timestamp || 0) * 1000
       ) / 1000;
 
     return {
@@ -68,9 +67,19 @@ export default class ReplayReader {
     } as EventTransaction;
   });
 
-  getRRWebEvents() {
-    return this._rrwebEvents;
-  }
+  getRRWebEvents = memoize(() => {
+    const spansEntry = this.getEntryType(EntryType.SPANS);
+
+    // Find LCP spans that have a valid replay node id, this will be used to
+    const highlights = createHighlightEvents(spansEntry.data);
+
+    // TODO(replays): ideally this would happen on SDK, but due
+    // to how plugins work, we are unable to specify a timestamp for an event
+    // (rrweb applies it), so it's possible actual LCP timestamp does not
+    // match when the observer happens and we emit an rrweb event (will
+    // look into this)
+    return mergeAndSortEvents(this._rrwebEvents, highlights);
+  });
 
   getEntryType = memoize((type: EntryType) => {
     switch (type) {
@@ -84,4 +93,12 @@ export default class ReplayReader {
         );
     }
   });
+
+  isMemorySpan = (span: RawSpanType) => {
+    return span.op === 'memory';
+  };
+
+  isNotMemorySpan = (span: RawSpanType) => {
+    return !this.isMemorySpan(span);
+  };
 }

+ 197 - 0
static/app/utils/replays/useReplayData.tsx

@@ -0,0 +1,197 @@
+import {useCallback, useEffect, useMemo, useState} from 'react';
+import * as Sentry from '@sentry/react';
+import type {eventWithTime} from 'rrweb/typings/types';
+
+import {IssueAttachment} from 'sentry/types';
+import {Event, EventTransaction} from 'sentry/types/event';
+import {generateEventSlug} from 'sentry/utils/discover/urls';
+import ReplayReader from 'sentry/utils/replays/replayReader';
+import RequestError from 'sentry/utils/requestError/requestError';
+import useApi from 'sentry/utils/useApi';
+
+type State = {
+  /**
+   * The root replay event
+   */
+  event: undefined | EventTransaction;
+
+  /**
+   * If any request returned an error then nothing is being returned
+   */
+  fetchError: undefined | RequestError;
+
+  /**
+   * If a fetch is underway for the requested root reply.
+   * This includes fetched all the sub-resources like attachments and `sentry-replay-event`
+   */
+  fetching: boolean;
+
+  /**
+   * The list of related `sentry-replay-event` objects that were captured during this `sentry-replay`
+   */
+  replayEvents: undefined | Event[];
+
+  /**
+   * The flattened list of rrweb events. These are stored as multiple attachments on the root replay object: the `event` prop.
+   */
+  rrwebEvents: undefined | eventWithTime[];
+};
+
+type Options = {
+  /**
+   * The projectSlug and eventId concatenated together
+   */
+  eventSlug: string;
+
+  /**
+   * The organization slug
+   */
+  orgId: string;
+};
+
+interface Result extends Pick<State, 'fetchError' | 'fetching'> {
+  onRetry: () => void;
+  replay: ReplayReader | null;
+}
+
+const IS_RRWEB_ATTACHMENT_FILENAME = /rrweb-[0-9]{13}.json/;
+
+function isRRWebEventAttachment(attachment: IssueAttachment) {
+  return IS_RRWEB_ATTACHMENT_FILENAME.test(attachment.name);
+}
+
+const INITIAL_STATE: State = Object.freeze({
+  event: undefined,
+  fetchError: undefined,
+  fetching: true,
+  replayEvents: undefined,
+  rrwebEvents: undefined,
+});
+
+/**
+ * A react hook to load core replay data over the network.
+ *
+ * Core replay data includes:
+ * 1. The root replay EventTransaction object
+ *    - This includes `startTimestamp` and `tags` data
+ * 2. Breadcrumb and Span data from all the related Event objects
+ *    - Data is merged for consumption
+ * 3. RRWeb payloads for the replayer video stream
+ *    - TODO(replay): incrementally load the stream to speedup pageload
+ *
+ * This function should stay focused on loading data over the network.
+ * Front-end processing, filtering and re-mixing of the different data streams
+ * must be delegated to the `ReplayReader` class.
+ *
+ * @param {orgId, eventSlug} Where to find the root replay event
+ * @returns An object representing a unified result of the network reqeusts. Either a single `ReplayReader` data object or fetch errors.
+ */
+function useReplayData({eventSlug, orgId}: Options): Result {
+  const [projectId, eventId] = eventSlug.split(':');
+
+  const api = useApi();
+  const [retry, setRetry] = useState(true);
+  const [state, setState] = useState<State>(INITIAL_STATE);
+
+  const fetchEvent = useCallback(() => {
+    return api.requestPromise(
+      `/organizations/${orgId}/events/${eventSlug}/`
+    ) as Promise<EventTransaction>;
+  }, [api, orgId, eventSlug]);
+
+  const fetchRRWebEvents = useCallback(async () => {
+    const attachmentIds = (await api.requestPromise(
+      `/projects/${orgId}/${projectId}/events/${eventId}/attachments/`
+    )) as IssueAttachment[];
+    const rrwebAttachmentIds = attachmentIds.filter(isRRWebEventAttachment);
+    const attachments = await Promise.all(
+      rrwebAttachmentIds.map(async attachment => {
+        const response = await api.requestPromise(
+          `/api/0/projects/${orgId}/${projectId}/events/${eventId}/attachments/${attachment.id}/?download`
+        );
+        return JSON.parse(response).events as eventWithTime;
+      })
+    );
+    return attachments.flat();
+  }, [api, eventId, orgId, projectId]);
+
+  const fetchReplayEvents = useCallback(async () => {
+    const replayEventList = await api.requestPromise(
+      `/organizations/${orgId}/eventsv2/`,
+      {
+        query: {
+          statsPeriod: '14d',
+          project: [],
+          environment: [],
+          field: ['timestamp', 'replayId'],
+          sort: 'timestamp',
+          per_page: 50,
+          query: ['transaction:sentry-replay-event', `replayId:${eventId}`].join(' '),
+        },
+      }
+    );
+
+    return Promise.all(
+      replayEventList.data.map(
+        event =>
+          api.requestPromise(
+            `/organizations/${orgId}/events/${generateEventSlug(event)}/`
+          ) as Promise<Event>
+      )
+    );
+  }, [api, eventId, orgId]);
+
+  const loadEvents = useCallback(
+    async function () {
+      setState(INITIAL_STATE);
+
+      try {
+        const [event, rrwebEvents, replayEvents] = await Promise.all([
+          fetchEvent(),
+          fetchRRWebEvents(),
+          fetchReplayEvents(),
+        ]);
+
+        setState({
+          event,
+          fetchError: undefined,
+          fetching: false,
+          replayEvents,
+          rrwebEvents,
+        });
+      } catch (error) {
+        Sentry.captureException(error);
+        setState({
+          ...INITIAL_STATE,
+          fetchError: error,
+          fetching: false,
+        });
+      }
+    },
+    [fetchEvent, fetchRRWebEvents, fetchReplayEvents]
+  );
+
+  useEffect(() => {
+    if (retry) {
+      setRetry(false);
+      loadEvents();
+    }
+  }, [retry, loadEvents]);
+
+  const onRetry = useCallback(() => {
+    setRetry(true);
+  }, []);
+
+  const replay = useMemo(() => {
+    return ReplayReader.factory(state.event, state.rrwebEvents, state.replayEvents);
+  }, [state.event, state.rrwebEvents, state.replayEvents]);
+
+  return {
+    fetchError: state.fetchError,
+    fetching: state.fetching,
+    onRetry,
+    replay,
+  };
+}
+
+export default useReplayData;

+ 28 - 19
static/app/views/replays/detail/focusArea.tsx

@@ -1,9 +1,10 @@
 import React from 'react';
 
 import EventEntry from 'sentry/components/events/eventEntry';
-import type {MemorySpanType} from 'sentry/components/events/interfaces/spans/types';
 import TagsTable from 'sentry/components/tagsTable';
 import type {Event} from 'sentry/types/event';
+import {EntryType} from 'sentry/types/event';
+import ReplayReader from 'sentry/utils/replays/replayReader';
 import {useLocation} from 'sentry/utils/useLocation';
 import useOrganization from 'sentry/utils/useOrganization';
 import {useRouteContext} from 'sentry/utils/useRouteContext';
@@ -15,9 +16,7 @@ import IssueList from './issueList';
 import MemoryChart from './memoryChart';
 
 type Props = {
-  event: Event;
-  eventWithSpans: Event | undefined;
-  memorySpans: MemorySpanType[] | undefined;
+  replay: ReplayReader;
 };
 
 const DEFAULT_TAB = ReplayTabs.PERFORMANCE;
@@ -35,30 +34,40 @@ function FocusArea(props: Props) {
   );
 }
 
-function ActiveTab({
-  active,
-  event,
-  eventWithSpans,
-  memorySpans,
-}: Props & {active: ReplayTabs}) {
+function ActiveTab({active, replay}: Props & {active: ReplayTabs}) {
   const {routes, router} = useRouteContext();
   const organization = useOrganization();
+
+  const event = replay.getEvent();
+  const spansEntry = replay.getEntryType(EntryType.SPANS);
+
   switch (active) {
-    case 'performance':
-      return eventWithSpans ? (
+    case 'performance': {
+      if (!spansEntry) {
+        return null;
+      }
+      const nonMemorySpans = {
+        ...spansEntry,
+        data: spansEntry.data.filter(replay.isNotMemorySpan),
+      };
+      const performanceEvent = {
+        ...event,
+        entries: [nonMemorySpans],
+      } as Event;
+      return (
         <div id="performance">
           <EventEntry
-            key={`${eventWithSpans.id}`}
-            projectSlug={getProjectSlug(eventWithSpans)}
+            projectSlug={getProjectSlug(performanceEvent)}
             // group={group}
             organization={organization}
-            event={eventWithSpans}
-            entry={eventWithSpans.entries[0]}
+            event={performanceEvent}
+            entry={nonMemorySpans}
             route={routes[routes.length - 1]}
             router={router}
           />
         </div>
-      ) : null;
+      );
+    }
     case 'issues':
       return (
         <div id="issues">
@@ -74,8 +83,8 @@ function ActiveTab({
     case 'memory':
       return (
         <MemoryChart
-          memorySpans={memorySpans}
-          startTimestamp={eventWithSpans?.entries[0]?.data[0]?.timestamp}
+          memorySpans={spansEntry?.data.filter(replay.isMemorySpan)}
+          startTimestamp={event.startTimestamp}
         />
       );
     default:

+ 13 - 21
static/app/views/replays/details.tsx

@@ -14,12 +14,13 @@ import ReplayPlayer from 'sentry/components/replays/replayPlayer';
 import useFullscreen from 'sentry/components/replays/useFullscreen';
 import {t} from 'sentry/locale';
 import {PageContent} from 'sentry/styles/organization';
+import {EntryType} from 'sentry/types/event';
+import useReplayData from 'sentry/utils/replays/useReplayData';
 import {useRouteContext} from 'sentry/utils/useRouteContext';
 
 import DetailLayout from './detail/detailLayout';
 import FocusArea from './detail/focusArea';
 import UserActionsNavigator from './detail/userActionsNavigator';
-import useReplayEvent from './utils/useReplayEvent';
 
 function ReplayDetails() {
   const {
@@ -31,18 +32,8 @@ function ReplayDetails() {
     t: initialTimeOffset, // Time, in seconds, where the video should start
   } = location.query;
 
-  const {
-    breadcrumbEntry,
-    event,
-    mergedReplayEvent,
-    memorySpans,
-    fetchError,
-    fetching,
-    onRetry,
-    replay,
-  } = useReplayEvent({
+  const {fetchError, fetching, onRetry, replay} = useReplayData({
     eventSlug,
-    location,
     orgId,
   });
 
@@ -94,9 +85,9 @@ function ReplayDetails() {
       initialTimeOffset={initialTimeOffset}
     >
       <DetailLayout
-        event={event}
+        event={replay.getEvent()}
         orgId={orgId}
-        crumbs={breadcrumbEntry?.data.values || []}
+        crumbs={replay.getEntryType(EntryType.BREADCRUMBS)?.data.values || []}
       >
         <Layout.Body>
           <ReplayLayout ref={fullscreenRef}>
@@ -113,17 +104,18 @@ function ReplayDetails() {
             </Panel>
           </ReplayLayout>
           <Layout.Side>
-            <UserActionsNavigator event={event} entry={breadcrumbEntry} />
+            <UserActionsNavigator
+              event={replay.getEvent()}
+              entry={replay.getEntryType(EntryType.BREADCRUMBS)}
+            />
           </Layout.Side>
           <Layout.Main fullWidth>
             <Panel>
-              <BreadcrumbTimeline crumbs={breadcrumbEntry?.data.values || []} />
+              <BreadcrumbTimeline
+                crumbs={replay.getEntryType(EntryType.BREADCRUMBS)?.data.values || []}
+              />
             </Panel>
-            <FocusArea
-              event={replay.getEvent()}
-              eventWithSpans={mergedReplayEvent}
-              memorySpans={memorySpans}
-            />
+            <FocusArea replay={replay} />
           </Layout.Main>
         </Layout.Body>
       </DetailLayout>

+ 0 - 33
static/app/views/replays/utils/mergeBreadcrumbsEntries.tsx

@@ -1,33 +0,0 @@
-import {Entry, EntryType, Event} from 'sentry/types/event';
-
-export default function mergeBreadcrumbsEntries(
-  events: Event[],
-  rootEvent: Event
-): Entry {
-  const rrwebEventIds = events.map(({id}) => id);
-
-  const notRRWebTransaction = crumb =>
-    !(
-      crumb.category === 'sentry.transaction' &&
-      (rootEvent.id === crumb.message || rrwebEventIds.includes(crumb.message))
-    );
-
-  const allValues = events.flatMap(event =>
-    event.entries.flatMap((entry: Entry) =>
-      entry.type === EntryType.BREADCRUMBS
-        ? entry.data.values.filter(notRRWebTransaction)
-        : []
-    )
-  );
-
-  const stringified = allValues.map(value => JSON.stringify(value));
-  const deduped = Array.from(new Set(stringified));
-  const values = deduped.map(value => JSON.parse(value));
-
-  return {
-    type: EntryType.BREADCRUMBS,
-    data: {
-      values,
-    },
-  };
-}

+ 0 - 20
static/app/views/replays/utils/mergeEventsWithSpans.tsx

@@ -1,20 +0,0 @@
-import type {RawSpanType} from 'sentry/components/events/interfaces/spans/types';
-import {Entry, EntryType, Event} from 'sentry/types/event';
-
-export default function mergeEventsWithSpans(events: Event[]): Event {
-  // Get a merged list of all spans from all replay events
-  const spans = events.flatMap(event =>
-    event.entries.flatMap((entry: Entry) =>
-      entry.type === EntryType.SPANS ? (entry.data as RawSpanType[]) : []
-    )
-  );
-
-  // Create a merged spans entry on the first replay event and fake the
-  // endTimestamp by using the timestamp of the final span
-  return {
-    ...events[0],
-    entries: [{type: EntryType.SPANS, data: spans}],
-    // This is probably better than taking the end timestamp of the last `replayEvent`
-    endTimestamp: spans[spans.length - 1]?.timestamp,
-  } as Event;
-}

+ 0 - 231
static/app/views/replays/utils/useReplayEvent.tsx

@@ -1,231 +0,0 @@
-import {useCallback, useEffect, useState} from 'react';
-import * as Sentry from '@sentry/react';
-import type {eventWithTime} from 'rrweb/typings/types';
-
-import {MemorySpanType} from 'sentry/components/events/interfaces/spans/types';
-import {IssueAttachment} from 'sentry/types';
-import {Entry, Event, EventTransaction} from 'sentry/types/event';
-import EventView from 'sentry/utils/discover/eventView';
-import {generateEventSlug} from 'sentry/utils/discover/urls';
-import ReplayReader from 'sentry/utils/replays/replayReader';
-import RequestError from 'sentry/utils/requestError/requestError';
-import useApi from 'sentry/utils/useApi';
-
-import createHighlightEvents from './createHighlightEvents';
-import mergeAndSortEvents from './mergeAndSortEvents';
-import mergeBreadcrumbsEntries from './mergeBreadcrumbsEntries';
-import mergeEventsWithSpans from './mergeEventsWithSpans';
-
-type State = {
-  /**
-   * List of breadcrumbs
-   */
-  breadcrumbEntry: undefined | Entry;
-
-  /**
-   * The root replay event
-   */
-  event: undefined | EventTransaction;
-
-  /**
-   * If any request returned an error then nothing is being returned
-   */
-  fetchError: undefined | RequestError;
-
-  /**
-   * If a fetch is underway for the requested root reply.
-   * This includes fetched all the sub-resources like attachments and `sentry-replay-event`
-   */
-  fetching: boolean;
-
-  memorySpans: undefined | MemorySpanType[];
-
-  mergedReplayEvent: undefined | Event;
-
-  /**
-   * The list of related `sentry-replay-event` objects that were captured during this `sentry-replay`
-   */
-  replayEvents: undefined | Event[];
-
-  /**
-   * The flattened list of rrweb events. These are stored as multiple attachments on the root replay object: the `event` prop.
-   */
-  rrwebEvents: undefined | eventWithTime[];
-};
-
-type Options = {
-  /**
-   * When provided, fetches specified replay event by slug
-   */
-  eventSlug: string;
-
-  /**
-   *
-   */
-  location: any;
-
-  /**
-   *
-   */
-  orgId: string;
-};
-
-interface Result extends State {
-  onRetry: () => void;
-  replay: ReplayReader | null;
-}
-
-const IS_RRWEB_ATTACHMENT_FILENAME = /rrweb-[0-9]{13}.json/;
-
-function isRRWebEventAttachment(attachment: IssueAttachment) {
-  return IS_RRWEB_ATTACHMENT_FILENAME.test(attachment.name);
-}
-
-const INITIAL_STATE: State = Object.freeze({
-  fetchError: undefined,
-  fetching: true,
-  breadcrumbEntry: undefined,
-  event: undefined,
-  replayEvents: undefined,
-  rrwebEvents: undefined,
-  mergedReplayEvent: undefined,
-  memorySpans: undefined,
-});
-
-function useReplayEvent({eventSlug, location, orgId}: Options): Result {
-  const [projectId, eventId] = eventSlug.split(':');
-
-  const api = useApi();
-  const [retry, setRetry] = useState(false);
-  const [state, setState] = useState<State>(INITIAL_STATE);
-
-  function fetchEvent() {
-    return api.requestPromise(
-      `/organizations/${orgId}/events/${eventSlug}/`
-    ) as Promise<EventTransaction>;
-  }
-
-  async function fetchRRWebEvents() {
-    const attachmentIds = (await api.requestPromise(
-      `/projects/${orgId}/${projectId}/events/${eventId}/attachments/`
-    )) as IssueAttachment[];
-    const rrwebAttachmentIds = attachmentIds.filter(isRRWebEventAttachment);
-    const attachments = await Promise.all(
-      rrwebAttachmentIds.map(async attachment => {
-        const response = await api.requestPromise(
-          `/api/0/projects/${orgId}/${projectId}/events/${eventId}/attachments/${attachment.id}/?download`
-        );
-        return JSON.parse(response).events as eventWithTime;
-      })
-    );
-    return attachments.flat();
-  }
-
-  async function fetchReplayEvents() {
-    const replayEventsView = EventView.fromSavedQuery({
-      id: '',
-      name: '',
-      version: 2,
-      fields: ['timestamp', 'replayId'],
-      orderby: 'timestamp',
-      projects: [],
-      range: '14d',
-      query: `transaction:sentry-replay-event`,
-    });
-    replayEventsView.additionalConditions.addFilterValues('replayId', [eventId]);
-    const replayEventsQuery = replayEventsView.getEventsAPIPayload(location);
-
-    const replayEventList = await api.requestPromise(
-      `/organizations/${orgId}/eventsv2/`,
-      {
-        query: replayEventsQuery,
-      }
-    );
-
-    return Promise.all(
-      replayEventList.data.map(
-        event =>
-          api.requestPromise(
-            `/organizations/${orgId}/events/${generateEventSlug(event)}/`
-          ) as Promise<Event>
-      )
-    );
-  }
-
-  async function loadEvents() {
-    setRetry(false);
-    setState({
-      ...INITIAL_STATE,
-    });
-
-    try {
-      const [event, rrwebEvents, replayEvents] = await Promise.all([
-        fetchEvent(),
-        fetchRRWebEvents(),
-        fetchReplayEvents(),
-      ]);
-
-      const breadcrumbEntry = mergeBreadcrumbsEntries(replayEvents || [], event);
-      const mergedReplayEvent = mergeEventsWithSpans(replayEvents || []);
-      const memorySpans =
-        mergedReplayEvent?.entries[0]?.data?.filter(datum => datum?.data?.memory) || [];
-
-      if (mergedReplayEvent.entries[0]) {
-        mergedReplayEvent.entries[0].data = mergedReplayEvent?.entries[0]?.data?.filter(
-          datum => !datum?.data?.memory
-        );
-      }
-
-      // Find LCP spans that have a valid replay node id, this will be used to
-      const highlights = createHighlightEvents(mergedReplayEvent?.entries[0].data);
-
-      // TODO(replays): ideally this would happen on SDK, but due
-      // to how plugins work, we are unable to specify a timestamp for an event
-      // (rrweb applies it), so it's possible actual LCP timestamp does not
-      // match when the observer happens and we emit an rrweb event (will
-      // look into this)
-      const rrwebEventsWithHighlights = mergeAndSortEvents(rrwebEvents, highlights);
-
-      setState({
-        ...state,
-        fetchError: undefined,
-        fetching: false,
-        event,
-        mergedReplayEvent,
-        replayEvents,
-        rrwebEvents: rrwebEventsWithHighlights,
-        breadcrumbEntry,
-        memorySpans,
-      });
-    } catch (error) {
-      Sentry.captureException(error);
-      setState({
-        ...INITIAL_STATE,
-        fetchError: error,
-        fetching: false,
-      });
-    }
-  }
-
-  useEffect(() => void loadEvents(), [orgId, eventSlug, retry]); // eslint-disable-line react-hooks/exhaustive-deps
-
-  const onRetry = useCallback(() => {
-    setRetry(true);
-  }, []);
-
-  return {
-    fetchError: state.fetchError,
-    fetching: state.fetching,
-    onRetry,
-    replay: ReplayReader.factory(state.event, state.rrwebEvents, state.replayEvents),
-
-    breadcrumbEntry: state.breadcrumbEntry,
-    event: state.event,
-    replayEvents: state.replayEvents,
-    rrwebEvents: state.rrwebEvents,
-    mergedReplayEvent: state.mergedReplayEvent,
-    memorySpans: state.memorySpans,
-  };
-}
-
-export default useReplayEvent;

+ 1 - 1
tests/js/spec/views/replays/utils/createHighlightEvents.spec.tsx → tests/js/spec/utils/replays/createHighlightEvents.spec.tsx

@@ -1,4 +1,4 @@
-import createHighlightEvents from 'sentry/views/replays/utils/createHighlightEvents';
+import createHighlightEvents from 'sentry/utils/replays/createHighlightEvents';
 
 function createSpan(extra: Record<string, any>) {
   return {

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