11import { useMutation } from "@tanstack/react-query" ;
2- import { type ChangeEvent , useState } from "react" ;
2+ import { type ChangeEvent , type KeyboardEvent , useState } from "react" ;
33
4+ import type { TaskSpecOutput } from "@/api/types.gen" ;
45import { PipelineRunsList } from "@/components/shared/PipelineRunDisplay/PipelineRunsList" ;
56import { typeSpecToString } from "@/components/shared/ReactFlow/FlowCanvas/TaskNode/ArgumentsEditor/utils" ;
67import { getArgumentsFromInputs } from "@/components/shared/ReactFlow/FlowCanvas/utils/getArgumentsFromInputs" ;
@@ -22,14 +23,20 @@ import {
2223 PopoverTrigger ,
2324} from "@/components/ui/popover" ;
2425import { ScrollArea } from "@/components/ui/scroll-area" ;
26+ import { Spinner } from "@/components/ui/spinner" ;
2527import { Paragraph } from "@/components/ui/typography" ;
2628import { cn } from "@/lib/utils" ;
2729import { useBackend } from "@/providers/BackendProvider" ;
28- import { fetchExecutionDetails } from "@/services/executionService" ;
30+ import {
31+ fetchExecutionDetails ,
32+ fetchPipelineRun ,
33+ } from "@/services/executionService" ;
2934import type { PipelineRun } from "@/types/pipelineRun" ;
3035import type { ComponentSpec , InputSpec } from "@/utils/componentSpec" ;
3136import { getArgumentValue } from "@/utils/nodes/taskArguments" ;
3237
38+ type TaskArguments = TaskSpecOutput [ "arguments" ] ;
39+
3340interface SubmitTaskArgumentsDialogProps {
3441 open : boolean ;
3542 onCancel : ( ) => void ;
@@ -85,7 +92,12 @@ export const SubmitTaskArgumentsDialog = ({
8592 < InlineStack align = "end" className = "w-full" >
8693 < CopyFromRunPopover
8794 componentSpec = { componentSpec }
88- onCopy = { setTaskArguments }
95+ onCopy = { ( newArgs ) => {
96+ setTaskArguments ( ( prev ) => ( {
97+ ...prev ,
98+ ...newArgs ,
99+ } ) ) ;
100+ } }
89101 />
90102 </ InlineStack >
91103 </ BlockStack >
@@ -133,34 +145,69 @@ const CopyFromRunPopover = ({
133145 const pipelineName = componentSpec . name ;
134146
135147 const [ popoverOpen , setPopoverOpen ] = useState ( false ) ;
148+ const [ customRunId , setCustomRunId ] = useState ( "" ) ;
136149
137- const { mutate : copyFromRunMutation , isPending : isCopyingFromRun } =
138- useMutation ( {
139- mutationFn : async ( run : PipelineRun ) => {
140- const executionDetails = await fetchExecutionDetails (
141- String ( run . root_execution_id ) ,
142- backendUrl ,
150+ const {
151+ mutate : copyFromRunMutation ,
152+ isPending : isCopyingFromRun ,
153+ isError,
154+ } = useMutation ( {
155+ /**
156+ * @param run - The run to copy arguments from. Can be a run ID or a run object.
157+ * @returns
158+ */
159+ mutationFn : async ( run : PipelineRun | string ) => {
160+ const executionId =
161+ typeof run === "string"
162+ ? ( await fetchPipelineRun ( run , backendUrl ) ) . root_execution_id
163+ : String ( run . root_execution_id ) ;
164+
165+ const executionDetails = await fetchExecutionDetails (
166+ executionId ,
167+ backendUrl ,
168+ ) ;
169+ return executionDetails . task_spec . arguments ;
170+ } ,
171+ onSuccess : ( runArguments : TaskArguments ) => {
172+ if ( runArguments ) {
173+ const componentSpecInputs = new Set (
174+ componentSpec . inputs ?. map ( ( input ) => input . name ) ?? [ ] ,
143175 ) ;
144- return executionDetails . task_spec . arguments ;
145- } ,
146- onSuccess : ( runArguments ) => {
147- if ( runArguments ) {
148- const newArgs = Object . fromEntries (
149- Object . entries ( runArguments )
150- . map ( ( [ name , _ ] ) => [ name , getArgumentValue ( runArguments , name ) ] )
151- . filter (
152- ( entry ) : entry is [ string , string ] => entry [ 1 ] !== undefined ,
153- ) ,
154- ) ;
155- onCopy ( newArgs ) ;
156- }
157- setPopoverOpen ( false ) ;
158- } ,
159- onError : ( error ) => {
160- console . error ( "Failed to fetch run arguments:" , error ) ;
161- setPopoverOpen ( false ) ;
162- } ,
163- } ) ;
176+
177+ const newArgs = Object . fromEntries (
178+ Object . entries ( runArguments )
179+ . map (
180+ ( [ name , _ ] ) =>
181+ [ name , getArgumentValue ( runArguments , name ) ] as const ,
182+ )
183+ . filter (
184+ ( entry ) : entry is [ string , string ] =>
185+ entry [ 1 ] !== undefined && componentSpecInputs . has ( entry [ 0 ] ) ,
186+ ) ,
187+ ) ;
188+
189+ onCopy ( newArgs ) ;
190+ }
191+ setPopoverOpen ( false ) ;
192+ setCustomRunId ( "" ) ;
193+ } ,
194+ onError : ( error ) => {
195+ console . error ( "Failed to fetch run arguments:" , error ) ;
196+ } ,
197+ } ) ;
198+
199+ const handleCustomRunIdSubmit = ( ) => {
200+ const trimmedId = customRunId . trim ( ) ;
201+ if ( trimmedId ) {
202+ copyFromRunMutation ( trimmedId ) ;
203+ }
204+ } ;
205+
206+ const handleKeyDown = ( e : KeyboardEvent < HTMLInputElement > ) => {
207+ if ( e . key === "Enter" ) {
208+ handleCustomRunIdSubmit ( ) ;
209+ }
210+ } ;
164211
165212 return (
166213 < Popover open = { popoverOpen } onOpenChange = { setPopoverOpen } >
@@ -171,17 +218,52 @@ const CopyFromRunPopover = ({
171218 </ Button >
172219 </ PopoverTrigger >
173220 < PopoverContent className = "w-100" align = "end" >
174- < PipelineRunsList
175- pipelineName = { pipelineName }
176- onRunClick = { copyFromRunMutation }
177- showTitle = { false }
178- showMoreButton = { true }
179- overviewConfig = { {
180- showName : false ,
181- showTaskStatusBar : false ,
182- } }
183- disabled = { isCopyingFromRun }
184- />
221+ < BlockStack gap = "4" >
222+ < BlockStack gap = "2" >
223+ < Paragraph size = "sm" weight = "semibold" >
224+ Enter run ID
225+ </ Paragraph >
226+ < InlineStack gap = "2" fill >
227+ < Input
228+ placeholder = "Run ID"
229+ value = { customRunId }
230+ onChange = { ( e ) => setCustomRunId ( e . target . value ) }
231+ onKeyDown = { handleKeyDown }
232+ disabled = { isCopyingFromRun }
233+ className = { cn ( isError && "border-red-300" , "flex-1" ) }
234+ />
235+ < Button
236+ size = "sm"
237+ onClick = { handleCustomRunIdSubmit }
238+ disabled = { isCopyingFromRun || ! customRunId . trim ( ) }
239+ >
240+ { isCopyingFromRun ? < Spinner /> : < Icon name = "ArrowRight" /> }
241+ </ Button >
242+ </ InlineStack >
243+ { isError && (
244+ < Paragraph size = "xs" tone = "critical" >
245+ Failed to fetch run. Please check the run ID.
246+ </ Paragraph >
247+ ) }
248+ </ BlockStack >
249+
250+ < BlockStack gap = "2" >
251+ < Paragraph size = "sm" weight = "semibold" >
252+ Or select from recent runs
253+ </ Paragraph >
254+ < PipelineRunsList
255+ pipelineName = { pipelineName }
256+ onRunClick = { copyFromRunMutation }
257+ showTitle = { false }
258+ showMoreButton = { true }
259+ overviewConfig = { {
260+ showName : false ,
261+ showTaskStatusBar : false ,
262+ } }
263+ disabled = { isCopyingFromRun }
264+ />
265+ </ BlockStack >
266+ </ BlockStack >
185267 </ PopoverContent >
186268 </ Popover >
187269 ) ;
0 commit comments