-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
feat(trace-tree-node): BaseNode implementation changes to achieve feature parity #101873
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: abdk/trace-tree-basenode-usage
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,10 +5,16 @@ import {pickBarColor} from 'sentry/components/performance/waterfall/utils'; | |
| import type {Organization} from 'sentry/types/organization'; | ||
| import type {TraceMetaQueryResults} from 'sentry/views/performance/newTraceDetails/traceApi/useTraceMeta'; | ||
| import type {TraceTreeNodeDetailsProps} from 'sentry/views/performance/newTraceDetails/traceDrawer/tabs/traceTreeNodeDetails'; | ||
| import { | ||
| isEAPTransactionNode, | ||
| isTransactionNode, | ||
| } from 'sentry/views/performance/newTraceDetails/traceGuards'; | ||
| import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; | ||
| import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; | ||
| import type {TraceRowProps} from 'sentry/views/performance/newTraceDetails/traceRow/traceRow'; | ||
|
|
||
| import type {EapSpanNode} from './eapSpanNode'; | ||
| import type {TransactionNode} from './transactionNode'; | ||
|
|
||
| export interface TraceTreeNodeExtra { | ||
| organization: Organization; | ||
| meta?: TraceMetaQueryResults['data'] | null; | ||
|
|
@@ -254,8 +260,16 @@ export abstract class BaseNode<T extends TraceTree.NodeValue = TraceTree.NodeVal | |
| return [...this.uniqueErrorIssues, ...this.uniqueOccurrenceIssues]; | ||
| } | ||
|
|
||
| get hasErrors(): boolean { | ||
| return this.errors.size > 0; | ||
| } | ||
|
|
||
| get hasOccurrences(): boolean { | ||
| return this.occurrences.size > 0; | ||
| } | ||
|
|
||
| get hasIssues(): boolean { | ||
| return this.errors.size > 0 || this.occurrences.size > 0; | ||
| return this.hasErrors || this.hasOccurrences; | ||
| } | ||
|
|
||
| get visibleChildren(): BaseNode[] { | ||
|
|
@@ -310,6 +324,22 @@ export abstract class BaseNode<T extends TraceTree.NodeValue = TraceTree.NodeVal | |
| return `${this.type}-${this.id}`; | ||
| } | ||
|
|
||
| get transactionId(): string | null { | ||
| const parentTransaction = this.findParentTransaction(); | ||
|
|
||
| if (parentTransaction) { | ||
| return parentTransaction.transactionId; | ||
| } | ||
|
|
||
| const parentEapTransaction = this.findParentEapTransaction(); | ||
|
|
||
| if (parentEapTransaction) { | ||
| return parentEapTransaction.transactionId; | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| isRootNodeChild(): boolean { | ||
| return this.parent?.value === null; | ||
| } | ||
|
|
@@ -319,7 +349,7 @@ export abstract class BaseNode<T extends TraceTree.NodeValue = TraceTree.NodeVal | |
| return false; | ||
| } | ||
|
|
||
| const visibleChildren = this.parent.visibleChildren; | ||
| const visibleChildren = this.parent.directVisibleChildren; | ||
| return visibleChildren[visibleChildren.length - 1] === this; | ||
| } | ||
|
|
||
|
|
@@ -334,7 +364,13 @@ export abstract class BaseNode<T extends TraceTree.NodeValue = TraceTree.NodeVal | |
| const hasMatchingOccurrences = Array.from(this.occurrences).some( | ||
| occurrence => occurrence.event_id === id | ||
| ); | ||
| return this.id === id || hasMatchingErrors || hasMatchingOccurrences; | ||
|
|
||
| return ( | ||
| this.id === id || | ||
| this.transactionId === id || | ||
| hasMatchingErrors || | ||
| hasMatchingOccurrences | ||
| ); | ||
| } | ||
|
|
||
| invalidate() { | ||
|
|
@@ -346,14 +382,16 @@ export abstract class BaseNode<T extends TraceTree.NodeValue = TraceTree.NodeVal | |
| return this.children; | ||
| } | ||
|
|
||
| findChild(predicate: (child: BaseNode) => boolean): BaseNode | null { | ||
| findChild<ChildType extends BaseNode = BaseNode>( | ||
| predicate: (child: BaseNode) => boolean | ||
| ): ChildType | null { | ||
| const queue: BaseNode[] = [...this.getNextTraversalNodes()]; | ||
|
|
||
| while (queue.length > 0) { | ||
| const next = queue.pop()!; | ||
|
|
||
| if (predicate(next)) { | ||
| return next; | ||
| return next as ChildType; | ||
| } | ||
|
|
||
| const children = next.getNextTraversalNodes(); | ||
|
|
@@ -365,15 +403,17 @@ export abstract class BaseNode<T extends TraceTree.NodeValue = TraceTree.NodeVal | |
| return null; | ||
| } | ||
|
|
||
| findAllChildren(predicate: (child: BaseNode) => boolean): BaseNode[] { | ||
| findAllChildren<ChildType extends BaseNode = BaseNode>( | ||
| predicate: (child: BaseNode) => boolean | ||
| ): ChildType[] { | ||
| const queue: BaseNode[] = [...this.getNextTraversalNodes()]; | ||
| const results: BaseNode[] = []; | ||
| const results: ChildType[] = []; | ||
|
|
||
| while (queue.length > 0) { | ||
| const next = queue.pop()!; | ||
|
|
||
| if (predicate(next)) { | ||
| results.push(next); | ||
| results.push(next as ChildType); | ||
| } | ||
|
|
||
| const children = next.getNextTraversalNodes(); | ||
|
|
@@ -400,17 +440,27 @@ export abstract class BaseNode<T extends TraceTree.NodeValue = TraceTree.NodeVal | |
| } | ||
| } | ||
|
|
||
| findParent(predicate: (parent: BaseNode) => boolean): BaseNode | null { | ||
| findParent<ChildType extends BaseNode = BaseNode>( | ||
| predicate: (parent: BaseNode) => boolean | ||
| ): ChildType | null { | ||
| let current = this.parent; | ||
| while (current) { | ||
| if (predicate(current)) { | ||
| return current; | ||
| return current as ChildType; | ||
| } | ||
| current = current.parent; | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| findParentTransaction(): TransactionNode | null { | ||
| return this.findParent<TransactionNode>(p => isTransactionNode(p)); | ||
| } | ||
|
|
||
| findParentEapTransaction(): EapSpanNode | null { | ||
| return this.findParent<EapSpanNode>(p => isEAPTransactionNode(p)); | ||
| } | ||
|
|
||
|
Comment on lines
+456
to
+463
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this possible to do without type guards?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gggritso, I think that this is a good use case for a type guard. We are looking for a specific node in the base node class. The problem initially was that such type guards were scattered all over the place, we mitigated that quite a lot. We can add is_transaction, is_eap_transaction, is_span... attrs to the baseNode classe but I don't really think that is any cleaner 🤔 We need methods like Open to suggestions, lmk! |
||
| expand(expanding: boolean, tree: TraceTree): boolean { | ||
| const index = tree.list.indexOf(this); | ||
|
|
||
|
|
@@ -506,7 +556,7 @@ export abstract class BaseNode<T extends TraceTree.NodeValue = TraceTree.NodeVal | |
| props: TraceRowProps<NodeType> | ||
| ): React.ReactNode; | ||
|
|
||
| abstract renderDetails<NodeType extends TraceTreeNode<TraceTree.NodeValue>>( | ||
| abstract renderDetails<NodeType extends BaseNode>( | ||
| props: TraceTreeNodeDetailsProps<NodeType> | ||
| ): React.ReactNode; | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Tree Traversal: A Performance Bottleneck
The
matchByIdmethod callsthis.transactionId, which triggers parent tree traversal viafindParentTransaction()andfindParentEapTransaction()on every invocation. WhenmatchByIdis called repeatedly during tree searches, this creates expensive redundant parent traversals for nodes that don't overridematchById, significantly impacting performance on large trace trees.