From fb3ef00def3bcc08e328f02d5a5b6400cb9a0888 Mon Sep 17 00:00:00 2001 From: nsdeschenes Date: Tue, 14 Apr 2026 08:58:04 -0300 Subject: [PATCH 1/3] fix(trace): Use visible parents for collapsed rows Compute row depth and connectors from the visible tree when collapsed EAP transactions surface nested descendants. This keeps summarized child transactions aligned under the collapsed transaction instead of inheriting indentation from hidden span parents. Co-Authored-By: Codex --- .../newTraceDetails/traceModels/traceTree.tsx | 58 ++++++++++++++----- .../traceModels/traceTreeNode/baseNode.tsx | 6 ++ .../newTraceDetails/traceRow/traceRow.tsx | 2 +- 3 files changed, 52 insertions(+), 14 deletions(-) diff --git a/static/app/views/performance/newTraceDetails/traceModels/traceTree.tsx b/static/app/views/performance/newTraceDetails/traceModels/traceTree.tsx index 3e9bbc9c364b99..bdbcb8326ef994 100644 --- a/static/app/views/performance/newTraceDetails/traceModels/traceTree.tsx +++ b/static/app/views/performance/newTraceDetails/traceModels/traceTree.tsx @@ -1212,16 +1212,43 @@ export class TraceTree extends TraceTreeEventDispatcher { return node.depth; } - let depth = -2; - let start: BaseNode | null = node; + const visibleParent = TraceTree.VisibleParent(node); + node.depth = visibleParent ? TraceTree.Depth(visibleParent) + 1 : 0; + return node.depth; + } + + static VisibleParent(node: BaseNode): BaseNode | null { + if (node.visibleParent !== undefined) { + return node.visibleParent; + } + + let start = node.parent; while (start) { - depth++; + if ( + start.directVisibleChildren.includes(node) && + (start.isRootNodeChild() || TraceTree.VisibleParent(start) !== null) + ) { + node.visibleParent = start; + return node.visibleParent; + } + start = start.parent; } - node.depth = depth; - return depth; + node.visibleParent = null; + return node.visibleParent; + } + + static IsLastVisibleChild(node: BaseNode): boolean { + const visibleParent = TraceTree.VisibleParent(node); + + if (!visibleParent) { + return false; + } + + const visibleChildren = visibleParent.directVisibleChildren; + return visibleChildren[visibleChildren.length - 1] === node; } static ConnectorsTo(node: BaseNode): number[] { @@ -1230,31 +1257,36 @@ export class TraceTree extends TraceTreeEventDispatcher { } const connectors: number[] = []; - let start = node.parent; + let start = TraceTree.VisibleParent(node); - if (start?.isRootNodeChild() && !node.isLastChild()) { + if (start?.isRootNodeChild() && !TraceTree.IsLastVisibleChild(node)) { node.connectors = [-TraceTree.Depth(node)]; return node.connectors; } - if (!node.isLastChild()) { + if (!TraceTree.IsLastVisibleChild(node)) { connectors.push(TraceTree.Depth(node)); } while (start) { - if (!start.value || !start.parent) { + if (!start.value) { break; } - if (start.isLastChild()) { - start = start.parent; + const visibleParent = TraceTree.VisibleParent(start); + if (!visibleParent) { + break; + } + + if (TraceTree.IsLastVisibleChild(start)) { + start = visibleParent; continue; } connectors.push( - start.parent.isRootNodeChild() ? -TraceTree.Depth(start) : TraceTree.Depth(start) + visibleParent.isRootNodeChild() ? -TraceTree.Depth(start) : TraceTree.Depth(start) ); - start = start.parent; + start = visibleParent; } node.connectors = connectors; diff --git a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode.tsx b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode.tsx index f127b561fa2fb1..ee0a29324303c9 100644 --- a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode.tsx +++ b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode.tsx @@ -114,6 +114,11 @@ export abstract class BaseNode : null} - {props.node.isLastChild() ? ( + {TraceTree.IsLastVisibleChild(props.node) ? ( ) : null} From 4a60581b14ee2a5d3c2297b486e030127fdc4d85 Mon Sep 17 00:00:00 2001 From: nsdeschenes Date: Tue, 14 Apr 2026 08:58:24 -0300 Subject: [PATCH 2/3] test(trace): Cover collapsed EAP summary indentation Add assertions for the visible parent and depth of nested EAP transactions when their ancestor transaction is collapsed, and update the rendered tree snapshots to match the corrected indentation. Co-Authored-By: Codex --- .../__snapshots__/traceTree.spec.tsx.snap | 6 +++--- .../newTraceDetails/traceModels/traceTree.spec.tsx | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/static/app/views/performance/newTraceDetails/traceModels/__snapshots__/traceTree.spec.tsx.snap b/static/app/views/performance/newTraceDetails/traceModels/__snapshots__/traceTree.spec.tsx.snap index f068d5d11a6ec1..29de4bdcb242f5 100644 --- a/static/app/views/performance/newTraceDetails/traceModels/__snapshots__/traceTree.spec.tsx.snap +++ b/static/app/views/performance/newTraceDetails/traceModels/__snapshots__/traceTree.spec.tsx.snap @@ -98,8 +98,8 @@ exports[`TraceTree eap trace correctly renders eap-transactions toggle state 1`] " eap trace root span.op - span.description (eap-transaction) + span.op - span.description (eap-transaction) span.op - span.description (eap-transaction) - span.op - span.description (eap-transaction) " `; @@ -109,7 +109,7 @@ eap trace root span.op - span.description (eap-transaction) span.op - span.description span.op - span.description (eap-transaction) - span.op - span.description (eap-transaction) + span.op - span.description (eap-transaction) " `; @@ -117,8 +117,8 @@ exports[`TraceTree eap trace correctly renders eap-transactions toggle state 3`] " eap trace root span.op - span.description (eap-transaction) + span.op - span.description (eap-transaction) span.op - span.description (eap-transaction) - span.op - span.description (eap-transaction) " `; diff --git a/static/app/views/performance/newTraceDetails/traceModels/traceTree.spec.tsx b/static/app/views/performance/newTraceDetails/traceModels/traceTree.spec.tsx index 73d488bb6b9568..8746989c56ab7b 100644 --- a/static/app/views/performance/newTraceDetails/traceModels/traceTree.spec.tsx +++ b/static/app/views/performance/newTraceDetails/traceModels/traceTree.spec.tsx @@ -844,6 +844,16 @@ describe('TraceTree', () => { childTransactionA, childTransactionB, ]); + expect(TraceTree.VisibleParent(childTransactionA)).toBe(rootTransaction); + expect(TraceTree.VisibleParent(childTransactionB)).toBe(rootTransaction); + expect(TraceTree.Depth(childTransactionA)).toBe( + TraceTree.Depth(rootTransaction) + 1 + ); + expect(TraceTree.Depth(childTransactionB)).toBe( + TraceTree.Depth(rootTransaction) + 1 + ); + expect(TraceTree.IsLastVisibleChild(childTransactionA)).toBe(false); + expect(TraceTree.IsLastVisibleChild(childTransactionB)).toBe(true); const rootTransactionIndex = tree.list.indexOf(rootTransaction); expect(tree.list.slice(rootTransactionIndex, rootTransactionIndex + 3)).toEqual([ rootTransaction, @@ -855,6 +865,10 @@ describe('TraceTree', () => { expect(childTransactionA.parent).toBe(spanA); expect(childTransactionB.parent).toBe(spanB); + expect(TraceTree.VisibleParent(childTransactionA)).toBe(spanA); + expect(TraceTree.VisibleParent(childTransactionB)).toBe(spanB); + expect(TraceTree.Depth(childTransactionA)).toBe(TraceTree.Depth(spanA) + 1); + expect(TraceTree.Depth(childTransactionB)).toBe(TraceTree.Depth(spanB) + 1); expect(tree.list.slice(rootTransactionIndex, rootTransactionIndex + 5)).toEqual([ rootTransaction, spanA, From b6f8cc86535be28e3877bfc5525cd6bf4806e3cb Mon Sep 17 00:00:00 2001 From: nsdeschenes Date: Tue, 14 Apr 2026 09:35:45 -0300 Subject: [PATCH 3/3] fix(trace): Keep root row connector-free Treat the synthetic trace root row as the last visible child when visible-parent lookup falls back to null. This preserves the collapsed-row indentation fix without adding an extra connector to the trace header. Co-Authored-By: Codex --- .../newTraceDetails/traceModels/traceTree.spec.tsx | 11 +++++++++++ .../newTraceDetails/traceModels/traceTree.tsx | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/static/app/views/performance/newTraceDetails/traceModels/traceTree.spec.tsx b/static/app/views/performance/newTraceDetails/traceModels/traceTree.spec.tsx index 8746989c56ab7b..e9bdb3b2f8c05d 100644 --- a/static/app/views/performance/newTraceDetails/traceModels/traceTree.spec.tsx +++ b/static/app/views/performance/newTraceDetails/traceModels/traceTree.spec.tsx @@ -2078,6 +2078,17 @@ describe('TraceTree', () => { }); }); + describe('IsLastVisibleChild', () => { + it('treats the trace root row as the last visible child', () => { + const tree = TraceTree.FromTrace(trace, traceOptions); + const traceRoot = tree.root.children[0]!; + + expect(TraceTree.VisibleParent(traceRoot)).toBeNull(); + expect(TraceTree.IsLastVisibleChild(traceRoot)).toBe(true); + expect(TraceTree.ConnectorsTo(traceRoot)).toEqual([]); + }); + }); + describe('Invalidate', () => { it('invalidates node', () => { const tree = TraceTree.FromTrace(trace, traceOptions); diff --git a/static/app/views/performance/newTraceDetails/traceModels/traceTree.tsx b/static/app/views/performance/newTraceDetails/traceModels/traceTree.tsx index bdbcb8326ef994..7f090cb077d57c 100644 --- a/static/app/views/performance/newTraceDetails/traceModels/traceTree.tsx +++ b/static/app/views/performance/newTraceDetails/traceModels/traceTree.tsx @@ -1244,7 +1244,7 @@ export class TraceTree extends TraceTreeEventDispatcher { const visibleParent = TraceTree.VisibleParent(node); if (!visibleParent) { - return false; + return node.isLastChild(); } const visibleChildren = visibleParent.directVisibleChildren;