Skip to content

Commit 7341b92

Browse files
authored
Export workflow (#987)
1 parent fd061d1 commit 7341b92

File tree

2 files changed

+67
-7
lines changed

2 files changed

+67
-7
lines changed

skyvern-frontend/src/routes/workflows/WorkflowActions.tsx

+66-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getClient } from "@/api/AxiosClient";
2+
import { GarbageIcon } from "@/components/icons/GarbageIcon";
23
import { Button } from "@/components/ui/button";
34
import {
45
Dialog,
@@ -14,35 +15,67 @@ import {
1415
DropdownMenu,
1516
DropdownMenuContent,
1617
DropdownMenuItem,
18+
DropdownMenuPortal,
19+
DropdownMenuSub,
20+
DropdownMenuSubContent,
21+
DropdownMenuSubTrigger,
1722
DropdownMenuTrigger,
1823
} from "@/components/ui/dropdown-menu";
1924
import { toast } from "@/components/ui/use-toast";
2025
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
2126
import {
2227
CopyIcon,
2328
DotsHorizontalIcon,
29+
DownloadIcon,
2430
ReloadIcon,
2531
} from "@radix-ui/react-icons";
2632
import { useMutation, useQueryClient } from "@tanstack/react-query";
2733
import { AxiosError } from "axios";
28-
import { useWorkflowQuery } from "./hooks/useWorkflowQuery";
34+
import { useNavigate } from "react-router-dom";
2935
import { stringify as convertToYAML } from "yaml";
36+
import { convert } from "./editor/workflowEditorUtils";
37+
import { useWorkflowQuery } from "./hooks/useWorkflowQuery";
3038
import { WorkflowApiResponse } from "./types/workflowTypes";
31-
import { useNavigate } from "react-router-dom";
3239
import { WorkflowCreateYAMLRequest } from "./types/workflowYamlTypes";
33-
import { convert } from "./editor/workflowEditorUtils";
34-
import { GarbageIcon } from "@/components/icons/GarbageIcon";
3540

3641
type Props = {
3742
id: string;
3843
};
3944

45+
function downloadFile(fileName: string, contents: string) {
46+
const element = document.createElement("a");
47+
element.setAttribute(
48+
"href",
49+
"data:text/plain;charset=utf-8," + encodeURIComponent(contents),
50+
);
51+
element.setAttribute("download", fileName);
52+
53+
element.style.display = "none";
54+
document.body.appendChild(element);
55+
56+
element.click();
57+
58+
document.body.removeChild(element);
59+
}
60+
4061
function WorkflowActions({ id }: Props) {
4162
const credentialGetter = useCredentialGetter();
4263
const queryClient = useQueryClient();
4364
const { data: workflow } = useWorkflowQuery({ workflowPermanentId: id });
4465
const navigate = useNavigate();
4566

67+
function handleExport(type: "json" | "yaml") {
68+
if (!workflow) {
69+
return;
70+
}
71+
const fileName = `${workflow.title}.${type}`;
72+
const contents =
73+
type === "json"
74+
? JSON.stringify(convert(workflow), null, 2)
75+
: convertToYAML(convert(workflow));
76+
downloadFile(fileName, contents);
77+
}
78+
4679
const createWorkflowMutation = useMutation({
4780
mutationFn: async (workflow: WorkflowCreateYAMLRequest) => {
4881
const client = await getClient(credentialGetter);
@@ -98,14 +131,42 @@ function WorkflowActions({ id }: Props) {
98131
if (!workflow) {
99132
return;
100133
}
101-
const clonedWorkflow = convert(workflow);
134+
const clonedWorkflow = convert({
135+
...workflow,
136+
title: `Copy of ${workflow.title}`,
137+
});
102138
createWorkflowMutation.mutate(clonedWorkflow);
103139
}}
104140
className="p-2"
105141
>
106142
<CopyIcon className="mr-2 h-4 w-4" />
107143
Clone Workflow
108144
</DropdownMenuItem>
145+
<DropdownMenuSub>
146+
<DropdownMenuSubTrigger>
147+
<DownloadIcon className="mr-2 h-4 w-4" />
148+
Export as...
149+
</DropdownMenuSubTrigger>
150+
<DropdownMenuPortal>
151+
<DropdownMenuSubContent>
152+
<DropdownMenuItem
153+
onSelect={() => {
154+
handleExport("yaml");
155+
}}
156+
>
157+
YAML
158+
</DropdownMenuItem>
159+
<DropdownMenuItem
160+
onSelect={() => {
161+
handleExport("json");
162+
}}
163+
>
164+
JSON
165+
</DropdownMenuItem>
166+
</DropdownMenuSubContent>
167+
</DropdownMenuPortal>
168+
</DropdownMenuSub>
169+
109170
<DialogTrigger>
110171
<DropdownMenuItem className="p-2">
111172
<GarbageIcon className="mr-2 h-4 w-4 text-destructive" />

skyvern-frontend/src/routes/workflows/editor/workflowEditorUtils.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1065,12 +1065,11 @@ function convertBlocks(blocks: Array<WorkflowBlock>): Array<BlockYAML> {
10651065
}
10661066

10671067
function convert(workflow: WorkflowApiResponse): WorkflowCreateYAMLRequest {
1068-
const title = `Copy of ${workflow.title}`;
10691068
const userParameters = workflow.workflow_definition.parameters.filter(
10701069
(parameter) => parameter.parameter_type !== "output",
10711070
);
10721071
return {
1073-
title: title,
1072+
title: workflow.title,
10741073
description: workflow.description,
10751074
proxy_location: workflow.proxy_location,
10761075
webhook_callback_url: workflow.webhook_callback_url,

0 commit comments

Comments
 (0)