@@ -8,11 +8,12 @@ import {
88 type QueuedDeploymentRequestWithStages ,
99 type QueuedDeploymentRequestWithStagesStagesInner ,
1010} from 'qovery-typescript-axios'
11- import { useState } from 'react'
11+ import { useContext , useState } from 'react'
1212import { Link , useLocation } from 'react-router-dom'
1313import { P , match } from 'ts-pattern'
1414import { type AnyService } from '@qovery/domains/services/data-access'
1515import { ServiceAvatar } from '@qovery/domains/services/feature'
16+ import { DevopsCopilotContext } from '@qovery/shared/devops-copilot/context'
1617import { DEPLOYMENT_LOGS_VERSION_URL , ENVIRONMENT_LOGS_URL , ENVIRONMENT_STAGES_URL } from '@qovery/shared/routes'
1718import { Indicator , StageStatusChip , StatusChip , Tooltip , TriggerActionIcon , Truncate } from '@qovery/shared/ui'
1819import { Icon } from '@qovery/shared/ui'
@@ -36,6 +37,7 @@ const MAX_VISIBLE_STAGES = 4
3637// https://github.com/radix-ui/primitives/issues/1294
3738export function DropdownServices ( { environment, deploymentHistory, stages } : DropdownServicesProps ) {
3839 const { pathname } = useLocation ( )
40+ const { setDevopsCopilotOpen, sendMessageRef } = useContext ( DevopsCopilotContext )
3941 const [ open , setOpen ] = useState ( false )
4042 const [ currentIndex , setCurrentIndex ] = useState < number | undefined > ( )
4143 const [ direction , setDirection ] = useState ( 0 )
@@ -235,9 +237,60 @@ export function DropdownServices({ environment, deploymentHistory, stages }: Dro
235237 </ div >
236238 </ div >
237239 { match ( stage )
238- . with ( P . when ( isDeploymentStageQueue ) , ( s ) =>
239- s . services . map ( ( service , index ) => {
240- return (
240+ . with ( P . when ( isDeploymentStageQueue ) , ( s ) => (
241+ < >
242+ { s . services . map ( ( service , index ) => {
243+ return (
244+ < DropdownMenu . Item
245+ key = { index }
246+ className = "flex h-[50px] w-full items-center gap-2 border-t border-neutral-200 pl-2 pr-3 text-xs text-neutral-400 transition-colors hover:bg-neutral-100 focus:bg-neutral-100 focus:outline-none"
247+ asChild
248+ >
249+ < Link
250+ to = {
251+ ENVIRONMENT_LOGS_URL (
252+ environment . organization . id ,
253+ environment . project . id ,
254+ environment . id
255+ ) + ENVIRONMENT_STAGES_URL ( )
256+ }
257+ state = { { prevUrl : pathname } }
258+ >
259+ { service . details && (
260+ < ServiceAvatar
261+ border = "solid"
262+ size = "sm"
263+ service = {
264+ 'job_type' in service . details
265+ ? {
266+ icon_uri : service . icon_uri ?? '' ,
267+ serviceType : 'JOB' as const ,
268+ job_type : service . details . job_type as 'CRON' | 'LIFECYCLE' ,
269+ }
270+ : {
271+ icon_uri : service . icon_uri ?? '' ,
272+ serviceType : service . identifier . service_type as Exclude <
273+ AnyService [ 'service_type' ] ,
274+ 'JOB'
275+ > ,
276+ }
277+ }
278+ />
279+ ) }
280+ < span className = "flex flex-col" >
281+ < span className = "truncate text-ssm" >
282+ < Truncate text = { service . identifier . name } truncateLimit = { 16 } />
283+ </ span >
284+ </ span >
285+ </ Link >
286+ </ DropdownMenu . Item >
287+ )
288+ } ) }
289+ </ >
290+ ) )
291+ . otherwise ( ( s ) => (
292+ < >
293+ { s . services . map ( ( service , index ) => (
241294 < DropdownMenu . Item
242295 key = { index }
243296 className = "flex h-[50px] w-full items-center gap-2 border-t border-neutral-200 pl-2 pr-3 text-xs text-neutral-400 transition-colors hover:bg-neutral-100 focus:bg-neutral-100 focus:outline-none"
@@ -249,7 +302,11 @@ export function DropdownServices({ environment, deploymentHistory, stages }: Dro
249302 environment . organization . id ,
250303 environment . project . id ,
251304 environment . id
252- ) + ENVIRONMENT_STAGES_URL ( )
305+ ) +
306+ DEPLOYMENT_LOGS_VERSION_URL (
307+ service . identifier . service_id ,
308+ service . identifier . execution_id
309+ )
253310 }
254311 state = { { prevUrl : pathname } }
255312 >
@@ -278,76 +335,42 @@ export function DropdownServices({ environment, deploymentHistory, stages }: Dro
278335 < span className = "truncate text-ssm" >
279336 < Truncate text = { service . identifier . name } truncateLimit = { 16 } />
280337 </ span >
338+ { service . total_duration && (
339+ < span
340+ title = { dateUTCString ( service . auditing_data . updated_at ) }
341+ className = "text-[11px]"
342+ >
343+ { formatDurationMinutesSeconds ( service . total_duration ?? '' ) }
344+ </ span >
345+ ) }
281346 </ span >
347+ { service . status_details && (
348+ < span className = "ml-auto" >
349+ < StatusChip status = { service . status_details . status } />
350+ </ span >
351+ ) }
282352 </ Link >
283353 </ DropdownMenu . Item >
284- )
285- } )
286- )
287- . otherwise ( ( s ) =>
288- s . services . map ( ( service , index ) => (
289- < DropdownMenu . Item
290- key = { index }
291- className = "flex h-[50px] w-full items-center gap-2 border-t border-neutral-200 pl-2 pr-3 text-xs text-neutral-400 transition-colors hover:bg-neutral-100 focus:bg-neutral-100 focus:outline-none"
292- asChild
293- >
294- < Link
295- to = {
296- ENVIRONMENT_LOGS_URL (
297- environment . organization . id ,
298- environment . project . id ,
299- environment . id
300- ) +
301- DEPLOYMENT_LOGS_VERSION_URL (
302- service . identifier . service_id ,
303- service . identifier . execution_id
304- )
305- }
306- state = { { prevUrl : pathname } }
354+ ) ) }
355+ { ( stage . status === 'ERROR' ||
356+ s . services . some ( ( service ) => service . status_details ?. status === 'ERROR' ) ) && (
357+ < DropdownMenu . Item
358+ className = "flex w-full cursor-pointer items-center justify-between gap-2 border-t border-neutral-200 bg-brand-50 px-2 py-2 text-xs text-brand-500 transition-colors hover:bg-neutral-100 focus:bg-neutral-100 focus:outline-none"
359+ onSelect = { ( ) => {
360+ const message = 'Why did my deployment fail?'
361+ setDevopsCopilotOpen ( true )
362+ sendMessageRef ?. current ?.( message )
363+ } }
307364 >
308- { service . details && (
309- < ServiceAvatar
310- border = "solid"
311- size = "sm"
312- service = {
313- 'job_type' in service . details
314- ? {
315- icon_uri : service . icon_uri ?? '' ,
316- serviceType : 'JOB' as const ,
317- job_type : service . details . job_type as 'CRON' | 'LIFECYCLE' ,
318- }
319- : {
320- icon_uri : service . icon_uri ?? '' ,
321- serviceType : service . identifier . service_type as Exclude <
322- AnyService [ 'service_type' ] ,
323- 'JOB'
324- > ,
325- }
326- }
327- />
328- ) }
329- < span className = "flex flex-col" >
330- < span className = "truncate text-ssm" >
331- < Truncate text = { service . identifier . name } truncateLimit = { 16 } />
332- </ span >
333- { service . total_duration && (
334- < span
335- title = { dateUTCString ( service . auditing_data . updated_at ) }
336- className = "text-[11px]"
337- >
338- { formatDurationMinutesSeconds ( service . total_duration ?? '' ) }
339- </ span >
340- ) }
341- </ span >
342- { service . status_details && (
343- < span className = "ml-auto" >
344- < StatusChip status = { service . status_details . status } />
345- </ span >
346- ) }
347- </ Link >
348- </ DropdownMenu . Item >
349- ) )
350- ) }
365+ < div className = "flex items-center justify-center gap-2" >
366+ < Icon iconName = "sparkles" iconStyle = "solid" className = "text-brand-500" />
367+ < span className = "font-thin" > Ask AI Copilot for diagnostic</ span >
368+ </ div >
369+ < Icon iconName = "arrow-right" />
370+ </ DropdownMenu . Item >
371+ ) }
372+ </ >
373+ ) ) }
351374 </ div >
352375 ) ) }
353376 </ motion . div >
0 commit comments