@@ -25,13 +25,32 @@ import PipelineIO from "../shared/Execution/PipelineIO";
2525import { InfoBox } from "../shared/InfoBox" ;
2626import { StatusBar , StatusText } from "../shared/Status" ;
2727import { TaskImplementation } from "../shared/TaskDetails" ;
28- import { CancelPipelineRunButton } from "./components/CancelPipelineRunButton" ;
29- import { ClonePipelineButton } from "./components/ClonePipelineButton" ;
30- import { InspectPipelineButton } from "./components/InspectPipelineButton" ;
31- import { RerunPipelineButton } from "./components/RerunPipelineButton" ;
28+ import { useNavigate } from "@tanstack/react-router" ;
29+ import useToastNotification from "@/hooks/useToastNotification" ;
30+ import { getInitialName } from "@/utils/getComponentName" ;
31+ import {
32+ cancelPipelineRun ,
33+ copyRunToPipeline ,
34+ } from "@/services/pipelineRunService" ;
35+ import { useMutation } from "@tanstack/react-query" ;
36+ import { APP_ROUTES } from "@/routes/router" ;
37+ import type { PipelineRun } from "@/types/pipelineRun" ;
38+ import { submitPipelineRun } from "@/utils/submitPipeline" ;
39+
40+ import { isAuthorizationRequired } from "../shared/Authentication/helpers" ;
41+ import { useAuthLocalStorage } from "../shared/Authentication/useAuthLocalStorage" ;
42+ import { useAwaitAuthorization } from "../shared/Authentication/useAwaitAuthorization" ;
3243
3344export const RunDetails = ( ) => {
34- const { configured } = useBackend ( ) ;
45+ const navigate = useNavigate ( ) ;
46+ const notify = useToastNotification ( ) ;
47+
48+ const { available, configured, backendUrl } = useBackend ( ) ;
49+
50+ const { awaitAuthorization, isAuthorized } = useAwaitAuthorization ( ) ;
51+ const { getToken } = useAuthLocalStorage ( ) ;
52+ const { data : currentUserDetails } = useUserDetails ( ) ;
53+
3554 const { componentSpec } = useComponentSpec ( ) ;
3655 const {
3756 rootDetails : details ,
@@ -41,7 +60,74 @@ export const RunDetails = () => {
4160 isLoading,
4261 error,
4362 } = useExecutionData ( ) ;
44- const { data : currentUserDetails } = useUserDetails ( ) ;
63+
64+ const { isPending : isPendingClone , mutate : clonePipeline } = useMutation ( {
65+ mutationFn : async ( ) => {
66+ const name = getInitialName ( componentSpec ) ;
67+ return copyRunToPipeline ( componentSpec , name ) ;
68+ } ,
69+ onSuccess : ( result ) => {
70+ if ( result ?. url ) {
71+ notify ( `Pipeline "${ result . name } " cloned` , "success" ) ;
72+ navigate ( { to : result . url } ) ;
73+ }
74+ } ,
75+ onError : ( error ) => {
76+ notify ( `Error cloning pipeline: ${ error } ` , "error" ) ;
77+ } ,
78+ } ) ;
79+
80+ const {
81+ mutate : cancelPipeline ,
82+ isPending : isPendingCancel ,
83+ isSuccess : isSuccessCancel ,
84+ } = useMutation ( {
85+ mutationFn : ( runId : string ) => cancelPipelineRun ( runId , backendUrl ) ,
86+ onSuccess : ( ) => {
87+ notify ( `Pipeline run ${ runId } cancelled` , "success" ) ;
88+ } ,
89+ onError : ( error ) => {
90+ notify ( `Error cancelling run: ${ error } ` , "error" ) ;
91+ } ,
92+ } ) ;
93+
94+ const onSuccess = ( response : PipelineRun ) => {
95+ navigate ( { to : `${ APP_ROUTES . RUNS } /${ response . id } ` } ) ;
96+ } ;
97+
98+ const onError = ( error : Error | string ) => {
99+ const message = `Failed to submit pipeline. ${ error instanceof Error ? error . message : String ( error ) } ` ;
100+ notify ( message , "error" ) ;
101+ } ;
102+
103+ const getAuthToken = async ( ) : Promise < string | undefined > => {
104+ const authorizationRequired = isAuthorizationRequired ( ) ;
105+
106+ if ( authorizationRequired && ! isAuthorized ) {
107+ const token = await awaitAuthorization ( ) ;
108+ if ( token ) {
109+ return token ;
110+ }
111+ }
112+
113+ return getToken ( ) ;
114+ } ;
115+
116+ const { mutate : rerunPipeline , isPending : isPendingRerun } = useMutation ( {
117+ mutationFn : async ( ) => {
118+ const authorizationToken = await getAuthToken ( ) ;
119+
120+ return new Promise < PipelineRun > ( ( resolve , reject ) => {
121+ submitPipelineRun ( componentSpec , backendUrl , {
122+ authorizationToken,
123+ onSuccess : resolve ,
124+ onError : reject ,
125+ } ) ;
126+ } ) ;
127+ } ,
128+ onSuccess,
129+ onError,
130+ } ) ;
45131
46132 const editorRoute = componentSpec . name
47133 ? `/editor/${ encodeURIComponent ( componentSpec . name ) } `
@@ -55,6 +141,36 @@ export const RunDetails = () => {
55141 const isRunCreator =
56142 currentUserDetails ?. id && metadata ?. created_by === currentUserDetails . id ;
57143
144+ const handleInspect = ( ) => {
145+ navigate ( { to : editorRoute } ) ;
146+ } ;
147+
148+ const handleClone = ( ) => {
149+ clonePipeline ( ) ;
150+ } ;
151+
152+ const handleCancel = ( ) => {
153+ if ( ! runId ) {
154+ notify ( `Failed to cancel run. No run ID found.` , "warning" ) ;
155+ return ;
156+ }
157+
158+ if ( ! available ) {
159+ notify ( `Backend is not available. Cannot cancel run.` , "warning" ) ;
160+ return ;
161+ }
162+
163+ try {
164+ cancelPipeline ( runId ) ;
165+ } catch ( error ) {
166+ notify ( `Error cancelling run: ${ error } ` , "error" ) ;
167+ }
168+ } ;
169+
170+ const handleRerun = ( ) => {
171+ rerunPipeline ( ) ;
172+ } ;
173+
58174 if ( error || ! details || ! state || ! componentSpec ) {
59175 return (
60176 < BlockStack align = "center" inlineAlign = "center" className = "h-full" >
@@ -92,35 +208,43 @@ export const RunDetails = () => {
92208
93209 const annotations = componentSpec . metadata ?. annotations || { } ;
94210
95- const actions : ActionOrReactNode [ ] = [ ] ;
96-
97- actions . push (
211+ const actions : ActionOrReactNode [ ] = [
98212 < TaskImplementation
99213 displayName = { componentSpec . name ?? "Pipeline" }
100214 componentSpec = { componentSpec }
101215 showInlineContent = { false }
102216 /> ,
103- ) ;
104-
105- if ( canAccessEditorSpec && componentSpec . name ) {
106- actions . push (
107- < InspectPipelineButton key = "inspect" pipelineName = { componentSpec . name } /> ,
108- ) ;
109- }
110-
111- actions . push (
112- < ClonePipelineButton key = "clone" componentSpec = { componentSpec } /> ,
113- ) ;
114-
115- if ( isInProgress && isRunCreator ) {
116- actions . push ( < CancelPipelineRunButton key = "cancel" runId = { runId } /> ) ;
117- }
118-
119- if ( isComplete ) {
120- actions . push (
121- < RerunPipelineButton key = "rerun" componentSpec = { componentSpec } /> ,
122- ) ;
123- }
217+ {
218+ label : "Inspect Pipeline" ,
219+ icon : "SquareMousePointer" ,
220+ hidden : ! canAccessEditorSpec ,
221+ onClick : handleInspect ,
222+ } ,
223+ {
224+ label : "Clone Pipeline" ,
225+ icon : "CopyPlus" ,
226+ disabled : isPendingClone ,
227+ onClick : handleClone ,
228+ } ,
229+ {
230+ label : "Cancel Run" ,
231+ confirmation :
232+ "The run will be scheduled for cancellation. This action cannot be undone." ,
233+ icon : isSuccessCancel ? "CircleSlash" : "CircleX" ,
234+ className : isSuccessCancel ? "bg-primary text-primary-foreground" : "" ,
235+ destructive : ! isSuccessCancel ,
236+ hidden : ! isInProgress || ! isRunCreator ,
237+ disabled : isPendingCancel || isSuccessCancel ,
238+ onClick : handleCancel ,
239+ } ,
240+ {
241+ label : "Rerun Pipeline" ,
242+ icon : "RefreshCcw" ,
243+ disabled : isPendingRerun ,
244+ hidden : ! isComplete ,
245+ onClick : handleRerun ,
246+ } ,
247+ ] ;
124248
125249 return (
126250 < BlockStack gap = "6" className = "p-2 h-full" >
0 commit comments