11import { useMutation } from "@tanstack/react-query" ;
22import { useCallback , useMemo , 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 ;
@@ -89,7 +96,12 @@ export const SubmitTaskArgumentsDialog = ({
8996 < InlineStack align = "end" className = "w-full" >
9097 < CopyFromRunPopover
9198 componentSpec = { componentSpec }
92- onCopy = { setTaskArguments }
99+ onCopy = { ( newArgs ) => {
100+ setTaskArguments ( ( prev ) => ( {
101+ ...prev ,
102+ ...newArgs ,
103+ } ) ) ;
104+ } }
93105 />
94106 </ InlineStack >
95107 </ BlockStack >
@@ -137,34 +149,72 @@ const CopyFromRunPopover = ({
137149 const pipelineName = componentSpec . name ;
138150
139151 const [ popoverOpen , setPopoverOpen ] = useState ( false ) ;
152+ const [ customRunId , setCustomRunId ] = useState ( "" ) ;
153+
154+ const {
155+ mutate : copyFromRunMutation ,
156+ isPending : isCopyingFromRun ,
157+ isError,
158+ } = useMutation ( {
159+ /**
160+ * @param run - The run to copy arguments from. Can be a run ID or a run object.
161+ * @returns
162+ */
163+ mutationFn : async ( run : PipelineRun | string ) => {
164+ const executionId =
165+ typeof run === "string"
166+ ? ( await fetchPipelineRun ( run , backendUrl ) ) . root_execution_id
167+ : String ( run . root_execution_id ) ;
140168
141- const { mutate : copyFromRunMutation , isPending : isCopyingFromRun } =
142- useMutation ( {
143- mutationFn : async ( run : PipelineRun ) => {
144- const executionDetails = await fetchExecutionDetails (
145- String ( run . root_execution_id ) ,
146- backendUrl ,
169+ const executionDetails = await fetchExecutionDetails (
170+ executionId ,
171+ backendUrl ,
172+ ) ;
173+ return executionDetails . task_spec . arguments ;
174+ } ,
175+ onSuccess : ( runArguments : TaskArguments ) => {
176+ if ( runArguments ) {
177+ const componentSpecInputs = new Set (
178+ componentSpec . inputs ?. map ( ( input ) => input . name ) ?? [ ] ,
147179 ) ;
148- return executionDetails . task_spec . arguments ;
149- } ,
150- onSuccess : ( runArguments ) => {
151- if ( runArguments ) {
152- const newArgs = Object . fromEntries (
153- Object . entries ( runArguments )
154- . map ( ( [ name , _ ] ) => [ name , getArgumentValue ( runArguments , name ) ] )
155- . filter (
156- ( entry ) : entry is [ string , string ] => entry [ 1 ] !== undefined ,
157- ) ,
158- ) ;
159- onCopy ( newArgs ) ;
160- }
161- setPopoverOpen ( false ) ;
162- } ,
163- onError : ( error ) => {
164- console . error ( "Failed to fetch run arguments:" , error ) ;
165- setPopoverOpen ( false ) ;
166- } ,
167- } ) ;
180+
181+ const newArgs = Object . fromEntries (
182+ Object . entries ( runArguments )
183+ . map (
184+ ( [ name , _ ] ) =>
185+ [ name , getArgumentValue ( runArguments , name ) ] as const ,
186+ )
187+ . filter (
188+ ( entry ) : entry is [ string , string ] =>
189+ entry [ 1 ] !== undefined && componentSpecInputs . has ( entry [ 0 ] ) ,
190+ ) ,
191+ ) ;
192+
193+ onCopy ( newArgs ) ;
194+ }
195+ setPopoverOpen ( false ) ;
196+ setCustomRunId ( "" ) ;
197+ } ,
198+ onError : ( error ) => {
199+ console . error ( "Failed to fetch run arguments:" , error ) ;
200+ } ,
201+ } ) ;
202+
203+ const handleCustomRunIdSubmit = useCallback ( ( ) => {
204+ const trimmedId = customRunId . trim ( ) ;
205+ if ( trimmedId ) {
206+ copyFromRunMutation ( trimmedId ) ;
207+ }
208+ } , [ customRunId , copyFromRunMutation ] ) ;
209+
210+ const handleKeyDown = useCallback (
211+ ( e : React . KeyboardEvent < HTMLInputElement > ) => {
212+ if ( e . key === "Enter" ) {
213+ handleCustomRunIdSubmit ( ) ;
214+ }
215+ } ,
216+ [ handleCustomRunIdSubmit ] ,
217+ ) ;
168218
169219 return (
170220 < Popover open = { popoverOpen } onOpenChange = { setPopoverOpen } >
@@ -175,17 +225,52 @@ const CopyFromRunPopover = ({
175225 </ Button >
176226 </ PopoverTrigger >
177227 < PopoverContent className = "w-100" align = "end" >
178- < PipelineRunsList
179- pipelineName = { pipelineName }
180- onRunClick = { copyFromRunMutation }
181- showTitle = { false }
182- showMoreButton = { true }
183- overviewConfig = { {
184- showName : false ,
185- showTaskStatusBar : false ,
186- } }
187- disabled = { isCopyingFromRun }
188- />
228+ < BlockStack gap = "4" >
229+ < BlockStack gap = "2" >
230+ < Paragraph size = "sm" weight = "semibold" >
231+ Enter run ID
232+ </ Paragraph >
233+ < InlineStack gap = "2" fill >
234+ < Input
235+ placeholder = "Run ID"
236+ value = { customRunId }
237+ onChange = { ( e ) => setCustomRunId ( e . target . value ) }
238+ onKeyDown = { handleKeyDown }
239+ disabled = { isCopyingFromRun }
240+ className = { cn ( isError && "border-red-300" , "flex-1" ) }
241+ />
242+ < Button
243+ size = "sm"
244+ onClick = { handleCustomRunIdSubmit }
245+ disabled = { isCopyingFromRun || ! customRunId . trim ( ) }
246+ >
247+ { isCopyingFromRun ? < Spinner /> : < Icon name = "ArrowRight" /> }
248+ </ Button >
249+ </ InlineStack >
250+ { isError && (
251+ < Paragraph size = "xs" tone = "critical" >
252+ Failed to fetch run. Please check the run ID.
253+ </ Paragraph >
254+ ) }
255+ </ BlockStack >
256+
257+ < BlockStack gap = "2" >
258+ < Paragraph size = "sm" weight = "semibold" >
259+ Or select from recent runs
260+ </ Paragraph >
261+ < PipelineRunsList
262+ pipelineName = { pipelineName }
263+ onRunClick = { copyFromRunMutation }
264+ showTitle = { false }
265+ showMoreButton = { true }
266+ overviewConfig = { {
267+ showName : false ,
268+ showTaskStatusBar : false ,
269+ } }
270+ disabled = { isCopyingFromRun }
271+ />
272+ </ BlockStack >
273+ </ BlockStack >
189274 </ PopoverContent >
190275 </ Popover >
191276 ) ;
0 commit comments