Add workflows settings in start node (#1270)
This commit is contained in:
@@ -10,12 +10,13 @@ import {
|
|||||||
type Props = {
|
type Props = {
|
||||||
value: ProxyLocation | null;
|
value: ProxyLocation | null;
|
||||||
onChange: (value: ProxyLocation) => void;
|
onChange: (value: ProxyLocation) => void;
|
||||||
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function ProxySelector({ value, onChange }: Props) {
|
function ProxySelector({ value, onChange, className }: Props) {
|
||||||
return (
|
return (
|
||||||
<Select value={value ?? ""} onValueChange={onChange}>
|
<Select value={value ?? ""} onValueChange={onChange}>
|
||||||
<SelectTrigger className="w-48">
|
<SelectTrigger className={className}>
|
||||||
<SelectValue placeholder="Proxy Location" />
|
<SelectValue placeholder="Proxy Location" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
|
|||||||
@@ -514,6 +514,7 @@ function CreateNewTaskForm({ initialValues }: Props) {
|
|||||||
<ProxySelector
|
<ProxySelector
|
||||||
value={field.value}
|
value={field.value}
|
||||||
onChange={field.onChange}
|
onChange={field.onChange}
|
||||||
|
className="w-48"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|||||||
@@ -686,6 +686,7 @@ function SavedTaskForm({ initialValues }: Props) {
|
|||||||
<ProxySelector
|
<ProxySelector
|
||||||
value={field.value}
|
value={field.value}
|
||||||
onChange={field.onChange}
|
onChange={field.onChange}
|
||||||
|
className="w-48"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { getClient } from "@/api/AxiosClient";
|
import { getClient } from "@/api/AxiosClient";
|
||||||
|
import { ProxyLocation } from "@/api/types";
|
||||||
|
import { ProxySelector } from "@/components/ProxySelector";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -7,28 +10,29 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
import { Input } from "@/components/ui/input";
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { Link, useParams } from "react-router-dom";
|
|
||||||
import { WorkflowParameterInput } from "./WorkflowParameterInput";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { toast } from "@/components/ui/use-toast";
|
import { toast } from "@/components/ui/use-toast";
|
||||||
|
import { useApiCredential } from "@/hooks/useApiCredential";
|
||||||
|
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||||
|
import { copyText } from "@/util/copyText";
|
||||||
|
import { apiBaseUrl } from "@/util/env";
|
||||||
import { CopyIcon, PlayIcon, ReloadIcon } from "@radix-ui/react-icons";
|
import { CopyIcon, PlayIcon, ReloadIcon } from "@radix-ui/react-icons";
|
||||||
import { ToastAction } from "@radix-ui/react-toast";
|
import { ToastAction } from "@radix-ui/react-toast";
|
||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import fetchToCurl from "fetch-to-curl";
|
import fetchToCurl from "fetch-to-curl";
|
||||||
import { apiBaseUrl } from "@/util/env";
|
import { useForm } from "react-hook-form";
|
||||||
import { useApiCredential } from "@/hooks/useApiCredential";
|
import { Link, useParams } from "react-router-dom";
|
||||||
import { copyText } from "@/util/copyText";
|
|
||||||
import { WorkflowParameter } from "./types/workflowTypes";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { ProxyLocation } from "@/api/types";
|
import { WorkflowParameter } from "./types/workflowTypes";
|
||||||
import { ProxySelector } from "@/components/ProxySelector";
|
import { WorkflowParameterInput } from "./WorkflowParameterInput";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workflowParameters: Array<WorkflowParameter>;
|
workflowParameters: Array<WorkflowParameter>;
|
||||||
initialValues: Record<string, unknown>;
|
initialValues: Record<string, unknown>;
|
||||||
|
initialSettings: {
|
||||||
|
proxyLocation: ProxyLocation;
|
||||||
|
webhookCallbackUrl: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function parseValuesForWorkflowRun(
|
function parseValuesForWorkflowRun(
|
||||||
@@ -92,19 +96,23 @@ function getRunWorkflowRequestBody(
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RunWorkflowFormType = Record<string, unknown> & {
|
type RunWorkflowFormType = Record<string, unknown> & {
|
||||||
webhookCallbackUrl: string | null;
|
webhookCallbackUrl: string;
|
||||||
proxyLocation: ProxyLocation | null;
|
proxyLocation: ProxyLocation;
|
||||||
};
|
};
|
||||||
|
|
||||||
function RunWorkflowForm({ workflowParameters, initialValues }: Props) {
|
function RunWorkflowForm({
|
||||||
|
workflowParameters,
|
||||||
|
initialValues,
|
||||||
|
initialSettings,
|
||||||
|
}: Props) {
|
||||||
const { workflowPermanentId } = useParams();
|
const { workflowPermanentId } = useParams();
|
||||||
const credentialGetter = useCredentialGetter();
|
const credentialGetter = useCredentialGetter();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const form = useForm<RunWorkflowFormType>({
|
const form = useForm<RunWorkflowFormType>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
...initialValues,
|
...initialValues,
|
||||||
webhookCallbackUrl: null,
|
webhookCallbackUrl: initialSettings.webhookCallbackUrl,
|
||||||
proxyLocation: ProxyLocation.Residential,
|
proxyLocation: initialSettings.proxyLocation,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const apiCredential = useApiCredential();
|
const apiCredential = useApiCredential();
|
||||||
@@ -313,6 +321,7 @@ function RunWorkflowForm({ workflowParameters, initialValues }: Props) {
|
|||||||
<ProxySelector
|
<ProxySelector
|
||||||
value={field.value}
|
value={field.value}
|
||||||
onChange={field.onChange}
|
onChange={field.onChange}
|
||||||
|
className="w-48"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useLocation, useParams } from "react-router-dom";
|
|||||||
import { RunWorkflowForm } from "./RunWorkflowForm";
|
import { RunWorkflowForm } from "./RunWorkflowForm";
|
||||||
import { WorkflowApiResponse } from "./types/workflowTypes";
|
import { WorkflowApiResponse } from "./types/workflowTypes";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
import { ProxyLocation } from "@/api/types";
|
||||||
|
|
||||||
function WorkflowRunParameters() {
|
function WorkflowRunParameters() {
|
||||||
const credentialGetter = useCredentialGetter();
|
const credentialGetter = useCredentialGetter();
|
||||||
@@ -93,6 +94,10 @@ function WorkflowRunParameters() {
|
|||||||
<RunWorkflowForm
|
<RunWorkflowForm
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
workflowParameters={workflowParameters}
|
workflowParameters={workflowParameters}
|
||||||
|
initialSettings={{
|
||||||
|
proxyLocation: workflow.proxy_location ?? ProxyLocation.Residential,
|
||||||
|
webhookCallbackUrl: workflow.webhook_callback_url ?? "",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import {
|
|||||||
AWSSecretParameter,
|
AWSSecretParameter,
|
||||||
WorkflowApiResponse,
|
WorkflowApiResponse,
|
||||||
WorkflowParameterValueType,
|
WorkflowParameterValueType,
|
||||||
|
WorkflowSettings,
|
||||||
} from "../types/workflowTypes";
|
} from "../types/workflowTypes";
|
||||||
import {
|
import {
|
||||||
BitwardenLoginCredentialParameterYAML,
|
BitwardenLoginCredentialParameterYAML,
|
||||||
@@ -50,7 +51,12 @@ import {
|
|||||||
import { WorkflowHeader } from "./WorkflowHeader";
|
import { WorkflowHeader } from "./WorkflowHeader";
|
||||||
import { WorkflowParametersStateContext } from "./WorkflowParametersStateContext";
|
import { WorkflowParametersStateContext } from "./WorkflowParametersStateContext";
|
||||||
import { edgeTypes } from "./edges";
|
import { edgeTypes } from "./edges";
|
||||||
import { AppNode, nodeTypes, WorkflowBlockNode } from "./nodes";
|
import {
|
||||||
|
AppNode,
|
||||||
|
isWorkflowBlockNode,
|
||||||
|
nodeTypes,
|
||||||
|
WorkflowBlockNode,
|
||||||
|
} from "./nodes";
|
||||||
import { WorkflowNodeLibraryPanel } from "./panels/WorkflowNodeLibraryPanel";
|
import { WorkflowNodeLibraryPanel } from "./panels/WorkflowNodeLibraryPanel";
|
||||||
import { WorkflowParametersPanel } from "./panels/WorkflowParametersPanel";
|
import { WorkflowParametersPanel } from "./panels/WorkflowParametersPanel";
|
||||||
import "./reactFlowOverrideStyles.css";
|
import "./reactFlowOverrideStyles.css";
|
||||||
@@ -63,6 +69,7 @@ import {
|
|||||||
getOutputParameterKey,
|
getOutputParameterKey,
|
||||||
getWorkflowBlocks,
|
getWorkflowBlocks,
|
||||||
getWorkflowErrors,
|
getWorkflowErrors,
|
||||||
|
getWorkflowSettings,
|
||||||
layout,
|
layout,
|
||||||
nodeAdderNode,
|
nodeAdderNode,
|
||||||
startNode,
|
startNode,
|
||||||
@@ -200,6 +207,7 @@ function FlowRenderer({
|
|||||||
parameters: Array<ParameterYAML>;
|
parameters: Array<ParameterYAML>;
|
||||||
blocks: Array<BlockYAML>;
|
blocks: Array<BlockYAML>;
|
||||||
title: string;
|
title: string;
|
||||||
|
settings: WorkflowSettings;
|
||||||
}) => {
|
}) => {
|
||||||
if (!workflowPermanentId) {
|
if (!workflowPermanentId) {
|
||||||
return;
|
return;
|
||||||
@@ -208,8 +216,9 @@ function FlowRenderer({
|
|||||||
const requestBody: WorkflowCreateYAMLRequest = {
|
const requestBody: WorkflowCreateYAMLRequest = {
|
||||||
title: data.title,
|
title: data.title,
|
||||||
description: workflow.description,
|
description: workflow.description,
|
||||||
proxy_location: workflow.proxy_location,
|
proxy_location: data.settings.proxyLocation,
|
||||||
webhook_callback_url: workflow.webhook_callback_url,
|
webhook_callback_url: data.settings.webhookCallbackUrl,
|
||||||
|
persist_browser_session: data.settings.persistBrowserSession,
|
||||||
totp_verification_url: workflow.totp_verification_url,
|
totp_verification_url: workflow.totp_verification_url,
|
||||||
workflow_definition: {
|
workflow_definition: {
|
||||||
parameters: data.parameters,
|
parameters: data.parameters,
|
||||||
@@ -267,6 +276,7 @@ function FlowRenderer({
|
|||||||
|
|
||||||
async function handleSave() {
|
async function handleSave() {
|
||||||
const blocks = getWorkflowBlocks(nodes, edges);
|
const blocks = getWorkflowBlocks(nodes, edges);
|
||||||
|
const settings = getWorkflowSettings(nodes);
|
||||||
const parametersInYAMLConvertibleJSON = convertToParametersYAML(parameters);
|
const parametersInYAMLConvertibleJSON = convertToParametersYAML(parameters);
|
||||||
const filteredParameters = workflow.workflow_definition.parameters.filter(
|
const filteredParameters = workflow.workflow_definition.parameters.filter(
|
||||||
(parameter) => {
|
(parameter) => {
|
||||||
@@ -295,6 +305,7 @@ function FlowRenderer({
|
|||||||
],
|
],
|
||||||
blocks,
|
blocks,
|
||||||
title,
|
title,
|
||||||
|
settings,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,10 +319,13 @@ function FlowRenderer({
|
|||||||
const newNodes: Array<AppNode> = [];
|
const newNodes: Array<AppNode> = [];
|
||||||
const newEdges: Array<Edge> = [];
|
const newEdges: Array<Edge> = [];
|
||||||
const id = nanoid();
|
const id = nanoid();
|
||||||
|
const existingLabels = nodes
|
||||||
|
.filter(isWorkflowBlockNode)
|
||||||
|
.map((node) => node.data.label);
|
||||||
const node = createNode(
|
const node = createNode(
|
||||||
{ id, parentId: parent },
|
{ id, parentId: parent },
|
||||||
nodeType,
|
nodeType,
|
||||||
generateNodeLabel(nodes.map((node) => node.data.label)),
|
generateNodeLabel(existingLabels),
|
||||||
);
|
);
|
||||||
newNodes.push(node);
|
newNodes.push(node);
|
||||||
if (previous) {
|
if (previous) {
|
||||||
@@ -343,7 +357,9 @@ function FlowRenderer({
|
|||||||
// when loop node is first created it needs an adder node so nodes can be added inside the loop
|
// when loop node is first created it needs an adder node so nodes can be added inside the loop
|
||||||
const startNodeId = nanoid();
|
const startNodeId = nanoid();
|
||||||
const adderNodeId = nanoid();
|
const adderNodeId = nanoid();
|
||||||
newNodes.push(startNode(startNodeId, id));
|
newNodes.push(
|
||||||
|
startNode(startNodeId, { withWorkflowSettings: false }, id),
|
||||||
|
);
|
||||||
newNodes.push(nodeAdderNode(adderNodeId, id));
|
newNodes.push(nodeAdderNode(adderNodeId, id));
|
||||||
newEdges.push(defaultEdge(startNodeId, adderNodeId));
|
newEdges.push(defaultEdge(startNodeId, adderNodeId));
|
||||||
}
|
}
|
||||||
@@ -370,7 +386,7 @@ function FlowRenderer({
|
|||||||
|
|
||||||
function deleteNode(id: string) {
|
function deleteNode(id: string) {
|
||||||
const node = nodes.find((node) => node.id === id);
|
const node = nodes.find((node) => node.id === id);
|
||||||
if (!node) {
|
if (!node || !isWorkflowBlockNode(node)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const deletedNodeLabel = node.data.label;
|
const deletedNodeLabel = node.data.label;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useWorkflowQuery } from "../hooks/useWorkflowQuery";
|
|||||||
import { FlowRenderer } from "./FlowRenderer";
|
import { FlowRenderer } from "./FlowRenderer";
|
||||||
import { getElements } from "./workflowEditorUtils";
|
import { getElements } from "./workflowEditorUtils";
|
||||||
import { LogoMinimized } from "@/components/LogoMinimized";
|
import { LogoMinimized } from "@/components/LogoMinimized";
|
||||||
|
import { WorkflowSettings } from "../types/workflowTypes";
|
||||||
|
|
||||||
function WorkflowEditor() {
|
function WorkflowEditor() {
|
||||||
const { workflowPermanentId } = useParams();
|
const { workflowPermanentId } = useParams();
|
||||||
@@ -39,7 +40,13 @@ function WorkflowEditor() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const elements = getElements(workflow.workflow_definition.blocks);
|
const settings: WorkflowSettings = {
|
||||||
|
persistBrowserSession: workflow.persist_browser_session,
|
||||||
|
proxyLocation: workflow.proxy_location,
|
||||||
|
webhookCallbackUrl: workflow.webhook_callback_url,
|
||||||
|
};
|
||||||
|
|
||||||
|
const elements = getElements(workflow.workflow_definition.blocks, settings);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen w-full">
|
<div className="h-screen w-full">
|
||||||
|
|||||||
@@ -1,6 +1,109 @@
|
|||||||
import { Handle, Position } from "@xyflow/react";
|
import { Handle, NodeProps, Position, useReactFlow } from "@xyflow/react";
|
||||||
|
import type { StartNode } from "./types";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "@/components/ui/accordion";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { ProxyLocation } from "@/api/types";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { HelpTooltip } from "@/components/HelpTooltip";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { ProxySelector } from "@/components/ProxySelector";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
|
||||||
|
function StartNode({ id, data }: NodeProps<StartNode>) {
|
||||||
|
const { updateNodeData } = useReactFlow();
|
||||||
|
const [inputs, setInputs] = useState({
|
||||||
|
webhookCallbackUrl: data.withWorkflowSettings
|
||||||
|
? data.webhookCallbackUrl
|
||||||
|
: "",
|
||||||
|
proxyLocation: data.withWorkflowSettings
|
||||||
|
? data.proxyLocation
|
||||||
|
: ProxyLocation.Residential,
|
||||||
|
persistBrowserSession: data.withWorkflowSettings
|
||||||
|
? data.persistBrowserSession
|
||||||
|
: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleChange(key: string, value: unknown) {
|
||||||
|
setInputs({ ...inputs, [key]: value });
|
||||||
|
updateNodeData(id, { [key]: value });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.withWorkflowSettings) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
position={Position.Bottom}
|
||||||
|
id="a"
|
||||||
|
className="opacity-0"
|
||||||
|
/>
|
||||||
|
<div className="w-[30rem] rounded-lg bg-slate-elevation3 px-6 py-4 text-center">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<header>Start</header>
|
||||||
|
<Separator />
|
||||||
|
<Accordion type="single" collapsible>
|
||||||
|
<AccordionItem value="settings" className="border-b-0">
|
||||||
|
<AccordionTrigger className="py-2">
|
||||||
|
Workflow Settings
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="pl-6 pr-1 pt-1">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label>Webhook Callback URL</Label>
|
||||||
|
<HelpTooltip content="The URL of a webhook endpoint to send the workflow results" />
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
value={inputs.webhookCallbackUrl}
|
||||||
|
placeholder="https://"
|
||||||
|
onChange={(event) => {
|
||||||
|
handleChange(
|
||||||
|
"webhookCallbackUrl",
|
||||||
|
event.target.value,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Label>Proxy Location</Label>
|
||||||
|
<HelpTooltip content="Route Skyvern through one of our available proxies." />
|
||||||
|
</div>
|
||||||
|
<ProxySelector
|
||||||
|
value={inputs.proxyLocation}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleChange("proxyLocation", value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Label>Persist Browser Session</Label>
|
||||||
|
<HelpTooltip content="Persist session information across workflow runs" />
|
||||||
|
<Switch
|
||||||
|
checked={inputs.persistBrowserSession}
|
||||||
|
onCheckedChange={(value) => {
|
||||||
|
handleChange("persistBrowserSession", value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function StartNode() {
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Handle
|
<Handle
|
||||||
|
|||||||
@@ -1,5 +1,28 @@
|
|||||||
|
import { ProxyLocation } from "@/api/types";
|
||||||
import type { Node } from "@xyflow/react";
|
import type { Node } from "@xyflow/react";
|
||||||
|
import { AppNode } from "..";
|
||||||
|
|
||||||
export type StartNodeData = Record<string, never>;
|
export type WorkflowStartNodeData = {
|
||||||
|
withWorkflowSettings: true;
|
||||||
|
webhookCallbackUrl: string;
|
||||||
|
proxyLocation: ProxyLocation;
|
||||||
|
persistBrowserSession: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OtherStartNodeData = {
|
||||||
|
withWorkflowSettings: false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type StartNodeData = WorkflowStartNodeData | OtherStartNodeData;
|
||||||
|
|
||||||
export type StartNode = Node<StartNodeData, "start">;
|
export type StartNode = Node<StartNodeData, "start">;
|
||||||
|
|
||||||
|
export function isStartNode(node: AppNode): node is StartNode {
|
||||||
|
return node.type === "start";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isWorkflowStartNodeData(
|
||||||
|
data: StartNodeData,
|
||||||
|
): data is WorkflowStartNodeData {
|
||||||
|
return data.withWorkflowSettings;
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import type {
|
|||||||
WorkflowApiResponse,
|
WorkflowApiResponse,
|
||||||
WorkflowBlock,
|
WorkflowBlock,
|
||||||
WorkflowParameterValueType,
|
WorkflowParameterValueType,
|
||||||
|
WorkflowSettings,
|
||||||
} from "../types/workflowTypes";
|
} from "../types/workflowTypes";
|
||||||
import {
|
import {
|
||||||
ActionBlockYAML,
|
ActionBlockYAML,
|
||||||
@@ -53,7 +54,12 @@ import {
|
|||||||
} from "./nodes/LoopNode/types";
|
} from "./nodes/LoopNode/types";
|
||||||
import { NodeAdderNode } from "./nodes/NodeAdderNode/types";
|
import { NodeAdderNode } from "./nodes/NodeAdderNode/types";
|
||||||
import { sendEmailNodeDefaultData } from "./nodes/SendEmailNode/types";
|
import { sendEmailNodeDefaultData } from "./nodes/SendEmailNode/types";
|
||||||
import { StartNode } from "./nodes/StartNode/types";
|
import {
|
||||||
|
isStartNode,
|
||||||
|
isWorkflowStartNodeData,
|
||||||
|
StartNode,
|
||||||
|
StartNodeData,
|
||||||
|
} from "./nodes/StartNode/types";
|
||||||
import { isTaskNode, taskNodeDefaultData } from "./nodes/TaskNode/types";
|
import { isTaskNode, taskNodeDefaultData } from "./nodes/TaskNode/types";
|
||||||
import { textPromptNodeDefaultData } from "./nodes/TextPromptNode/types";
|
import { textPromptNodeDefaultData } from "./nodes/TextPromptNode/types";
|
||||||
import { NodeBaseData } from "./nodes/types";
|
import { NodeBaseData } from "./nodes/types";
|
||||||
@@ -73,6 +79,7 @@ import {
|
|||||||
} from "./nodes/ExtractionNode/types";
|
} from "./nodes/ExtractionNode/types";
|
||||||
import { loginNodeDefaultData } from "./nodes/LoginNode/types";
|
import { loginNodeDefaultData } from "./nodes/LoginNode/types";
|
||||||
import { waitNodeDefaultData } from "./nodes/WaitNode/types";
|
import { waitNodeDefaultData } from "./nodes/WaitNode/types";
|
||||||
|
import { ProxyLocation } from "@/api/types";
|
||||||
|
|
||||||
export const NEW_NODE_LABEL_PREFIX = "block_";
|
export const NEW_NODE_LABEL_PREFIX = "block_";
|
||||||
|
|
||||||
@@ -458,12 +465,16 @@ export function edgeWithAddButton(source: string, target: string) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startNode(id: string, parentId?: string): StartNode {
|
export function startNode(
|
||||||
|
id: string,
|
||||||
|
data: StartNodeData,
|
||||||
|
parentId?: string,
|
||||||
|
): StartNode {
|
||||||
const node: StartNode = {
|
const node: StartNode = {
|
||||||
id,
|
id,
|
||||||
type: "start",
|
type: "start",
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
data: {},
|
data,
|
||||||
draggable: false,
|
draggable: false,
|
||||||
connectable: false,
|
connectable: false,
|
||||||
};
|
};
|
||||||
@@ -488,7 +499,10 @@ export function nodeAdderNode(id: string, parentId?: string): NodeAdderNode {
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getElements(blocks: Array<WorkflowBlock>): {
|
function getElements(
|
||||||
|
blocks: Array<WorkflowBlock>,
|
||||||
|
settings: WorkflowSettings,
|
||||||
|
): {
|
||||||
nodes: Array<AppNode>;
|
nodes: Array<AppNode>;
|
||||||
edges: Array<Edge>;
|
edges: Array<Edge>;
|
||||||
} {
|
} {
|
||||||
@@ -497,7 +511,14 @@ function getElements(blocks: Array<WorkflowBlock>): {
|
|||||||
const edges: Array<Edge> = [];
|
const edges: Array<Edge> = [];
|
||||||
|
|
||||||
const startNodeId = nanoid();
|
const startNodeId = nanoid();
|
||||||
nodes.push(startNode(startNodeId));
|
nodes.push(
|
||||||
|
startNode(startNodeId, {
|
||||||
|
withWorkflowSettings: true,
|
||||||
|
persistBrowserSession: settings.persistBrowserSession,
|
||||||
|
proxyLocation: settings.proxyLocation ?? ProxyLocation.Residential,
|
||||||
|
webhookCallbackUrl: settings.webhookCallbackUrl ?? "",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
data.forEach((d, index) => {
|
data.forEach((d, index) => {
|
||||||
const node = convertToNode(
|
const node = convertToNode(
|
||||||
@@ -519,7 +540,15 @@ function getElements(blocks: Array<WorkflowBlock>): {
|
|||||||
const loopBlocks = data.filter((d) => d.block.block_type === "for_loop");
|
const loopBlocks = data.filter((d) => d.block.block_type === "for_loop");
|
||||||
loopBlocks.forEach((block) => {
|
loopBlocks.forEach((block) => {
|
||||||
const startNodeId = nanoid();
|
const startNodeId = nanoid();
|
||||||
nodes.push(startNode(startNodeId, block.id));
|
nodes.push(
|
||||||
|
startNode(
|
||||||
|
startNodeId,
|
||||||
|
{
|
||||||
|
withWorkflowSettings: false,
|
||||||
|
},
|
||||||
|
block.id,
|
||||||
|
),
|
||||||
|
);
|
||||||
const children = data.filter((b) => b.parentId === block.id);
|
const children = data.filter((b) => b.parentId === block.id);
|
||||||
if (children.length === 0) {
|
if (children.length === 0) {
|
||||||
const adderNodeId = nanoid();
|
const adderNodeId = nanoid();
|
||||||
@@ -555,7 +584,7 @@ function createNode(
|
|||||||
identifiers: { id: string; parentId?: string },
|
identifiers: { id: string; parentId?: string },
|
||||||
nodeType: NonNullable<WorkflowBlockNode["type"]>,
|
nodeType: NonNullable<WorkflowBlockNode["type"]>,
|
||||||
label: string,
|
label: string,
|
||||||
): AppNode {
|
): WorkflowBlockNode {
|
||||||
const common = {
|
const common = {
|
||||||
draggable: false,
|
draggable: false,
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
@@ -991,6 +1020,30 @@ function getWorkflowBlocks(
|
|||||||
return getWorkflowBlocksUtil(nodes, edges);
|
return getWorkflowBlocksUtil(nodes, edges);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getWorkflowSettings(nodes: Array<AppNode>): WorkflowSettings {
|
||||||
|
const defaultSettings = {
|
||||||
|
persistBrowserSession: false,
|
||||||
|
proxyLocation: ProxyLocation.Residential,
|
||||||
|
webhookCallbackUrl: null,
|
||||||
|
};
|
||||||
|
const startNodes = nodes.filter(isStartNode);
|
||||||
|
const startNodeWithWorkflowSettings = startNodes.find(
|
||||||
|
(node) => node.data.withWorkflowSettings,
|
||||||
|
);
|
||||||
|
if (!startNodeWithWorkflowSettings) {
|
||||||
|
return defaultSettings;
|
||||||
|
}
|
||||||
|
const data = startNodeWithWorkflowSettings.data;
|
||||||
|
if (isWorkflowStartNodeData(data)) {
|
||||||
|
return {
|
||||||
|
persistBrowserSession: data.persistBrowserSession,
|
||||||
|
proxyLocation: data.proxyLocation,
|
||||||
|
webhookCallbackUrl: data.webhookCallbackUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return defaultSettings;
|
||||||
|
}
|
||||||
|
|
||||||
function generateNodeLabel(existingLabels: Array<string>) {
|
function generateNodeLabel(existingLabels: Array<string>) {
|
||||||
for (let i = 1; i < existingLabels.length + 2; i++) {
|
for (let i = 1; i < existingLabels.length + 2; i++) {
|
||||||
const label = NEW_NODE_LABEL_PREFIX + i;
|
const label = NEW_NODE_LABEL_PREFIX + i;
|
||||||
@@ -1608,6 +1661,7 @@ export {
|
|||||||
getBlockNameOfOutputParameterKey,
|
getBlockNameOfOutputParameterKey,
|
||||||
getDefaultValueForParameterType,
|
getDefaultValueForParameterType,
|
||||||
getElements,
|
getElements,
|
||||||
|
getWorkflowSettings,
|
||||||
getOutputParameterKey,
|
getOutputParameterKey,
|
||||||
getPreviousNodeIds,
|
getPreviousNodeIds,
|
||||||
getUniqueLabelForExistingNode,
|
getUniqueLabelForExistingNode,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useNodes, useReactFlow } from "@xyflow/react";
|
import { useNodes, useReactFlow } from "@xyflow/react";
|
||||||
import { AppNode } from "../editor/nodes";
|
import { AppNode, isWorkflowBlockNode } from "../editor/nodes";
|
||||||
import {
|
import {
|
||||||
getUniqueLabelForExistingNode,
|
getUniqueLabelForExistingNode,
|
||||||
getUpdatedNodesAfterLabelUpdateForParameterKeys,
|
getUpdatedNodesAfterLabelUpdateForParameterKeys,
|
||||||
@@ -21,7 +21,9 @@ function useNodeLabelChangeHandler({ id, initialValue }: Props) {
|
|||||||
useWorkflowParametersState();
|
useWorkflowParametersState();
|
||||||
|
|
||||||
function handleLabelChange(value: string) {
|
function handleLabelChange(value: string) {
|
||||||
const existingLabels = nodes.map((n) => n.data.label);
|
const existingLabels = nodes
|
||||||
|
.filter(isWorkflowBlockNode)
|
||||||
|
.map((n) => n.data.label);
|
||||||
const labelWithoutWhitespace = value.replace(/\s+/g, "_");
|
const labelWithoutWhitespace = value.replace(/\s+/g, "_");
|
||||||
const newLabel = getUniqueLabelForExistingNode(
|
const newLabel = getUniqueLabelForExistingNode(
|
||||||
labelWithoutWhitespace,
|
labelWithoutWhitespace,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ProxyLocation } from "@/api/types";
|
||||||
|
|
||||||
export type WorkflowParameterBase = {
|
export type WorkflowParameterBase = {
|
||||||
parameter_type: WorkflowParameterType;
|
parameter_type: WorkflowParameterType;
|
||||||
key: string;
|
key: string;
|
||||||
@@ -296,14 +298,22 @@ export type WorkflowApiResponse = {
|
|||||||
version: number;
|
version: number;
|
||||||
description: string;
|
description: string;
|
||||||
workflow_definition: WorkflowDefinition;
|
workflow_definition: WorkflowDefinition;
|
||||||
proxy_location: string;
|
proxy_location: ProxyLocation | null;
|
||||||
webhook_callback_url: string;
|
webhook_callback_url: string | null;
|
||||||
totp_verification_url: string;
|
persist_browser_session: boolean;
|
||||||
|
totp_verification_url: string | null;
|
||||||
|
totp_identifier: string | null;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
modified_at: string;
|
modified_at: string;
|
||||||
deleted_at: string | null;
|
deleted_at: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type WorkflowSettings = {
|
||||||
|
proxyLocation: ProxyLocation | null;
|
||||||
|
webhookCallbackUrl: string | null;
|
||||||
|
persistBrowserSession: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export function isOutputParameter(
|
export function isOutputParameter(
|
||||||
parameter: Parameter,
|
parameter: Parameter,
|
||||||
): parameter is OutputParameter {
|
): parameter is OutputParameter {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ export type WorkflowCreateYAMLRequest = {
|
|||||||
description?: string | null;
|
description?: string | null;
|
||||||
proxy_location?: string | null;
|
proxy_location?: string | null;
|
||||||
webhook_callback_url?: string | null;
|
webhook_callback_url?: string | null;
|
||||||
|
persist_browser_session?: boolean;
|
||||||
totp_verification_url?: string | null;
|
totp_verification_url?: string | null;
|
||||||
workflow_definition: WorkflowDefinitionYAML;
|
workflow_definition: WorkflowDefinitionYAML;
|
||||||
is_saved_task?: boolean;
|
is_saved_task?: boolean;
|
||||||
|
|||||||
Reference in New Issue
Block a user