Add workflows settings in start node (#1270)
This commit is contained in:
@@ -10,12 +10,13 @@ import {
|
||||
type Props = {
|
||||
value: ProxyLocation | null;
|
||||
onChange: (value: ProxyLocation) => void;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function ProxySelector({ value, onChange }: Props) {
|
||||
function ProxySelector({ value, onChange, className }: Props) {
|
||||
return (
|
||||
<Select value={value ?? ""} onValueChange={onChange}>
|
||||
<SelectTrigger className="w-48">
|
||||
<SelectTrigger className={className}>
|
||||
<SelectValue placeholder="Proxy Location" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
||||
@@ -514,6 +514,7 @@ function CreateNewTaskForm({ initialValues }: Props) {
|
||||
<ProxySelector
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
className="w-48"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
|
||||
@@ -686,6 +686,7 @@ function SavedTaskForm({ initialValues }: Props) {
|
||||
<ProxySelector
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
className="w-48"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { getClient } from "@/api/AxiosClient";
|
||||
import { ProxyLocation } from "@/api/types";
|
||||
import { ProxySelector } from "@/components/ProxySelector";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -7,28 +10,29 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||
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 { Input } from "@/components/ui/input";
|
||||
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 { ToastAction } from "@radix-ui/react-toast";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import fetchToCurl from "fetch-to-curl";
|
||||
import { apiBaseUrl } from "@/util/env";
|
||||
import { useApiCredential } from "@/hooks/useApiCredential";
|
||||
import { copyText } from "@/util/copyText";
|
||||
import { WorkflowParameter } from "./types/workflowTypes";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import { z } from "zod";
|
||||
import { ProxyLocation } from "@/api/types";
|
||||
import { ProxySelector } from "@/components/ProxySelector";
|
||||
import { WorkflowParameter } from "./types/workflowTypes";
|
||||
import { WorkflowParameterInput } from "./WorkflowParameterInput";
|
||||
|
||||
type Props = {
|
||||
workflowParameters: Array<WorkflowParameter>;
|
||||
initialValues: Record<string, unknown>;
|
||||
initialSettings: {
|
||||
proxyLocation: ProxyLocation;
|
||||
webhookCallbackUrl: string;
|
||||
};
|
||||
};
|
||||
|
||||
function parseValuesForWorkflowRun(
|
||||
@@ -92,19 +96,23 @@ function getRunWorkflowRequestBody(
|
||||
}
|
||||
|
||||
type RunWorkflowFormType = Record<string, unknown> & {
|
||||
webhookCallbackUrl: string | null;
|
||||
proxyLocation: ProxyLocation | null;
|
||||
webhookCallbackUrl: string;
|
||||
proxyLocation: ProxyLocation;
|
||||
};
|
||||
|
||||
function RunWorkflowForm({ workflowParameters, initialValues }: Props) {
|
||||
function RunWorkflowForm({
|
||||
workflowParameters,
|
||||
initialValues,
|
||||
initialSettings,
|
||||
}: Props) {
|
||||
const { workflowPermanentId } = useParams();
|
||||
const credentialGetter = useCredentialGetter();
|
||||
const queryClient = useQueryClient();
|
||||
const form = useForm<RunWorkflowFormType>({
|
||||
defaultValues: {
|
||||
...initialValues,
|
||||
webhookCallbackUrl: null,
|
||||
proxyLocation: ProxyLocation.Residential,
|
||||
webhookCallbackUrl: initialSettings.webhookCallbackUrl,
|
||||
proxyLocation: initialSettings.proxyLocation,
|
||||
},
|
||||
});
|
||||
const apiCredential = useApiCredential();
|
||||
@@ -313,6 +321,7 @@ function RunWorkflowForm({ workflowParameters, initialValues }: Props) {
|
||||
<ProxySelector
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
className="w-48"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useLocation, useParams } from "react-router-dom";
|
||||
import { RunWorkflowForm } from "./RunWorkflowForm";
|
||||
import { WorkflowApiResponse } from "./types/workflowTypes";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { ProxyLocation } from "@/api/types";
|
||||
|
||||
function WorkflowRunParameters() {
|
||||
const credentialGetter = useCredentialGetter();
|
||||
@@ -93,6 +94,10 @@ function WorkflowRunParameters() {
|
||||
<RunWorkflowForm
|
||||
initialValues={initialValues}
|
||||
workflowParameters={workflowParameters}
|
||||
initialSettings={{
|
||||
proxyLocation: workflow.proxy_location ?? ProxyLocation.Residential,
|
||||
webhookCallbackUrl: workflow.webhook_callback_url ?? "",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
AWSSecretParameter,
|
||||
WorkflowApiResponse,
|
||||
WorkflowParameterValueType,
|
||||
WorkflowSettings,
|
||||
} from "../types/workflowTypes";
|
||||
import {
|
||||
BitwardenLoginCredentialParameterYAML,
|
||||
@@ -50,7 +51,12 @@ import {
|
||||
import { WorkflowHeader } from "./WorkflowHeader";
|
||||
import { WorkflowParametersStateContext } from "./WorkflowParametersStateContext";
|
||||
import { edgeTypes } from "./edges";
|
||||
import { AppNode, nodeTypes, WorkflowBlockNode } from "./nodes";
|
||||
import {
|
||||
AppNode,
|
||||
isWorkflowBlockNode,
|
||||
nodeTypes,
|
||||
WorkflowBlockNode,
|
||||
} from "./nodes";
|
||||
import { WorkflowNodeLibraryPanel } from "./panels/WorkflowNodeLibraryPanel";
|
||||
import { WorkflowParametersPanel } from "./panels/WorkflowParametersPanel";
|
||||
import "./reactFlowOverrideStyles.css";
|
||||
@@ -63,6 +69,7 @@ import {
|
||||
getOutputParameterKey,
|
||||
getWorkflowBlocks,
|
||||
getWorkflowErrors,
|
||||
getWorkflowSettings,
|
||||
layout,
|
||||
nodeAdderNode,
|
||||
startNode,
|
||||
@@ -200,6 +207,7 @@ function FlowRenderer({
|
||||
parameters: Array<ParameterYAML>;
|
||||
blocks: Array<BlockYAML>;
|
||||
title: string;
|
||||
settings: WorkflowSettings;
|
||||
}) => {
|
||||
if (!workflowPermanentId) {
|
||||
return;
|
||||
@@ -208,8 +216,9 @@ function FlowRenderer({
|
||||
const requestBody: WorkflowCreateYAMLRequest = {
|
||||
title: data.title,
|
||||
description: workflow.description,
|
||||
proxy_location: workflow.proxy_location,
|
||||
webhook_callback_url: workflow.webhook_callback_url,
|
||||
proxy_location: data.settings.proxyLocation,
|
||||
webhook_callback_url: data.settings.webhookCallbackUrl,
|
||||
persist_browser_session: data.settings.persistBrowserSession,
|
||||
totp_verification_url: workflow.totp_verification_url,
|
||||
workflow_definition: {
|
||||
parameters: data.parameters,
|
||||
@@ -267,6 +276,7 @@ function FlowRenderer({
|
||||
|
||||
async function handleSave() {
|
||||
const blocks = getWorkflowBlocks(nodes, edges);
|
||||
const settings = getWorkflowSettings(nodes);
|
||||
const parametersInYAMLConvertibleJSON = convertToParametersYAML(parameters);
|
||||
const filteredParameters = workflow.workflow_definition.parameters.filter(
|
||||
(parameter) => {
|
||||
@@ -295,6 +305,7 @@ function FlowRenderer({
|
||||
],
|
||||
blocks,
|
||||
title,
|
||||
settings,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -308,10 +319,13 @@ function FlowRenderer({
|
||||
const newNodes: Array<AppNode> = [];
|
||||
const newEdges: Array<Edge> = [];
|
||||
const id = nanoid();
|
||||
const existingLabels = nodes
|
||||
.filter(isWorkflowBlockNode)
|
||||
.map((node) => node.data.label);
|
||||
const node = createNode(
|
||||
{ id, parentId: parent },
|
||||
nodeType,
|
||||
generateNodeLabel(nodes.map((node) => node.data.label)),
|
||||
generateNodeLabel(existingLabels),
|
||||
);
|
||||
newNodes.push(node);
|
||||
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
|
||||
const startNodeId = nanoid();
|
||||
const adderNodeId = nanoid();
|
||||
newNodes.push(startNode(startNodeId, id));
|
||||
newNodes.push(
|
||||
startNode(startNodeId, { withWorkflowSettings: false }, id),
|
||||
);
|
||||
newNodes.push(nodeAdderNode(adderNodeId, id));
|
||||
newEdges.push(defaultEdge(startNodeId, adderNodeId));
|
||||
}
|
||||
@@ -370,7 +386,7 @@ function FlowRenderer({
|
||||
|
||||
function deleteNode(id: string) {
|
||||
const node = nodes.find((node) => node.id === id);
|
||||
if (!node) {
|
||||
if (!node || !isWorkflowBlockNode(node)) {
|
||||
return;
|
||||
}
|
||||
const deletedNodeLabel = node.data.label;
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useWorkflowQuery } from "../hooks/useWorkflowQuery";
|
||||
import { FlowRenderer } from "./FlowRenderer";
|
||||
import { getElements } from "./workflowEditorUtils";
|
||||
import { LogoMinimized } from "@/components/LogoMinimized";
|
||||
import { WorkflowSettings } from "../types/workflowTypes";
|
||||
|
||||
function WorkflowEditor() {
|
||||
const { workflowPermanentId } = useParams();
|
||||
@@ -39,7 +40,13 @@ function WorkflowEditor() {
|
||||
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 (
|
||||
<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 (
|
||||
<div>
|
||||
<Handle
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
import { ProxyLocation } from "@/api/types";
|
||||
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 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,
|
||||
WorkflowBlock,
|
||||
WorkflowParameterValueType,
|
||||
WorkflowSettings,
|
||||
} from "../types/workflowTypes";
|
||||
import {
|
||||
ActionBlockYAML,
|
||||
@@ -53,7 +54,12 @@ import {
|
||||
} from "./nodes/LoopNode/types";
|
||||
import { NodeAdderNode } from "./nodes/NodeAdderNode/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 { textPromptNodeDefaultData } from "./nodes/TextPromptNode/types";
|
||||
import { NodeBaseData } from "./nodes/types";
|
||||
@@ -73,6 +79,7 @@ import {
|
||||
} from "./nodes/ExtractionNode/types";
|
||||
import { loginNodeDefaultData } from "./nodes/LoginNode/types";
|
||||
import { waitNodeDefaultData } from "./nodes/WaitNode/types";
|
||||
import { ProxyLocation } from "@/api/types";
|
||||
|
||||
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 = {
|
||||
id,
|
||||
type: "start",
|
||||
position: { x: 0, y: 0 },
|
||||
data: {},
|
||||
data,
|
||||
draggable: false,
|
||||
connectable: false,
|
||||
};
|
||||
@@ -488,7 +499,10 @@ export function nodeAdderNode(id: string, parentId?: string): NodeAdderNode {
|
||||
return node;
|
||||
}
|
||||
|
||||
function getElements(blocks: Array<WorkflowBlock>): {
|
||||
function getElements(
|
||||
blocks: Array<WorkflowBlock>,
|
||||
settings: WorkflowSettings,
|
||||
): {
|
||||
nodes: Array<AppNode>;
|
||||
edges: Array<Edge>;
|
||||
} {
|
||||
@@ -497,7 +511,14 @@ function getElements(blocks: Array<WorkflowBlock>): {
|
||||
const edges: Array<Edge> = [];
|
||||
|
||||
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) => {
|
||||
const node = convertToNode(
|
||||
@@ -519,7 +540,15 @@ function getElements(blocks: Array<WorkflowBlock>): {
|
||||
const loopBlocks = data.filter((d) => d.block.block_type === "for_loop");
|
||||
loopBlocks.forEach((block) => {
|
||||
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);
|
||||
if (children.length === 0) {
|
||||
const adderNodeId = nanoid();
|
||||
@@ -555,7 +584,7 @@ function createNode(
|
||||
identifiers: { id: string; parentId?: string },
|
||||
nodeType: NonNullable<WorkflowBlockNode["type"]>,
|
||||
label: string,
|
||||
): AppNode {
|
||||
): WorkflowBlockNode {
|
||||
const common = {
|
||||
draggable: false,
|
||||
position: { x: 0, y: 0 },
|
||||
@@ -991,6 +1020,30 @@ function getWorkflowBlocks(
|
||||
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>) {
|
||||
for (let i = 1; i < existingLabels.length + 2; i++) {
|
||||
const label = NEW_NODE_LABEL_PREFIX + i;
|
||||
@@ -1608,6 +1661,7 @@ export {
|
||||
getBlockNameOfOutputParameterKey,
|
||||
getDefaultValueForParameterType,
|
||||
getElements,
|
||||
getWorkflowSettings,
|
||||
getOutputParameterKey,
|
||||
getPreviousNodeIds,
|
||||
getUniqueLabelForExistingNode,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useNodes, useReactFlow } from "@xyflow/react";
|
||||
import { AppNode } from "../editor/nodes";
|
||||
import { AppNode, isWorkflowBlockNode } from "../editor/nodes";
|
||||
import {
|
||||
getUniqueLabelForExistingNode,
|
||||
getUpdatedNodesAfterLabelUpdateForParameterKeys,
|
||||
@@ -21,7 +21,9 @@ function useNodeLabelChangeHandler({ id, initialValue }: Props) {
|
||||
useWorkflowParametersState();
|
||||
|
||||
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 newLabel = getUniqueLabelForExistingNode(
|
||||
labelWithoutWhitespace,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ProxyLocation } from "@/api/types";
|
||||
|
||||
export type WorkflowParameterBase = {
|
||||
parameter_type: WorkflowParameterType;
|
||||
key: string;
|
||||
@@ -296,14 +298,22 @@ export type WorkflowApiResponse = {
|
||||
version: number;
|
||||
description: string;
|
||||
workflow_definition: WorkflowDefinition;
|
||||
proxy_location: string;
|
||||
webhook_callback_url: string;
|
||||
totp_verification_url: string;
|
||||
proxy_location: ProxyLocation | null;
|
||||
webhook_callback_url: string | null;
|
||||
persist_browser_session: boolean;
|
||||
totp_verification_url: string | null;
|
||||
totp_identifier: string | null;
|
||||
created_at: string;
|
||||
modified_at: string;
|
||||
deleted_at: string | null;
|
||||
};
|
||||
|
||||
export type WorkflowSettings = {
|
||||
proxyLocation: ProxyLocation | null;
|
||||
webhookCallbackUrl: string | null;
|
||||
persistBrowserSession: boolean;
|
||||
};
|
||||
|
||||
export function isOutputParameter(
|
||||
parameter: Parameter,
|
||||
): parameter is OutputParameter {
|
||||
|
||||
@@ -3,6 +3,7 @@ export type WorkflowCreateYAMLRequest = {
|
||||
description?: string | null;
|
||||
proxy_location?: string | null;
|
||||
webhook_callback_url?: string | null;
|
||||
persist_browser_session?: boolean;
|
||||
totp_verification_url?: string | null;
|
||||
workflow_definition: WorkflowDefinitionYAML;
|
||||
is_saved_task?: boolean;
|
||||
|
||||
Reference in New Issue
Block a user