Skip to content

Commit fdeae58

Browse files
Abdkhan14Abdullah Khan
andauthored
fix(trace-eap-waterfall-replay): Unable to load span details (#93899)
- We have multiple traces associated to a replay, and in the replay trace tab we load one trace, and append the rest in batches of three, in to one waterfall. - The error below was happening because, we were using the trace_id of the first trace fetched, to fetch the attributes of all the spans in the waterfall using `/trace-item/..../?trace_id={slug}`. Leading to a 404 for spans that are not part of the first trace. - Instead of concatenating the fetched results from the batch and appending as one tree, we take the results and individually append them to the tree, hence collecting and passing on the correct traceslugs that the drawer uses to fetch eap span attrs. <img width="1431" alt="Screenshot 2025-06-19 at 1 08 00 AM" src="https://github.com/user-attachments/assets/271f4fc8-56db-4c3f-97f8-80703984784b" /> - Also fixed the order of traces processed. Co-authored-by: Abdullah Khan <abdullahkhan@PG9Y57YDXQ.local>
1 parent 7f656e6 commit fdeae58

File tree

3 files changed

+48
-41
lines changed

3 files changed

+48
-41
lines changed

static/app/views/performance/newTraceDetails/traceDrawer/details/span/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ function EAPSpanNodeDetails({
383383
} = useTraceItemDetails({
384384
traceItemId: node.value.event_id,
385385
projectId: node.value.project_id.toString(),
386-
traceId,
386+
traceId: node.metadata.replayTraceSlug ?? traceId,
387387
traceItemType: TraceItemDataset.SPANS,
388388
referrer: 'api.explore.log-item-details', // TODO: change to span details
389389
enabled: true,

static/app/views/performance/newTraceDetails/traceModels/traceTree.tsx

Lines changed: 23 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,11 @@ export declare namespace TraceTree {
265265
type Metadata = {
266266
event_id: string | undefined;
267267
project_slug: string | undefined;
268+
// This is used to track the traceslug associated with a trace in a replay.
269+
// This is necessary because a replay has multiple traces and the current ui requires
270+
// us to merge them into one trace. We still need to keep track of the original traceSlug
271+
// to be able to fetch the correct trace-item details from EAP, in the trace drawer.
272+
replayTraceSlug?: string;
268273
spans?: number;
269274
};
270275

@@ -386,6 +391,11 @@ export class TraceTree extends TraceTreeEventDispatcher {
386391
meta: TraceMetaQueryResults['data'] | null;
387392
replay: ReplayRecord | null;
388393
preferences?: Pick<TracePreferencesState, 'autogroup' | 'missing_instrumentation'>;
394+
// This is used to track the traceslug associated with a trace in a replay.
395+
// This is necessary because a replay has multiple traces and the current ui requires
396+
// us to merge them into one trace. We still need to keep track of the original traceSlug
397+
// to be able to fetch the correct trace-item details from EAP, in the trace drawer.
398+
replayTraceSlug?: string;
389399
}
390400
): TraceTree {
391401
const tree = new TraceTree();
@@ -423,6 +433,7 @@ export class TraceTree extends TraceTreeEventDispatcher {
423433
spans: options.meta?.transaction_child_count_map[value.event_id] ?? 0,
424434
project_slug: value && 'project_slug' in value ? value.project_slug : undefined,
425435
event_id: value && 'event_id' in value ? value.event_id : undefined,
436+
replayTraceSlug: options.replayTraceSlug,
426437
});
427438

428439
if (isTransactionNode(node) || isEAPTransactionNode(node)) {
@@ -2202,47 +2213,21 @@ export class TraceTree extends TraceTreeEventDispatcher {
22022213
return;
22032214
}
22042215

2205-
const accumulator: TraceSplitResults<TraceTree.Transaction> | TraceTree.EAPTrace =
2206-
options.type === 'eap'
2207-
? []
2208-
: {
2209-
transactions: [],
2210-
orphan_errors: [],
2211-
};
2212-
2213-
const updatedData = results.reduce((acc, result) => {
2216+
results.forEach((result, index) => {
2217+
const traceSlug = batch[index]?.traceSlug;
22142218
// Ignoring the error case for now
22152219
if (result.status === 'fulfilled') {
2216-
if (
2217-
isTraceSplitResult<
2218-
TraceSplitResults<TraceTree.Transaction>,
2219-
TraceTree.EAPTrace
2220-
>(result.value) &&
2221-
isTraceSplitResult<
2222-
TraceSplitResults<TraceTree.Transaction>,
2223-
TraceTree.EAPTrace
2224-
>(acc)
2225-
) {
2226-
const {transactions, orphan_errors} = result.value;
2227-
acc.transactions.push(...transactions);
2228-
acc.orphan_errors.push(...orphan_errors);
2229-
} else if (Array.isArray(acc) && Array.isArray(result.value)) {
2230-
// accumulate eap trace
2231-
acc.push(...result.value);
2232-
}
2220+
this.appendTree(
2221+
TraceTree.FromTrace(result.value, {
2222+
meta: options.meta?.data,
2223+
replay: null,
2224+
preferences: options.preferences,
2225+
replayTraceSlug: traceSlug,
2226+
})
2227+
);
2228+
rerender();
22332229
}
2234-
2235-
return acc;
2236-
}, accumulator);
2237-
2238-
this.appendTree(
2239-
TraceTree.FromTrace(updatedData, {
2240-
meta: options.meta?.data,
2241-
replay: null,
2242-
preferences: options.preferences,
2243-
})
2244-
);
2245-
rerender();
2230+
});
22462231
}
22472232

22482233
root.fetchStatus = 'idle';

static/app/views/replays/detail/trace/useReplayTraces.tsx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export function useReplayTraces({
5353
return EventView.fromSavedQuery({
5454
id: undefined,
5555
name: `Traces in replay ${replayId}`,
56-
fields: ['trace', 'count(trace)', 'min(timestamp)'],
56+
fields: ['trace', 'min(timestamp)', 'max(transaction.duration)'],
5757
orderby: 'min_timestamp',
5858
query: `replayId:${replayId}`,
5959
projects: [Number(projectId)],
@@ -89,7 +89,7 @@ export function useReplayTraces({
8989
end,
9090
limit: 10,
9191
} as unknown as Location),
92-
sort: ['min_timestamp', 'trace'],
92+
sort: ['min_timestamp'],
9393
cursor: cursor.cursor,
9494
};
9595

@@ -102,6 +102,28 @@ export function useReplayTraces({
102102

103103
const parsedData = data
104104
.filter(row => row.trace) // Filter out items where trace is not truthy
105+
.sort((a, b) => {
106+
const aDuration = a['max(transaction.duration)'];
107+
const bDuration = b['max(transaction.duration)'];
108+
const aMinTimestamp = getTimeStampFromTableDateField(a['min(timestamp)']);
109+
const bMinTimestamp = getTimeStampFromTableDateField(b['min(timestamp)']);
110+
111+
if (
112+
!aMinTimestamp ||
113+
!bMinTimestamp ||
114+
typeof aDuration !== 'number' ||
115+
typeof bDuration !== 'number'
116+
) {
117+
return 0;
118+
}
119+
120+
// We don't have a way to get the min start time of a trace, so we'll use the min timestamp and subtract the max duration
121+
// of a transaction in that trace, to make the best guess.
122+
const aMinStart = aMinTimestamp - aDuration / 1000;
123+
const bMinStart = bMinTimestamp - bDuration / 1000;
124+
125+
return aMinStart - bMinStart;
126+
})
105127
.map(row => ({
106128
traceSlug: row.trace!.toString(),
107129
timestamp: getTimeStampFromTableDateField(row['min(timestamp)']),

0 commit comments

Comments
 (0)