diff --git a/static/app/views/insights/agents/components/aiSpanList.tsx b/static/app/views/insights/agents/components/aiSpanList.tsx index 4e8b3bbabf8f58..b5253e0061401c 100644 --- a/static/app/views/insights/agents/components/aiSpanList.tsx +++ b/static/app/views/insights/agents/components/aiSpanList.tsx @@ -12,8 +12,6 @@ import {IconTool} from 'sentry/icons/iconTool'; import {space} from 'sentry/styles/space'; import getDuration from 'sentry/utils/duration/getDuration'; import {LLMCosts} from 'sentry/views/insights/agents/components/llmCosts'; -import {getIsAiRunNode} from 'sentry/views/insights/agents/utils/aiTraceNodes'; -import {getNodeId} from 'sentry/views/insights/agents/utils/getNodeId'; import { getIsAiCreateAgentSpan, getIsAiGenerationSpan, @@ -29,8 +27,9 @@ import { isTransactionNode, isTransactionNodeEquivalent, } from 'sentry/views/performance/newTraceDetails/traceGuards'; -import {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {BaseNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode'; +import type {EapSpanNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/eapSpanNode'; +import type {TransactionNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/transactionNode'; function getNodeTimeBounds(node: AITraceSpanNode | AITraceSpanNode[]) { let startTime = 0; @@ -52,11 +51,9 @@ function getNodeTimeBounds(node: AITraceSpanNode | AITraceSpanNode[]) { } else { if (!node.value) return {startTime: 0, endTime: 0, duration: 0}; - startTime = node.value.start_timestamp; - if (isTransactionNode(node) || isSpanNode(node)) { - endTime = node.value.timestamp; - } else if (isEAPSpanNode(node)) { - endTime = node.value.end_timestamp; + if (node.startTimestamp && node.endTimestamp) { + startTime = node.startTimestamp; + endTime = node.endTimestamp; } } @@ -69,16 +66,6 @@ function getNodeTimeBounds(node: AITraceSpanNode | AITraceSpanNode[]) { }; } -function getClosestNode( - node: AITraceSpanNode, - predicate: (node: TraceTreeNode) => node is T -): T | null { - if (predicate(node)) { - return node; - } - return TraceTree.ParentNode(node, predicate) as T | null; -} - export function AISpanList({ nodes, selectedNodeKey, @@ -89,12 +76,11 @@ export function AISpanList({ selectedNodeKey: string | null; }) { const nodesByTransaction = useMemo(() => { - const result: Map< - TraceTreeNode, - AITraceSpanNode[] - > = new Map(); + const result: Map = new Map(); for (const node of nodes) { - const transaction = getClosestNode(node, isTransactionNodeEquivalent); + const transaction = node.findParent(isTransactionNodeEquivalent) as + | TransactionNode + | EapSpanNode; if (!transaction) { continue; } @@ -107,7 +93,7 @@ export function AISpanList({ return ( {nodesByTransaction.entries().map(([transaction, transactionNodes]) => ( - + 1} transaction={transaction} @@ -132,19 +118,19 @@ function TransactionWrapper({ nodes: AITraceSpanNode[]; onSelectNode: (node: AITraceSpanNode) => void; selectedNodeKey: string | null; - transaction: TraceTreeNode; + transaction: TransactionNode | EapSpanNode; }) { const [isExpanded, setIsExpanded] = useState(true); const theme = useTheme(); const colors = [...theme.chart.getColorPalette(5), theme.red300]; const timeBounds = getNodeTimeBounds(nodes); - const nodeAiRunParentsMap = useMemo>(() => { - const parents: Record = {}; + const nodeAiRunParentsMap = useMemo>(() => { + const parents: Record = {}; for (const node of nodes) { - const parent = getClosestNode(node, getIsAiRunNode); + const parent = node.findParent(p => getIsAiRunSpan({op: p.op})); if (parent) { - parents[getNodeId(node)] = parent; + parents[node.id] = parent; } } return parents; @@ -171,12 +157,12 @@ function TransactionWrapper({ {isExpanded && nodes.map(node => { - const aiRunNode = nodeAiRunParentsMap[getNodeId(node)]; + const aiRunNode = nodeAiRunParentsMap[node.id]; // Only indent if the node is not the ai run node const shouldIndent = aiRunNode && aiRunNode !== node; - const uniqueKey = getNodeId(node); + const uniqueKey = node.id; return ( , + node: BaseNode, traceBounds: TraceBounds ): {leftPercent: number; widthPercent: number} { if (!node.value) return {leftPercent: 0, widthPercent: 0}; let startTime: number, endTime: number; - if (isTransactionNode(node)) { - startTime = node.value.start_timestamp; - endTime = node.value.timestamp; - } else if (isSpanNode(node)) { - startTime = node.value.start_timestamp; - endTime = node.value.timestamp; - } else if (isEAPSpanNode(node)) { - startTime = node.value.start_timestamp; - endTime = node.value.end_timestamp; + if (node.startTimestamp && node.endTimestamp) { + startTime = node.startTimestamp; + endTime = node.endTimestamp; } else { return {leftPercent: 0, widthPercent: 0}; } diff --git a/static/app/views/insights/agents/components/drawer.tsx b/static/app/views/insights/agents/components/drawer.tsx index 0de19a4dff5fbc..6270c15f554088 100644 --- a/static/app/views/insights/agents/components/drawer.tsx +++ b/static/app/views/insights/agents/components/drawer.tsx @@ -16,7 +16,6 @@ import {useLocationSyncedState} from 'sentry/views/insights/agents/hooks/useLoca import {useNodeDetailsLink} from 'sentry/views/insights/agents/hooks/useNodeDetailsLink'; import {useUrlTraceDrawer} from 'sentry/views/insights/agents/hooks/useUrlTraceDrawer'; import {getDefaultSelectedNode} from 'sentry/views/insights/agents/utils/getDefaultSelectedNode'; -import {getNodeId} from 'sentry/views/insights/agents/utils/getNodeId'; import type {AITraceSpanNode} from 'sentry/views/insights/agents/utils/types'; import {DrawerUrlParams} from 'sentry/views/insights/agents/utils/urlParams'; import {TraceTreeNodeDetails} from 'sentry/views/performance/newTraceDetails/traceDrawer/tabs/traceTreeNodeDetails'; @@ -53,7 +52,7 @@ const TraceViewDrawer = memo(function TraceViewDrawer({ const handleSelectNode = useCallback( (node: AITraceSpanNode) => { - const uniqueKey = getNodeId(node); + const uniqueKey = node.id; setSelectedNodeKey(uniqueKey); trackAnalytics('agent-monitoring.drawer.span-select', { @@ -64,7 +63,7 @@ const TraceViewDrawer = memo(function TraceViewDrawer({ ); const selectedNode = - (selectedNodeKey && nodes.find(node => getNodeId(node) === selectedNodeKey)) || + (selectedNodeKey && nodes.find(node => node.id === selectedNodeKey)) || getDefaultSelectedNode(nodes); const nodeDetailsLink = useNodeDetailsLink({ @@ -183,7 +182,7 @@ function AITraceView({ {t('AI Spans')} diff --git a/static/app/views/insights/agents/hooks/useAITrace.tsx b/static/app/views/insights/agents/hooks/useAITrace.tsx index 79b6ee8bb131cf..00be568e9372fe 100644 --- a/static/app/views/insights/agents/hooks/useAITrace.tsx +++ b/static/app/views/insights/agents/hooks/useAITrace.tsx @@ -2,18 +2,16 @@ import {useEffect, useState} from 'react'; import useApi from 'sentry/utils/useApi'; import useOrganization from 'sentry/utils/useOrganization'; -import {getIsAiNode} from 'sentry/views/insights/agents/utils/aiTraceNodes'; +import {getIsAiSpan} from 'sentry/views/insights/agents/utils/query'; import type {AITraceSpanNode} from 'sentry/views/insights/agents/utils/types'; import {SpanFields} from 'sentry/views/insights/types'; import {useTrace} from 'sentry/views/performance/newTraceDetails/traceApi/useTrace'; import { isEAPSpanNode, isSpanNode, - isTransactionNode, isTransactionNodeEquivalent, } from 'sentry/views/performance/newTraceDetails/traceGuards'; import {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; import {DEFAULT_TRACE_VIEW_PREFERENCES} from 'sentry/views/performance/newTraceDetails/traceState/tracePreferences'; import {useTraceQueryParams} from 'sentry/views/performance/newTraceDetails/useTraceQueryParams'; @@ -69,29 +67,19 @@ export function useAITrace(traceSlug: string): UseAITraceResult { tree.build(); - const fetchableTransactions = TraceTree.FindAll(tree.root, node => { - return isTransactionNode(node) && node.canFetch && node.value !== null; - }).filter((node): node is TraceTreeNode => - isTransactionNode(node) - ); - - const uniqueTransactions = fetchableTransactions.filter( - (node, index, array) => - index === array.findIndex(tx => tx.value.event_id === node.value.event_id) - ); - - const zoomPromises = uniqueTransactions.map(node => - tree.zoom(node, true, { + const fetchableNodes = tree.root.findAllChildren(node => node.canFetchChildren); + const fetchPromises = fetchableNodes.map(node => + tree.fetchNodeSubTree(true, node, { api, organization, preferences: DEFAULT_TRACE_VIEW_PREFERENCES, }) ); - await Promise.all(zoomPromises); + await Promise.all(fetchPromises); // Keep only transactions that include AI spans and the AI spans themselves - const flattenedNodes = TraceTree.FindAll(tree.root, node => { + const flattenedNodes = tree.root.findAllChildren(node => { if ( !isTransactionNodeEquivalent(node) && !isSpanNode(node) && @@ -100,7 +88,7 @@ export function useAITrace(traceSlug: string): UseAITraceResult { return false; } - return getIsAiNode(node); + return getIsAiSpan({op: node.op}); }) as AITraceSpanNode[]; setNodes(flattenedNodes); diff --git a/static/app/views/insights/agents/utils/aiTraceNodes.tsx b/static/app/views/insights/agents/utils/aiTraceNodes.tsx index 8eeca45b145540..7dd6163978a08f 100644 --- a/static/app/views/insights/agents/utils/aiTraceNodes.tsx +++ b/static/app/views/insights/agents/utils/aiTraceNodes.tsx @@ -1,20 +1,12 @@ import type {EventTransaction} from 'sentry/types/event'; import {prettifyAttributeName} from 'sentry/views/explore/components/traceItemAttributes/utils'; import type {TraceItemResponseAttribute} from 'sentry/views/explore/hooks/useTraceItemDetails'; -import { - getIsAiGenerationSpan, - getIsAiRunSpan, - getIsAiSpan, - getIsExecuteToolSpan, -} from 'sentry/views/insights/agents/utils/query'; -import type {AITraceSpanNode} from 'sentry/views/insights/agents/utils/types'; import { isEAPSpanNode, isSpanNode, 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 {BaseNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode'; // TODO(aknaus): Remove the special handling for tags once the endpoint returns the correct type function getAttributeValue( @@ -37,7 +29,7 @@ function getAttributeValue( } export function ensureAttributeObject( - node: TraceTreeNode, + node: BaseNode, event?: EventTransaction, attributes?: TraceItemResponseAttribute[] ) { @@ -66,26 +58,10 @@ export function ensureAttributeObject( export function getTraceNodeAttribute( name: string, - node: TraceTreeNode, + node: BaseNode, event?: EventTransaction, attributes?: TraceItemResponseAttribute[] ): string | number | boolean | undefined { const attributeObject = ensureAttributeObject(node, event, attributes); return attributeObject?.[name]; } - -function createGetIsAiNode(predicate: ({op}: {op?: string}) => boolean) { - return (node: TraceTreeNode): node is AITraceSpanNode => { - if (!isTransactionNode(node) && !isSpanNode(node) && !isEAPSpanNode(node)) { - return false; - } - - const op = isTransactionNode(node) ? node.value?.['transaction.op'] : node.value?.op; - return predicate({op}); - }; -} - -export const getIsAiNode = createGetIsAiNode(getIsAiSpan); -export const getIsAiRunNode = createGetIsAiNode(getIsAiRunSpan); -export const getIsAiGenerationNode = createGetIsAiNode(getIsAiGenerationSpan); -export const getIsExecuteToolNode = createGetIsAiNode(getIsExecuteToolSpan); diff --git a/static/app/views/insights/agents/utils/getDefaultSelectedNode.tsx b/static/app/views/insights/agents/utils/getDefaultSelectedNode.tsx index c9c88831e1cc94..53c69b01f50d48 100644 --- a/static/app/views/insights/agents/utils/getDefaultSelectedNode.tsx +++ b/static/app/views/insights/agents/utils/getDefaultSelectedNode.tsx @@ -1,7 +1,8 @@ -import {getIsAiNode} from 'sentry/views/insights/agents/utils/aiTraceNodes'; import type {AITraceSpanNode} from 'sentry/views/insights/agents/utils/types'; +import {getIsAiSpan} from './query'; + export function getDefaultSelectedNode(nodes: AITraceSpanNode[]) { - const firstAiSpan = nodes.find(getIsAiNode); + const firstAiSpan = nodes.find(node => getIsAiSpan({op: node.op})); return firstAiSpan ?? nodes[0]; } diff --git a/static/app/views/insights/agents/utils/getNodeId.tsx b/static/app/views/insights/agents/utils/getNodeId.tsx deleted file mode 100644 index 23ca2125e9e79c..00000000000000 --- a/static/app/views/insights/agents/utils/getNodeId.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type {AITraceSpanNode} from 'sentry/views/insights/agents/utils/types'; -import { - isEAPSpanNode, - isSpanNode, - isTransactionNode, -} from 'sentry/views/performance/newTraceDetails/traceGuards'; - -export function getNodeId(node: AITraceSpanNode): string { - if (isEAPSpanNode(node)) { - return node.metadata.event_id as string; - } - if (isTransactionNode(node)) { - return node.value.event_id; - } - - if (isSpanNode(node)) { - return node.value.span_id; - } - - // We should never reach this point as AITraceSpanNode only contains the above node types - throw new Error('Invalid node type'); -} diff --git a/static/app/views/insights/agents/utils/types.tsx b/static/app/views/insights/agents/utils/types.tsx index 8f844dbfcd15e3..2065e864c6a71a 100644 --- a/static/app/views/insights/agents/utils/types.tsx +++ b/static/app/views/insights/agents/utils/types.tsx @@ -1,6 +1,5 @@ -import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {EapSpanNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/eapSpanNode'; +import type {SpanNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/spanNode'; +import type {TransactionNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/transactionNode'; -export type AITraceSpanNode = TraceTreeNode< - TraceTree.Transaction | TraceTree.EAPSpan | TraceTree.Span ->; +export type AITraceSpanNode = SpanNode | EapSpanNode | TransactionNode; diff --git a/static/app/views/insights/mcp/utils/mcpTraceNodes.tsx b/static/app/views/insights/mcp/utils/mcpTraceNodes.tsx index ef4fcf439fbb45..53b4515ad3a3ce 100644 --- a/static/app/views/insights/mcp/utils/mcpTraceNodes.tsx +++ b/static/app/views/insights/mcp/utils/mcpTraceNodes.tsx @@ -3,14 +3,12 @@ import { isSpanNode, 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 {BaseNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode'; -export function getIsMCPNode(node: TraceTreeNode) { +export function getIsMCPNode(node: BaseNode) { if (!isTransactionNode(node) && !isSpanNode(node) && !isEAPSpanNode(node)) { return false; } - const op = isTransactionNode(node) ? node.value['transaction.op'] : node.value.op; - return op?.startsWith('mcp.'); + return node.op?.startsWith('mcp.'); } diff --git a/static/app/views/performance/newTraceDetails/issuesTraceWaterfall.tsx b/static/app/views/performance/newTraceDetails/issuesTraceWaterfall.tsx index e1c9f2beda5391..8953d68098600f 100644 --- a/static/app/views/performance/newTraceDetails/issuesTraceWaterfall.tsx +++ b/static/app/views/performance/newTraceDetails/issuesTraceWaterfall.tsx @@ -17,27 +17,15 @@ import {trackAnalytics} from 'sentry/utils/analytics'; import useOrganization from 'sentry/utils/useOrganization'; import useProjects from 'sentry/utils/useProjects'; import {IssueTraceWaterfallOverlay} from 'sentry/views/performance/newTraceDetails/issuesTraceWaterfallOverlay'; -import { - isEAPErrorNode, - isEAPSpanNode, - isEAPTransactionNode, - isNonTransactionEAPSpanNode, - isSpanNode, - isTraceErrorNode, - isTransactionNode, -} from 'sentry/views/performance/newTraceDetails/traceGuards'; +import {isTraceErrorNode} from 'sentry/views/performance/newTraceDetails/traceGuards'; import {IssuesTraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/issuesTraceTree'; -import {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; import {useDividerResizeSync} from 'sentry/views/performance/newTraceDetails/useDividerResizeSync'; import {useTraceSpaceListeners} from 'sentry/views/performance/newTraceDetails/useTraceSpaceListeners'; -import type {TraceTreeNode} from './traceModels/traceTreeNode'; +import type {BaseNode} from './traceModels/traceTreeNode/baseNode'; import {useTraceState, useTraceStateDispatch} from './traceState/traceStateProvider'; import {Trace} from './trace'; -import { - traceNodeAdjacentAnalyticsProperties, - traceNodeAnalyticsName, -} from './traceTreeAnalytics'; +import {traceNodeAdjacentAnalyticsProperties} from './traceTreeAnalytics'; import type {TraceWaterfallProps} from './traceWaterfall'; import {TraceGrid} from './traceWaterfall'; import {TraceWaterfallState} from './traceWaterfallState'; @@ -80,9 +68,7 @@ export function IssuesTraceWaterfall(props: IssuesTraceWaterfallProps) { }); }, [props.organization, props.source]); - const previouslyFocusedNodeRef = useRef | null>( - null - ); + const previouslyFocusedNodeRef = useRef(null); const {viewManager, traceScheduler, traceView} = useTraceWaterfallModels(); @@ -95,17 +81,13 @@ export function IssuesTraceWaterfall(props: IssuesTraceWaterfallProps) { }, [props.tree.list.length, traceDispatch]); const onRowClick = useCallback( - ( - node: TraceTreeNode, - _event: React.MouseEvent, - index: number - ) => { + (node: BaseNode, _event: React.MouseEvent, index: number) => { trackAnalytics('trace.trace_layout.span_row_click', { organization, num_children: node.children.length, - type: traceNodeAnalyticsName(node), + type: node.analyticsName(), project_platform: - projects.find(p => p.slug === node.metadata.project_slug)?.platform || 'other', + projects.find(p => p.slug === node.projectSlug)?.platform || 'other', ...traceNodeAdjacentAnalyticsProperties(node), }); @@ -134,73 +116,19 @@ export function IssuesTraceWaterfall(props: IssuesTraceWaterfallProps) { // Find all the nodes that match the event id from the error so that we can try and // link the user to the most specific one. - const nodes = IssuesTraceTree.FindAll(props.tree.root, n => { - if (isTraceErrorNode(n) || isEAPErrorNode(n)) { - return n.value.event_id === props.event.eventID; - } - if (isTransactionNode(n)) { - if (n.value.event_id === props.event.eventID) { - return true; - } - - for (const e of n.errors) { - if (e.event_id === props.event.eventID) { - return true; - } - } - - for (const o of n.occurrences) { - if (o.event_id === props.event.eventID) { - return true; - } - } - } - if (isSpanNode(n)) { - if (n.value.span_id === props.event.eventID) { - return true; - } - for (const e of n.errors) { - if (e.event_id === props.event.eventID) { - return true; - } - } - for (const o of n.occurrences) { - if (o.event_id === props.event.eventID) { - return true; - } - } - } - - if (isEAPSpanNode(n)) { - if (n.value.event_id === props.event.eventID) { - return true; - } - for (const e of n.errors) { - if (e.event_id === props.event.eventID) { - return true; - } - } - for (const o of n.occurrences) { - if (o.event_id === props.event.eventID) { - return true; - } - } - } - return false; - }); + const nodes = props.tree.root.findAllChildren(node => + node.matchById(props.event.eventID) + ); // By order of priority, we want to find the error node, then the span node, then the transaction node. // This is because the error node as standalone is the most specific one, otherwise we look for the span that // the error may have been attributed to, otherwise we look at the transaction. - const node = - nodes?.find(n => isTraceErrorNode(n) || isEAPErrorNode(n)) || - nodes?.find(n => isSpanNode(n) || isNonTransactionEAPSpanNode(n)) || - nodes?.find(n => isTransactionNode(n) || isEAPTransactionNode(n)); + const node = nodes.sort((a, b) => b.searchPriority - a.searchPriority)[0]; const index = node ? IssuesTraceTree.EnforceVisibility(props.tree, node) : -1; if (node) { - const preserveNodes: Array> = [node]; + const preserveNodes: BaseNode[] = [node]; let start = index; while (--start > 0) { diff --git a/static/app/views/performance/newTraceDetails/issuesTraceWaterfallOverlay.tsx b/static/app/views/performance/newTraceDetails/issuesTraceWaterfallOverlay.tsx index 51a53cde1fda4f..50c2892f333e62 100644 --- a/static/app/views/performance/newTraceDetails/issuesTraceWaterfallOverlay.tsx +++ b/static/app/views/performance/newTraceDetails/issuesTraceWaterfallOverlay.tsx @@ -21,7 +21,7 @@ import type {VirtualizedViewManager} from './traceRenderers/virtualizedViewManag interface RowPosition { height: number; left: number; - pathToNode: ReturnType; + pathToNode: TraceTree.NodePath[]; top: number; width: number; } @@ -90,7 +90,7 @@ export function IssueTraceWaterfallOverlay({ return; } - const pathToNode = TraceTree.PathToNode(node); + const pathToNode = node.pathToNode(); if (!pathToNode) { return; @@ -155,7 +155,7 @@ export function IssueTraceWaterfallOverlay({ export function getTraceLinkForIssue( traceTarget: LocationDescriptor, - pathToNode?: ReturnType + pathToNode?: TraceTree.NodePath[] ) { if (typeof traceTarget === 'string') { return traceTarget; diff --git a/static/app/views/performance/newTraceDetails/trace.tsx b/static/app/views/performance/newTraceDetails/trace.tsx index fb96b3751f5478..c3a3e0781835ab 100644 --- a/static/app/views/performance/newTraceDetails/trace.tsx +++ b/static/app/views/performance/newTraceDetails/trace.tsx @@ -35,19 +35,14 @@ import { } from 'sentry/views/insights/browser/webVitals/utils/scoreToStatus'; import {TraceTree} from './traceModels/traceTree'; -import type {TraceTreeNode} from './traceModels/traceTreeNode'; +import type {BaseNode} from './traceModels/traceTreeNode/baseNode'; import type {TraceEvents, TraceScheduler} from './traceRenderers/traceScheduler'; import { useVirtualizedList, type VirtualizedRow, } from './traceRenderers/traceVirtualizedList'; import type {VirtualizedViewManager} from './traceRenderers/virtualizedViewManager'; -import {TraceAutogroupedRow} from './traceRow/traceAutogroupedRow'; -import {TraceCollapsedRow} from './traceRow/traceCollapsedRow'; -import {TraceErrorRow} from './traceRow/traceErrorRow'; import {TraceLoadingRow} from './traceRow/traceLoadingRow'; -import {TraceMissingInstrumentationRow} from './traceRow/traceMissingInstrumentationRow'; -import {TraceRootRow} from './traceRow/traceRootNode'; import { TRACE_CHILDREN_COUNT_WRAPPER_CLASSNAME, TRACE_CHILDREN_COUNT_WRAPPER_ORPHANED_CLASSNAME, @@ -55,27 +50,11 @@ import { TRACE_RIGHT_COLUMN_ODD_CLASSNAME, type TraceRowProps, } from './traceRow/traceRow'; -import {TraceSpanRow} from './traceRow/traceSpanRow'; -import {TraceTransactionRow} from './traceRow/traceTransactionRow'; import { getRovingIndexActionFromDOMEvent, type RovingTabIndexUserActions, } from './traceState/traceRovingTabIndex'; import {useTraceState, useTraceStateDispatch} from './traceState/traceStateProvider'; -import { - isAutogroupedNode, - isCollapsedNode, - isEAPErrorNode, - isEAPSpanNode, - isEAPTraceNode, - isMissingInstrumentationNode, - isSpanNode, - isTraceErrorNode, - isTraceNode, - isTransactionNode, - isUptimeCheckNode, - isUptimeCheckTimingNode, -} from './traceGuards'; import type {TraceReducerState} from './traceState'; function computeNextIndexFromAction( @@ -108,16 +87,16 @@ interface TraceProps { isLoading: boolean; manager: VirtualizedViewManager; onRowClick: ( - node: TraceTreeNode, + node: BaseNode, event: React.MouseEvent, index: number ) => void; onTraceSearch: ( query: string, - node: TraceTreeNode, + node: BaseNode, behavior: 'track result' | 'persist' ) => void; - previouslyFocusedNodeRef: React.MutableRefObject | null>; + previouslyFocusedNodeRef: React.MutableRefObject; rerender: () => void; scheduler: TraceScheduler; trace: TraceTree; @@ -148,7 +127,7 @@ export function Trace({ rerenderRef.current = rerender; const treePromiseStatusRef = useRef, + BaseNode, 'loading' | 'error' | 'success' > | null>(null); @@ -208,10 +187,10 @@ export function Trace({ const onNodeZoomIn = useCallback( ( event: React.MouseEvent | React.KeyboardEvent, - node: TraceTreeNode, + node: BaseNode, value: boolean ) => { - if (!isTransactionNode(node) && !isSpanNode(node)) { + if (!node.canFetchChildren) { throw new TypeError('Node must be a transaction or span'); } @@ -219,7 +198,7 @@ export function Trace({ rerenderRef.current(); treeRef.current - .zoom(node, value, { + .fetchNodeSubTree(value, node, { api, organization, preferences: traceStatePreferencesRef.current, @@ -245,23 +224,19 @@ export function Trace({ const onNodeExpand = useCallback( ( event: React.MouseEvent | React.KeyboardEvent, - node: TraceTreeNode, + node: BaseNode, value: boolean ) => { event.stopPropagation(); - treeRef.current.expand(node, value); + node.expand(value, treeRef.current); rerenderRef.current(); }, [] ); const onRowKeyDown = useCallback( - ( - event: React.KeyboardEvent, - index: number, - node: TraceTreeNode - ) => { + (event: React.KeyboardEvent, index: number, node: BaseNode) => { if (!manager.list) { return; } @@ -283,13 +258,13 @@ export function Trace({ } if (event.key === 'ArrowLeft') { - if (node.zoomedIn) { + if (node.hasFetchedChildren) { onNodeZoomIn(event, node, false); } else if (node.expanded) { onNodeExpand(event, node, false); } } else if (event.key === 'ArrowRight') { - if (node.canFetch) { + if (node.canFetchChildren) { onNodeZoomIn(event, node, true); } else { onNodeExpand(event, node, true); @@ -508,29 +483,17 @@ function RenderTraceRow(props: { index: number; isSearchResult: boolean; manager: VirtualizedViewManager; - node: TraceTreeNode; - onExpand: ( - event: React.MouseEvent, - node: TraceTreeNode, - value: boolean - ) => void; + node: BaseNode; + onExpand: (event: React.MouseEvent, node: BaseNode, value: boolean) => void; onRowClick: ( - node: TraceTreeNode, + node: BaseNode, event: React.MouseEvent, index: number ) => void; - onRowKeyDown: ( - event: React.KeyboardEvent, - index: number, - node: TraceTreeNode - ) => void; - onZoomIn: ( - event: React.MouseEvent, - node: TraceTreeNode, - value: boolean - ) => void; + onRowKeyDown: (event: React.KeyboardEvent, index: number, node: BaseNode) => void; + onZoomIn: (event: React.MouseEvent, node: BaseNode, value: boolean) => void; organization: Organization; - previouslyFocusedNodeRef: React.MutableRefObject | null>; + previouslyFocusedNodeRef: React.MutableRefObject; projects: Record; searchResultsIteratorIndex: number | null; style: React.CSSProperties; @@ -607,7 +570,7 @@ function RenderTraceRow(props: { const onZoomInProp = props.onZoomIn; const onZoomIn = useCallback( (e: React.MouseEvent) => { - onZoomInProp(e, node, !node.zoomedIn); + onZoomInProp(e, node, !node.hasFetchedChildren); }, [node, onZoomInProp] ); @@ -620,7 +583,7 @@ function RenderTraceRow(props: { ? TRACE_RIGHT_COLUMN_ODD_CLASSNAME : TRACE_RIGHT_COLUMN_EVEN_CLASSNAME; - const listColumnClassName = isTraceNode(node) + const listColumnClassName = node.isRootNodeChild() ? TRACE_CHILDREN_COUNT_WRAPPER_ORPHANED_CLASSNAME : TRACE_CHILDREN_COUNT_WRAPPER_CLASSNAME; @@ -628,7 +591,7 @@ function RenderTraceRow(props: { paddingLeft: TraceTree.Depth(node) * props.manager.row_depth_padding, }; - const rowProps: TraceRowProps> = { + const rowProps: TraceRowProps = { onExpand, onZoomIn, onRowClick, @@ -655,40 +618,7 @@ function RenderTraceRow(props: { registerSpanArrowRef, }; - if (isTransactionNode(node)) { - return ; - } - - if ( - isSpanNode(node) || - isEAPSpanNode(node) || - isUptimeCheckNode(node) || - isUptimeCheckTimingNode(node) - ) { - return ; - } - - if (isMissingInstrumentationNode(node)) { - return ; - } - - if (isAutogroupedNode(node)) { - return ; - } - - if (isTraceErrorNode(node) || isEAPErrorNode(node)) { - return ; - } - - if (isTraceNode(node) || isEAPTraceNode(node)) { - return ; - } - - if (isCollapsedNode(node)) { - return ; - } - - return null; + return node.renderWaterfallRow(rowProps); } function VerticalTimestampIndicators({ diff --git a/static/app/views/performance/newTraceDetails/traceAnalytics.tsx b/static/app/views/performance/newTraceDetails/traceAnalytics.tsx index 9e86cca64c4de0..f4b700182b7b04 100644 --- a/static/app/views/performance/newTraceDetails/traceAnalytics.tsx +++ b/static/app/views/performance/newTraceDetails/traceAnalytics.tsx @@ -28,7 +28,7 @@ const trackTraceSuccessState = ( const trace_duration_seconds = (tree.root.space?.[1] ?? 0) / 1000; const projectSlugs = [ ...new Set( - tree.list.map(node => node.metadata.project_slug).filter(slug => slug !== undefined) + tree.list.map(node => node.projectSlug).filter(slug => slug !== undefined) ), ]; diff --git a/static/app/views/performance/newTraceDetails/traceApi/useTraceAverageTransactionDuration.tsx b/static/app/views/performance/newTraceDetails/traceApi/useTraceAverageTransactionDuration.tsx index 0e1249ddadccf9..d63beaba7c4096 100644 --- a/static/app/views/performance/newTraceDetails/traceApi/useTraceAverageTransactionDuration.tsx +++ b/static/app/views/performance/newTraceDetails/traceApi/useTraceAverageTransactionDuration.tsx @@ -4,12 +4,11 @@ import type {Organization} from 'sentry/types/organization'; import {useDiscoverQuery} from 'sentry/utils/discover/discoverQuery'; import EventView from 'sentry/utils/discover/eventView'; import {MutableSearch} from 'sentry/utils/tokenizeSearch'; -import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {TransactionNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/transactionNode'; type Props = { location: Location; - node: TraceTreeNode; + node: TransactionNode; organization: Organization; }; diff --git a/static/app/views/performance/newTraceDetails/traceApi/useTransaction.tsx b/static/app/views/performance/newTraceDetails/traceApi/useTransaction.tsx index 44ff3cdefb1bef..a0877af35e555b 100644 --- a/static/app/views/performance/newTraceDetails/traceApi/useTransaction.tsx +++ b/static/app/views/performance/newTraceDetails/traceApi/useTransaction.tsx @@ -3,9 +3,9 @@ import type {Organization} from 'sentry/types/organization'; import {useApiQuery} from 'sentry/utils/queryClient'; interface UseTransactionProps { - event_id: string; + event_id: string | undefined; organization: Organization; - project_slug: string; + project_slug: string | undefined; } export function useTransaction(props: UseTransactionProps) { diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/autogroup/index.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/autogroup/index.tsx index 4472bcf598da2f..62d9970e47ccab 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/autogroup/index.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/autogroup/index.tsx @@ -4,8 +4,8 @@ import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {TraceDrawerComponents} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/styles'; import type {TraceTreeNodeDetailsProps} from 'sentry/views/performance/newTraceDetails/traceDrawer/tabs/traceTreeNodeDetails'; -import type {ParentAutogroupNode} from 'sentry/views/performance/newTraceDetails/traceModels/parentAutogroupNode'; -import type {SiblingAutogroupNode} from 'sentry/views/performance/newTraceDetails/traceModels/siblingAutogroupNode'; +import type {ParentAutogroupNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/parentAutogroupNode'; +import type {SiblingAutogroupNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/siblingAutogroupNode'; export function AutogroupNodeDetails( props: TraceTreeNodeDetailsProps diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/error.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/error.tsx index 3a9a72d75d559c..0bbd7ca9c756e3 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/error.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/error.tsx @@ -2,17 +2,12 @@ import {useMemo} from 'react'; import {t} from 'sentry/locale'; import type {TraceTreeNodeDetailsProps} from 'sentry/views/performance/newTraceDetails/traceDrawer/tabs/traceTreeNodeDetails'; -import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {ErrorNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/errorNode'; import {IssueList} from './issues/issues'; import {TraceDrawerComponents} from './styles'; -export function ErrorNodeDetails( - props: TraceTreeNodeDetailsProps< - TraceTreeNode | TraceTreeNode - > -) { +export function ErrorNodeDetails(props: TraceTreeNodeDetailsProps) { const {node, organization, onTabScrollToNode} = props; const issues = useMemo(() => { return [...node.errors]; diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/issues/issues.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/issues/issues.tsx index a366ddd2e33b13..c66dc495c27385 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/issues/issues.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/issues/issues.tsx @@ -17,7 +17,7 @@ import {TraceDrawerComponents} from 'sentry/views/performance/newTraceDetails/tr import {isTraceOccurence} from 'sentry/views/performance/newTraceDetails/traceGuards'; import {TraceIcons} from 'sentry/views/performance/newTraceDetails/traceIcons'; import {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {BaseNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode'; type IssueProps = { issue: TraceTree.TraceIssue; @@ -182,14 +182,14 @@ const SummaryWrapper = styled('div')` type IssueListProps = { issues: TraceTree.TraceIssue[]; - node: TraceTreeNode; + node: BaseNode; organization: Organization; }; export function IssueList({issues, node, organization}: IssueListProps) { const uniqueIssues = [ - ...TraceTree.UniqueErrorIssues(node).sort(sortIssuesByLevel), - ...TraceTree.UniqueOccurrences(node), + ...node.uniqueErrorIssues.sort(sortIssuesByLevel), + ...node.uniqueOccurrenceIssues, ]; if (!issues.length) { diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/missingInstrumentation.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/missingInstrumentation.tsx index 10e2d94eb0517b..9db7c9b0490474 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/missingInstrumentation.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/missingInstrumentation.tsx @@ -11,17 +11,19 @@ import {useTransaction} from 'sentry/views/performance/newTraceDetails/traceApi/ import {getCustomInstrumentationLink} from 'sentry/views/performance/newTraceDetails/traceConfigurations'; import {ProfilePreview} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/profiling/profilePreview'; import type {TraceTreeNodeDetailsProps} from 'sentry/views/performance/newTraceDetails/traceDrawer/tabs/traceTreeNodeDetails'; -import {isEAPSpanNode} from 'sentry/views/performance/newTraceDetails/traceGuards'; -import type {MissingInstrumentationNode} from 'sentry/views/performance/newTraceDetails/traceModels/missingInstrumentationNode'; -import {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import { + isEAPSpanNode, + isSpanNode, +} from 'sentry/views/performance/newTraceDetails/traceGuards'; +import type {EapSpanNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/eapSpanNode'; +import type {NoInstrumentationNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/noInstrumentationNode'; import {ProfileGroupProvider} from 'sentry/views/profiling/profileGroupProvider'; import {ProfileContext, ProfilesProvider} from 'sentry/views/profiling/profilesProvider'; import {TraceDrawerComponents} from './styles'; import {getProfileMeta} from './utils'; -interface BaseProps extends TraceTreeNodeDetailsProps { +interface BaseProps extends TraceTreeNodeDetailsProps { event: EventTransaction | null; profileId: string | undefined; profileMeta: ReturnType; @@ -31,39 +33,44 @@ interface BaseProps extends TraceTreeNodeDetailsProps) { +}: TraceTreeNodeDetailsProps) { const {node} = props; const {projects} = useProjects(); + if (isSpanNode(node.previous)) { + const event = node.previous.event ?? null; + const project = projects.find(proj => proj.slug === event?.projectSlug); + const profileMeta = getProfileMeta(event) || ''; + const profileContext = event?.contexts?.profile ?? {}; + + return ( + + ); + } + if (isEAPSpanNode(node.previous)) { return ; } - const event = node.previous.event ?? node.next.event ?? null; - const project = projects.find(proj => proj.slug === event?.projectSlug); - const profileMeta = getProfileMeta(event) || ''; - const profileContext = event?.contexts?.profile ?? {}; - - return ( - - ); + return null; } function EAPMissingInstrumentationNodeDetails({ projects, ...props -}: TraceTreeNodeDetailsProps & { +}: TraceTreeNodeDetailsProps & { projects: Project[]; }) { const {node} = props; - const previous = node.previous as TraceTreeNode; + + const previous = node.previous as EapSpanNode; const { data: eventTransaction = null, diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/profiling/profilePreview.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/profiling/profilePreview.tsx index c5148b22e13124..3cc8e63c3bcb64 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/profiling/profilePreview.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/profiling/profilePreview.tsx @@ -28,15 +28,13 @@ import {Rect} from 'sentry/utils/profiling/speedscope'; import useOrganization from 'sentry/utils/useOrganization'; import {SectionDivider} from 'sentry/views/issueDetails/streamline/foldSection'; import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection'; -import {isEAPSpanNode} from 'sentry/views/performance/newTraceDetails/traceGuards'; -import type {MissingInstrumentationNode} from 'sentry/views/performance/newTraceDetails/traceModels/missingInstrumentationNode'; -import {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; +import type {NoInstrumentationNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/noInstrumentationNode'; import {useProfileGroup} from 'sentry/views/profiling/profileGroupProvider'; import {useProfiles} from 'sentry/views/profiling/profilesProvider'; interface SpanProfileProps { event: Readonly | null; - missingInstrumentationNode: MissingInstrumentationNode; + missingInstrumentationNode: NoInstrumentationNode; profileID: string | undefined; profilerID: string | undefined; project: Project | undefined; @@ -73,21 +71,17 @@ export function ProfilePreview({ return null; }, [profileGroup.profiles, profileGroup.activeProfileIndex, spanThreadId]); - const transactionHasProfile = useMemo(() => { - return isEAPSpanNode(missingInstrumentationNode.previous) - ? (TraceTree.ParentEAPTransaction(missingInstrumentationNode)?.profiles?.length ?? - 0) > 0 - : (TraceTree.ParentTransaction(missingInstrumentationNode)?.profiles?.length ?? 0) > - 0; + const parentHasProfile = useMemo(() => { + return missingInstrumentationNode.findParent(p => p.profiles.size > 0); }, [missingInstrumentationNode]); const flamegraph = useMemo(() => { - if (!transactionHasProfile || !profile) { + if (!parentHasProfile || !profile) { return FlamegraphModel.Example(); } return new FlamegraphModel(profile, {}); - }, [transactionHasProfile, profile]); + }, [parentHasProfile, profile]); const target = useMemo(() => { if (project?.slug) { @@ -149,11 +143,11 @@ export function ProfilePreview({ // the next best thing. const startTimestamp = profile?.timestamp ?? event?.startTimestamp; const relativeStartTimestamp = - transactionHasProfile && defined(startTimestamp) + parentHasProfile && defined(startTimestamp) ? missingInstrumentationNode.value.start_timestamp - startTimestamp : 0; const relativeStopTimestamp = - transactionHasProfile && defined(startTimestamp) + parentHasProfile && defined(startTimestamp) ? missingInstrumentationNode.value.timestamp - startTimestamp : flamegraph.configSpace.width; @@ -168,7 +162,7 @@ export function ProfilePreview({ {t('Or, see if profiling can provide more context on this:')} ); - if (target && transactionHasProfile) { + if (target && parentHasProfile) { return ( {message} diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/aiIOAlert.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/aiIOAlert.tsx index 1b7ccc3fb2c160..26e3ef595f2bea 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/aiIOAlert.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/aiIOAlert.tsx @@ -12,15 +12,14 @@ import type {EventTransaction} from 'sentry/types/event'; import {isChonkTheme} from 'sentry/utils/theme/withChonk'; import useDismissAlert from 'sentry/utils/useDismissAlert'; import type {TraceItemResponseAttribute} from 'sentry/views/explore/hooks/useTraceItemDetails'; +import {getTraceNodeAttribute} from 'sentry/views/insights/agents/utils/aiTraceNodes'; import { - getIsAiGenerationNode, - getIsExecuteToolNode, - getTraceNodeAttribute, -} from 'sentry/views/insights/agents/utils/aiTraceNodes'; + getIsAiGenerationSpan, + getIsExecuteToolSpan, +} from 'sentry/views/insights/agents/utils/query'; +import type {AITraceSpanNode} from 'sentry/views/insights/agents/utils/types'; import {hasAIInputAttribute} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/aiInput'; import {hasAIOutputAttribute} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/aiOutput'; -import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; type SupportedSDKLanguage = 'javascript' | 'python'; @@ -71,7 +70,7 @@ export function AIIOAlert({ attributes, event, }: { - node: TraceTreeNode; + node: AITraceSpanNode; attributes?: TraceItemResponseAttribute[]; event?: EventTransaction; }) { @@ -79,7 +78,8 @@ export function AIIOAlert({ const isChonk = isChonkTheme(theme); const {dismiss, isDismissed} = useDismissAlert({key: 'genai-io-alert-dismissed'}); - const isSupportedNodeType = getIsAiGenerationNode(node) || getIsExecuteToolNode(node); + const isSupportedNodeType = + getIsAiGenerationSpan({op: node.op}) || getIsExecuteToolSpan({op: node.op}); const hasData = hasAIInputAttribute(node, attributes, event) || hasAIOutputAttribute(node, attributes, event); diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/aiInput.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/aiInput.tsx index 29b1388bf341e7..19b6662f19deb7 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/aiInput.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/aiInput.tsx @@ -9,15 +9,12 @@ import type {EventTransaction} from 'sentry/types/event'; import {defined} from 'sentry/utils'; import usePrevious from 'sentry/utils/usePrevious'; import type {TraceItemResponseAttribute} from 'sentry/views/explore/hooks/useTraceItemDetails'; -import { - getIsAiNode, - getTraceNodeAttribute, -} from 'sentry/views/insights/agents/utils/aiTraceNodes'; +import {getTraceNodeAttribute} from 'sentry/views/insights/agents/utils/aiTraceNodes'; +import {getIsAiSpan} from 'sentry/views/insights/agents/utils/query'; +import type {AITraceSpanNode} from 'sentry/views/insights/agents/utils/types'; import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; import {TraceDrawerComponents} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/styles'; -import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; interface AIMessage { content: React.ReactNode; @@ -125,7 +122,7 @@ function transformPrompt(prompt: string) { } export function hasAIInputAttribute( - node: TraceTreeNode, + node: AITraceSpanNode, attributes?: TraceItemResponseAttribute[], event?: EventTransaction ) { @@ -167,11 +164,12 @@ export function AIInputSection({ attributes, event, }: { - node: TraceTreeNode; + node: AITraceSpanNode; attributes?: TraceItemResponseAttribute[]; event?: EventTransaction; }) { - const shouldRender = getIsAiNode(node) && hasAIInputAttribute(node, attributes, event); + const shouldRender = + getIsAiSpan({op: node.op}) && hasAIInputAttribute(node, attributes, event); let promptMessages = shouldRender ? getTraceNodeAttribute('gen_ai.request.messages', node, event, attributes) diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/aiOutput.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/aiOutput.tsx index 07bc6320fe413a..20c159cfdd350d 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/aiOutput.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/aiOutput.tsx @@ -3,15 +3,12 @@ import {Fragment} from 'react'; import {t} from 'sentry/locale'; import type {EventTransaction} from 'sentry/types/event'; import type {TraceItemResponseAttribute} from 'sentry/views/explore/hooks/useTraceItemDetails'; -import { - getIsAiNode, - getTraceNodeAttribute, -} from 'sentry/views/insights/agents/utils/aiTraceNodes'; +import {getTraceNodeAttribute} from 'sentry/views/insights/agents/utils/aiTraceNodes'; +import {getIsAiSpan} from 'sentry/views/insights/agents/utils/query'; +import type {AITraceSpanNode} from 'sentry/views/insights/agents/utils/types'; import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; import {TraceDrawerComponents} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/styles'; -import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; function isJson(value: string) { try { @@ -31,7 +28,7 @@ function renderAIResponse(text: string) { } export function hasAIOutputAttribute( - node: TraceTreeNode, + node: AITraceSpanNode, attributes?: TraceItemResponseAttribute[], event?: EventTransaction ) { @@ -48,11 +45,11 @@ export function AIOutputSection({ attributes, event, }: { - node: TraceTreeNode; + node: AITraceSpanNode; attributes?: TraceItemResponseAttribute[]; event?: EventTransaction; }) { - if (!getIsAiNode(node) || !hasAIOutputAttribute(node, attributes, event)) { + if (!getIsAiSpan({op: node.op}) || !hasAIOutputAttribute(node, attributes, event)) { return null; } diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/attributes.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/attributes.tsx index e83d9bfa78727e..10c7bad442af86 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/attributes.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/attributes.tsx @@ -28,8 +28,8 @@ import { getTraceAttributesTreeActions, sortAttributes, } from 'sentry/views/performance/newTraceDetails/traceDrawer/details/utils'; -import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {EapSpanNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/eapSpanNode'; +import type {UptimeCheckNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/uptimeCheckNode'; import {useTraceState} from 'sentry/views/performance/newTraceDetails/traceState/traceStateProvider'; import {useOTelFriendlyUI} from 'sentry/views/performance/otlp/useOTelFriendlyUI'; import {makeReplaysPathname} from 'sentry/views/replays/pathnames'; @@ -79,7 +79,7 @@ export function Attributes({ }: { attributes: TraceItemResponseAttribute[]; location: Location; - node: TraceTreeNode; + node: EapSpanNode | UptimeCheckNode; organization: Organization; project: Project | undefined; theme: Theme; diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/description.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/description.tsx index 5fea0cc3c0d45c..e4e5b9614c7821 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/description.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/description.tsx @@ -45,7 +45,7 @@ import { TraceDrawerActionKind, } from 'sentry/views/performance/newTraceDetails/traceDrawer/details/utils'; import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {EapSpanNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/eapSpanNode'; import {useOTelFriendlyUI} from 'sentry/views/performance/otlp/useOTelFriendlyUI'; import {transactionSummaryRouteWithQuery} from 'sentry/views/performance/transactionSummary/utils'; import {usePerformanceGeneralProjectSettings} from 'sentry/views/performance/utils'; @@ -64,13 +64,13 @@ export function SpanDescription({ attributes: TraceItemResponseAttribute[]; avgSpanDuration: number | undefined; location: Location; - node: TraceTreeNode; + node: EapSpanNode; organization: Organization; project: Project | undefined; hideNodeActions?: boolean; }) { const {data: event} = useEventDetails({ - eventId: node.event?.eventID, + eventId: node.value.transaction_id, projectSlug: project?.slug, }); const span = node.value; @@ -140,7 +140,7 @@ export function SpanDescription({ to={getSearchInExploreTarget( organization, location, - node.event?.projectID, + node.projectId?.toString(), exploreAttributeName, exploreAttributeValue, TraceDrawerActionKind.INCLUDE @@ -177,7 +177,7 @@ export function SpanDescription({ {codeFilepath ? ( ; + node: EapSpanNode; }) { const span = node.value; @@ -292,7 +292,7 @@ function ResourceImageDescription({ ) : ( setShowLinks(true)} - projectSlug={span.project_slug ?? node.event?.projectSlug} + projectSlug={span.project_slug ?? node.projectSlug} /> )} diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/mcpInput.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/mcpInput.tsx index 682505797def87..ae888b127d0cfc 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/mcpInput.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/mcpInput.tsx @@ -2,12 +2,12 @@ import {t} from 'sentry/locale'; import type {EventTransaction} from 'sentry/types/event'; import type {TraceItemResponseAttribute} from 'sentry/views/explore/hooks/useTraceItemDetails'; import {ensureAttributeObject} from 'sentry/views/insights/agents/utils/aiTraceNodes'; +import type {AITraceSpanNode} from 'sentry/views/insights/agents/utils/types'; import {getIsMCPNode} from 'sentry/views/insights/mcp/utils/mcpTraceNodes'; import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; import {TraceDrawerComponents} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/styles'; -import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {BaseNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode'; const ARGUMENTS_KEY_PREFIX = 'mcp.request.argument.'; @@ -19,7 +19,7 @@ function shortenKey(key: string) { } function getInputAttributes( - node: TraceTreeNode, + node: BaseNode, event?: EventTransaction, attributes?: TraceItemResponseAttribute[] ): Array<[string, string | number | boolean]> { @@ -39,7 +39,7 @@ export function MCPInputSection({ attributes, event, }: { - node: TraceTreeNode; + node: AITraceSpanNode; attributes?: TraceItemResponseAttribute[]; event?: EventTransaction; }) { diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/mcpOutput.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/mcpOutput.tsx index afb2486faeee33..826cb970a97c4a 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/mcpOutput.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/mcpOutput.tsx @@ -2,12 +2,11 @@ import {t} from 'sentry/locale'; import type {EventTransaction} from 'sentry/types/event'; import type {TraceItemResponseAttribute} from 'sentry/views/explore/hooks/useTraceItemDetails'; import {ensureAttributeObject} from 'sentry/views/insights/agents/utils/aiTraceNodes'; +import type {AITraceSpanNode} from 'sentry/views/insights/agents/utils/types'; import {getIsMCPNode} from 'sentry/views/insights/mcp/utils/mcpTraceNodes'; import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; import {TraceDrawerComponents} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/styles'; -import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; const TOOL_OUTPUT_ATTRIBUTE = 'mcp.tool.result.content'; const PROMPT_OUTPUT_PREFIX = 'mcp.prompt.result.'; @@ -17,7 +16,7 @@ export function MCPOutputSection({ attributes, event, }: { - node: TraceTreeNode; + node: AITraceSpanNode; attributes?: TraceItemResponseAttribute[]; event?: EventTransaction; }) { diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/traceSpanLinks.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/traceSpanLinks.tsx index b849156ae95c2d..a19c41a1c085e9 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/traceSpanLinks.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/traceSpanLinks.tsx @@ -21,15 +21,16 @@ import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; import {TraceDrawerComponents} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/styles'; import {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {BaseNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode'; +import type {EapSpanNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/eapSpanNode'; import {useTraceStateDispatch} from 'sentry/views/performance/newTraceDetails/traceState/traceStateProvider'; import {TraceLayoutTabKeys} from 'sentry/views/performance/newTraceDetails/useTraceLayoutTabs'; interface TraceSpanLinksProps { links: TraceItemResponseLink[]; location: Location; - node: TraceTreeNode; - onTabScrollToNode: (node: TraceTreeNode) => void; + node: EapSpanNode; + onTabScrollToNode: (node: BaseNode) => void; organization: Organization; theme: Theme; traceId: string; @@ -125,7 +126,8 @@ export function TraceSpanLinks({ } // If the link is to the same trace, we look for and navigate to the span in the same trace waterfall - const spanNode = TraceTree.FindByID(tree.root, link.itemId); + const spanNode = tree.root.findChild(p => p.matchById(link.itemId)); + if (spanNode) { onTabScrollToNode(spanNode); } diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/index.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/index.tsx index 8c9a9633b5c539..2e465a7d144480 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/index.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/index.tsx @@ -51,7 +51,9 @@ import { isEAPTransactionNode, } from 'sentry/views/performance/newTraceDetails/traceGuards'; import {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {BaseNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode'; +import type {EapSpanNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/eapSpanNode'; +import type {SpanNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/spanNode'; import {ProfileGroupProvider} from 'sentry/views/profiling/profileGroupProvider'; import {ProfileContext, ProfilesProvider} from 'sentry/views/profiling/profilesProvider'; @@ -73,8 +75,8 @@ function SpanNodeDetailHeader({ onTabScrollToNode, hideNodeActions, }: { - node: TraceTreeNode | TraceTreeNode; - onTabScrollToNode: (node: TraceTreeNode) => void; + node: EapSpanNode | SpanNode; + onTabScrollToNode: (node: BaseNode) => void; organization: Organization; hideNodeActions?: boolean; }) { @@ -108,8 +110,8 @@ function SpanSections({ onParentClick, }: { location: Location; - node: TraceTreeNode; - onParentClick: (node: TraceTreeNode) => void; + node: SpanNode; + onParentClick: (node: BaseNode) => void; organization: Organization; project: Project | undefined; }) { @@ -150,30 +152,25 @@ function SpanSections({ } export function SpanNodeDetails( - props: TraceTreeNodeDetailsProps< - TraceTreeNode | TraceTreeNode - > + props: TraceTreeNodeDetailsProps ) { const {node, organization} = props; const location = useLocation(); const theme = useTheme(); const {projects} = useProjects(); - const issues = TraceTree.UniqueIssues(node); - - const parentTransaction = isEAPSpanNode(node) - ? isEAPTransactionNode(node) - ? node - : TraceTree.ParentEAPTransaction(node) - : TraceTree.ParentTransaction(node); - const profileId = parentTransaction?.value.profile_id; - const profilerId = parentTransaction?.value.profiler_id; - - const profilerStart = parentTransaction?.value.start_timestamp; - const profilerEnd = parentTransaction - ? 'timestamp' in parentTransaction.value - ? parentTransaction.value.timestamp - : parentTransaction.value.end_timestamp - : undefined; + const issues = node.uniqueIssues; + + const parentWithProfile = node.findParent(p => p.profiles.size > 0); + + // TODO Abdullah Khan: Clean this up, by storing both profile_id and profiler_id separately in the node. + const firstProfile = Array.from(node.profiles)[0]; + const profileId = + firstProfile && 'profile_id' in firstProfile ? firstProfile.profile_id : undefined; + const profilerId = + firstProfile && 'profiler_id' in firstProfile ? firstProfile.profiler_id : undefined; + + const profilerStart = parentWithProfile?.startTimestamp; + const profilerEnd = parentWithProfile?.endTimestamp; const profileMeta = useMemo(() => { if (profileId) { @@ -191,9 +188,7 @@ export function SpanNodeDetails( return ''; }, [profileId, profilerId, profilerStart, profilerEnd]); - const project = projects.find( - proj => proj.slug === (node.value.project_slug ?? node.event?.projectSlug) - ); + const project = projects.find(proj => proj.slug === node.projectSlug); const spanId = isEAPSpanNode(node) ? node.value.event_id : node.value.span_id; @@ -259,7 +254,7 @@ function SpanNodeDetailsContent({ issues, location, onParentClick, -}: TraceTreeNodeDetailsProps> & { +}: TraceTreeNodeDetailsProps & { issues: TraceTree.TraceIssue[]; location: Location; project: Project | undefined; @@ -351,9 +346,7 @@ function useAvgSpanDuration( return result.data?.[0]?.['avg(span.duration)']; } -type EAPSpanNodeDetailsProps = TraceTreeNodeDetailsProps< - TraceTreeNode -> & { +type EAPSpanNodeDetailsProps = TraceTreeNodeDetailsProps & { issues: TraceTree.TraceIssue[]; location: Location; project: Project | undefined; @@ -369,7 +362,7 @@ function EAPSpanNodeDetails(props: EAPSpanNodeDetailsProps) { } = useTraceItemDetails({ traceItemId: node.value.event_id, projectId: node.value.project_id.toString(), - traceId: node.metadata.replayTraceSlug ?? traceId, + traceId: node.extra?.replayTraceSlug ?? traceId, traceItemType: TraceItemDataset.SPANS, referrer: 'api.explore.log-item-details', // TODO: change to span details enabled: true, @@ -379,7 +372,7 @@ function EAPSpanNodeDetails(props: EAPSpanNodeDetailsProps) { // In that case we use the transaction id attached to the direct parent EAP span where is_transaction=true. const transaction_event_id = node.value.transaction_id ?? - TraceTree.ParentEAPTransaction(node)?.value.transaction_id; + node.findParent(p => isEAPTransactionNode(p))?.value.transaction_id; const {data: eventTransaction, isLoading: isEventTransactionLoading} = useTransaction({ event_id: transaction_event_id, project_slug: node.value.project_slug, @@ -512,7 +505,7 @@ function EAPSpanNodeDetailsContent({ theme={theme} location={location} organization={organization} - traceId={node.metadata.replayTraceSlug ?? traceId} + traceId={node.extra?.replayTraceSlug ?? traceId} onTabScrollToNode={onTabScrollToNode} /> ) : null} diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/alerts.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/alerts.tsx index 77e85690002950..c2785cd011fc62 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/alerts.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/alerts.tsx @@ -1,10 +1,9 @@ import {Alert} from 'sentry/components/core/alert'; import {isOrphanSpan} from 'sentry/components/events/interfaces/spans/utils'; import {t} from 'sentry/locale'; -import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {SpanNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/spanNode'; -function Alerts({node}: {node: TraceTreeNode}) { +function Alerts({node}: {node: SpanNode}) { if (!isOrphanSpan(node.value)) { return null; } diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/ancestry.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/ancestry.tsx index e945ab61851d39..c559c4805544ab 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/ancestry.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/ancestry.tsx @@ -10,23 +10,26 @@ import { type SectionCardKeyValueList, } from 'sentry/views/performance/newTraceDetails/traceDrawer/details/styles'; import {isTransactionNode} from 'sentry/views/performance/newTraceDetails/traceGuards'; -import {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; -import {getTraceTabTitle} from 'sentry/views/performance/newTraceDetails/traceState/traceTabs'; +import type {BaseNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode'; +import type {SpanNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/spanNode'; +import type {TransactionNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/transactionNode'; export function useSpanAncestryAndGroupingItems({ node, onParentClick, }: { location: Location; - node: TraceTreeNode; - onParentClick: (node: TraceTreeNode) => void; + node: SpanNode; + onParentClick: (node: BaseNode) => void; organization: Organization; }): SectionCardKeyValueList { - const parentTransaction = useMemo(() => TraceTree.ParentTransaction(node), [node]); + const parentTransaction = useMemo( + () => node.findParent(p => isTransactionNode(p)), + [node] + ); const childTransactions = useMemo(() => { - const transactions: Array> = []; - TraceTree.ForEachChild(node, c => { + const transactions: TransactionNode[] = []; + node.forEachChild(c => { if (isTransactionNode(c)) { transactions.push(c); } @@ -41,7 +44,7 @@ export function useSpanAncestryAndGroupingItems({ key: 'parent_transaction', value: ( onParentClick(parentTransaction)}> - {getTraceTabTitle(parentTransaction)} + {parentTransaction.drawerTabsTitle} ), subject: t('Parent Transaction'), diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/description.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/description.tsx index 50307e7f64f68c..fa98bf953b21f0 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/description.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/description.tsx @@ -42,7 +42,7 @@ import { TraceDrawerActionKind, } from 'sentry/views/performance/newTraceDetails/traceDrawer/details/utils'; import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {SpanNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/spanNode'; import {usePerformanceGeneralProjectSettings} from 'sentry/views/performance/utils'; const formatter = new SQLishFormatter(); @@ -55,7 +55,7 @@ export function SpanDescription({ hideNodeActions, }: { location: Location; - node: TraceTreeNode; + node: SpanNode; organization: Organization; project: Project | undefined; hideNodeActions?: boolean; @@ -226,7 +226,7 @@ function ResourceImageDescription({ node, }: { formattedDescription: string; - node: TraceTreeNode; + node: SpanNode; }) { const projectID = node.event?.projectID ? Number(node.event?.projectID) : undefined; const span = node.value; diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/generalInfo.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/generalInfo.tsx index 85ace89ee6fa80..cdfcbad2609cb6 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/generalInfo.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/generalInfo.tsx @@ -22,18 +22,19 @@ import { type SectionCardKeyValueList, } from 'sentry/views/performance/newTraceDetails/traceDrawer/details/styles'; import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {BaseNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode'; +import type {SpanNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/spanNode'; import {useSpanAncestryAndGroupingItems} from './ancestry'; type GeneralnfoProps = { location: Location; - node: TraceTreeNode; - onParentClick: (node: TraceTreeNode) => void; + node: SpanNode; + onParentClick: (node: BaseNode) => void; organization: Organization; }; -function SpanDuration({node}: {node: TraceTreeNode}) { +function SpanDuration({node}: {node: SpanNode}) { const span = node.value; const startTimestamp: number = span.start_timestamp; const endTimestamp: number = span.timestamp; @@ -51,7 +52,7 @@ function SpanDuration({node}: {node: TraceTreeNode}) { ); } -function SpanSelfTime({node}: {node: TraceTreeNode}) { +function SpanSelfTime({node}: {node: SpanNode}) { const span = node.value; const startTimestamp: number = span.start_timestamp; const endTimestamp: number = span.timestamp; diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/keys.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/keys.tsx index b905f89d8b21e7..babeb4a2dd0918 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/keys.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/keys.tsx @@ -24,8 +24,7 @@ import { } from 'sentry/views/performance/newTraceDetails/traceDrawer/details/styles'; import {TraceDrawerActionValueKind} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/utils'; import {isSpanNode} from 'sentry/views/performance/newTraceDetails/traceGuards'; -import {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {SpanNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/spanNode'; import {getPerformanceDuration} from 'sentry/views/performance/utils/getPerformanceDuration'; const SIZE_DATA_KEYS = [ @@ -71,13 +70,13 @@ function partitionSizes(data: RawSpanType['data']): { }; } -function getSpanAggregateMeasurements(node: TraceTreeNode) { +function getSpanAggregateMeasurements(node: SpanNode) { if (!/^ai\.pipeline($|\.)/.test(node.value.op ?? '')) { return []; } let sum = 0; - TraceTree.ForEachChild(node, n => { + node.forEachChild(n => { if ( isSpanNode(n) && typeof n?.value?.measurements?.ai_total_tokens_used?.value === 'number' @@ -94,7 +93,7 @@ function getSpanAggregateMeasurements(node: TraceTreeNode) { ]; } -export function hasSpanKeys(node: TraceTreeNode, theme: Theme) { +export function hasSpanKeys(node: SpanNode, theme: Theme) { const span = node.value; const {sizeKeys, nonSizeKeys} = partitionSizes(span?.data ?? {}); const allZeroSizes = SIZE_DATA_KEYS.map(key => sizeKeys[key]).every( @@ -117,7 +116,7 @@ export function hasSpanKeys(node: TraceTreeNode, theme: Theme) { ); } -export function SpanKeys({node}: {node: TraceTreeNode}) { +export function SpanKeys({node}: {node: SpanNode}) { const theme = useTheme(); const span = node.value; const projectIds = node.event?.projectID; diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/measurements.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/measurements.tsx index 55354ab5676d50..3ce1a6e92a939a 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/measurements.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/measurements.tsx @@ -24,7 +24,7 @@ import { } from 'sentry/views/performance/newTraceDetails/traceDrawer/details/styles'; import {TraceDrawerActionValueKind} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/utils'; import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {SpanNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/spanNode'; export function hasSpanMeasurements(span: TraceTree.Span) { return !!span.measurements && Object.keys(span.measurements).length > 0; @@ -36,7 +36,7 @@ function Measurements({ organization, }: { location: Location; - node: TraceTreeNode; + node: SpanNode; organization: Organization; }) { const theme = useTheme(); diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/tags.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/tags.tsx index 8da91a5a22797b..acc0dc35c12a1c 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/tags.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/tags.tsx @@ -5,14 +5,13 @@ import { type SectionCardKeyValueList, } from 'sentry/views/performance/newTraceDetails/traceDrawer/details/styles'; import {TraceDrawerActionValueKind} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/utils'; -import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {SpanNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/spanNode'; export function hasSpanTags(span: RawSpanType) { return !!span.tags && Object.keys(span.tags).length > 0; } -export function Tags({node}: {node: TraceTreeNode}) { +export function Tags({node}: {node: SpanNode}) { const span = node.value; const tags: Record | undefined = span?.tags; diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/styles.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/styles.tsx index 1903ec473914e3..b5f973e6ba9b14 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/styles.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/styles.tsx @@ -53,7 +53,7 @@ import type {Color, ColorOrAlias} from 'sentry/utils/theme'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import {useParams} from 'sentry/utils/useParams'; -import {getIsAiNode} from 'sentry/views/insights/agents/utils/aiTraceNodes'; +import {getIsAiSpan} from 'sentry/views/insights/agents/utils/query'; import {getIsMCPNode} from 'sentry/views/insights/mcp/utils/mcpTraceNodes'; import {traceAnalytics} from 'sentry/views/performance/newTraceDetails/traceAnalytics'; import {useTransaction} from 'sentry/views/performance/newTraceDetails/traceApi/useTransaction'; @@ -68,11 +68,8 @@ import { isSpanNode, isTransactionNode, } from 'sentry/views/performance/newTraceDetails/traceGuards'; -import type {MissingInstrumentationNode} from 'sentry/views/performance/newTraceDetails/traceModels/missingInstrumentationNode'; -import type {ParentAutogroupNode} from 'sentry/views/performance/newTraceDetails/traceModels/parentAutogroupNode'; -import type {SiblingAutogroupNode} from 'sentry/views/performance/newTraceDetails/traceModels/siblingAutogroupNode'; -import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {BaseNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode'; +import type {EapSpanNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/eapSpanNode'; import { useTraceState, useTraceStateDispatch, @@ -329,7 +326,7 @@ const getDurationComparison = ( type DurationProps = { baseline: number | undefined; duration: number; - node: TraceTreeNode; + node: BaseNode; baseDescription?: string; ratio?: number; }; @@ -412,7 +409,7 @@ type HighlightProps = { avgDuration: number | undefined; bodyContent: React.ReactNode; headerContent: React.ReactNode; - node: TraceTreeNode; + node: BaseNode; project: Project | undefined; transaction: EventTransaction | undefined; hideNodeActions?: boolean; @@ -444,7 +441,7 @@ function Highlights({ return null; } - const isAiNode = getIsAiNode(node); + const isAiNode = getIsAiSpan({op: node.op}); const isMCPNode = getIsMCPNode(node); const hidePanelAndBreakdown = isAiNode || isMCPNode; @@ -466,9 +463,9 @@ function Highlights({ - + @@ -476,9 +473,7 @@ function Highlights({ - - {isTransactionNode(node) ? node.value?.['transaction.op'] : node.value?.op} - + {node.op} {getDuration(durationInSeconds, 2, true)} @@ -591,11 +586,11 @@ function HighLightEAPOpsBreakdown({ node, onRowClick, }: { - node: TraceTreeNode; + node: EapSpanNode; onRowClick: (op: string) => void; }) { const theme = useTheme(); - const breakdown = node.eapSpanOpsBreakdown; + const breakdown = node.opsBreakdown; if (breakdown.length === 0) { return null; @@ -765,13 +760,7 @@ const HighlightsRightColumn = styled('div')` overflow: hidden; `; -function IssuesLink({ - node, - children, -}: { - children: React.ReactNode; - node: TraceTreeNode; -}) { +function IssuesLink({node, children}: {children: React.ReactNode; node: BaseNode}) { const organization = useOrganization(); const params = useParams<{traceSlug?: string}>(); const traceSlug = params.traceSlug?.trim() ?? ''; @@ -840,7 +829,7 @@ const ValueTd = styled('td')` `; function getThreadIdFromNode( - node: TraceTreeNode, + node: BaseNode, transaction: EventTransaction | undefined ): string | undefined { if (isSpanNode(node) && node.value.data?.['thread.id']) { @@ -997,14 +986,8 @@ function PanelPositionDropDown({organization}: {organization: Organization}) { } function NodeActions(props: { - node: TraceTreeNode; - onTabScrollToNode: ( - node: - | TraceTreeNode - | ParentAutogroupNode - | SiblingAutogroupNode - | MissingInstrumentationNode - ) => void; + node: BaseNode; + onTabScrollToNode: (node: BaseNode) => void; organization: Organization; eventSize?: number | undefined; }) { @@ -1019,7 +1002,7 @@ function NodeActions(props: { const {data: transaction} = useTransaction({ event_id: transactionId, - project_slug: props.node.value.project_slug, + project_slug: props.node.projectSlug, organization, }); @@ -1034,7 +1017,7 @@ function NodeActions(props: { } return makeTransactionProfilingLink(profileId, { organization, - projectSlug: props.node.metadata.project_slug ?? '', + projectSlug: props.node.projectSlug ?? '', }); }, [organization, props.node]); @@ -1049,7 +1032,7 @@ function NodeActions(props: { } return makeTraceContinuousProfilingLink(props.node, profilerId, { organization, - projectSlug: props.node.metadata.project_slug ?? '', + projectSlug: props.node.projectSlug ?? '', traceId: params.traceSlug ?? '', threadId: getThreadIdFromNode(props.node, transaction), }); diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/transaction/index.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/transaction/index.tsx index 38ec757cf762fd..e98499ab324b1b 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/transaction/index.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/transaction/index.tsx @@ -32,8 +32,8 @@ import {MCPInputSection} from 'sentry/views/performance/newTraceDetails/traceDra import {MCPOutputSection} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/mcpOutput'; import {TraceDrawerComponents} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/styles'; import type {TraceTreeNodeDetailsProps} from 'sentry/views/performance/newTraceDetails/traceDrawer/tabs/traceTreeNodeDetails'; -import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {BaseNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode'; +import type {TransactionNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/transactionNode'; import {AdditionalData, hasAdditionalData} from './sections/additionalData'; import {BreadCrumbs} from './sections/breadCrumbs'; @@ -48,8 +48,8 @@ import {hasSDKContext} from './sections/sdk'; type TransactionNodeDetailHeaderProps = { event: EventTransaction; - node: TraceTreeNode; - onTabScrollToNode: (node: TraceTreeNode) => void; + node: TransactionNode; + onTabScrollToNode: (node: BaseNode) => void; organization: Organization; hideNodeActions?: boolean; }; @@ -93,7 +93,7 @@ export function TransactionNodeDetails({ onParentClick, replay, hideNodeActions, -}: TraceTreeNodeDetailsProps>) { +}: TraceTreeNodeDetailsProps) { const {projects} = useProjects(); const issues = useMemo(() => { return [...node.errors, ...node.occurrences]; @@ -137,7 +137,7 @@ export function TransactionNodeDetails({ hideNodeActions={hideNodeActions} /> - {node.canFetch ? null : ( + {node.canFetchChildren ? null : ( {tct( @@ -228,8 +228,8 @@ export function TransactionNodeDetails({ type TransactionSpecificSectionsProps = { cacheMetrics: Array>; event: EventTransaction; - node: TraceTreeNode; - onParentClick: (node: TraceTreeNode) => void; + node: TransactionNode; + onParentClick: (node: BaseNode) => void; organization: Organization; }; diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/transaction/sections/generalInfo.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/transaction/sections/generalInfo.tsx index 052963e5370004..8b78dc22bcf4fe 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/transaction/sections/generalInfo.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/transaction/sections/generalInfo.tsx @@ -16,16 +16,16 @@ import { TraceDrawerComponents, type SectionCardKeyValueList, } from 'sentry/views/performance/newTraceDetails/traceDrawer/details/styles'; -import {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; -import {getTraceTabTitle} from 'sentry/views/performance/newTraceDetails/traceState/traceTabs'; +import {isTransactionNode} from 'sentry/views/performance/newTraceDetails/traceGuards'; +import type {BaseNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode'; +import type {TransactionNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/transactionNode'; type GeneralInfoProps = { cacheMetrics: Array>; event: EventTransaction; location: Location; - node: TraceTreeNode; - onParentClick: (node: TraceTreeNode) => void; + node: TransactionNode; + onParentClick: (node: BaseNode) => void; organization: Organization; }; @@ -42,7 +42,7 @@ function GeneralInfo(props: GeneralInfoProps) { endTimestamp / 1e3 ); - const parentTransaction = TraceTree.ParentTransaction(node); + const parentTransaction = node.findParent(p => isTransactionNode(p)); const items: SectionCardKeyValueList = [ { @@ -90,7 +90,7 @@ function GeneralInfo(props: GeneralInfoProps) { subject: t('Parent Transaction'), value: ( onParentClick(parentTransaction)}> - {getTraceTabTitle(parentTransaction)} + {parentTransaction.drawerTabsTitle} ), }); diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/transaction/sections/highlights.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/transaction/sections/highlights.tsx index 665fa8a721f7a8..739737bfde71d1 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/transaction/sections/highlights.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/transaction/sections/highlights.tsx @@ -16,13 +16,12 @@ import {useTraceAverageTransactionDuration} from 'sentry/views/performance/newTr import {getHighlightedSpanAttributes} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/highlightedAttributes'; import {TraceDrawerComponents} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/styles'; import {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 {TransactionNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/transactionNode'; import {transactionSummaryRouteWithQuery} from 'sentry/views/performance/transactionSummary/utils'; type HighlightProps = { event: EventTransaction; - node: TraceTreeNode; + node: TransactionNode; organization: Organization; project: Project | undefined; hideNodeActions?: boolean; diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/uptime/index.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/uptime/index.tsx index 872d2da8a5f523..bb2f653895a2f6 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/uptime/index.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/uptime/index.tsx @@ -16,8 +16,8 @@ import {TraceItemDataset} from 'sentry/views/explore/types'; import {Attributes} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/attributes'; import {TraceDrawerComponents} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/styles'; import type {TraceTreeNodeDetailsProps} from 'sentry/views/performance/newTraceDetails/traceDrawer/tabs/traceTreeNodeDetails'; -import {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {BaseNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode'; +import type {UptimeCheckNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/uptimeCheckNode'; function UptimeNodeDetailsHeader({ node, @@ -25,8 +25,8 @@ function UptimeNodeDetailsHeader({ onTabScrollToNode, hideNodeActions, }: { - node: TraceTreeNode; - onTabScrollToNode: (node: TraceTreeNode) => void; + node: UptimeCheckNode; + onTabScrollToNode: (node: BaseNode) => void; organization: Organization; hideNodeActions?: boolean; }) { @@ -55,16 +55,14 @@ function UptimeNodeDetailsHeader({ ); } -export function UptimeNodeDetails( - props: TraceTreeNodeDetailsProps> -) { +export function UptimeNodeDetails(props: TraceTreeNodeDetailsProps) { const {node} = props; const location = useLocation(); const theme = useTheme(); const {projects} = useProjects(); const project = projects.find( - proj => proj.slug === (node.value.project_slug ?? node.event?.projectSlug) + proj => proj.slug === (node.value.project_slug ?? node.projectSlug) ); return ( @@ -78,9 +76,7 @@ export function UptimeNodeDetails( ); } -type UptimeSpanNodeDetailsProps = TraceTreeNodeDetailsProps< - TraceTreeNode -> & { +type UptimeSpanNodeDetailsProps = TraceTreeNodeDetailsProps & { location: Location; project: Project | undefined; theme: Theme; @@ -95,7 +91,7 @@ function UptimeSpanNodeDetails(props: UptimeSpanNodeDetailsProps) { } = useTraceItemDetails({ traceItemId: node.value.event_id, projectId: node.value.project_id.toString(), - traceId: node.metadata.replayTraceSlug ?? traceId, + traceId: node.extra?.replayTraceSlug ?? traceId, traceItemType: TraceItemDataset.UPTIME_RESULTS, referrer: 'api.explore.log-item-details', // TODO: change to span details enabled: true, diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/uptime/timing.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/uptime/timing.tsx index 14853ed765d293..1feda879444df1 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/uptime/timing.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/uptime/timing.tsx @@ -9,8 +9,7 @@ import {AttributesTree} from 'sentry/views/explore/components/traceItemAttribute import type {TraceItemResponseAttribute} from 'sentry/views/explore/hooks/useTraceItemDetails'; import {TraceDrawerComponents} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/styles'; import type {TraceTreeNodeDetailsProps} from 'sentry/views/performance/newTraceDetails/traceDrawer/tabs/traceTreeNodeDetails'; -import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {UptimeCheckTimingNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/uptimeCheckTimingNode'; const UPTIME_PHASE_DESCRIPTIONS = { 'dns.lookup.duration': t( @@ -32,7 +31,7 @@ const UPTIME_PHASE_DESCRIPTIONS = { }; export function UptimeTimingDetails( - props: TraceTreeNodeDetailsProps> + props: TraceTreeNodeDetailsProps ) { const {node} = props; const {op, description, duration, start_timestamp, end_timestamp} = node.value; diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiSpans.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiSpans.tsx index 2eb84242732095..03ab861ad846ad 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiSpans.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiSpans.tsx @@ -13,7 +13,6 @@ import useOrganization from 'sentry/utils/useOrganization'; import {AISpanList} from 'sentry/views/insights/agents/components/aiSpanList'; import {useAITrace} from 'sentry/views/insights/agents/hooks/useAITrace'; import {getDefaultSelectedNode} from 'sentry/views/insights/agents/utils/getDefaultSelectedNode'; -import {getNodeId} from 'sentry/views/insights/agents/utils/getNodeId'; import type {AITraceSpanNode} from 'sentry/views/insights/agents/utils/types'; import {TraceTreeNodeDetails} from 'sentry/views/performance/newTraceDetails/traceDrawer/tabs/traceTreeNodeDetails'; import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; @@ -39,14 +38,13 @@ function TraceAiSpans({traceSlug}: {traceSlug: string}) { const selectedNode = useMemo(() => { return ( - nodes.find(node => getNodeId(node) === selectedNodeKey) || - getDefaultSelectedNode(nodes) + nodes.find(node => node.id === selectedNodeKey) || getDefaultSelectedNode(nodes) ); }, [nodes, selectedNodeKey]); const handleSelectNode = useCallback( (node: AITraceSpanNode) => { - const eventId = getNodeId(node); + const eventId = node.id; if (!eventId) { return; } @@ -113,7 +111,7 @@ function TraceAiSpans({traceSlug}: {traceSlug: string}) { diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceProfiles.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceProfiles.tsx index 65db1867bf76b8..532e2bc0b5c6ed 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceProfiles.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceProfiles.tsx @@ -20,6 +20,7 @@ import { isTransactionNode, } from 'sentry/views/performance/newTraceDetails/traceGuards'; import {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; +import type {TransactionNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/transactionNode'; export function TraceProfiles({tree}: {tree: TraceTree}) { const {projects} = useProjects(); @@ -59,7 +60,7 @@ export function TraceProfiles({tree}: {tree: TraceTree}) { {profiles.map((node, index) => { - const profile = node.profiles?.[0]; + const profile = Array.from(node.profiles)?.[0]; if (!profile) { return null; @@ -71,7 +72,8 @@ export function TraceProfiles({tree}: {tree: TraceTree}) { } : isSpanNode(node) ? { - eventId: TraceTree.ParentTransaction(node)?.value?.event_id, + eventId: node.findParent(p => isTransactionNode(p)) + ?.value?.event_id, } : {}; @@ -82,12 +84,12 @@ export function TraceProfiles({tree}: {tree: TraceTree}) { profilerId: profile.profiler_id, start: new Date(node.space[0]).toISOString(), end: new Date(node.space[0] + node.space[1]).toISOString(), - projectSlug: node.metadata.project_slug as string, + projectSlug: node.projectSlug as string, query, }) : generateProfileFlamechartRouteWithQuery({ organization, - projectSlug: node.metadata.project_slug as string, + projectSlug: node.projectSlug as string, profileId: profile.profile_id, query, }); diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceTreeNodeDetails.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceTreeNodeDetails.tsx index 133dcaf86b1bd0..44e8bf96a719f5 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceTreeNodeDetails.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceTreeNodeDetails.tsx @@ -18,15 +18,15 @@ import { isUptimeCheckTimingNode, } 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 {BaseNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode'; import type {VirtualizedViewManager} from 'sentry/views/performance/newTraceDetails/traceRenderers/virtualizedViewManager'; import type {ReplayRecord} from 'sentry/views/replays/types'; export interface TraceTreeNodeDetailsProps { manager: VirtualizedViewManager | null; node: T; - onParentClick: (node: TraceTreeNode) => void; - onTabScrollToNode: (node: TraceTreeNode) => void; + onParentClick: (node: BaseNode) => void; + onTabScrollToNode: (node: BaseNode) => void; organization: Organization; replay: ReplayRecord | null; traceId: string; diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/traceDrawer.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/traceDrawer.tsx index c2dd69c6f360d7..1311f4db1b21f5 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/traceDrawer.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/traceDrawer.tsx @@ -24,10 +24,9 @@ import type { TraceShape, TraceTree, } from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {BaseNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode'; import type {TraceScheduler} from 'sentry/views/performance/newTraceDetails/traceRenderers/traceScheduler'; import type {VirtualizedViewManager} from 'sentry/views/performance/newTraceDetails/traceRenderers/virtualizedViewManager'; -import {makeTraceNodeBarColor} from 'sentry/views/performance/newTraceDetails/traceRow/traceBar'; import type { TraceReducerAction, TraceReducerState, @@ -37,10 +36,7 @@ import { useTraceState, useTraceStateDispatch, } from 'sentry/views/performance/newTraceDetails/traceState/traceStateProvider'; -import { - getTraceTabTitle, - type TraceTabsReducerState, -} from 'sentry/views/performance/newTraceDetails/traceState/traceTabs'; +import {type TraceTabsReducerState} from 'sentry/views/performance/newTraceDetails/traceState/traceTabs'; import type {ReplayRecord} from 'sentry/views/replays/types'; import {TraceTreeNodeDetails} from './tabs/traceTreeNodeDetails'; @@ -48,8 +44,8 @@ import {TraceTreeNodeDetails} from './tabs/traceTreeNodeDetails'; type TraceDrawerProps = { manager: VirtualizedViewManager; meta: TraceMetaQueryResults; - onScrollToNode: (node: TraceTreeNode) => void; - onTabScrollToNode: (node: TraceTreeNode) => void; + onScrollToNode: (node: BaseNode) => void; + onTabScrollToNode: (node: BaseNode) => void; replay: ReplayRecord | null; scheduler: TraceScheduler; trace: TraceTree; @@ -206,7 +202,7 @@ export function TraceDrawer(props: TraceDrawerProps) { const {onMouseDown, size} = usePassiveResizableDrawer(resizableDrawerOptions); const onParentClick = useCallback( - (node: TraceTreeNode) => { + (node: BaseNode) => { props.onTabScrollToNode(node); traceDispatch({ type: 'activate tab', @@ -410,7 +406,7 @@ export function TraceDrawer(props: TraceDrawerProps) { interface TraceDrawerTabProps { index: number; - onTabScrollToNode: (node: TraceTreeNode) => void; + onTabScrollToNode: (node: BaseNode) => void; pinned: boolean; tab: TraceTabsReducerState['tabs'][number]; theme: Theme; @@ -443,9 +439,7 @@ function TraceDrawerTab(props: TraceDrawerTabProps) { {props.tab.node === 'trace' || props.tab.node === 'vitals' || props.tab.node === 'profiles' ? null : ( - + )} {props.tab.label ?? node} @@ -462,11 +456,8 @@ function TraceDrawerTab(props: TraceDrawerTabProps) { props.traceDispatch({type: 'activate tab', payload: props.index}); }} > - - {getTraceTabTitle(node)} + + {node.drawerTabsTitle} { diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/traceProfilingLink.ts b/static/app/views/performance/newTraceDetails/traceDrawer/traceProfilingLink.ts index 85d47662a4b747..9195c1a6d6cdca 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/traceProfilingLink.ts +++ b/static/app/views/performance/newTraceDetails/traceDrawer/traceProfilingLink.ts @@ -6,29 +6,16 @@ import { generateContinuousProfileFlamechartRouteWithQuery, generateProfileFlamechartRouteWithQuery, } from 'sentry/utils/profiling/routes'; -import { - isSpanNode, - isTransactionNode, -} from 'sentry/views/performance/newTraceDetails/traceGuards'; -import {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; - -function getNodeId(node: TraceTreeNode): string | undefined { - if (isTransactionNode(node)) { - return node.value.event_id; - } - if (isSpanNode(node)) { - return node.value.span_id; - } - return undefined; -} +import {isTransactionNode} from 'sentry/views/performance/newTraceDetails/traceGuards'; +import type {BaseNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode'; +import type {TransactionNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/transactionNode'; // In the current version, a segment is a parent transaction -function getEventId(node: TraceTreeNode): string | undefined { +function getEventId(node: BaseNode): string | undefined { if (isTransactionNode(node)) { return node.value.event_id; } - return TraceTree.ParentTransaction(node)?.value?.event_id; + return node.findParent(p => isTransactionNode(p))?.value?.event_id; } export function makeTransactionProfilingLink( @@ -54,7 +41,7 @@ export function makeTransactionProfilingLink( * Generates a link to a continuous profile for a given trace element type */ export function makeTraceContinuousProfilingLink( - node: TraceTreeNode, + node: BaseNode, profilerId: string, options: { organization: Organization; @@ -70,7 +57,9 @@ export function makeTraceContinuousProfilingLink( // We compute a time offset based on the duration of the span so that // users can see some context of things that occurred before and after the span. - const transaction = isTransactionNode(node) ? node : TraceTree.ParentTransaction(node); + const transaction = isTransactionNode(node) + ? node + : node.findParent(p => isTransactionNode(p)); if (!transaction) { return null; } @@ -116,7 +105,7 @@ export function makeTraceContinuousProfilingLink( queryWithEventData.tid = options.threadId; } - const spanId = getNodeId(node); + const spanId = node.id; if (spanId) { queryWithEventData.spanId = spanId; } diff --git a/static/app/views/performance/newTraceDetails/traceHeader/meta.tsx b/static/app/views/performance/newTraceDetails/traceHeader/meta.tsx index 085dda6dbe4706..d2e05848b0addb 100644 --- a/static/app/views/performance/newTraceDetails/traceHeader/meta.tsx +++ b/static/app/views/performance/newTraceDetails/traceHeader/meta.tsx @@ -79,7 +79,7 @@ export function Meta(props: MetaProps) { ? props.tree.eap_spans_count : (props.meta?.span_count ?? 0); - const uniqueIssuesCount = traceNode ? TraceTree.UniqueIssues(traceNode).length : 0; + const uniqueIssuesCount = traceNode ? traceNode.uniqueIssues.length : 0; // If there is no trace data, use the timestamp from the query params as an approximation for // the age of the trace. diff --git a/static/app/views/performance/newTraceDetails/traceModels/issuesTraceTree.tsx b/static/app/views/performance/newTraceDetails/traceModels/issuesTraceTree.tsx index 7377165b973ceb..bca5b8dfb0963b 100644 --- a/static/app/views/performance/newTraceDetails/traceModels/issuesTraceTree.tsx +++ b/static/app/views/performance/newTraceDetails/traceModels/issuesTraceTree.tsx @@ -77,7 +77,7 @@ export class IssuesTraceTree extends TraceTree { if (node) { if (node.canFetchChildren) { - return node.fetchChildren(true, tree, options).then(() => {}); + return tree.fetchNodeSubTree(true, node, options).then(() => {}); } node.expand(true, tree); } diff --git a/static/app/views/performance/newTraceDetails/traceModels/missingInstrumentationNode.spec.tsx b/static/app/views/performance/newTraceDetails/traceModels/missingInstrumentationNode.spec.tsx deleted file mode 100644 index 59a1df91f7e0c8..00000000000000 --- a/static/app/views/performance/newTraceDetails/traceModels/missingInstrumentationNode.spec.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; -import { - makeNodeMetadata, - makeSpan, -} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeTestUtils'; - -import {MissingInstrumentationNode} from './missingInstrumentationNode'; - -describe('MissingInstrumentationNode', () => { - it('stores references to previous and next spans', () => { - const previous = new TraceTreeNode(null, makeSpan(), makeNodeMetadata()); - const current = new TraceTreeNode(null, makeSpan(), makeNodeMetadata()); - - const node = new MissingInstrumentationNode( - new TraceTreeNode(null, makeSpan(), makeNodeMetadata()), - { - type: 'missing_instrumentation', - start_timestamp: previous.value.timestamp, - timestamp: current.value.start_timestamp, - }, - makeNodeMetadata(), - previous, - current - ); - - expect(node.previous).toBe(previous); - expect(node.next).toBe(current); - }); -}); diff --git a/static/app/views/performance/newTraceDetails/traceModels/missingInstrumentationNode.tsx b/static/app/views/performance/newTraceDetails/traceModels/missingInstrumentationNode.tsx deleted file mode 100644 index 2272c6ec82f450..00000000000000 --- a/static/app/views/performance/newTraceDetails/traceModels/missingInstrumentationNode.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import {isSpanNode} from 'sentry/views/performance/newTraceDetails/traceGuards'; - -import type {TraceTree} from './traceTree'; -import {TraceTreeNode} from './traceTreeNode'; - -export class MissingInstrumentationNode extends TraceTreeNode { - next: TraceTreeNode | TraceTreeNode; - previous: TraceTreeNode | TraceTreeNode; - - constructor( - parent: TraceTreeNode, - node: TraceTree.MissingInstrumentationSpan, - metadata: TraceTree.Metadata, - previous: TraceTreeNode | TraceTreeNode, - next: TraceTreeNode | TraceTreeNode - ) { - super(parent, node, metadata); - - this.next = next; - this.previous = previous; - - // The space of a missing instrumentation node is gap between previous end and next start - const previousEndTimestamp = isSpanNode(previous) - ? previous.value.timestamp - : previous.value.end_timestamp; - this.space = [ - previousEndTimestamp * 1e3, - (next.value.start_timestamp - previousEndTimestamp) * 1e3, - ]; - } -} diff --git a/static/app/views/performance/newTraceDetails/traceModels/traceTree.tsx b/static/app/views/performance/newTraceDetails/traceModels/traceTree.tsx index 2dc8c4470451f1..3629994748901c 100644 --- a/static/app/views/performance/newTraceDetails/traceModels/traceTree.tsx +++ b/static/app/views/performance/newTraceDetails/traceModels/traceTree.tsx @@ -459,6 +459,11 @@ export class TraceTree extends TraceTreeEventDispatcher { }); tree.eap_spans_count++; + + // We only want to add transactions as profiled events. + if ((node as EapSpanNode).value.is_transaction && node.profiles.size > 0) { + tree.profiled_events.add(node); + } } else if (isUptimeCheck(value)) { node = new UptimeCheckNode(parent, value, { organization: options.organization, @@ -475,6 +480,11 @@ export class TraceTree extends TraceTreeEventDispatcher { replayTraceSlug: options.replayTraceSlug, meta: options.meta, }); + + // We only want to add transactions as profiled events. + if (node.profiles.size > 0) { + tree.profiled_events.add(node); + } } if (node.canFetchChildren || !node.expanded) { @@ -507,10 +517,6 @@ export class TraceTree extends TraceTreeEventDispatcher { traceNode.occurrences.add(occurrence); } - if (c.profiles.size > 0) { - tree.profiled_events.add(c); - } - if (c.value && 'measurements' in c.value) { tree.indicators = tree.indicators.concat( collectTraceMeasurements( @@ -626,6 +632,8 @@ export class TraceTree extends TraceTreeEventDispatcher { if (options.preferences) { TraceTree.ApplyPreferences(node, options); } + + this.build(); } } @@ -842,7 +850,9 @@ export class TraceTree extends TraceTreeEventDispatcher { throw new Error('Parent node is missing, this should be unreachable code'); } - const children = node.parent.directVisibleChildren; + const children = isParentAutogroupedNode(node.parent) + ? node.parent.tail.children + : node.parent.children; const index = children.indexOf(node); if (index === -1) { throw new Error('Node is not a child of its parent'); @@ -912,7 +922,7 @@ export class TraceTree extends TraceTreeEventDispatcher { queue.push(...node.getNextTraversalNodes()); - if (node.children.length < 5) { + if (node.children.length < 5 || !node.canAutogroup) { continue; } diff --git a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode.tsx b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode.tsx index 3bb4f6931e7f62..60e5f0afd2f811 100644 --- a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode.tsx +++ b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode.tsx @@ -75,6 +75,11 @@ export abstract class BaseNode boolean): BaseNode | null { + findChild( + predicate: (child: BaseNode) => boolean + ): NodeType | null { const queue: BaseNode[] = [...this.getNextTraversalNodes()]; while (queue.length > 0) { const next = queue.pop()!; if (predicate(next)) { - return next; + return next as NodeType; } const children = next.getNextTraversalNodes(); @@ -400,11 +407,13 @@ export abstract class BaseNode boolean): BaseNode | null { + findParent( + predicate: (parent: BaseNode) => boolean + ): NodeType | null { let current = this.parent; while (current) { if (predicate(current)) { - return current; + return current as NodeType; } current = current.parent; } diff --git a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/collapsedNode.tsx b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/collapsedNode.tsx index c25efb88bcab7f..92abaf70a96569 100644 --- a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/collapsedNode.tsx +++ b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/collapsedNode.tsx @@ -22,8 +22,6 @@ export class CollapsedNode extends BaseNode { this.id = uuid4(); this.type = 'collapsed'; this.canShowDetails = false; - - this.parent?.children.push(this); } get drawerTabsTitle(): string { @@ -45,7 +43,7 @@ export class CollapsedNode extends BaseNode { renderWaterfallRow( props: TraceRowProps ): React.ReactNode { - return ; + return ; } renderDetails>( diff --git a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/eapSpanNode.tsx b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/eapSpanNode.tsx index da3e03da53d224..6b9dcfbc6ae3fe 100644 --- a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/eapSpanNode.tsx +++ b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/eapSpanNode.tsx @@ -35,9 +35,8 @@ export class EapSpanNode extends BaseNode { ) { // For eap transactions, on load we only display the embedded transactions children. // Mimics the behavior of non-eap traces, enabling a less noisy/summarized view of the trace - const parentNode = value.is_transaction - ? (parent?.findParent(p => isEAPTransaction(p.value)) ?? parent) - : parent; + const eapParent = parent?.findParent(p => isEAPTransaction(p.value)); + const parentNode = value.is_transaction ? (eapParent ?? parent) : parent; super(parentNode, value, extra); @@ -51,17 +50,17 @@ export class EapSpanNode extends BaseNode { this.allowNoInstrumentationNodes = !value.is_transaction; if (!value.is_transaction) { - if (this.parent) { + if (eapParent) { // Propagate errors to the closest EAP transaction for visibility in the initially collapsed // eap-transactions only view, on load for (const error of value.errors) { - this.parent.errors.add(error); + eapParent.errors.add(error); } // Propagate occurrences to the closest EAP transaction for visibility in the initially collapsed // eap-transactions only view, on load for (const occurrence of value.occurrences) { - this.parent.occurrences.add(occurrence); + eapParent.occurrences.add(occurrence); } } } @@ -250,15 +249,13 @@ export class EapSpanNode extends BaseNode { renderWaterfallRow( props: TraceRowProps ): React.ReactNode { - return ; + return ; } renderDetails>( props: TraceTreeNodeDetailsProps ): React.ReactNode { - return ( - } /> - ); + return ; } matchWithFreeText(query: string): boolean { diff --git a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/errorNode.tsx b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/errorNode.tsx index 1384ccc660b706..34d2cbd5d7cbd8 100644 --- a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/errorNode.tsx +++ b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/errorNode.tsx @@ -42,6 +42,10 @@ export class ErrorNode extends BaseNode { this.parent?.children.push(this); } + get startTimestamp(): number { + return this.space[0]; + } + get description(): string | undefined { return isTraceError(this.value) ? this.value.title || this.value.message @@ -73,22 +77,13 @@ export class ErrorNode extends BaseNode { renderWaterfallRow( props: TraceRowProps ): React.ReactNode { - return ; + return ; } renderDetails>( props: TraceTreeNodeDetailsProps ): React.ReactNode { - return ( - - | TraceTreeNode - } - /> - ); + return ; } matchWithFreeText(query: string): boolean { diff --git a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/noInstrumentationNode.tsx b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/noInstrumentationNode.tsx index 331c21b6eb3410..171590f556bbc5 100644 --- a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/noInstrumentationNode.tsx +++ b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/noInstrumentationNode.tsx @@ -4,7 +4,6 @@ import {uuid4} from '@sentry/core'; import {t} from 'sentry/locale'; import {MissingInstrumentationNodeDetails} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/missingInstrumentation'; import type {TraceTreeNodeDetailsProps} from 'sentry/views/performance/newTraceDetails/traceDrawer/tabs/traceTreeNodeDetails'; -import type {MissingInstrumentationNode} from 'sentry/views/performance/newTraceDetails/traceModels/missingInstrumentationNode'; import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; import {TraceMissingInstrumentationRow} from 'sentry/views/performance/newTraceDetails/traceRow/traceMissingInstrumentationRow'; @@ -66,19 +65,14 @@ export class NoInstrumentationNode extends BaseNode( props: TraceRowProps ): React.ReactNode { - return ; + return ; } renderDetails>( props: TraceTreeNodeDetailsProps ): React.ReactNode { // Won't need this cast once we use BaseNode type for props.node - return ( - - ); + return ; } matchById(id: string): boolean { diff --git a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/parentAutogroupNode.tsx b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/parentAutogroupNode.tsx index abf08709cd8cf9..4103684ffc3597 100644 --- a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/parentAutogroupNode.tsx +++ b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/parentAutogroupNode.tsx @@ -3,7 +3,6 @@ import type {Theme} from '@emotion/react'; import {t} from 'sentry/locale'; import {AutogroupNodeDetails} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/autogroup'; import type {TraceTreeNodeDetailsProps} from 'sentry/views/performance/newTraceDetails/traceDrawer/tabs/traceTreeNodeDetails'; -import type {ParentAutogroupNode as LegacyParentAutogroupNode} from 'sentry/views/performance/newTraceDetails/traceModels/parentAutogroupNode'; import {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; import {TraceAutogroupedRow} from 'sentry/views/performance/newTraceDetails/traceRow/traceAutogroupedRow'; @@ -35,6 +34,7 @@ export class ParentAutogroupNode extends BaseNode { this.id = this.head.id || this.tail.id; this.type = 'ag'; this.expanded = false; + this.canAutoExpandOnLoad = false; } get autogroupedSegments(): Array<[number, number]> { @@ -75,18 +75,13 @@ export class ParentAutogroupNode extends BaseNode { renderWaterfallRow( props: TraceRowProps ): React.ReactNode { - return ; + return ; } renderDetails>( props: TraceTreeNodeDetailsProps ): React.ReactNode { - return ( - - ); + return ; } matchWithFreeText(query: string): boolean { diff --git a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/siblingAutogroupNode.tsx b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/siblingAutogroupNode.tsx index 72b5557919268f..2078138849627e 100644 --- a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/siblingAutogroupNode.tsx +++ b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/siblingAutogroupNode.tsx @@ -31,6 +31,7 @@ export class SiblingAutogroupNode extends BaseNode { this.type = 'ag'; this.expanded = false; + this.canAutoExpandOnLoad = false; } get op(): string { @@ -75,13 +76,13 @@ export class SiblingAutogroupNode extends BaseNode { renderWaterfallRow( props: TraceRowProps ): React.ReactNode { - return ; + return ; } renderDetails>( props: TraceTreeNodeDetailsProps ): React.ReactNode { - return ; + return ; } matchById(_id: string): boolean { diff --git a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/spanNode.tsx b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/spanNode.tsx index 8c6feb5f0f1758..752a71596ee358 100644 --- a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/spanNode.tsx +++ b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/spanNode.tsx @@ -49,6 +49,12 @@ export class SpanNode extends BaseNode { return this.value.description; } + get projectSlug(): string { + // The span value does not have a project slug, so we need to find a parent that has one. + // If we don't find one, we return the default project slug. + return this.findParent(p => !!p.projectSlug)?.projectSlug ?? 'default'; + } + get endTimestamp(): number { return this.value.timestamp; } @@ -94,15 +100,13 @@ export class SpanNode extends BaseNode { renderWaterfallRow( props: TraceRowProps ): React.ReactNode { - return ; + return ; } renderDetails>( props: TraceTreeNodeDetailsProps ): React.ReactNode { - return ( - } /> - ); + return ; } matchWithFreeText(query: string): boolean { diff --git a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/traceNode.tsx b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/traceNode.tsx index 59864978153964..b7fc2da3116a24 100644 --- a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/traceNode.tsx +++ b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/traceNode.tsx @@ -53,7 +53,7 @@ export class TraceNode extends BaseNode { renderWaterfallRow( props: TraceRowProps ): React.ReactNode { - return ; + return ; } renderDetails>( diff --git a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/transactionNode.tsx b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/transactionNode.tsx index 09f58c3c378f33..561c684da58e9e 100644 --- a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/transactionNode.tsx +++ b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/transactionNode.tsx @@ -179,7 +179,7 @@ export class TransactionNode extends BaseNode { } renderWaterfallRow(props: TraceRowProps): React.ReactNode { - return ; + return ; } renderDetails>( @@ -189,7 +189,7 @@ export class TransactionNode extends BaseNode { } + node={this} /> ); } @@ -432,6 +432,10 @@ export class TransactionNode extends BaseNode { if (index !== -1) { tree.list.splice(index + 1, 0, ...this.visibleChildren); } + + this.invalidate(); + this.forEachChild(child => child.invalidate()); + return data; }) .catch(_e => { diff --git a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/uptimeCheckNode.tsx b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/uptimeCheckNode.tsx index daac7027e7e90b..958c3245f2bf64 100644 --- a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/uptimeCheckNode.tsx +++ b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/uptimeCheckNode.tsx @@ -141,18 +141,13 @@ export class UptimeCheckNode extends BaseNode { renderWaterfallRow( props: TraceRowProps ): React.ReactNode { - return ; + return ; } renderDetails>( props: TraceTreeNodeDetailsProps ): React.ReactNode { - return ( - } - /> - ); + return ; } matchWithFreeText(query: string): boolean { diff --git a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/uptimeCheckTimingNode.tsx b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/uptimeCheckTimingNode.tsx index cf1375565ae82b..50910c501e4ab8 100644 --- a/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/uptimeCheckTimingNode.tsx +++ b/static/app/views/performance/newTraceDetails/traceModels/traceTreeNode/uptimeCheckTimingNode.tsx @@ -33,13 +33,7 @@ export class UptimeCheckTimingNode extends BaseNode renderWaterfallRow( props: TraceRowProps ): React.ReactNode { - return ( - } - /> - ); + return ; } renderDetails>( diff --git a/static/app/views/performance/newTraceDetails/traceRenderers/traceVirtualizedList.tsx b/static/app/views/performance/newTraceDetails/traceRenderers/traceVirtualizedList.tsx index 3cff7d64aed63e..9869f77a1a6621 100644 --- a/static/app/views/performance/newTraceDetails/traceRenderers/traceVirtualizedList.tsx +++ b/static/app/views/performance/newTraceDetails/traceRenderers/traceVirtualizedList.tsx @@ -1,8 +1,7 @@ import {useLayoutEffect, useRef, useState} from 'react'; import {requestAnimationTimeout} from 'sentry/utils/profiling/hooks/useVirtualizedTree/virtualizedTreeUtils'; -import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {BaseNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode'; import type {TraceScheduler} from 'sentry/views/performance/newTraceDetails/traceRenderers/traceScheduler'; import { VirtualizedList, @@ -11,13 +10,13 @@ import { export interface VirtualizedRow { index: number; - item: TraceTreeNode; + item: BaseNode; key: number; style: React.CSSProperties; } interface UseVirtualizedListProps { container: HTMLElement | null; - items: ReadonlyArray>; + items: readonly BaseNode[]; manager: VirtualizedViewManager; render: (item: VirtualizedRow) => React.ReactNode; scheduler: TraceScheduler; @@ -61,7 +60,7 @@ export const useVirtualizedList = ( const renderRef = useRef<(item: VirtualizedRow) => React.ReactNode>(props.render); renderRef.current = props.render; - const itemsRef = useRef>>(props.items); + const itemsRef = useRef(props.items); itemsRef.current = props.items; const managerRef = useRef(props.manager); managerRef.current = props.manager; @@ -268,7 +267,7 @@ function findRenderedItems({ render, manager, }: { - items: ReadonlyArray>; + items: readonly BaseNode[]; manager: VirtualizedViewManager; overscroll: number; render: (arg: VirtualizedRow) => React.ReactNode; @@ -352,7 +351,7 @@ function findOptimisticStartIndex({ scrollTop, viewport, }: { - items: ReadonlyArray>; + items: readonly BaseNode[]; overscroll: number; rowHeight: number; scrollTop: number; diff --git a/static/app/views/performance/newTraceDetails/traceRenderers/virtualizedViewManager.tsx b/static/app/views/performance/newTraceDetails/traceRenderers/virtualizedViewManager.tsx index 12e1e65e201246..8b52ed03c8081d 100644 --- a/static/app/views/performance/newTraceDetails/traceRenderers/virtualizedViewManager.tsx +++ b/static/app/views/performance/newTraceDetails/traceRenderers/virtualizedViewManager.tsx @@ -14,7 +14,7 @@ import { isMissingInstrumentationNode, } from 'sentry/views/performance/newTraceDetails/traceGuards'; import {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree'; -import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; +import type {BaseNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/baseNode'; import {TraceRowWidthMeasurer} from 'sentry/views/performance/newTraceDetails/traceRenderers/traceRowWidthMeasurer'; import {TraceTextMeasurer} from 'sentry/views/performance/newTraceDetails/traceRenderers/traceTextMeasurer'; import type {TraceView} from 'sentry/views/performance/newTraceDetails/traceRenderers/traceView'; @@ -36,7 +36,7 @@ function getHorizontalDelta(x: number, y: number): number { } type ViewColumn = { - column_nodes: Array>; + column_nodes: BaseNode[]; column_refs: Array; translate: [number, number]; width: number; @@ -54,8 +54,7 @@ type VerticalIndicator = { export type ViewManagerScrollAnchor = 'top' | 'center if outside' | 'center'; export class VirtualizedViewManager { - row_measurer: TraceRowWidthMeasurer> = - new TraceRowWidthMeasurer(); + row_measurer: TraceRowWidthMeasurer = new TraceRowWidthMeasurer(); indicator_label_measurer: TraceRowWidthMeasurer = new TraceRowWidthMeasurer(); text_measurer: TraceTextMeasurer; @@ -370,7 +369,7 @@ export class VirtualizedViewManager { column: string, ref: HTMLElement | null, index: number, - node: TraceTreeNode + node: BaseNode ) { if (column === 'list' && ref) { const scrollableElement = ref.children[0] as HTMLElement | undefined; @@ -949,7 +948,7 @@ export class VirtualizedViewManager { const translation = this.columns.list.translate[0]; let min = Number.POSITIVE_INFINITY; let max = Number.NEGATIVE_INFINITY; - let innerMostNode: TraceTreeNode | undefined; + let innerMostNode: BaseNode | undefined; for (let i = 5; i < this.columns.span_list.column_refs.length - 5; i++) { const width = this.row_measurer.cache.get(this.columns.list.column_nodes[i]!); @@ -980,7 +979,7 @@ export class VirtualizedViewManager { } } - isOutsideOfView(node: TraceTreeNode): boolean { + isOutsideOfView(node: BaseNode): boolean { const width = this.row_measurer.cache.get(node); if (width === undefined) { @@ -997,7 +996,7 @@ export class VirtualizedViewManager { } scrollRowIntoViewHorizontally( - node: TraceTreeNode, + node: BaseNode, duration = 600, offset_px = 0, position: 'exact' | 'measured' = 'measured' @@ -1129,7 +1128,7 @@ export class VirtualizedViewManager { } computeSpanTextPlacement( - node: TraceTreeNode, + node: BaseNode, span_space: [number, number], text: string ): [number, number] { @@ -1449,7 +1448,7 @@ export class VirtualizedViewManager { ); } - drawSpanText(span_text: this['span_text'][0], node: TraceTreeNode | undefined) { + drawSpanText(span_text: this['span_text'][0], node: BaseNode | undefined) { if (!span_text) { return; } @@ -1689,7 +1688,7 @@ export class VirtualizedViewManager { // of the span to include the icon. We need this because when the icon is close to the edge // it can extend it and cause overlaps with duration labels function getIconTimestamps( - node: TraceTreeNode, + node: BaseNode, span_space: [number, number], icon_width: number ) { diff --git a/static/app/views/performance/newTraceDetails/traceRow/traceAutogroupedRow.tsx b/static/app/views/performance/newTraceDetails/traceRow/traceAutogroupedRow.tsx index 7fa05fc932a8f4..df6d268159e6da 100644 --- a/static/app/views/performance/newTraceDetails/traceRow/traceAutogroupedRow.tsx +++ b/static/app/views/performance/newTraceDetails/traceRow/traceAutogroupedRow.tsx @@ -1,11 +1,8 @@ import {t} from 'sentry/locale'; import {TraceIcons} from 'sentry/views/performance/newTraceDetails/traceIcons'; -import type {ParentAutogroupNode} from 'sentry/views/performance/newTraceDetails/traceModels/parentAutogroupNode'; -import type {SiblingAutogroupNode} from 'sentry/views/performance/newTraceDetails/traceModels/siblingAutogroupNode'; -import { - AutogroupedTraceBar, - makeTraceNodeBarColor, -} from 'sentry/views/performance/newTraceDetails/traceRow/traceBar'; +import type {ParentAutogroupNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/parentAutogroupNode'; +import type {SiblingAutogroupNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode/siblingAutogroupNode'; +import {AutogroupedTraceBar} from 'sentry/views/performance/newTraceDetails/traceRow/traceBar'; import { maybeFocusTraceRow, TRACE_COUNT_FORMATTER, @@ -26,7 +23,7 @@ export function TraceAutogroupedRow( : undefined } tabIndex={props.tabIndex} - className={`Autogrouped TraceRow ${props.rowSearchClassName} ${props.node.hasErrors ? props.node.maxIssueSeverity : ''}`} + className={`Autogrouped TraceRow ${props.rowSearchClassName} ${props.node.errors.size > 0 ? props.node.maxIssueSeverity : ''}`} onPointerDown={props.onRowClick} onKeyDown={props.onRowKeyDown} style={props.style} @@ -68,10 +65,10 @@ export function TraceAutogroupedRow( entire_space={props.node.space} errors={props.node.errors} virtualized_index={props.virtualized_index} - color={makeTraceNodeBarColor(props.theme, props.node)} + color={props.node.makeBarColor(props.theme)} node_spaces={props.node.autogroupedSegments} occurrences={props.node.occurrences} - profiles={props.node.profiles} + profiles={Array.from(props.node.profiles)} />