1+ import { useMutation } from "@tanstack/react-query" ;
2+ import { useNavigate } from "@tanstack/react-router" ;
3+
14import { CopyText } from "@/components/shared/CopyText/CopyText" ;
25import { BlockStack , InlineStack } from "@/components/ui/layout" ;
36import { Spinner } from "@/components/ui/spinner" ;
47import { Paragraph , Text } from "@/components/ui/typography" ;
58import { useCheckComponentSpecFromPath } from "@/hooks/useCheckComponentSpecFromPath" ;
9+ import useToastNotification from "@/hooks/useToastNotification" ;
610import { useUserDetails } from "@/hooks/useUserDetails" ;
711import { useBackend } from "@/providers/BackendProvider" ;
812import { useComponentSpec } from "@/providers/ComponentSpecProvider" ;
913import { useExecutionData } from "@/providers/ExecutionDataProvider" ;
14+ import { APP_ROUTES } from "@/routes/router" ;
1015import {
1116 countTaskStatuses ,
1217 getRunStatus ,
1318 isStatusComplete ,
1419 isStatusInProgress ,
1520} from "@/services/executionService" ;
21+ import {
22+ cancelPipelineRun ,
23+ copyRunToPipeline ,
24+ } from "@/services/pipelineRunService" ;
25+ import type { PipelineRun } from "@/types/pipelineRun" ;
26+ import { getInitialName } from "@/utils/getComponentName" ;
27+ import { submitPipelineRun } from "@/utils/submitPipeline" ;
28+
29+ import { isAuthorizationRequired } from "../shared/Authentication/helpers" ;
30+ import { useAuthLocalStorage } from "../shared/Authentication/useAuthLocalStorage" ;
31+ import { useAwaitAuthorization } from "../shared/Authentication/useAwaitAuthorization" ;
1632import {
1733 ActionBlock ,
1834 type ActionOrReactNode ,
@@ -23,18 +39,20 @@ import { TextBlock } from "../shared/ContextPanel/Blocks/TextBlock";
2339import PipelineIO from "../shared/Execution/PipelineIO" ;
2440import { InfoBox } from "../shared/InfoBox" ;
2541import { StatusBar , StatusText } from "../shared/Status" ;
26- import { CancelPipelineRunButton } from "./components/CancelPipelineRunButton" ;
27- import { ClonePipelineButton } from "./components/ClonePipelineButton" ;
28- import { InspectPipelineButton } from "./components/InspectPipelineButton" ;
29- import { RerunPipelineButton } from "./components/RerunPipelineButton" ;
3042import { useState } from "react" ;
31- import { componentSpecToText } from "@/utils/yaml" ;
3243import { CodeViewer } from "../shared/CodeViewer" ;
33- import TooltipButton from "../shared/Buttons/TooltipButton" ;
34- import { Icon } from "../ui/icon" ;
44+ import { componentSpecToText } from "@/utils/yaml" ;
3545
3646export const RunDetails = ( ) => {
37- const { configured } = useBackend ( ) ;
47+ const navigate = useNavigate ( ) ;
48+ const notify = useToastNotification ( ) ;
49+
50+ const { available, configured, backendUrl } = useBackend ( ) ;
51+
52+ const { awaitAuthorization, isAuthorized } = useAwaitAuthorization ( ) ;
53+ const { getToken } = useAuthLocalStorage ( ) ;
54+ const { data : currentUserDetails } = useUserDetails ( ) ;
55+
3856 const { componentSpec } = useComponentSpec ( ) ;
3957 const {
4058 rootDetails : details ,
@@ -44,7 +62,74 @@ export const RunDetails = () => {
4462 isLoading,
4563 error,
4664 } = useExecutionData ( ) ;
47- const { data : currentUserDetails } = useUserDetails ( ) ;
65+
66+ const { isPending : isPendingClone , mutate : clonePipeline } = useMutation ( {
67+ mutationFn : async ( ) => {
68+ const name = getInitialName ( componentSpec ) ;
69+ return copyRunToPipeline ( componentSpec , name ) ;
70+ } ,
71+ onSuccess : ( result ) => {
72+ if ( result ?. url ) {
73+ notify ( `Pipeline "${ result . name } " cloned` , "success" ) ;
74+ navigate ( { to : result . url } ) ;
75+ }
76+ } ,
77+ onError : ( error ) => {
78+ notify ( `Error cloning pipeline: ${ error } ` , "error" ) ;
79+ } ,
80+ } ) ;
81+
82+ const {
83+ mutate : cancelPipeline ,
84+ isPending : isPendingCancel ,
85+ isSuccess : isSuccessCancel ,
86+ } = useMutation ( {
87+ mutationFn : ( runId : string ) => cancelPipelineRun ( runId , backendUrl ) ,
88+ onSuccess : ( ) => {
89+ notify ( `Pipeline run ${ runId } cancelled` , "success" ) ;
90+ } ,
91+ onError : ( error ) => {
92+ notify ( `Error cancelling run: ${ error } ` , "error" ) ;
93+ } ,
94+ } ) ;
95+
96+ const onSuccess = ( response : PipelineRun ) => {
97+ navigate ( { to : `${ APP_ROUTES . RUNS } /${ response . id } ` } ) ;
98+ } ;
99+
100+ const onError = ( error : Error | string ) => {
101+ const message = `Failed to submit pipeline. ${ error instanceof Error ? error . message : String ( error ) } ` ;
102+ notify ( message , "error" ) ;
103+ } ;
104+
105+ const getAuthToken = async ( ) : Promise < string | undefined > => {
106+ const authorizationRequired = isAuthorizationRequired ( ) ;
107+
108+ if ( authorizationRequired && ! isAuthorized ) {
109+ const token = await awaitAuthorization ( ) ;
110+ if ( token ) {
111+ return token ;
112+ }
113+ }
114+
115+ return getToken ( ) ;
116+ } ;
117+
118+ const { mutate : rerunPipeline , isPending : isPendingRerun } = useMutation ( {
119+ mutationFn : async ( ) => {
120+ const authorizationToken = await getAuthToken ( ) ;
121+
122+ return new Promise < PipelineRun > ( ( resolve , reject ) => {
123+ submitPipelineRun ( componentSpec , backendUrl , {
124+ authorizationToken,
125+ onSuccess : resolve ,
126+ onError : reject ,
127+ } ) ;
128+ } ) ;
129+ } ,
130+ onSuccess,
131+ onError,
132+ } ) ;
48133
49134 const [ isYamlFullscreen , setIsYamlFullscreen ] = useState ( false ) ;
50135
@@ -60,6 +145,36 @@ export const RunDetails = () => {
60145 const isRunCreator =
61146 currentUserDetails ?. id && metadata ?. created_by === currentUserDetails . id ;
62147
148+ const handleInspect = ( ) => {
149+ navigate ( { to : editorRoute } ) ;
150+ } ;
151+
152+ const handleClone = ( ) => {
153+ clonePipeline ( ) ;
154+ } ;
155+
156+ const handleCancel = ( ) => {
157+ if ( ! runId ) {
158+ notify ( `Failed to cancel run. No run ID found.` , "warning" ) ;
159+ return ;
160+ }
161+
162+ if ( ! available ) {
163+ notify ( `Backend is not available. Cannot cancel run.` , "warning" ) ;
164+ return ;
165+ }
166+
167+ try {
168+ cancelPipeline ( runId ) ;
169+ } catch ( error ) {
170+ notify ( `Error cancelling run: ${ error } ` , "error" ) ;
171+ }
172+ } ;
173+
174+ const handleRerun = ( ) => {
175+ rerunPipeline ( ) ;
176+ } ;
177+
63178 if ( error || ! details || ! state || ! componentSpec ) {
64179 return (
65180 < BlockStack align = "center" inlineAlign = "center" className = "h-full" >
@@ -97,37 +212,43 @@ export const RunDetails = () => {
97212
98213 const annotations = componentSpec . metadata ?. annotations || { } ;
99214
100- const actions : ActionOrReactNode [ ] = [ ] ;
101-
102- actions . push (
103- < TooltipButton
104- variant = "outline"
105- tooltip = "View YAML"
106- onClick = { ( ) => setIsYamlFullscreen ( true ) }
107- >
108- < Icon name = "FileCodeCorner" />
109- </ TooltipButton > ,
110- ) ;
111-
112- if ( canAccessEditorSpec && componentSpec . name ) {
113- actions . push (
114- < InspectPipelineButton key = "inspect" pipelineName = { componentSpec . name } /> ,
115- ) ;
116- }
117-
118- actions . push (
119- < ClonePipelineButton key = "clone" componentSpec = { componentSpec } /> ,
120- ) ;
121-
122- if ( isInProgress && isRunCreator ) {
123- actions . push ( < CancelPipelineRunButton key = "cancel" runId = { runId } /> ) ;
124- }
125-
126- if ( isComplete ) {
127- actions . push (
128- < RerunPipelineButton key = "rerun" componentSpec = { componentSpec } /> ,
129- ) ;
130- }
215+ const actions : ActionOrReactNode [ ] = [
216+ {
217+ label : "View YAML" ,
218+ icon : "FileCodeCorner" ,
219+ onClick : ( ) => setIsYamlFullscreen ( true ) ,
220+ } ,
221+ {
222+ label : "Inspect Pipeline" ,
223+ icon : "SquareMousePointer" ,
224+ hidden : ! canAccessEditorSpec ,
225+ onClick : handleInspect ,
226+ } ,
227+ {
228+ label : "Clone Pipeline" ,
229+ icon : "CopyPlus" ,
230+ disabled : isPendingClone ,
231+ onClick : handleClone ,
232+ } ,
233+ {
234+ label : "Cancel Run" ,
235+ confirmation :
236+ "The run will be scheduled for cancellation. This action cannot be undone." ,
237+ icon : isSuccessCancel ? "CircleSlash" : "CircleX" ,
238+ className : isSuccessCancel ? "bg-primary text-primary-foreground" : "" ,
239+ destructive : ! isSuccessCancel ,
240+ hidden : ! isInProgress || ! isRunCreator ,
241+ disabled : isPendingCancel || isSuccessCancel ,
242+ onClick : handleCancel ,
243+ } ,
244+ {
245+ label : "Rerun Pipeline" ,
246+ icon : "RefreshCcw" ,
247+ disabled : isPendingRerun ,
248+ hidden : ! isComplete ,
249+ onClick : handleRerun ,
250+ } ,
251+ ] ;
131252
132253 return (
133254 < >
0 commit comments