@@ -10,25 +10,49 @@ import {
1010 Separator ,
1111} from '../ui' ;
1212import * as PopoverPrimitive from '@radix-ui/react-popover' ;
13- import { ErrorDetailResponse } from '@pinpoint-fe/ui/src/constants' ;
13+ import { ErrorLike } from '@pinpoint-fe/ui/src/constants' ;
1414import { cn } from '../../lib' ;
1515import { HighLightCode } from '../HighLightCode' ;
1616import { RxChevronDown , RxChevronUp } from 'react-icons/rx' ;
1717
1818export interface ErrorDetailDialogProps {
19- error : ErrorDetailResponse | Error ;
19+ error : Error | ErrorLike ;
2020 contentOption ?: PopoverPrimitive . PopoverContentProps ;
2121 contentClassName ?: string ;
2222}
2323
24+ function getDisplayMessage ( error : Error | ErrorLike ) : string {
25+ const msg = ( error as Error ) . message ;
26+ const detail = ( error as ErrorLike ) . detail ;
27+ return detail ?? msg ?? '' ;
28+ }
29+
30+ function getClientStack ( error : Error | ErrorLike ) : string | null {
31+ const stack = ( error as Error ) . stack ;
32+ return stack ?? null ;
33+ }
34+
35+ function getServerTrace ( error : Error | ErrorLike ) : string | null {
36+ const trace = ( error as ErrorLike ) . trace ;
37+ if ( Array . isArray ( trace ) && trace . length > 0 ) return trace . join ( '\n' ) ;
38+ if ( typeof trace === 'string' ) return trace ;
39+ return null ;
40+ }
41+
2442export const ErrorDetailDialog = ( {
2543 error,
2644 contentOption,
2745 contentClassName,
2846} : ErrorDetailDialogProps ) => {
29- const [ headerOpen , setHeaderOpen ] = React . useState ( false ) ;
30- const [ paremetersOpen , setParametersOpen ] = React . useState ( false ) ;
31- const [ stackTraceOpen , setStackTraceOpen ] = React . useState ( true ) ;
47+ const [ serverTraceOpen , setServerTraceOpen ] = React . useState ( false ) ;
48+ const serverError = error as ErrorLike ;
49+ const hasMethod = serverError ?. method != null ;
50+ const hasStatus = serverError ?. status != null ;
51+ const params = serverError ?. parameters ?? { } ;
52+ const hasParams = Object . keys ( params ) . length > 0 ;
53+ const clientStack = getClientStack ( error ) ;
54+ const serverTrace = getServerTrace ( error ) ;
55+
3256 return (
3357 < Dialog >
3458 < DialogTrigger asChild >
@@ -42,113 +66,113 @@ export const ErrorDetailDialog = ({
4266 onMouseDown = { ( e ) => e . stopPropagation ( ) }
4367 { ...contentOption }
4468 >
45- < div className = "flex flex-col gap-4 overflow-hidden " >
69+ < div className = "flex overflow-hidden flex-col gap-4" >
4670 < div className = "space-y-2" >
47- < h4 className = "flex items-center gap-1 font-medium" >
71+ < h4 className = "flex gap-1 items-center font-medium" >
4872 < div className = "w-1 h-4 rounded-sm bg-status-fail" />
4973 Error Details
5074 </ h4 >
51- < div className = "flex items-center gap-1" >
52- < a
53- className = "text-sm font-semibold text-primary hover:underline"
54- href = { ( error as ErrorDetailResponse ) ?. url }
55- target = "_blank"
56- >
57- { ( error as ErrorDetailResponse ) ?. instance }
58- </ a >
59- </ div >
60- < p className = "text-sm break-all text-muted-foreground" > { error ?. message } </ p >
61- </ div >
62- < Separator />
63- { ( error as Error ) ?. stack && (
64- < div className = "overflow-auto" >
65- < pre > { ( error as Error ) ?. stack } </ pre >
66- </ div >
67- ) }
68- { ( error as ErrorDetailResponse ) ?. data && (
69- < div className = "grid gap-2 text-sm scrollbar-hide" >
70- < div className = "grid grid-cols-[7rem_auto] gap-2" >
75+
76+ { serverError ?. instance && (
77+ < div className = "flex gap-1 items-center" >
78+ < a
79+ className = "text-sm font-semibold text-primary hover:underline"
80+ href = { serverError ?. url }
81+ target = "_blank"
82+ rel = "noreferrer"
83+ >
84+ { serverError . instance }
85+ </ a >
86+ </ div >
87+ ) }
88+ { hasMethod && (
89+ < div className = "grid grid-cols-[5rem_auto] gap-x-2 gap-y-1 text-sm items-baseline" >
7190 < div className = "text-muted-foreground" > Method</ div >
72- < div > { ( error as ErrorDetailResponse ) ?. data ?. requestInfo ? .method } </ div >
91+ < div > { serverError . method } </ div >
7392 </ div >
74- < Collapsible className = "space-y-2" open = { headerOpen } onOpenChange = { setHeaderOpen } >
75- < CollapsibleTrigger
76- className = "flex items-center gap-1 text-muted-foreground hover:text-foreground"
77- onClick = { ( e ) => e . stopPropagation ( ) }
78- >
79- Header { headerOpen ? < RxChevronUp /> : < RxChevronDown /> }
80- </ CollapsibleTrigger >
81- < CollapsibleContent >
82- < div className = "grid grid-cols-[7rem_auto] gap-2 text-xs p-2" >
83- { ( error as ErrorDetailResponse ) ?. data ?. requestInfo ?. headers &&
84- Object . keys ( ( error as ErrorDetailResponse ) ?. data ?. requestInfo ?. headers )
85- . sort ( )
86- . map ( ( key ) => {
87- return (
88- < React . Fragment key = { key } >
89- < div className = "text-muted-foreground" > { key } </ div >
90- < div className = "break-all" >
91- { ( error as ErrorDetailResponse ) ?. data ?. requestInfo ?. headers ?. [ key ] }
92- </ div >
93- </ React . Fragment >
94- ) ;
95- } ) }
96- </ div >
97- </ CollapsibleContent >
98- </ Collapsible >
99- < Collapsible
100- className = "space-y-2"
101- open = { paremetersOpen }
102- onOpenChange = { setParametersOpen }
103- >
104- < CollapsibleTrigger
105- className = "flex items-center gap-1 text-muted-foreground hover:text-foreground"
106- onClick = { ( e ) => e . stopPropagation ( ) }
107- >
108- Parameters { paremetersOpen ? < RxChevronUp /> : < RxChevronDown /> }
109- </ CollapsibleTrigger >
110- < CollapsibleContent >
111- < div className = "grid grid-cols-[7rem_auto] gap-2 text-xs p-2" >
112- { ( error as ErrorDetailResponse ) ?. data ?. requestInfo ?. parameters &&
113- Object . keys ( ( error as ErrorDetailResponse ) ?. data ?. requestInfo ?. parameters )
114- . sort ( )
115- . map ( ( key ) => {
116- return (
117- < React . Fragment key = { key } >
118- < div className = "text-muted-foreground" > { key } </ div >
119- < div className = "break-all" >
120- {
121- ( error as ErrorDetailResponse ) ?. data ?. requestInfo ?. parameters ?. [
122- key
123- ]
124- }
125- </ div >
126- </ React . Fragment >
127- ) ;
128- } ) }
129- </ div >
130- </ CollapsibleContent >
131- </ Collapsible >
93+ ) }
94+ { hasStatus && (
95+ < div className = "grid grid-cols-[5rem_auto] gap-x-2 gap-y-1 text-sm items-baseline" >
96+ < div className = "text-muted-foreground" > Status</ div >
97+ < div > { serverError . status } </ div >
98+ </ div >
99+ ) }
100+ { serverError . title != null && serverError . title !== '' && (
101+ < div className = "grid grid-cols-[5rem_auto] gap-x-2 gap-y-1 text-sm items-baseline" >
102+ < div className = "text-muted-foreground" > Title</ div >
103+ < div > { serverError . title } </ div >
104+ </ div >
105+ ) }
106+ { serverError . detail != null && serverError . detail !== '' && (
107+ < div className = "grid grid-cols-[5rem_auto] gap-x-2 gap-y-1 text-sm items-baseline" >
108+ < div className = "text-muted-foreground" > Detail</ div >
109+ < p className = "text-sm break-all" > { getDisplayMessage ( error ) || '—' } </ p >
110+ </ div >
111+ ) }
112+ { ! hasStatus && (
113+ < p className = "text-sm break-all text-muted-foreground" >
114+ { getDisplayMessage ( error ) || '—' }
115+ </ p >
116+ ) }
117+ { hasParams && (
118+ < >
119+ < div className = "grid gap-2 text-sm scrollbar-hide" >
120+ { hasParams && (
121+ < >
122+ < div className = "text-muted-foreground" > Parameters</ div >
123+ < div className = "grid grid-cols-[10rem_auto] gap-2 text-xs" >
124+ { Object . keys ( params )
125+ . sort ( )
126+ . map ( ( key ) => {
127+ const value = params [ key ] ;
128+ return (
129+ < React . Fragment key = { key } >
130+ < div className = "pl-3 text-muted-foreground" > { key } </ div >
131+ < div className = "pl-3 break-all" >
132+ { Array . isArray ( value ) ? value . join ( ', ' ) : String ( value ) }
133+ </ div >
134+ </ React . Fragment >
135+ ) ;
136+ } ) }
137+ </ div >
138+ </ >
139+ ) }
140+ </ div >
141+ </ >
142+ ) }
143+ </ div >
144+ { clientStack != null && (
145+ < >
146+ < Separator />
147+ < div className = "overflow-auto" >
148+ < pre className = "p-2 text-sm" > { clientStack } </ pre >
149+ </ div >
150+ </ >
151+ ) }
152+ { serverTrace != null && (
153+ < >
154+ < Separator />
132155 < Collapsible
133156 className = "space-y-2"
134- open = { stackTraceOpen }
135- onOpenChange = { setStackTraceOpen }
157+ open = { serverTraceOpen }
158+ onOpenChange = { setServerTraceOpen }
159+ defaultOpen = { false }
136160 >
137161 < CollapsibleTrigger
138- className = "flex items-center gap-1 text-muted-foreground hover:text-foreground"
162+ className = "flex gap-1 items-center text-sm text-muted-foreground hover:text-foreground"
139163 onClick = { ( e ) => e . stopPropagation ( ) }
140164 >
141- StackTrace { stackTraceOpen ? < RxChevronUp /> : < RxChevronDown /> }
165+ Server stack trace { serverTraceOpen ? < RxChevronUp /> : < RxChevronDown /> }
142166 </ CollapsibleTrigger >
143167 < CollapsibleContent >
144168 < HighLightCode
145169 language = "java"
146- code = { ( error as ErrorDetailResponse ) ?. trace }
147- className = "p-2 text-xs"
170+ code = { serverTrace }
171+ className = "overflow-auto p-2 text-xs"
148172 />
149173 </ CollapsibleContent >
150174 </ Collapsible >
151- </ div >
175+ </ >
152176 ) }
153177 </ div >
154178 </ DialogContent >
0 commit comments