1- import { ChevronsUpDown , ExternalLink } from "lucide- react" ;
1+ import { useMemo } from "react" ;
22
33import type { GetContainerExecutionStateResponse } from "@/api/types.gen" ;
4- import { Button } from "@/components/ui/button" ;
5- import {
6- Collapsible ,
7- CollapsibleContent ,
8- CollapsibleTrigger ,
9- } from "@/components/ui/collapsible" ;
4+ import { BlockStack , InlineStack } from "@/components/ui/layout" ;
105import { Skeleton } from "@/components/ui/skeleton" ;
116import { useBackend } from "@/providers/BackendProvider" ;
127import { useFetchContainerExecutionState } from "@/services/executionService" ;
@@ -17,16 +12,23 @@ import {
1712import { EXIT_CODE_OOM } from "@/utils/constants" ;
1813import { formatDate , formatDuration } from "@/utils/date" ;
1914
15+ import { ContentBlock } from "../ContextPanel/Blocks/ContentBlock" ;
16+ import {
17+ ListBlock ,
18+ type ListBlockItemProps ,
19+ } from "../ContextPanel/Blocks/ListBlock" ;
2020import { InfoBox } from "../InfoBox" ;
2121
2222interface ExecutionDetailsProps {
2323 executionId : string ;
2424 componentSpec ?: ComponentSpec ;
25+ className ?: string ;
2526}
2627
2728export const ExecutionDetails = ( {
2829 executionId,
2930 componentSpec,
31+ className,
3032} : ExecutionDetailsProps ) => {
3133 const { backendUrl } = useBackend ( ) ;
3234
@@ -37,156 +39,102 @@ export const ExecutionDetails = ({
3739 data : containerState ,
3840 isLoading : isLoadingContainerState ,
3941 error : containerStateError ,
40- } = useFetchContainerExecutionState ( executionId || "" , backendUrl ) ;
41-
42- // Don't render if no execution data is available
43- const hasExecutionData =
44- executionId ||
45- containerState ||
46- isLoadingContainerState ||
47- containerStateError ;
48- if ( ! hasExecutionData ) {
49- return null ;
50- }
42+ } = useFetchContainerExecutionState ( executionId , backendUrl ) ;
43+
44+ const loadingMarkup = (
45+ < BlockStack gap = "2" >
46+ < InlineStack gap = "2" blockAlign = "center" >
47+ < Skeleton className = "h-3 w-12" />
48+ < Skeleton className = "h-3 w-32" />
49+ </ InlineStack >
50+ < InlineStack gap = "2" blockAlign = "center" >
51+ < Skeleton className = "h-3 w-20" />
52+ < Skeleton className = "h-3 w-24" />
53+ < Skeleton className = "h-3 w-40" />
54+ </ InlineStack >
55+ </ BlockStack >
56+ ) ;
5157
52- const podName = executionPodName ( containerState ) ;
53- const executionJobLinks = getExecutionJobLinks ( containerState ) ;
58+ const executionItems = useMemo ( ( ) => {
59+ const items : ListBlockItemProps [ ] = [
60+ { name : "Execution ID" , value : executionId } ,
61+ ] ;
62+
63+ if ( isSubgraph ) {
64+ return items ;
65+ }
66+
67+ if ( containerState ?. exit_code && containerState . exit_code > 0 ) {
68+ const exitCodeValue =
69+ containerState . exit_code === EXIT_CODE_OOM
70+ ? `${ containerState . exit_code } (Out of Memory)`
71+ : `${ containerState . exit_code } ` ;
72+
73+ items . push ( {
74+ name : "Exit Code" ,
75+ value : exitCodeValue ,
76+ critical : true ,
77+ } ) ;
78+ }
79+
80+ if ( containerState ?. started_at ) {
81+ items . push ( {
82+ name : "Started" ,
83+ value : formatDate ( containerState . started_at ) ,
84+ } ) ;
85+ }
86+
87+ if ( containerState ?. ended_at && containerState ?. started_at ) {
88+ items . push ( {
89+ name : "Completed in" ,
90+ value : `${ formatDuration (
91+ containerState . started_at ,
92+ containerState . ended_at ,
93+ ) } (${ formatDate ( containerState . ended_at ) } )`,
94+ } ) ;
95+ }
96+
97+ const podName = executionPodName ( containerState ) ;
98+ if ( podName ) {
99+ items . push ( { name : "Pod Name" , value : podName } ) ;
100+ }
101+
102+ const executionJobLinks = getExecutionJobLinks ( containerState ) ;
103+ if ( executionJobLinks ) {
104+ executionJobLinks . forEach ( ( linkInfo ) => {
105+ items . push ( {
106+ name : linkInfo . name ,
107+ value : { text : linkInfo . value , href : linkInfo . url } ,
108+ } ) ;
109+ } ) ;
110+ }
111+
112+ return items ;
113+ } , [ executionId , isSubgraph , containerState ] ) ;
54114
55115 return (
56- < div className = "flex flex-col px-3 py-2" >
57- < Collapsible defaultOpen >
58- < div className = "font-medium text-sm text-foreground flex items-center gap-1" >
59- Execution Details
60- < CollapsibleTrigger asChild >
61- < Button variant = "ghost" size = "sm" >
62- < ChevronsUpDown className = "h-4 w-4" />
63- < span className = "sr-only" > Toggle</ span >
64- </ Button >
65- </ CollapsibleTrigger >
66- </ div >
67-
68- < CollapsibleContent className = "mt-2 space-y-2" >
69- < div className = "flex items-center gap-2" >
70- < span className = "font-medium text-xs text-foreground min-w-fit" >
71- Execution ID:
72- </ span >
73- < span className = "text-xs text-muted-foreground font-mono truncate" >
74- { executionId }
75- </ span >
76- </ div >
77-
78- { ! isSubgraph && (
79- < >
80- { isLoadingContainerState && (
81- < div className = "space-y-2" >
82- < div className = "flex items-center gap-2" >
83- < Skeleton className = "h-3 w-12" />
84- < Skeleton className = "h-3 w-32" />
85- </ div >
86- < div className = "flex items-center gap-2" >
87- < Skeleton className = "h-3 w-20" />
88- < Skeleton className = "h-3 w-24" />
89- < Skeleton className = "h-3 w-40" />
90- </ div >
91- </ div >
92- ) }
93-
94- { containerStateError && (
95- < InfoBox title = "Failed to load container state" variant = "error" >
96- { containerStateError . message }
97- </ InfoBox >
98- ) }
99-
100- { ! ! containerState ?. exit_code && (
101- < div className = "flex flex-wrap items-center gap-1" >
102- < span className = "font-medium text-xs text-destructive" >
103- Exit Code:
104- </ span >
105- < span className = "text-xs text-destructive" >
106- { containerState . exit_code }
107- </ span >
108- { containerState . exit_code === EXIT_CODE_OOM && (
109- < span className = "text-xs text-destructive" >
110- (Out of Memory)
111- </ span >
112- ) }
113- </ div >
114- ) }
115-
116- { containerState ?. started_at && (
117- < div className = "flex items-center gap-2" >
118- < span className = "font-medium text-xs text-foreground min-w-fit" >
119- Started:
120- </ span >
121- < span className = "text-xs text-muted-foreground" >
122- { formatDate ( containerState . started_at ) }
123- </ span >
124- </ div >
125- ) }
126-
127- { containerState ?. ended_at && containerState ?. started_at && (
128- < div className = "flex items-center gap-2" >
129- < span className = "font-medium text-xs text-foreground min-w-fit" >
130- Completed in:
131- </ span >
132- < span className = "text-xs text-muted-foreground" >
133- { formatDuration (
134- containerState . started_at ,
135- containerState . ended_at ,
136- ) }
137- </ span >
138- < span className = "text-xs text-muted-foreground" >
139- ({ formatDate ( containerState . ended_at ) } )
140- </ span >
141- </ div >
142- ) }
143-
144- { podName && (
145- < div className = "flex items-center gap-2" >
146- < span className = "font-medium text-xs text-foreground min-w-fit" >
147- Pod Name:
148- </ span >
149- < span className = "text-xs text-muted-foreground" >
150- { podName }
151- </ span >
152- </ div >
153- ) }
154- { executionJobLinks && (
155- < >
156- { executionJobLinks . map ( ( linkInfo ) => (
157- < div
158- key = { linkInfo . name }
159- className = "flex text-xs items-center gap-2"
160- >
161- < span className = "font-medium text-foreground min-w-fit" >
162- { linkInfo . name } :
163- </ span >
164- < a
165- href = { linkInfo . url }
166- className = "text-sky-500 hover:underline flex items-center gap-1"
167- target = "_blank"
168- rel = "noopener noreferrer"
169- >
170- { linkInfo . value }
171- < ExternalLink className = "size-3 shrink-0" />
172- </ a >
173- </ div >
174- ) ) }
175- </ >
176- ) }
177-
178- { ! isLoadingContainerState &&
179- ! containerState &&
180- ! containerStateError && (
181- < div className = "text-xs text-muted-foreground" >
182- Container state not available
183- </ div >
184- ) }
185- </ >
186- ) }
187- </ CollapsibleContent >
188- </ Collapsible >
189- </ div >
116+ < ContentBlock
117+ title = "Execution Details"
118+ collapsible
119+ defaultOpen
120+ className = { className }
121+ >
122+ < BlockStack gap = "2" >
123+ < ListBlock items = { executionItems } marker = "none" />
124+
125+ { ! isSubgraph && isLoadingContainerState && loadingMarkup }
126+
127+ { ! isSubgraph && containerStateError && (
128+ < InfoBox
129+ title = "Failed to load container state"
130+ variant = "error"
131+ className = "wrap-anywhere"
132+ >
133+ { containerStateError . message }
134+ </ InfoBox >
135+ ) }
136+ </ BlockStack >
137+ </ ContentBlock >
190138 ) ;
191139} ;
192140
0 commit comments