diff --git a/static/app/components/profiling/flamegraph/continuousFlamegraph.tsx b/static/app/components/profiling/flamegraph/continuousFlamegraph.tsx index 09128b671bbc95..a52084c3a8ec01 100644 --- a/static/app/components/profiling/flamegraph/continuousFlamegraph.tsx +++ b/static/app/components/profiling/flamegraph/continuousFlamegraph.tsx @@ -334,7 +334,11 @@ export function ContinuousFlamegraph(): ReactElement { return profileGroup.profiles.find(p => p.threadId === flamegraphProfiles.threadId); }, [profileGroup, flamegraphProfiles.threadId]); - const spanTree: SpanTree = useMemo(() => { + const spanTree: SpanTree | null = useMemo(() => { + if (segment.type === 'empty') { + return null; + } + if (segment.type === 'resolved' && segment.data) { return new SpanTree( segment.data, @@ -346,7 +350,7 @@ export function ContinuousFlamegraph(): ReactElement { }, [segment]); const spanChart = useMemo(() => { - if (!profile) { + if (!profile || !spanTree) { return null; } diff --git a/static/app/types/core.tsx b/static/app/types/core.tsx index e0ee2d9c2715d2..999c4ebc56fd68 100644 --- a/static/app/types/core.tsx +++ b/static/app/types/core.tsx @@ -189,6 +189,8 @@ export type PageFilters = { projects: number[]; }; +type EmptyState = {type: 'empty'}; + type InitialState = {type: 'initial'}; type LoadingState = {type: 'loading'}; @@ -204,6 +206,7 @@ type ErroredState = { }; export type RequestState = + | EmptyState | InitialState | LoadingState | ResolvedState diff --git a/static/app/utils/profiling/hooks/useSentryEvent.tsx b/static/app/utils/profiling/hooks/useSentryEvent.tsx index 0f517470430ec7..cd05b4b6bb9cf6 100644 --- a/static/app/utils/profiling/hooks/useSentryEvent.tsx +++ b/static/app/utils/profiling/hooks/useSentryEvent.tsx @@ -19,7 +19,8 @@ function fetchSentryEvent( export function useSentryEvent( organizationSlug: string, projectSlug: string, - eventId: string | null + eventId: string | null, + disabled?: boolean ): RequestState { const api = useApi(); const [requestState, setRequestState] = useState>({ @@ -27,7 +28,7 @@ export function useSentryEvent( }); useLayoutEffect(() => { - if (eventId === null || !projectSlug || !organizationSlug) { + if (disabled || !eventId || !projectSlug || !organizationSlug) { return undefined; } @@ -47,7 +48,7 @@ export function useSentryEvent( return () => { api.clear(); }; - }, [api, organizationSlug, projectSlug, eventId]); + }, [api, organizationSlug, projectSlug, eventId, disabled]); - return requestState; + return disabled ? {type: 'empty'} : requestState; } diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceProfiles.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceProfiles.tsx index 1a6066d029f788..ebea731772e811 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 {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode'; export function TraceProfiles({tree}: {tree: TraceTree}) { const {projects} = useProjects(); @@ -65,22 +66,7 @@ export function TraceProfiles({tree}: {tree: TraceTree}) { return null; } - const threadId = isEAPSpanNode(node) - ? (node.value.additional_attributes?.['thread.id'] ?? undefined) - : undefined; - const tid = typeof threadId === 'string' ? threadId : undefined; - - const query = isTransactionNode(node) - ? { - eventId: node.value.event_id, - tid, - } - : isSpanNode(node) - ? { - eventId: TraceTree.ParentTransaction(node)?.value?.event_id, - tid, - } - : {tid}; + const query = getProfileRouteQueryFromNode(node); const link = 'profiler_id' in profile @@ -158,6 +144,23 @@ export function TraceProfiles({tree}: {tree: TraceTree}) { ); } +function getProfileRouteQueryFromNode(node: TraceTreeNode) { + if (isTransactionNode(node)) { + return {eventId: node.value.event_id}; + } + if (isSpanNode(node)) { + return {eventId: TraceTree.ParentTransaction(node)?.value?.event_id}; + } + if (isEAPSpanNode(node)) { + const threadId = node.value.additional_attributes?.['thread.id'] ?? undefined; + return { + eventId: node.value.transaction_id, + tid: typeof threadId === 'string' ? threadId : undefined, + }; + } + return {}; +} + const ProfilesTable = styled('div')` display: grid !important; grid-template-columns: 1fr min-content; diff --git a/static/app/views/profiling/continuousProfileProvider.tsx b/static/app/views/profiling/continuousProfileProvider.tsx index 0cd65b12b0068b..abe795aa86712a 100644 --- a/static/app/views/profiling/continuousProfileProvider.tsx +++ b/static/app/views/profiling/continuousProfileProvider.tsx @@ -43,14 +43,17 @@ export default function ProfileAndTransactionProvider(): React.ReactElement { }; }, [location.query.start, location.query.end, location.query.profilerId]); + const eventId = decodeScalar(location.query.eventId) || null; + const [profile, setProfile] = useState>({ - type: 'initial', + type: eventId ? 'initial' : 'empty', }); const profileTransaction = useSentryEvent( organization.slug, projectSlug, - decodeScalar(location.query.eventId) || null + eventId, + !eventId // disable if no event id ); return (