Import workflow (#986)
This commit is contained in:
101
skyvern-frontend/src/routes/workflows/ImportWorkflowButton.tsx
Normal file
101
skyvern-frontend/src/routes/workflows/ImportWorkflowButton.tsx
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import { getClient } from "@/api/AxiosClient";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { ReloadIcon, UploadIcon } from "@radix-ui/react-icons";
|
||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { useId } from "react";
|
||||||
|
import { stringify as convertToYAML } from "yaml";
|
||||||
|
import { WorkflowApiResponse } from "./types/workflowTypes";
|
||||||
|
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
import { toast } from "@/components/ui/use-toast";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip";
|
||||||
|
|
||||||
|
function isJsonString(str: string): boolean {
|
||||||
|
try {
|
||||||
|
JSON.parse(str);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ImportWorkflowButton() {
|
||||||
|
const inputId = useId();
|
||||||
|
const credentialGetter = useCredentialGetter();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const createWorkflowFromYamlMutation = useMutation({
|
||||||
|
mutationFn: async (yaml: string) => {
|
||||||
|
const client = await getClient(credentialGetter);
|
||||||
|
return client.post<string, { data: WorkflowApiResponse }>(
|
||||||
|
"/workflows",
|
||||||
|
yaml,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "text/plain",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onSuccess: (response) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["workflows"],
|
||||||
|
});
|
||||||
|
navigate(`/workflows/${response.data.workflow_permanent_id}/edit`);
|
||||||
|
},
|
||||||
|
onError: (error: AxiosError) => {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: "Error importing workflow",
|
||||||
|
description: error.message || "An error occurred",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<Label htmlFor={inputId}>
|
||||||
|
<input
|
||||||
|
id={inputId}
|
||||||
|
type="file"
|
||||||
|
accept=".yaml,.yml,.json"
|
||||||
|
className="hidden"
|
||||||
|
onChange={async (event) => {
|
||||||
|
if (event.target.files && event.target.files[0]) {
|
||||||
|
const fileTextContent = await event.target.files[0].text();
|
||||||
|
const isJson = isJsonString(fileTextContent);
|
||||||
|
const content = isJson
|
||||||
|
? convertToYAML(JSON.parse(fileTextContent))
|
||||||
|
: fileTextContent;
|
||||||
|
createWorkflowFromYamlMutation.mutate(content);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="flex h-full cursor-pointer items-center gap-2 rounded-md bg-secondary px-4 py-2 font-bold text-secondary-foreground hover:bg-secondary/90">
|
||||||
|
{createWorkflowFromYamlMutation.isPending ? (
|
||||||
|
<ReloadIcon className="h-4 w-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<UploadIcon className="h-4 w-4" />
|
||||||
|
)}
|
||||||
|
Import
|
||||||
|
</div>
|
||||||
|
</Label>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
Import a workflow from a YAML or JSON file
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ImportWorkflowButton };
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { getClient } from "@/api/AxiosClient";
|
import { getClient } from "@/api/AxiosClient";
|
||||||
import { WorkflowApiResponse, WorkflowRunApiResponse } from "@/api/types";
|
import { WorkflowApiResponse, WorkflowRunApiResponse } from "@/api/types";
|
||||||
import { StatusBadge } from "@/components/StatusBadge";
|
import { StatusBadge } from "@/components/StatusBadge";
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Pagination,
|
Pagination,
|
||||||
@@ -37,10 +38,10 @@ import {
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||||
import { stringify as convertToYAML } from "yaml";
|
import { stringify as convertToYAML } from "yaml";
|
||||||
|
import { ImportWorkflowButton } from "./ImportWorkflowButton";
|
||||||
import { WorkflowCreateYAMLRequest } from "./types/workflowYamlTypes";
|
import { WorkflowCreateYAMLRequest } from "./types/workflowYamlTypes";
|
||||||
import { WorkflowTitle } from "./WorkflowTitle";
|
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
|
||||||
import { WorkflowActions } from "./WorkflowActions";
|
import { WorkflowActions } from "./WorkflowActions";
|
||||||
|
import { WorkflowTitle } from "./WorkflowTitle";
|
||||||
|
|
||||||
const emptyWorkflowRequest: WorkflowCreateYAMLRequest = {
|
const emptyWorkflowRequest: WorkflowCreateYAMLRequest = {
|
||||||
title: "New Workflow",
|
title: "New Workflow",
|
||||||
@@ -178,19 +179,22 @@ function Workflows() {
|
|||||||
|
|
||||||
<header className="flex items-center justify-between">
|
<header className="flex items-center justify-between">
|
||||||
<h1 className="text-2xl font-semibold">Workflows</h1>
|
<h1 className="text-2xl font-semibold">Workflows</h1>
|
||||||
<Button
|
<div className="flex gap-2">
|
||||||
disabled={createNewWorkflowMutation.isPending}
|
<ImportWorkflowButton />
|
||||||
onClick={() => {
|
<Button
|
||||||
createNewWorkflowMutation.mutate();
|
disabled={createNewWorkflowMutation.isPending}
|
||||||
}}
|
onClick={() => {
|
||||||
>
|
createNewWorkflowMutation.mutate();
|
||||||
{createNewWorkflowMutation.isPending ? (
|
}}
|
||||||
<ReloadIcon className="mr-2 h-4 w-4 animate-spin" />
|
>
|
||||||
) : (
|
{createNewWorkflowMutation.isPending ? (
|
||||||
<PlusIcon className="mr-2 h-4 w-4" />
|
<ReloadIcon className="mr-2 h-4 w-4 animate-spin" />
|
||||||
)}
|
) : (
|
||||||
Create Workflow
|
<PlusIcon className="mr-2 h-4 w-4" />
|
||||||
</Button>
|
)}
|
||||||
|
Create Workflow
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div className="rounded-md border">
|
<div className="rounded-md border">
|
||||||
<Table>
|
<Table>
|
||||||
|
|||||||
Reference in New Issue
Block a user