From ec05e95588181966ded878090b1241e3b3fb2775 Mon Sep 17 00:00:00 2001 From: Abdullah Khan <60121741+Abdkhan14@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:46:25 -0500 Subject: [PATCH] =?UTF-8?q?feat(new-trace):=20Replacing=20trace=20duration?= =?UTF-8?q?=20with=20root=20Duration=20in=20trace=E2=80=A6=20(#81974)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Screenshot 2024-12-11 at 2 03 36 PM --------- Co-authored-by: Abdullah Khan --- .../newTraceDetails/traceHeader/index.tsx | 53 +++++++++++++++- .../newTraceDetails/traceHeader/meta.tsx | 12 +++- .../newTraceDetails/traceHeader/title.tsx | 62 +++---------------- .../traceRow/traceRootNode.tsx | 41 +++++++----- 4 files changed, 93 insertions(+), 75 deletions(-) diff --git a/static/app/views/performance/newTraceDetails/traceHeader/index.tsx b/static/app/views/performance/newTraceDetails/traceHeader/index.tsx index 11e24a96029a1d..6c381fc5cd12ed 100644 --- a/static/app/views/performance/newTraceDetails/traceHeader/index.tsx +++ b/static/app/views/performance/newTraceDetails/traceHeader/index.tsx @@ -24,8 +24,10 @@ import {useModuleURLBuilder} from 'sentry/views/insights/common/utils/useModuleU import {useDomainViewFilters} from 'sentry/views/insights/pages/useFilters'; import {useTraceStateDispatch} from 'sentry/views/performance/newTraceDetails/traceState/traceStateProvider'; +import {isRootTransaction} from '../../traceDetails/utils'; import type {TraceMetaQueryResults} from '../traceApi/useTraceMeta'; import TraceConfigurations from '../traceConfigurations'; +import {isTraceNode} from '../traceGuards'; import type {TraceTree} from '../traceModels/traceTree'; import {useHasTraceNewUi} from '../useHasTraceNewUi'; @@ -102,6 +104,49 @@ const StyledPlaceholder = styled(Placeholder)<{_height: number; _width: number}> width: ${p => p._width}px; `; +const CANDIDATE_TRACE_TITLE_OPS = ['pageload', 'navigation']; + +export const getRepresentativeTransaction = ( + tree: TraceTree +): TraceTree.Transaction | null => { + const traceNode = tree.root.children[0]; + + if (!traceNode) { + return null; + } + + if (!isTraceNode(traceNode)) { + throw new TypeError('Not trace node'); + } + + let firstRootTransaction: TraceTree.Transaction | null = null; + let candidateTransaction: TraceTree.Transaction | null = null; + let firstTransaction: TraceTree.Transaction | null = null; + + for (const transaction of traceNode.value.transactions || []) { + // If we find a root transaction, we can stop looking and use it for the title. + if (!firstRootTransaction && isRootTransaction(transaction)) { + firstRootTransaction = transaction; + break; + } else if ( + // If we haven't found a root transaction, but we found a candidate transaction + // with an op that we care about, we can use it for the title. We keep looking for + // a root. + !candidateTransaction && + CANDIDATE_TRACE_TITLE_OPS.includes(transaction['transaction.op']) + ) { + candidateTransaction = transaction; + continue; + } else if (!firstTransaction) { + // If we haven't found a root or candidate transaction, we can use the first transaction + // in the trace for the title. + firstTransaction = transaction; + } + } + + return firstRootTransaction ?? candidateTransaction ?? firstTransaction; +}; + function LegacyTraceMetadataHeader(props: TraceMetadataHeaderProps) { const location = useLocation(); const {view} = useDomainViewFilters(); @@ -181,6 +226,8 @@ export function TraceMetaDataHeader(props: TraceMetadataHeaderProps) { return ; } + const representativeTransaction = getRepresentativeTransaction(props.tree); + return ( @@ -195,12 +242,16 @@ export function TraceMetaDataHeader(props: TraceMetadataHeaderProps) { /> - + <Title + traceSlug={props.traceSlug} + representativeTransaction={representativeTransaction} + /> <Meta organization={props.organization} rootEventResults={props.rootEventResults} tree={props.tree} meta={props.metaResults.data} + representativeTransaction={representativeTransaction} /> </HeaderRow> <StyledBreak /> diff --git a/static/app/views/performance/newTraceDetails/traceHeader/meta.tsx b/static/app/views/performance/newTraceDetails/traceHeader/meta.tsx index ae721071709ed7..477dc48cb487a8 100644 --- a/static/app/views/performance/newTraceDetails/traceHeader/meta.tsx +++ b/static/app/views/performance/newTraceDetails/traceHeader/meta.tsx @@ -52,6 +52,7 @@ const SectionBody = styled('div')<{rightAlign?: boolean}>` interface MetaProps { meta: TraceMeta | undefined; organization: Organization; + representativeTransaction: TraceTree.Transaction | null; rootEventResults: UseApiQueryResult<EventTransaction, RequestError>; tree: TraceTree; } @@ -134,11 +135,16 @@ export function Meta(props: MetaProps) { ) : null} {traceNode ? ( <MetaSection - headingText={t('Trace Duration')} + headingText={t('Root Duration')} rightAlignBody bodyText={ - traceNode.space[1] > 0 - ? getDuration(traceNode.space[1] / 1e3, 2, true) + props.representativeTransaction + ? getDuration( + props.representativeTransaction.timestamp - + props.representativeTransaction.start_timestamp, + 2, + true + ) : '\u2014' } /> diff --git a/static/app/views/performance/newTraceDetails/traceHeader/title.tsx b/static/app/views/performance/newTraceDetails/traceHeader/title.tsx index e13ad4a635d0fd..6d96899b7ed7b2 100644 --- a/static/app/views/performance/newTraceDetails/traceHeader/title.tsx +++ b/static/app/views/performance/newTraceDetails/traceHeader/title.tsx @@ -1,4 +1,4 @@ -import {Fragment, useMemo} from 'react'; +import {Fragment} from 'react'; import styled from '@emotion/styled'; import {CopyToClipboardButton} from 'sentry/components/copyToClipboardButton'; @@ -6,66 +6,20 @@ import ExternalLink from 'sentry/components/links/externalLink'; import {Tooltip} from 'sentry/components/tooltip'; import {t, tct} from 'sentry/locale'; -import {isRootTransaction} from '../../traceDetails/utils'; -import {isTraceNode} from '../traceGuards'; import type {TraceTree} from '../traceModels/traceTree'; -const CANDIDATE_TRACE_TITLE_OPS = ['pageload', 'navigation']; - -type TraceTitle = { - op: string; - transaction?: string; -} | null; - interface TitleProps { + representativeTransaction: TraceTree.Transaction | null; traceSlug: string; - tree: TraceTree; } -export function Title({traceSlug, tree}: TitleProps) { - const traceTitle: TraceTitle = useMemo(() => { - const trace = tree.root.children[0]; - - if (!trace) { - return null; - } - - if (!isTraceNode(trace)) { - throw new TypeError('Not trace node'); - } - - let firstRootTransaction: TraceTitle = null; - let candidateTransaction: TraceTitle = null; - let firstTransaction: TraceTitle = null; - - for (const transaction of trace.value.transactions || []) { - const title = { - op: transaction['transaction.op'], - transaction: transaction.transaction, - }; - - // If we find a root transaction, we can stop looking and use it for the title. - if (!firstRootTransaction && isRootTransaction(transaction)) { - firstRootTransaction = title; - break; - } else if ( - // If we haven't found a root transaction, but we found a candidate transaction - // with an op that we care about, we can use it for the title. We keep looking for - // a root. - !candidateTransaction && - CANDIDATE_TRACE_TITLE_OPS.includes(transaction['transaction.op']) - ) { - candidateTransaction = title; - continue; - } else if (!firstTransaction) { - // If we haven't found a root or candidate transaction, we can use the first transaction - // in the trace for the title. - firstTransaction = title; +export function Title({traceSlug, representativeTransaction}: TitleProps) { + const traceTitle = representativeTransaction + ? { + op: representativeTransaction['transaction.op'], + transaction: representativeTransaction.transaction, } - } - - return firstRootTransaction ?? candidateTransaction ?? firstTransaction; - }, [tree.root.children]); + : null; return ( <div> diff --git a/static/app/views/performance/newTraceDetails/traceRow/traceRootNode.tsx b/static/app/views/performance/newTraceDetails/traceRow/traceRootNode.tsx index 61a51c4c8e28d5..0112bb5b0bd521 100644 --- a/static/app/views/performance/newTraceDetails/traceRow/traceRootNode.tsx +++ b/static/app/views/performance/newTraceDetails/traceRow/traceRootNode.tsx @@ -14,12 +14,15 @@ import { TraceRowConnectors, type TraceRowProps, } from '../traceRow/traceRow'; +import {useHasTraceNewUi} from '../useHasTraceNewUi'; const NO_ERRORS = new Set<TraceTree.TraceError>(); const NO_PERFORMANCE_ISSUES = new Set<TraceTree.TracePerformanceIssue>(); const NO_PROFILES = []; export function TraceRootRow(props: TraceRowProps<TraceTreeNode<TraceTree.Trace>>) { + const hasTraceNewUi = useHasTraceNewUi(); + if (!isTraceNode(props.node)) { throw new Error('Trace row rendered called on row that is not root'); } @@ -77,23 +80,27 @@ export function TraceRootRow(props: TraceRowProps<TraceTreeNode<TraceTree.Trace> className={props.spanColumnClassName} onDoubleClick={props.onRowDoubleClick} > - <TraceBar - node={props.node} - virtualized_index={props.virtualized_index} - manager={props.manager} - color={makeTraceNodeBarColor(props.theme, props.node)} - node_space={props.node.space} - errors={NO_ERRORS} - performance_issues={NO_PERFORMANCE_ISSUES} - profiles={NO_PROFILES} - /> - <button - ref={props.registerSpanArrowRef} - className="TraceArrow" - onClick={props.onSpanArrowClick} - > - <TraceIcons.Chevron direction="left" /> - </button> + {!hasTraceNewUi && ( + <Fragment> + <TraceBar + node={props.node} + virtualized_index={props.virtualized_index} + manager={props.manager} + color={makeTraceNodeBarColor(props.theme, props.node)} + node_space={props.node.space} + errors={NO_ERRORS} + performance_issues={NO_PERFORMANCE_ISSUES} + profiles={NO_PROFILES} + /> + <button + ref={props.registerSpanArrowRef} + className="TraceArrow" + onClick={props.onSpanArrowClick} + > + <TraceIcons.Chevron direction="left" /> + </button> + </Fragment> + )} </div> </div> );