@@ -3,143 +3,149 @@ import styled from '@emotion/styled';
33
44import { ExternalLink , Link } from 'sentry/components/core/link' ;
55import { Tooltip } from 'sentry/components/core/tooltip' ;
6- import type {
7- SpanLink ,
8- TraceContextType ,
9- } from 'sentry/components/events/interfaces/spans/types' ;
106import { normalizeDateTimeParams } from 'sentry/components/organizations/pageFilters/parse' ;
117import { IconChevron } from 'sentry/icons' ;
128import { t , tct } from 'sentry/locale' ;
139import { space } from 'sentry/styles/space' ;
1410import { useLocation } from 'sentry/utils/useLocation' ;
1511import useOrganization from 'sentry/utils/useOrganization' ;
16- import { useTrace } from 'sentry/views/performance/newTraceDetails/traceApi/useTrace' ;
17- import { isEmptyTrace } from 'sentry/views/performance/newTraceDetails/traceApi/utils' ;
18- import { useFindNextTrace } from 'sentry/views/performance/newTraceDetails/traceLinksNavigation/useFindNextTrace' ;
12+ import type { TraceItemResponseAttribute } from 'sentry/views/explore/hooks/useTraceItemDetails' ;
13+ import {
14+ useFindNextTrace ,
15+ useFindPreviousTrace ,
16+ } from 'sentry/views/performance/newTraceDetails/traceLinksNavigation/useFindLinkedTraces' ;
17+ import { useTraceStateDispatch } from 'sentry/views/performance/newTraceDetails/traceState/traceStateProvider' ;
1918import { getTraceDetailsUrl } from 'sentry/views/performance/traceDetails/utils' ;
2019
2120export type ConnectedTraceConnection = 'previous' | 'next' ;
2221
2322const LINKED_TRACE_MAX_DURATION = 3600 ; // 1h in seconds
2423
25- function useIsTraceAvailable (
26- traceID ?: SpanLink [ 'trace_id' ] ,
27- linkedTraceTimestamp ?: number
28- ) : {
29- isAvailable : boolean ;
30- isLoading : boolean ;
31- } {
32- const trace = useTrace ( {
33- traceSlug : traceID ,
34- timestamp : linkedTraceTimestamp ,
35- } ) ;
36-
37- const isAvailable = useMemo ( ( ) => {
38- if ( ! traceID ) {
39- return false ;
40- }
41-
42- return Boolean ( trace . data && ! isEmptyTrace ( trace . data ) ) ;
43- } , [ traceID , trace ] ) ;
44-
45- return {
46- isAvailable,
47- isLoading : trace . isLoading ,
48- } ;
49- }
50-
5124type TraceLinkNavigationButtonProps = {
52- currentTraceTimestamps : { end ?: number ; start ?: number } ;
25+ attributes : TraceItemResponseAttribute [ ] ;
26+ currentTraceStartTimestamp : number ;
5327 direction : ConnectedTraceConnection ;
54- isLoading ?: boolean ;
55- projectID ?: string ;
56- traceContext ?: TraceContextType ;
5728} ;
5829
5930export function TraceLinkNavigationButton ( {
6031 direction,
61- traceContext,
62- isLoading,
63- projectID,
64- currentTraceTimestamps,
32+ attributes,
33+ currentTraceStartTimestamp,
6534} : TraceLinkNavigationButtonProps ) {
6635 const organization = useOrganization ( ) ;
6736 const location = useLocation ( ) ;
6837
6938 // We connect traces over a 1h period - As we don't have timestamps of the linked trace, it is calculated based on this timeframe
70- const linkedTraceTimestamp =
71- direction === 'previous' && currentTraceTimestamps . start
72- ? currentTraceTimestamps . start - LINKED_TRACE_MAX_DURATION // Earliest start time of previous trace (- 1h)
73- : direction === 'next' && currentTraceTimestamps . end
74- ? currentTraceTimestamps . end + LINKED_TRACE_MAX_DURATION // Latest end time of next trace (+ 1h)
75- : undefined ;
76-
77- const previousTraceLink = traceContext ?. links ?. find (
78- link => link . attributes ?. [ 'sentry.link.type' ] === `${ direction } _trace`
79- ) ;
39+ const linkedTraceWindowTimestamp =
40+ direction === 'previous'
41+ ? currentTraceStartTimestamp - LINKED_TRACE_MAX_DURATION // Earliest start time of previous trace (- 1h)
42+ : currentTraceStartTimestamp + LINKED_TRACE_MAX_DURATION ; // Latest end time of next trace (+ 1h)
43+
44+ const {
45+ available : isPreviousTraceAvailable ,
46+ id : previousTraceSpanId ,
47+ trace : previousTraceId ,
48+ sampled : previousTraceSampled ,
49+ isLoading : isPreviousTraceLoading ,
50+ } = useFindPreviousTrace ( {
51+ direction,
52+ previousTraceEndTimestamp : currentTraceStartTimestamp ,
53+ previousTraceStartTimestamp : linkedTraceWindowTimestamp ,
54+ attributes,
55+ } ) ;
8056
81- const nextTraceData = useFindNextTrace ( {
57+ const {
58+ id : nextTraceSpanId ,
59+ trace : nextTraceId ,
60+ isLoading : isNextTraceLoading ,
61+ } = useFindNextTrace ( {
8262 direction,
83- currentTraceID : traceContext ?. trace_id ,
84- linkedTraceStartTimestamp : currentTraceTimestamps . end ,
85- linkedTraceEndTimestamp : linkedTraceTimestamp ,
86- projectID,
63+ nextTraceEndTimestamp : linkedTraceWindowTimestamp ,
64+ nextTraceStartTimestamp : currentTraceStartTimestamp ,
65+ attributes,
8766 } ) ;
8867
8968 const dateSelection = useMemo (
9069 ( ) => normalizeDateTimeParams ( location . query ) ,
9170 [ location . query ]
9271 ) ;
9372
94- const { isAvailable : isLinkedTraceAvailable } = useIsTraceAvailable (
95- direction === 'previous' ? previousTraceLink ?. trace_id : nextTraceData ?. trace_id ,
96- linkedTraceTimestamp
97- ) ;
73+ const traceDispatch = useTraceStateDispatch ( ) ;
9874
99- if ( isLoading ) {
100- // We don't show a placeholder/skeleton here as it would cause layout shifts most of the time.
101- // Most traces don't have a next/previous trace and the hard to avoid layout shift should only occur if the actual button can be shown.
102- return null ;
75+ function closeSpanDetailsDrawer ( ) {
76+ traceDispatch ( {
77+ type : 'minimize drawer' ,
78+ payload : true ,
79+ } ) ;
10380 }
10481
105- if ( previousTraceLink && isLinkedTraceAvailable ) {
106- return (
107- < StyledTooltip
108- position = "right"
109- delay = { 400 }
110- isHoverable
111- title = { tct (
112- `This links to the previous trace within the same session. To learn more, [link:read the docs].` ,
113- {
114- link : (
115- < ExternalLink
116- href = {
117- 'https://docs.sentry.io/concepts/key-terms/tracing/trace-view/#previous-and-next-traces'
118- }
119- />
120- ) ,
121- }
122- ) }
123- >
124- < TraceLink
125- color = "gray500"
126- to = { getTraceDetailsUrl ( {
127- traceSlug : previousTraceLink . trace_id ,
128- spanId : previousTraceLink . span_id ,
129- dateSelection,
130- timestamp : linkedTraceTimestamp ,
131- location,
132- organization,
133- } ) }
82+ if ( direction === 'previous' && previousTraceId && ! isPreviousTraceLoading ) {
83+ if ( isPreviousTraceAvailable ) {
84+ return (
85+ < StyledTooltip
86+ position = "right"
87+ delay = { 400 }
88+ isHoverable
89+ title = { tct (
90+ `This links to the previous trace within the same session. To learn more, [link:read the docs].` ,
91+ {
92+ link : (
93+ < ExternalLink
94+ href = {
95+ 'https://docs.sentry.io/concepts/key-terms/tracing/trace-view/#previous-and-next-traces'
96+ }
97+ />
98+ ) ,
99+ }
100+ ) }
134101 >
135- < IconChevron direction = "left" />
136- < TraceLinkText > { t ( 'Go to Previous Trace' ) } </ TraceLinkText >
137- </ TraceLink >
138- </ StyledTooltip >
139- ) ;
102+ < TraceLink
103+ color = "gray500"
104+ onClick = { ( ) => closeSpanDetailsDrawer ( ) }
105+ to = { getTraceDetailsUrl ( {
106+ traceSlug : previousTraceId ,
107+ spanId : previousTraceSpanId ,
108+ dateSelection,
109+ timestamp : linkedTraceWindowTimestamp ,
110+ location,
111+ organization,
112+ } ) }
113+ >
114+ < IconChevron direction = "left" />
115+ < TraceLinkText > { t ( 'Go to Previous Trace' ) } </ TraceLinkText >
116+ </ TraceLink >
117+ </ StyledTooltip >
118+ ) ;
119+ }
120+
121+ if ( ! previousTraceSampled ) {
122+ return (
123+ < StyledTooltip
124+ position = "right"
125+ title = { t (
126+ 'Trace contains a link to an unsampled trace. Increase traces sample rate in SDK settings to see more connected traces'
127+ ) }
128+ >
129+ < TraceLinkText > { t ( 'Previous trace not sampled' ) } </ TraceLinkText >
130+ </ StyledTooltip >
131+ ) ;
132+ }
133+
134+ if ( ! isPreviousTraceAvailable ) {
135+ return (
136+ < StyledTooltip
137+ position = "right"
138+ title = { t (
139+ 'Trace contains a link to a trace that is not available. This means that the trace was not stored.'
140+ ) }
141+ >
142+ < TraceLinkText > { t ( 'Previous trace not available' ) } </ TraceLinkText >
143+ </ StyledTooltip >
144+ ) ;
145+ }
140146 }
141147
142- if ( nextTraceData ?. trace_id && nextTraceData . span_id && isLinkedTraceAvailable ) {
148+ if ( direction === 'next' && ! isNextTraceLoading && nextTraceId && nextTraceSpanId ) {
143149 return (
144150 < StyledTooltip
145151 position = "left"
@@ -160,11 +166,12 @@ export function TraceLinkNavigationButton({
160166 >
161167 < TraceLink
162168 color = "gray500"
169+ onClick = { closeSpanDetailsDrawer }
163170 to = { getTraceDetailsUrl ( {
164- traceSlug : nextTraceData . trace_id ,
165- spanId : nextTraceData . span_id ,
171+ traceSlug : nextTraceId ,
172+ spanId : nextTraceSpanId ,
166173 dateSelection,
167- timestamp : linkedTraceTimestamp ,
174+ timestamp : linkedTraceWindowTimestamp ,
168175 location,
169176 organization,
170177 } ) }
@@ -176,25 +183,23 @@ export function TraceLinkNavigationButton({
176183 ) ;
177184 }
178185
179- if ( previousTraceLink ?. sampled === false ) {
180- return (
181- < StyledTooltip
182- position = "right"
183- title = { t (
184- 'Trace contains a link to unsampled trace. Increase traces sample rate in SDK settings to see more connected traces'
185- ) }
186- >
187- < TraceLinkText > { t ( 'Previous trace not available' ) } </ TraceLinkText >
188- </ StyledTooltip >
189- ) ;
190- }
186+ // If there's no linked trace, let's render a placeholder for now to avoid layout shifts
187+ // We should reconsider the place where we render these buttons, to avoid reducing the
188+ // waterfall height permanently
189+ return < TraceLinkNavigationButtonPlaceHolder /> ;
190+ }
191191
192- // If there is no linked trace or an undefined sampling decision
193- return null ;
192+ export function TraceLinkNavigationButtonPlaceHolder ( ) {
193+ return < PlaceHolderText > </ PlaceHolderText > ;
194194}
195195
196+ const PlaceHolderText = styled ( 'span' ) `
197+ padding: ${ space ( 0.5 ) } ${ space ( 0.5 ) } ;
198+ visibility: hidden;
199+ ` ;
200+
196201const StyledTooltip = styled ( Tooltip ) `
197- padding: ${ space ( 0.5 ) } ${ space ( 1 ) } ;
202+ padding: ${ space ( 0.5 ) } ${ space ( 0.5 ) } ;
198203 text-decoration: underline dotted
199204 ${ p => ( p . disabled ? p . theme . gray300 : p . theme . gray300 ) } ;
200205` ;
0 commit comments