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