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,15 +23,21 @@ 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 useToastNotification from "@/hooks/useToastNotification" ;
2729import { cn } from "@/lib/utils" ;
2830import { useBackend } from "@/providers/BackendProvider" ;
29- import { fetchExecutionDetails } from "@/services/executionService" ;
31+ import {
32+ fetchExecutionDetails ,
33+ fetchPipelineRun ,
34+ } from "@/services/executionService" ;
3035import type { PipelineRun } from "@/types/pipelineRun" ;
3136import type { ComponentSpec , InputSpec } from "@/utils/componentSpec" ;
3237import { getArgumentValue } from "@/utils/nodes/taskArguments" ;
3338
39+ type TaskArguments = TaskSpecOutput [ "arguments" ] ;
40+
3441interface SubmitTaskArgumentsDialogProps {
3542 open : boolean ;
3643 onCancel : ( ) => void ;
@@ -161,34 +168,69 @@ const CopyFromRunPopover = ({
161168 const pipelineName = componentSpec . name ;
162169
163170 const [ popoverOpen , setPopoverOpen ] = useState ( false ) ;
171+ const [ customRunId , setCustomRunId ] = useState ( "" ) ;
164172
165- const { mutate : copyFromRunMutation , isPending : isCopyingFromRun } =
166- useMutation ( {
167- mutationFn : async ( run : PipelineRun ) => {
168- const executionDetails = await fetchExecutionDetails (
169- String ( run . root_execution_id ) ,
170- backendUrl ,
173+ const {
174+ mutate : copyFromRunMutation ,
175+ isPending : isCopyingFromRun ,
176+ isError,
177+ } = useMutation ( {
178+ /**
179+ * @param run - The run to copy arguments from. Can be a run ID or a run object.
180+ * @returns
181+ */
182+ mutationFn : async ( run : PipelineRun | string ) => {
183+ const executionId =
184+ typeof run === "string"
185+ ? ( await fetchPipelineRun ( run , backendUrl ) ) . root_execution_id
186+ : String ( run . root_execution_id ) ;
187+
188+ const executionDetails = await fetchExecutionDetails (
189+ executionId ,
190+ backendUrl ,
191+ ) ;
192+ return executionDetails . task_spec . arguments ;
193+ } ,
194+ onSuccess : ( runArguments : TaskArguments ) => {
195+ if ( runArguments ) {
196+ const componentSpecInputs = new Set (
197+ componentSpec . inputs ?. map ( ( input ) => input . name ) ?? [ ] ,
171198 ) ;
172- return executionDetails . task_spec . arguments ;
173- } ,
174- onSuccess : ( runArguments ) => {
175- if ( runArguments ) {
176- const newArgs = Object . fromEntries (
177- Object . entries ( runArguments )
178- . map ( ( [ name , _ ] ) => [ name , getArgumentValue ( runArguments , name ) ] )
179- . filter (
180- ( entry ) : entry is [ string , string ] => entry [ 1 ] !== undefined ,
181- ) ,
182- ) ;
183- onCopy ( newArgs ) ;
184- }
185- setPopoverOpen ( false ) ;
186- } ,
187- onError : ( error ) => {
188- console . error ( "Failed to fetch run arguments:" , error ) ;
189- setPopoverOpen ( false ) ;
190- } ,
191- } ) ;
199+
200+ const newArgs = Object . fromEntries (
201+ Object . entries ( runArguments )
202+ . map (
203+ ( [ name , _ ] ) =>
204+ [ name , getArgumentValue ( runArguments , name ) ] as const ,
205+ )
206+ . filter (
207+ ( entry ) : entry is [ string , string ] =>
208+ entry [ 1 ] !== undefined && componentSpecInputs . has ( entry [ 0 ] ) ,
209+ ) ,
210+ ) ;
211+
212+ onCopy ( newArgs ) ;
213+ }
214+ setPopoverOpen ( false ) ;
215+ setCustomRunId ( "" ) ;
216+ } ,
217+ onError : ( error ) => {
218+ console . error ( "Failed to fetch run arguments:" , error ) ;
219+ } ,
220+ } ) ;
221+
222+ const handleCustomRunIdSubmit = ( ) => {
223+ const trimmedId = customRunId . trim ( ) ;
224+ if ( trimmedId ) {
225+ copyFromRunMutation ( trimmedId ) ;
226+ }
227+ } ;
228+
229+ const handleKeyDown = ( e : KeyboardEvent < HTMLInputElement > ) => {
230+ if ( e . key === "Enter" ) {
231+ handleCustomRunIdSubmit ( ) ;
232+ }
233+ } ;
192234
193235 return (
194236 < Popover open = { popoverOpen } onOpenChange = { setPopoverOpen } >
@@ -199,17 +241,52 @@ const CopyFromRunPopover = ({
199241 </ Button >
200242 </ PopoverTrigger >
201243 < PopoverContent className = "w-100" align = "end" >
202- < PipelineRunsList
203- pipelineName = { pipelineName }
204- onRunClick = { copyFromRunMutation }
205- showTitle = { false }
206- showMoreButton = { true }
207- overviewConfig = { {
208- showName : false ,
209- showTaskStatusBar : false ,
210- } }
211- disabled = { isCopyingFromRun }
212- />
244+ < BlockStack gap = "4" >
245+ < BlockStack gap = "2" >
246+ < Paragraph size = "sm" weight = "semibold" >
247+ Enter run ID
248+ </ Paragraph >
249+ < InlineStack gap = "2" fill >
250+ < Input
251+ placeholder = "Run ID"
252+ value = { customRunId }
253+ onChange = { ( e ) => setCustomRunId ( e . target . value ) }
254+ onKeyDown = { handleKeyDown }
255+ disabled = { isCopyingFromRun }
256+ className = { cn ( isError && "border-red-300" , "flex-1" ) }
257+ />
258+ < Button
259+ size = "sm"
260+ onClick = { handleCustomRunIdSubmit }
261+ disabled = { isCopyingFromRun || ! customRunId . trim ( ) }
262+ >
263+ { isCopyingFromRun ? < Spinner /> : < Icon name = "ArrowRight" /> }
264+ </ Button >
265+ </ InlineStack >
266+ { isError && (
267+ < Paragraph size = "xs" tone = "critical" >
268+ Failed to fetch run. Please check the run ID.
269+ </ Paragraph >
270+ ) }
271+ </ BlockStack >
272+
273+ < BlockStack gap = "2" >
274+ < Paragraph size = "sm" weight = "semibold" >
275+ Or select from recent runs
276+ </ Paragraph >
277+ < PipelineRunsList
278+ pipelineName = { pipelineName }
279+ onRunClick = { copyFromRunMutation }
280+ showTitle = { false }
281+ showMoreButton = { true }
282+ overviewConfig = { {
283+ showName : false ,
284+ showTaskStatusBar : false ,
285+ } }
286+ disabled = { isCopyingFromRun }
287+ />
288+ </ BlockStack >
289+ </ BlockStack >
213290 </ PopoverContent >
214291 </ Popover >
215292 ) ;
0 commit comments