|
@@ -2,13 +2,29 @@ import {useEffect, useState} from 'react';
|
|
|
import type {eventWithTime} from 'rrweb/typings/types';
|
|
|
|
|
|
import {IssueAttachment} from 'sentry/types';
|
|
|
-import {Event} from 'sentry/types/event';
|
|
|
+import {Entry, EntryType, Event} from 'sentry/types/event';
|
|
|
import EventView from 'sentry/utils/discover/eventView';
|
|
|
import {generateEventSlug} from 'sentry/utils/discover/urls';
|
|
|
import RequestError from 'sentry/utils/requestError/requestError';
|
|
|
import useApi from 'sentry/utils/useApi';
|
|
|
|
|
|
+import mergeBreadcrumbsEntries from './mergeBreadcrumbsEntries';
|
|
|
+
|
|
|
+function isReplayEventEntity(entry: Entry) {
|
|
|
+ // Starting with an allowlist, might be better to block only a few types (like Tags)
|
|
|
+ switch (entry.type) {
|
|
|
+ case EntryType.SPANS:
|
|
|
+ return true;
|
|
|
+ default:
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+}
|
|
|
type State = {
|
|
|
+ /**
|
|
|
+ * List of breadcrumbs
|
|
|
+ */
|
|
|
+ breadcrumbEntry: undefined | Entry;
|
|
|
+
|
|
|
/**
|
|
|
* The root replay event
|
|
|
*/
|
|
@@ -25,6 +41,8 @@ type State = {
|
|
|
*/
|
|
|
fetching: boolean;
|
|
|
|
|
|
+ mergedReplayEvent: undefined | Event;
|
|
|
+
|
|
|
/**
|
|
|
* The list of related `sentry-replay-event` objects that were captured during this `sentry-replay`
|
|
|
*/
|
|
@@ -67,9 +85,11 @@ function useReplayEvent({eventSlug, location, orgId}: Options): Result {
|
|
|
const [state, setState] = useState<State>({
|
|
|
fetchError: undefined,
|
|
|
fetching: true,
|
|
|
+ breadcrumbEntry: undefined,
|
|
|
event: undefined,
|
|
|
replayEvents: undefined,
|
|
|
rrwebEvents: undefined,
|
|
|
+ mergedReplayEvent: undefined,
|
|
|
});
|
|
|
|
|
|
function fetchEvent() {
|
|
@@ -130,9 +150,11 @@ function useReplayEvent({eventSlug, location, orgId}: Options): Result {
|
|
|
fetchError: undefined,
|
|
|
fetching: true,
|
|
|
|
|
|
+ breadcrumbEntry: undefined,
|
|
|
event: undefined,
|
|
|
replayEvents: undefined,
|
|
|
rrwebEvents: undefined,
|
|
|
+ mergedReplayEvent: undefined,
|
|
|
});
|
|
|
try {
|
|
|
const [event, rrwebEvents, replayEvents] = await Promise.all([
|
|
@@ -141,22 +163,43 @@ function useReplayEvent({eventSlug, location, orgId}: Options): Result {
|
|
|
fetchReplayEvents(),
|
|
|
]);
|
|
|
|
|
|
+ const breadcrumbEntry = mergeBreadcrumbsEntries(replayEvents || []);
|
|
|
+
|
|
|
+ // Get a merged list of all spans from all replay events
|
|
|
+ const spans = replayEvents.flatMap(
|
|
|
+ replayEvent => replayEvent.entries.find(isReplayEventEntity).data
|
|
|
+ );
|
|
|
+
|
|
|
+ // Create a merged spans entry on the first replay event and fake the
|
|
|
+ // endTimestamp by using the timestamp of the final span
|
|
|
+ const mergedReplayEvent = {
|
|
|
+ ...replayEvents[0],
|
|
|
+ breakdowns: null,
|
|
|
+ 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,
|
|
|
+ };
|
|
|
+
|
|
|
setState({
|
|
|
...state,
|
|
|
fetchError: undefined,
|
|
|
fetching: false,
|
|
|
event,
|
|
|
+ mergedReplayEvent,
|
|
|
replayEvents,
|
|
|
rrwebEvents,
|
|
|
+ breadcrumbEntry,
|
|
|
});
|
|
|
} catch (error) {
|
|
|
setState({
|
|
|
fetchError: error,
|
|
|
fetching: false,
|
|
|
|
|
|
+ breadcrumbEntry: undefined,
|
|
|
event: undefined,
|
|
|
replayEvents: undefined,
|
|
|
rrwebEvents: undefined,
|
|
|
+ mergedReplayEvent: undefined,
|
|
|
});
|
|
|
}
|
|
|
}
|
|
@@ -167,9 +210,11 @@ function useReplayEvent({eventSlug, location, orgId}: Options): Result {
|
|
|
fetchError: state.fetchError,
|
|
|
fetching: state.fetching,
|
|
|
|
|
|
+ breadcrumbEntry: state.breadcrumbEntry,
|
|
|
event: state.event,
|
|
|
replayEvents: state.replayEvents,
|
|
|
rrwebEvents: state.rrwebEvents,
|
|
|
+ mergedReplayEvent: state.mergedReplayEvent,
|
|
|
};
|
|
|
}
|
|
|
|