11import { get } from "lodash" ;
2- import { ChartNoAxesGantt , ListFilter , Minus , Plus , Search , Sparkles } from "lucide-react" ;
2+ import { AlertTriangle , ChartNoAxesGantt , ListFilter , Minus , Plus , Search , Sparkles } from "lucide-react" ;
33import { useParams , usePathname , useRouter , useSearchParams } from "next/navigation" ;
44import React , { useCallback , useEffect , useMemo } from "react" ;
55
@@ -27,7 +27,6 @@ import { StatefulFilter, StatefulFilterList } from "@/components/ui/datatable-fi
2727import { useFiltersContextProvider } from "@/components/ui/datatable-filter/context" ;
2828import { DatatableFilter } from "@/components/ui/datatable-filter/utils" ;
2929import { Skeleton } from "@/components/ui/skeleton" ;
30- import { useToast } from "@/lib/hooks/use-toast" ;
3130import { SpanType } from "@/lib/traces/types" ;
3231import { cn } from "@/lib/utils.ts" ;
3332
@@ -52,7 +51,6 @@ const PureTraceView = ({ traceId, spanId, onClose, propsTrace }: TraceViewProps)
5251 const router = useRouter ( ) ;
5352 const pathName = usePathname ( ) ;
5453 const { projectId } = useParams ( ) ;
55- const { toast } = useToast ( ) ;
5654
5755 // Data states
5856 const {
@@ -66,6 +64,10 @@ const PureTraceView = ({ traceId, spanId, onClose, propsTrace }: TraceViewProps)
6664 isTraceLoading,
6765 setIsTraceLoading,
6866 setIsSpansLoading,
67+ traceError,
68+ setTraceError,
69+ spansError,
70+ setSpansError,
6971 } = useTraceViewStoreContext ( ( state ) => ( {
7072 selectedSpan : state . selectedSpan ,
7173 setSelectedSpan : state . setSelectedSpan ,
@@ -77,6 +79,10 @@ const PureTraceView = ({ traceId, spanId, onClose, propsTrace }: TraceViewProps)
7779 isSpansLoading : state . isSpansLoading ,
7880 setIsSpansLoading : state . setIsSpansLoading ,
7981 setIsTraceLoading : state . setIsTraceLoading ,
82+ traceError : state . traceError ,
83+ setTraceError : state . setTraceError ,
84+ spansError : state . spansError ,
85+ setSpansError : state . setSpansError ,
8086 } ) ) ;
8187
8288 // UI states
@@ -135,31 +141,31 @@ const PureTraceView = ({ traceId, spanId, onClose, propsTrace }: TraceViewProps)
135141 const handleFetchTrace = useCallback ( async ( ) => {
136142 try {
137143 setIsTraceLoading ( true ) ;
144+ setTraceError ( undefined ) ;
145+
138146 if ( propsTrace ) {
139147 setTrace ( propsTrace ) ;
140148 } else {
141149 const response = await fetch ( `/api/projects/${ projectId } /traces/${ traceId } ` ) ;
150+
142151 if ( ! response . ok ) {
143- toast ( {
144- variant : "destructive" ,
145- title : "Error" ,
146- description : "Failed to load trace. Please try again." ,
147- } ) ;
152+ const errorData = await response . json ( ) . catch ( ( ) => ( { error : "Unknown error" } ) ) ;
153+ const errorMessage = errorData . error || "Failed to load trace" ;
154+
155+ setTraceError ( errorMessage ) ;
148156 return ;
149157 }
158+
150159 const traceData = ( await response . json ( ) ) as TraceViewTrace ;
151160 setTrace ( traceData ) ;
152161 }
153162 } catch ( e ) {
154- toast ( {
155- variant : "destructive" ,
156- title : "Error" ,
157- description : "Failed to load trace. Please try again." ,
158- } ) ;
163+ const errorMessage = e instanceof Error ? e . message : "Failed to load trace. Please try again." ;
164+ setTraceError ( errorMessage ) ;
159165 } finally {
160166 setIsTraceLoading ( false ) ;
161167 }
162- } , [ projectId , propsTrace , setBrowserSession , setIsTraceLoading , setTrace , toast , traceId ] ) ;
168+ } , [ projectId , propsTrace , setIsTraceLoading , setTrace , setTraceError , traceId ] ) ;
163169
164170 const handleSpanSelect = useCallback (
165171 ( span ?: TraceViewSpan ) => {
@@ -186,6 +192,7 @@ const PureTraceView = ({ traceId, spanId, onClose, propsTrace }: TraceViewProps)
186192 async ( search : string , searchIn : string [ ] , filters : DatatableFilter [ ] ) => {
187193 try {
188194 setIsSpansLoading ( true ) ;
195+ setSpansError ( undefined ) ;
189196
190197 const params = new URLSearchParams ( ) ;
191198 if ( search ) {
@@ -201,8 +208,16 @@ const PureTraceView = ({ traceId, spanId, onClose, propsTrace }: TraceViewProps)
201208
202209 const url = `/api/projects/${ projectId } /traces/${ traceId } /spans?${ params . toString ( ) } ` ;
203210 const response = await fetch ( url ) ;
204- const results = ( await response . json ( ) ) as TraceViewSpan [ ] ;
205211
212+ if ( ! response . ok ) {
213+ const errorData = await response . json ( ) . catch ( ( ) => ( { error : "Unknown error" } ) ) ;
214+ const errorMessage = errorData . error || "Failed to load spans" ;
215+
216+ setSpansError ( errorMessage ) ;
217+ return ;
218+ }
219+
220+ const results = ( await response . json ( ) ) as TraceViewSpan [ ] ;
206221 const spans = search || filters ?. length > 0 ? results : enrichSpansWithPending ( results ) ;
207222
208223 setSpans ( spans ) ;
@@ -219,19 +234,25 @@ const PureTraceView = ({ traceId, spanId, onClose, propsTrace }: TraceViewProps)
219234 setSelectedSpan ( undefined ) ;
220235 }
221236 } catch ( e ) {
237+ const errorMessage = e instanceof Error ? e . message : "Failed to load spans" ;
238+ setSpansError ( errorMessage ) ;
239+
222240 console . error ( e ) ;
223241 } finally {
224242 setIsSpansLoading ( false ) ;
225243 }
226244 } ,
227245 [
228- trace ,
229246 setIsSpansLoading ,
230- search ,
247+ setSpansError ,
248+ setSearch ,
249+ setSearchEnabled ,
231250 projectId ,
232251 traceId ,
233252 setSpans ,
234- setSearch ,
253+ hasBrowserSession ,
254+ setHasBrowserSession ,
255+ setBrowserSession ,
235256 spanId ,
236257 searchParams ,
237258 spanPath ,
@@ -278,7 +299,7 @@ const PureTraceView = ({ traceId, spanId, onClose, propsTrace }: TraceViewProps)
278299 setSearchEnabled ( ! searchEnabled ) ;
279300 } , [ fetchSpans , searchEnabled , setSearch , setSearchEnabled , search ] ) ;
280301
281- const isLoading = ! trace || ( isSpansLoading && isTraceLoading ) ;
302+ const isLoading = isTraceLoading || ( isSpansLoading && ! traceError && ! spansError ) ;
282303
283304 useEffect ( ( ) => {
284305 if ( ! isSpansLoading ) {
@@ -304,8 +325,20 @@ const PureTraceView = ({ traceId, spanId, onClose, propsTrace }: TraceViewProps)
304325 setBrowserSession ( false ) ;
305326 setSearch ( "" ) ;
306327 setSearchEnabled ( false ) ;
328+ setTraceError ( undefined ) ;
329+ setSpansError ( undefined ) ;
307330 } ;
308- } , [ traceId , projectId , filters , setSpans , setBrowserSession , setSearch , setSearchEnabled ] ) ;
331+ } , [
332+ traceId ,
333+ projectId ,
334+ filters ,
335+ setSpans ,
336+ setBrowserSession ,
337+ setSearch ,
338+ setSearchEnabled ,
339+ setTraceError ,
340+ setSpansError ,
341+ ] ) ;
309342
310343 useEffect ( ( ) => {
311344 if ( ! traceId || ! projectId ) {
@@ -353,6 +386,21 @@ const PureTraceView = ({ traceId, spanId, onClose, propsTrace }: TraceViewProps)
353386 ) ;
354387 }
355388
389+ if ( traceError ) {
390+ return (
391+ < div className = "flex flex-col h-full w-full overflow-hidden" >
392+ < Header handleClose = { handleClose } />
393+ < div className = "flex flex-col items-center justify-center flex-1 p-8 text-center" >
394+ < div className = "max-w-md mx-auto" >
395+ < AlertTriangle className = "w-12 h-12 text-destructive mx-auto mb-4" />
396+ < h3 className = "text-lg font-semibold text-destructive mb-4" > Error Loading Trace</ h3 >
397+ < p className = "text-sm text-muted-foreground" > { traceError } </ p >
398+ </ div >
399+ </ div >
400+ </ div >
401+ ) ;
402+ }
403+
356404 return (
357405 < ScrollContextProvider >
358406 < div className = "flex flex-col h-full w-full overflow-hidden" >
@@ -430,35 +478,43 @@ const PureTraceView = ({ traceId, spanId, onClose, propsTrace }: TraceViewProps)
430478 className = "rounded-none w-full border-0 border-b ring-0 bg-background"
431479 />
432480 ) }
433- < >
434- { tab === "chat" && (
435- < Chat
436- trace = { trace }
437- onSetSpanId = { ( spanId ) => {
438- const span = spans . find ( ( span ) => span . spanId === spanId ) ;
439- if ( span ) {
440- handleSpanSelect ( span ) ;
441- }
442- } }
443- />
444- ) }
481+ { spansError ? (
482+ < div className = "flex flex-col items-center justify-center flex-1 p-4 text-center" >
483+ < AlertTriangle className = "w-8 h-8 text-destructive mb-3" />
484+ < h4 className = "text-sm font-semibold text-destructive mb-2" > Error Loading Spans</ h4 >
485+ < p className = "text-xs text-muted-foreground" > { spansError } </ p >
486+ </ div >
487+ ) : (
445488 < >
446- { tab === "timeline" && < Timeline /> }
447- { tab === "tree" &&
448- ( isSpansLoading ? (
449- < div className = "flex flex-col gap-2 p-2 pb-4 w-full min-w-full" >
450- < Skeleton className = "h-8 w-full" />
451- < Skeleton className = "h-8 w-full" />
452- < Skeleton className = "h-8 w-full" />
453- </ div >
454- ) : (
455- < div className = "flex flex-1 overflow-hidden relative" >
456- < Tree onSpanSelect = { handleSpanSelect } />
457- < Minimap onSpanSelect = { handleSpanSelect } />
458- </ div >
459- ) ) }
489+ { tab === "chat" && trace && (
490+ < Chat
491+ trace = { trace }
492+ onSetSpanId = { ( spanId ) => {
493+ const span = spans . find ( ( span ) => span . spanId === spanId ) ;
494+ if ( span ) {
495+ handleSpanSelect ( span ) ;
496+ }
497+ } }
498+ />
499+ ) }
500+ < >
501+ { tab === "timeline" && < Timeline /> }
502+ { tab === "tree" &&
503+ ( isSpansLoading ? (
504+ < div className = "flex flex-col gap-2 p-2 pb-4 w-full min-w-full" >
505+ < Skeleton className = "h-8 w-full" />
506+ < Skeleton className = "h-8 w-full" />
507+ < Skeleton className = "h-8 w-full" />
508+ </ div >
509+ ) : (
510+ < div className = "flex flex-1 overflow-hidden relative" >
511+ < Tree onSpanSelect = { handleSpanSelect } />
512+ < Minimap onSpanSelect = { handleSpanSelect } />
513+ </ div >
514+ ) ) }
515+ </ >
460516 </ >
461- </ >
517+ ) }
462518 < div
463519 className = "absolute top-0 right-0 h-full cursor-col-resize z-50 group w-2"
464520 onMouseDown = { handleResizeTreeView }
0 commit comments