Skip to content

Commit 87847a8

Browse files
committed
fix: probe trace endpoint so 'View full trace' link shows when transaction exists
The trace context block was rendered with a synthetic summary that had zero spans, so the link-visibility guard hid 'View full trace' even when a transaction was captured for that trace_id. Fetch /api/sentry/traces/ {traceId} on mount: populate the real span_count / duration_ms / preview spans when present (link renders), keep the synthetic minimal summary on 404 (link stays hidden, no broken navigation).
1 parent db33095 commit 87847a8

1 file changed

Lines changed: 85 additions & 11 deletions

File tree

src/entities/sentry/ui/sentry-page/sentry-page.vue

Lines changed: 85 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
<script lang="ts" setup>
22
import moment from 'moment'
3-
import { computed, ref } from 'vue'
3+
import { computed, ref, watch } from 'vue'
44
import type { NormalizedEvent } from '@/shared/types'
55
import { EventDetailLayout, PageTabs, PageTab } from '@/shared/ui'
6-
import type { Sentry } from '../../types'
6+
import { useSentryRequests } from '../../lib/use-sentry-requests'
7+
import type { Sentry, SentryTraceSummary } from '../../types'
78
import { SentryException } from '../sentry-exception'
89
import { SentryPageApp } from '../sentry-page-app'
910
import { SentryPageBreadcrumbs } from '../sentry-page-breadcrumbs'
@@ -79,6 +80,86 @@ const scrollToException = (idx: number) => {
7980
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
8081
}
8182
}
83+
84+
// Trace summary for the "Trace context" block.
85+
//
86+
// The canonical event payload carries `contexts.trace.trace_id` but no
87+
// information about whether a transaction was captured for that trace. We
88+
// probe /api/sentry/traces/{traceId} on mount: when it returns we get real
89+
// span counts/durations and the "View full trace" link can render; when it
90+
// 404s we keep a minimal synthetic summary and the link stays hidden (the
91+
// detail page would 404 anyway).
92+
const traceId = computed(() => {
93+
const id = props.event.payload.contexts?.trace?.trace_id
94+
return typeof id === 'string' ? id : ''
95+
})
96+
97+
const traceOp = computed(() => {
98+
const op = props.event.payload.contexts?.trace?.op
99+
return typeof op === 'string' ? op : ''
100+
})
101+
102+
// SentrySpanPreview narrows peer_type to a fixed set of categories. Map
103+
// anything else from the API response to null so the type checker is happy.
104+
const normalizePeerType = (
105+
v: string | null | undefined
106+
): 'db' | 'http' | 'cache' | 'queue' | null => {
107+
if (v === 'db' || v === 'http' || v === 'cache' || v === 'queue') return v
108+
return null
109+
}
110+
111+
const buildSyntheticSummary = (): SentryTraceSummary => ({
112+
trace_id: traceId.value,
113+
transaction_name: props.event.payload.transaction ?? '',
114+
op: traceOp.value,
115+
duration_ms: 0,
116+
span_count: 0,
117+
preview_spans: []
118+
})
119+
120+
const traceSummary = ref<SentryTraceSummary | null>(null)
121+
const { getTraceDetail } = useSentryRequests()
122+
123+
const loadTraceSummary = async (id: string) => {
124+
if (!id) {
125+
traceSummary.value = null
126+
return
127+
}
128+
// Default to the synthetic summary so the block still shows the trace id
129+
// even if the probe fails.
130+
traceSummary.value = buildSyntheticSummary()
131+
try {
132+
const detail = await getTraceDetail(id)
133+
const spans = detail.spans ?? []
134+
traceSummary.value = {
135+
trace_id: detail.trace_id,
136+
transaction_name:
137+
detail.transaction?.transaction_name ?? props.event.payload.transaction ?? '',
138+
op: detail.transaction?.op ?? traceOp.value,
139+
duration_ms: detail.transaction?.duration_ms ?? 0,
140+
span_count: spans.length,
141+
preview_spans: spans.slice(0, 5).map((s) => ({
142+
span_id: s.span_id,
143+
op: s.op ?? '',
144+
description: s.description ?? '',
145+
start_offset_ms: s.start_offset_ms ?? 0,
146+
duration_ms: s.duration_ms ?? 0,
147+
peer_type: normalizePeerType(s.peer_type),
148+
is_error: s.is_error ?? false
149+
}))
150+
}
151+
} catch {
152+
// 404 / network error → synthetic summary already in place, link hidden.
153+
}
154+
}
155+
156+
watch(
157+
traceId,
158+
(id) => {
159+
loadTraceSummary(id)
160+
},
161+
{ immediate: true }
162+
)
82163
</script>
83164

84165
<template>
@@ -235,15 +316,8 @@ const scrollToException = (idx: number) => {
235316
<SentryPageTags :payload="event.payload" />
236317

237318
<TraceContextBlock
238-
v-if="hasTraceContext && event.payload.contexts?.trace"
239-
:trace-summary="{
240-
trace_id: String(event.payload.contexts!.trace!.trace_id || ''),
241-
transaction_name: String(event.payload.transaction || ''),
242-
op: String(event.payload.contexts!.trace!.op || ''),
243-
duration_ms: 0,
244-
span_count: 0,
245-
preview_spans: []
246-
}"
319+
v-if="hasTraceContext && traceSummary"
320+
:trace-summary="traceSummary"
247321
/>
248322

249323
<SentryPageApp

0 commit comments

Comments
 (0)