Skip to content

Commit 10479d0

Browse files
committed
feat: copy submit run arguments from recent run
1 parent b99e8dd commit 10479d0

File tree

3 files changed

+142
-20
lines changed

3 files changed

+142
-20
lines changed

src/components/shared/PipelineRunDisplay/PipelineRunsList.tsx

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,32 @@ import { withSuspenseWrapper } from "@/components/shared/SuspenseWrapper";
55
import { Button } from "@/components/ui/button";
66
import { InlineStack } from "@/components/ui/layout";
77
import { ScrollArea } from "@/components/ui/scroll-area";
8+
import { cn } from "@/lib/utils";
9+
import type { PipelineRun } from "@/types/pipelineRun";
810

911
import { RecentRunsTitle } from "./components/RecentRunsTitle";
1012
import { usePipelineRuns } from "./usePipelineRuns";
1113

1214
const DEFAULT_SHOWING_RUNS = 4;
1315

16+
interface PipelineRunsListProps {
17+
pipelineName?: string;
18+
showMoreButton?: boolean;
19+
showTitle?: boolean;
20+
disabled?: boolean;
21+
overviewConfig?: ComponentProps<typeof RunOverview>["config"];
22+
onRunClick?: (run: PipelineRun) => void;
23+
}
24+
1425
export const PipelineRunsList = withSuspenseWrapper(
1526
({
1627
pipelineName,
1728
showMoreButton = true,
29+
showTitle = true,
30+
disabled = false,
1831
overviewConfig,
19-
}: {
20-
pipelineName?: string;
21-
showMoreButton?: boolean;
22-
overviewConfig?: ComponentProps<typeof RunOverview>["config"];
23-
}) => {
32+
onRunClick,
33+
}: PipelineRunsListProps) => {
2434
const { data: pipelineRuns } = usePipelineRuns(pipelineName);
2535

2636
const [showingRuns, setShowingRuns] = useState(DEFAULT_SHOWING_RUNS);
@@ -31,13 +41,22 @@ export const PipelineRunsList = withSuspenseWrapper(
3141

3242
return (
3343
<>
34-
<RecentRunsTitle
35-
pipelineName={pipelineName}
36-
runsCount={pipelineRuns.length}
37-
/>
38-
<ScrollArea>
44+
{showTitle && (
45+
<RecentRunsTitle
46+
pipelineName={pipelineName}
47+
runsCount={pipelineRuns.length}
48+
/>
49+
)}
50+
<ScrollArea
51+
className={cn(disabled && "opacity-50 pointer-events-none")}
52+
>
3953
{pipelineRuns.slice(0, showingRuns).map((run) => (
40-
<RunOverview key={run.id} run={run} config={overviewConfig} />
54+
<RunOverview
55+
key={run.id}
56+
run={run}
57+
config={overviewConfig}
58+
onClick={onRunClick}
59+
/>
4160
))}
4261
{showMoreButton && pipelineRuns.length > showingRuns && (
4362
<InlineStack className="w-full" align="center">

src/components/shared/PipelineRunDisplay/RunOverview.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ interface RunOverviewProps {
4141
showAuthor?: boolean;
4242
};
4343
className?: string;
44+
onClick?: (run: PipelineRun) => void;
4445
}
4546

4647
const defaultConfig = {
@@ -53,20 +54,31 @@ const defaultConfig = {
5354
showAuthor: false,
5455
};
5556

56-
const RunOverview = ({ run, config, className = "" }: RunOverviewProps) => {
57+
const RunOverview = ({
58+
run,
59+
config,
60+
className = "",
61+
onClick,
62+
}: RunOverviewProps) => {
5763
const navigate = useNavigate();
5864

5965
const combinedConfig = {
6066
...defaultConfig,
6167
...config,
6268
};
6369

70+
const handleClick = (e: React.MouseEvent) => {
71+
e.stopPropagation();
72+
if (onClick) {
73+
onClick(run);
74+
} else {
75+
navigate({ to: `${APP_ROUTES.RUNS}/${run.id}` });
76+
}
77+
};
78+
6479
return (
6580
<div
66-
onClick={(e) => {
67-
e.stopPropagation();
68-
navigate({ to: `${APP_ROUTES.RUNS}/${run.id}` });
69-
}}
81+
onClick={handleClick}
7082
className={cn(
7183
"flex flex-col p-2 text-sm hover:bg-gray-50 cursor-pointer",
7284
className,

src/components/shared/Submitters/Oasis/components/SubmitTaskArgumentsDialog.tsx

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import { useMutation } from "@tanstack/react-query";
12
import { useCallback, useMemo, useState } from "react";
23

4+
import { PipelineRunsList } from "@/components/shared/PipelineRunDisplay/PipelineRunsList";
35
import { typeSpecToString } from "@/components/shared/ReactFlow/FlowCanvas/TaskNode/ArgumentsEditor/utils";
46
import { getArgumentsFromInputs } from "@/components/shared/ReactFlow/FlowCanvas/utils/getArgumentsFromInputs";
57
import { Button } from "@/components/ui/button";
@@ -11,12 +13,22 @@ import {
1113
DialogHeader,
1214
DialogTitle,
1315
} from "@/components/ui/dialog";
16+
import { Icon } from "@/components/ui/icon";
1417
import { Input } from "@/components/ui/input";
1518
import { BlockStack, InlineStack } from "@/components/ui/layout";
19+
import {
20+
Popover,
21+
PopoverContent,
22+
PopoverTrigger,
23+
} from "@/components/ui/popover";
1624
import { ScrollArea } from "@/components/ui/scroll-area";
1725
import { Paragraph } from "@/components/ui/typography";
1826
import { cn } from "@/lib/utils";
27+
import { useBackend } from "@/providers/BackendProvider";
28+
import { fetchExecutionDetails } from "@/services/executionService";
29+
import type { PipelineRun } from "@/types/pipelineRun";
1930
import type { ComponentSpec, InputSpec } from "@/utils/componentSpec";
31+
import { getArgumentValue } from "@/utils/nodes/taskArguments";
2032

2133
interface SubmitTaskArgumentsDialogProps {
2234
open: boolean;
@@ -64,14 +76,28 @@ export const SubmitTaskArgumentsDialog = ({
6476
<DialogHeader>
6577
<DialogTitle>Submit Run with Arguments</DialogTitle>
6678
<DialogDescription>
67-
{hasInputs
68-
? "Customize the pipeline input values before submitting."
69-
: "This pipeline has no configurable inputs."}
79+
{hasInputs ? (
80+
<BlockStack gap="2">
81+
<Paragraph tone="subdued">
82+
Customize the pipeline input values before submitting.
83+
</Paragraph>
84+
<InlineStack align="end" className="w-full">
85+
<CopyFromRunPopover
86+
componentSpec={componentSpec}
87+
onCopy={setTaskArguments}
88+
/>
89+
</InlineStack>
90+
</BlockStack>
91+
) : (
92+
<Paragraph tone="subdued">
93+
This pipeline has no configurable inputs.
94+
</Paragraph>
95+
)}
7096
</DialogDescription>
7197
</DialogHeader>
7298

7399
{hasInputs && (
74-
<ScrollArea className="max-h-[60vh] pr-4">
100+
<ScrollArea className="max-h-[60vh] pr-4 w-full">
75101
<BlockStack gap="4">
76102
{inputs.map((input) => (
77103
<ArgumentField
@@ -96,6 +122,71 @@ export const SubmitTaskArgumentsDialog = ({
96122
);
97123
};
98124

125+
const CopyFromRunPopover = ({
126+
componentSpec,
127+
onCopy,
128+
}: {
129+
componentSpec: ComponentSpec;
130+
onCopy: (args: Record<string, string>) => void;
131+
}) => {
132+
const { backendUrl } = useBackend();
133+
const pipelineName = componentSpec.name;
134+
135+
const [popoverOpen, setPopoverOpen] = useState(false);
136+
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,
143+
);
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+
});
164+
165+
return (
166+
<Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
167+
<PopoverTrigger asChild>
168+
<Button variant="link" size="sm">
169+
<Icon name="Copy" />
170+
Copy from recent run
171+
</Button>
172+
</PopoverTrigger>
173+
<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+
/>
185+
</PopoverContent>
186+
</Popover>
187+
);
188+
};
189+
99190
interface ArgumentFieldProps {
100191
input: InputSpec;
101192
value: string;

0 commit comments

Comments
 (0)