make other script enabled blocks flip-to-script; add type-checked lis… (#3179)

This commit is contained in:
Jonathan Dobson
2025-08-13 15:17:04 -04:00
committed by GitHub
parent a22a3aedba
commit 399fd4ea74
10 changed files with 1667 additions and 1508 deletions

View File

@@ -1,3 +1,5 @@
import { useEffect } from "react";
import { Flippable } from "@/components/Flippable";
import { import {
Accordion, Accordion,
AccordionContent, AccordionContent,
@@ -23,6 +25,7 @@ import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { placeholders, helpTooltips } from "../../helpContent"; import { placeholders, helpTooltips } from "../../helpContent";
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea"; import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor";
import { WorkflowBlockInput } from "@/components/WorkflowBlockInput"; import { WorkflowBlockInput } from "@/components/WorkflowBlockInput";
import { AppNode } from ".."; import { AppNode } from "..";
import { getAvailableOutputParameterKeys } from "../../workflowEditorUtils"; import { getAvailableOutputParameterKeys } from "../../workflowEditorUtils";
@@ -31,6 +34,7 @@ import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow"
import { RunEngineSelector } from "@/components/EngineSelector"; import { RunEngineSelector } from "@/components/EngineSelector";
import { ModelSelector } from "@/components/ModelSelector"; import { ModelSelector } from "@/components/ModelSelector";
import { useDebugStore } from "@/store/useDebugStore"; import { useDebugStore } from "@/store/useDebugStore";
import { useBlockScriptStore } from "@/store/BlockScriptStore";
import { cn } from "@/util/utils"; import { cn } from "@/util/utils";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { NodeHeader } from "../components/NodeHeader"; import { NodeHeader } from "../components/NodeHeader";
@@ -44,7 +48,10 @@ const navigationGoalPlaceholder = 'Input {{ name }} into "Name" field.';
function ActionNode({ id, data, type }: NodeProps<ActionNode>) { function ActionNode({ id, data, type }: NodeProps<ActionNode>) {
const { updateNodeData } = useReactFlow(); const { updateNodeData } = useReactFlow();
const [facing, setFacing] = useState<"front" | "back">("front");
const blockScriptStore = useBlockScriptStore();
const { editable, debuggable, label } = data; const { editable, debuggable, label } = data;
const script = blockScriptStore.scripts[label];
const [inputs, setInputs] = useState({ const [inputs, setInputs] = useState({
url: data.url, url: data.url,
navigationGoal: data.navigationGoal, navigationGoal: data.navigationGoal,
@@ -78,7 +85,12 @@ function ActionNode({ id, data, type }: NodeProps<ActionNode>) {
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id }); const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
useEffect(() => {
setFacing(data.showCode ? "back" : "front");
}, [data.showCode]);
return ( return (
<Flippable facing={facing} preserveFrontsideHeight={true}>
<div> <div>
<Handle <Handle
type="source" type="source"
@@ -158,8 +170,8 @@ function ActionNode({ id, data, type }: NodeProps<ActionNode>) {
</div> </div>
<div className="rounded-md bg-slate-800 p-2"> <div className="rounded-md bg-slate-800 p-2">
<div className="space-y-1 text-xs text-slate-400"> <div className="space-y-1 text-xs text-slate-400">
Tip: While executing the action block, Skyvern will only take one Tip: While executing the action block, Skyvern will only take
action. one action.
</div> </div>
</div> </div>
</div> </div>
@@ -227,7 +239,11 @@ function ActionNode({ id, data, type }: NodeProps<ActionNode>) {
handleChange( handleChange(
"errorCodeMapping", "errorCodeMapping",
checked checked
? JSON.stringify(errorMappingExampleValue, null, 2) ? JSON.stringify(
errorMappingExampleValue,
null,
2,
)
: "null", : "null",
); );
}} }}
@@ -380,6 +396,8 @@ function ActionNode({ id, data, type }: NodeProps<ActionNode>) {
</Accordion> </Accordion>
</div> </div>
</div> </div>
<BlockCodeEditor blockLabel={label} blockType={type} script={script} />
</Flippable>
); );
} }

View File

@@ -1,3 +1,5 @@
import { useEffect } from "react";
import { Flippable } from "@/components/Flippable";
import { HelpTooltip } from "@/components/HelpTooltip"; import { HelpTooltip } from "@/components/HelpTooltip";
import { import {
Accordion, Accordion,
@@ -22,6 +24,7 @@ import { dataSchemaExampleValue } from "../types";
import type { ExtractionNode } from "./types"; import type { ExtractionNode } from "./types";
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea"; import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor";
import { helpTooltips, placeholders } from "../../helpContent"; import { helpTooltips, placeholders } from "../../helpContent";
import { AppNode } from ".."; import { AppNode } from "..";
import { getAvailableOutputParameterKeys } from "../../workflowEditorUtils"; import { getAvailableOutputParameterKeys } from "../../workflowEditorUtils";
@@ -31,13 +34,17 @@ import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow"
import { RunEngineSelector } from "@/components/EngineSelector"; import { RunEngineSelector } from "@/components/EngineSelector";
import { ModelSelector } from "@/components/ModelSelector"; import { ModelSelector } from "@/components/ModelSelector";
import { useDebugStore } from "@/store/useDebugStore"; import { useDebugStore } from "@/store/useDebugStore";
import { useBlockScriptStore } from "@/store/BlockScriptStore";
import { cn } from "@/util/utils"; import { cn } from "@/util/utils";
import { NodeHeader } from "../components/NodeHeader"; import { NodeHeader } from "../components/NodeHeader";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
function ExtractionNode({ id, data, type }: NodeProps<ExtractionNode>) { function ExtractionNode({ id, data, type }: NodeProps<ExtractionNode>) {
const { updateNodeData } = useReactFlow(); const { updateNodeData } = useReactFlow();
const [facing, setFacing] = useState<"front" | "back">("front");
const blockScriptStore = useBlockScriptStore();
const { debuggable, editable, label } = data; const { debuggable, editable, label } = data;
const script = blockScriptStore.scripts[label];
const debugStore = useDebugStore(); const debugStore = useDebugStore();
const elideFromDebugging = debugStore.isDebugMode && !debuggable; const elideFromDebugging = debugStore.isDebugMode && !debuggable;
const { blockLabel: urlBlockLabel } = useParams(); const { blockLabel: urlBlockLabel } = useParams();
@@ -67,7 +74,12 @@ function ExtractionNode({ id, data, type }: NodeProps<ExtractionNode>) {
updateNodeData(id, { [key]: value }); updateNodeData(id, { [key]: value });
} }
useEffect(() => {
setFacing(data.showCode ? "back" : "front");
}, [data.showCode]);
return ( return (
<Flippable facing={facing} preserveFrontsideHeight={true}>
<div> <div>
<Handle <Handle
type="source" type="source"
@@ -189,7 +201,9 @@ function ExtractionNode({ id, data, type }: NodeProps<ExtractionNode>) {
</div> </div>
<Input <Input
type="number" type="number"
placeholder={placeholders["extraction"]["maxStepsOverride"]} placeholder={
placeholders["extraction"]["maxStepsOverride"]
}
className="nopan w-52 text-xs" className="nopan w-52 text-xs"
min="0" min="0"
value={inputs.maxStepsOverride ?? ""} value={inputs.maxStepsOverride ?? ""}
@@ -212,7 +226,9 @@ function ExtractionNode({ id, data, type }: NodeProps<ExtractionNode>) {
Continue on Failure Continue on Failure
</Label> </Label>
<HelpTooltip <HelpTooltip
content={helpTooltips["extraction"]["continueOnFailure"]} content={
helpTooltips["extraction"]["continueOnFailure"]
}
/> />
</div> </div>
<div className="w-52"> <div className="w-52">
@@ -254,6 +270,8 @@ function ExtractionNode({ id, data, type }: NodeProps<ExtractionNode>) {
</Accordion> </Accordion>
</div> </div>
</div> </div>
<BlockCodeEditor blockLabel={label} blockType={type} script={script} />
</Flippable>
); );
} }

View File

@@ -1,3 +1,5 @@
import { useEffect } from "react";
import { Flippable } from "@/components/Flippable";
import { HelpTooltip } from "@/components/HelpTooltip"; import { HelpTooltip } from "@/components/HelpTooltip";
import { import {
Accordion, Accordion,
@@ -11,7 +13,9 @@ import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea"; import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor";
import { CodeEditor } from "@/routes/workflows/components/CodeEditor"; import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
import { useBlockScriptStore } from "@/store/BlockScriptStore";
import { import {
Handle, Handle,
NodeProps, NodeProps,
@@ -44,7 +48,10 @@ const navigationGoalPlaceholder = "Tell Skyvern which file to download.";
function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) { function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
const { updateNodeData } = useReactFlow(); const { updateNodeData } = useReactFlow();
const [facing, setFacing] = useState<"front" | "back">("front");
const blockScriptStore = useBlockScriptStore();
const { debuggable, editable, label } = data; const { debuggable, editable, label } = data;
const script = blockScriptStore.scripts[label];
const debugStore = useDebugStore(); const debugStore = useDebugStore();
const { blockLabel: urlBlockLabel } = useParams(); const { blockLabel: urlBlockLabel } = useParams();
const thisBlockIsPlaying = const thisBlockIsPlaying =
@@ -75,7 +82,12 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
updateNodeData(id, { [key]: value }); updateNodeData(id, { [key]: value });
} }
useEffect(() => {
setFacing(data.showCode ? "back" : "front");
}, [data.showCode]);
return ( return (
<Flippable facing={facing} preserveFrontsideHeight={true}>
<div> <div>
<Handle <Handle
type="source" type="source"
@@ -229,7 +241,11 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
handleChange( handleChange(
"errorCodeMapping", "errorCodeMapping",
checked checked
? JSON.stringify(errorMappingExampleValue, null, 2) ? JSON.stringify(
errorMappingExampleValue,
null,
2,
)
: "null", : "null",
); );
}} }}
@@ -351,6 +367,12 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
</Accordion> </Accordion>
</div> </div>
</div> </div>
<BlockCodeEditor
blockLabel={label}
blockType="file_download" // sic: naming is not consistent
script={script}
/>
</Flippable>
); );
} }

View File

@@ -1,3 +1,5 @@
import { useEffect } from "react";
import { Flippable } from "@/components/Flippable";
import { HelpTooltip } from "@/components/HelpTooltip"; import { HelpTooltip } from "@/components/HelpTooltip";
import { import {
Accordion, Accordion,
@@ -11,7 +13,9 @@ import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea"; import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor";
import { CodeEditor } from "@/routes/workflows/components/CodeEditor"; import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
import { useBlockScriptStore } from "@/store/BlockScriptStore";
import { import {
Handle, Handle,
NodeProps, NodeProps,
@@ -38,7 +42,10 @@ import { useParams } from "react-router-dom";
function LoginNode({ id, data, type }: NodeProps<LoginNode>) { function LoginNode({ id, data, type }: NodeProps<LoginNode>) {
const { updateNodeData } = useReactFlow(); const { updateNodeData } = useReactFlow();
const [facing, setFacing] = useState<"front" | "back">("front");
const blockScriptStore = useBlockScriptStore();
const { debuggable, editable, label } = data; const { debuggable, editable, label } = data;
const script = blockScriptStore.scripts[label];
const debugStore = useDebugStore(); const debugStore = useDebugStore();
const elideFromDebugging = debugStore.isDebugMode && !debuggable; const elideFromDebugging = debugStore.isDebugMode && !debuggable;
const { blockLabel: urlBlockLabel } = useParams(); const { blockLabel: urlBlockLabel } = useParams();
@@ -72,7 +79,12 @@ function LoginNode({ id, data, type }: NodeProps<LoginNode>) {
updateNodeData(id, { [key]: value }); updateNodeData(id, { [key]: value });
} }
useEffect(() => {
setFacing(data.showCode ? "back" : "front");
}, [data.showCode]);
return ( return (
<Flippable facing={facing} preserveFrontsideHeight={true}>
<div> <div>
<Handle <Handle
type="source" type="source"
@@ -132,7 +144,9 @@ function LoginNode({ id, data, type }: NodeProps<LoginNode>) {
<div className="space-y-2"> <div className="space-y-2">
<div className="flex gap-2"> <div className="flex gap-2">
<Label className="text-xs text-slate-300">Login Goal</Label> <Label className="text-xs text-slate-300">Login Goal</Label>
<HelpTooltip content={helpTooltips["login"]["navigationGoal"]} /> <HelpTooltip
content={helpTooltips["login"]["navigationGoal"]}
/>
</div> </div>
<WorkflowBlockInputTextarea <WorkflowBlockInputTextarea
nodeId={id} nodeId={id}
@@ -255,7 +269,11 @@ function LoginNode({ id, data, type }: NodeProps<LoginNode>) {
handleChange( handleChange(
"errorCodeMapping", "errorCodeMapping",
checked checked
? JSON.stringify(errorMappingExampleValue, null, 2) ? JSON.stringify(
errorMappingExampleValue,
null,
2,
)
: "null", : "null",
); );
}} }}
@@ -357,6 +375,8 @@ function LoginNode({ id, data, type }: NodeProps<LoginNode>) {
</Accordion> </Accordion>
</div> </div>
</div> </div>
<BlockCodeEditor blockLabel={label} blockType={type} script={script} />
</Flippable>
); );
} }

View File

@@ -10,11 +10,16 @@ import { DotsHorizontalIcon } from "@radix-ui/react-icons";
import { OrgWalled } from "@/components/Orgwalled"; import { OrgWalled } from "@/components/Orgwalled";
type Props = { type Props = {
isScriptable?: boolean;
onDelete: () => void; onDelete: () => void;
onShowScript?: () => void; onShowScript?: () => void;
}; };
function NodeActionMenu({ onDelete, onShowScript }: Props) { function NodeActionMenu({
isScriptable = false,
onDelete,
onShowScript,
}: Props) {
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
@@ -30,6 +35,7 @@ function NodeActionMenu({ onDelete, onShowScript }: Props) {
> >
Delete Block Delete Block
</DropdownMenuItem> </DropdownMenuItem>
{isScriptable && (
<OrgWalled className="p-0"> <OrgWalled className="p-0">
{onShowScript && ( {onShowScript && (
<DropdownMenuItem <DropdownMenuItem
@@ -41,6 +47,7 @@ function NodeActionMenu({ onDelete, onShowScript }: Props) {
</DropdownMenuItem> </DropdownMenuItem>
)} )}
</OrgWalled> </OrgWalled>
)}
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
); );

View File

@@ -1,3 +1,5 @@
import { useEffect } from "react";
import { Flippable } from "@/components/Flippable";
import { HelpTooltip } from "@/components/HelpTooltip"; import { HelpTooltip } from "@/components/HelpTooltip";
import { import {
Accordion, Accordion,
@@ -12,7 +14,9 @@ import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { WorkflowBlockInput } from "@/components/WorkflowBlockInput"; import { WorkflowBlockInput } from "@/components/WorkflowBlockInput";
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea"; import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor";
import { CodeEditor } from "@/routes/workflows/components/CodeEditor"; import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
import { useBlockScriptStore } from "@/store/BlockScriptStore";
import { import {
Handle, Handle,
NodeProps, NodeProps,
@@ -39,7 +43,10 @@ import { useParams } from "react-router-dom";
function TaskNode({ id, data, type }: NodeProps<TaskNode>) { function TaskNode({ id, data, type }: NodeProps<TaskNode>) {
const { updateNodeData } = useReactFlow(); const { updateNodeData } = useReactFlow();
const [facing, setFacing] = useState<"front" | "back">("front");
const blockScriptStore = useBlockScriptStore();
const { debuggable, editable, label } = data; const { debuggable, editable, label } = data;
const script = blockScriptStore.scripts[label];
const debugStore = useDebugStore(); const debugStore = useDebugStore();
const elideFromDebugging = debugStore.isDebugMode && !debuggable; const elideFromDebugging = debugStore.isDebugMode && !debuggable;
const { blockLabel: urlBlockLabel } = useParams(); const { blockLabel: urlBlockLabel } = useParams();
@@ -78,7 +85,12 @@ function TaskNode({ id, data, type }: NodeProps<TaskNode>) {
updateNodeData(id, { [key]: value }); updateNodeData(id, { [key]: value });
} }
useEffect(() => {
setFacing(data.showCode ? "back" : "front");
}, [data.showCode]);
return ( return (
<Flippable facing={facing} preserveFrontsideHeight={true}>
<div> <div>
<Handle <Handle
type="source" type="source"
@@ -285,7 +297,11 @@ function TaskNode({ id, data, type }: NodeProps<TaskNode>) {
handleChange( handleChange(
"errorCodeMapping", "errorCodeMapping",
checked checked
? JSON.stringify(errorMappingExampleValue, null, 2) ? JSON.stringify(
errorMappingExampleValue,
null,
2,
)
: "null", : "null",
); );
}} }}
@@ -391,7 +407,9 @@ function TaskNode({ id, data, type }: NodeProps<TaskNode>) {
<Label className="text-xs font-normal text-slate-300"> <Label className="text-xs font-normal text-slate-300">
File Suffix File Suffix
</Label> </Label>
<HelpTooltip content={helpTooltips["task"]["fileSuffix"]} /> <HelpTooltip
content={helpTooltips["task"]["fileSuffix"]}
/>
</div> </div>
<WorkflowBlockInput <WorkflowBlockInput
nodeId={id} nodeId={id}
@@ -449,6 +467,8 @@ function TaskNode({ id, data, type }: NodeProps<TaskNode>) {
</Accordion> </Accordion>
</div> </div>
</div> </div>
<BlockCodeEditor blockLabel={label} blockType={type} script={script} />
</Flippable>
); );
} }

View File

@@ -1,10 +1,14 @@
import { useEffect } from "react";
import { Flippable } from "@/components/Flippable";
import { Handle, NodeProps, Position, useReactFlow } from "@xyflow/react"; import { Handle, NodeProps, Position, useReactFlow } from "@xyflow/react";
import type { URLNode } from "./types"; import type { URLNode } from "./types";
import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow"; import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow";
import { useState } from "react"; import { useState } from "react";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea"; import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor";
import { placeholders } from "../../helpContent"; import { placeholders } from "../../helpContent";
import { useBlockScriptStore } from "@/store/BlockScriptStore";
import { useDebugStore } from "@/store/useDebugStore"; import { useDebugStore } from "@/store/useDebugStore";
import { cn } from "@/util/utils"; import { cn } from "@/util/utils";
import { NodeHeader } from "../components/NodeHeader"; import { NodeHeader } from "../components/NodeHeader";
@@ -12,7 +16,10 @@ import { useParams } from "react-router-dom";
function URLNode({ id, data, type }: NodeProps<URLNode>) { function URLNode({ id, data, type }: NodeProps<URLNode>) {
const { updateNodeData } = useReactFlow(); const { updateNodeData } = useReactFlow();
const [facing, setFacing] = useState<"front" | "back">("front");
const blockScriptStore = useBlockScriptStore();
const { debuggable, editable, label } = data; const { debuggable, editable, label } = data;
const script = blockScriptStore.scripts[label];
const debugStore = useDebugStore(); const debugStore = useDebugStore();
const elideFromDebugging = debugStore.isDebugMode && !debuggable; const elideFromDebugging = debugStore.isDebugMode && !debuggable;
const { blockLabel: urlBlockLabel } = useParams(); const { blockLabel: urlBlockLabel } = useParams();
@@ -32,7 +39,12 @@ function URLNode({ id, data, type }: NodeProps<URLNode>) {
updateNodeData(id, { [key]: value }); updateNodeData(id, { [key]: value });
} }
useEffect(() => {
setFacing(data.showCode ? "back" : "front");
}, [data.showCode]);
return ( return (
<Flippable facing={facing} preserveFrontsideHeight={true}>
<div> <div>
<Handle <Handle
type="source" type="source"
@@ -88,6 +100,12 @@ function URLNode({ id, data, type }: NodeProps<URLNode>) {
</div> </div>
</div> </div>
</div> </div>
<BlockCodeEditor
blockLabel={label}
blockType="goto_url" // sic: the naming is inconsistent
script={script}
/>
</Flippable>
); );
} }

View File

@@ -1,3 +1,5 @@
import { useEffect } from "react";
import { Flippable } from "@/components/Flippable";
import { HelpTooltip } from "@/components/HelpTooltip"; import { HelpTooltip } from "@/components/HelpTooltip";
import { import {
Accordion, Accordion,
@@ -10,7 +12,9 @@ import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea"; import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor";
import { CodeEditor } from "@/routes/workflows/components/CodeEditor"; import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
import { useBlockScriptStore } from "@/store/BlockScriptStore";
import { import {
Handle, Handle,
NodeProps, NodeProps,
@@ -35,7 +39,10 @@ import { useParams } from "react-router-dom";
function ValidationNode({ id, data, type }: NodeProps<ValidationNode>) { function ValidationNode({ id, data, type }: NodeProps<ValidationNode>) {
const { updateNodeData } = useReactFlow(); const { updateNodeData } = useReactFlow();
const [facing, setFacing] = useState<"front" | "back">("front");
const blockScriptStore = useBlockScriptStore();
const { debuggable, editable, label } = data; const { debuggable, editable, label } = data;
const script = blockScriptStore.scripts[label];
const debugStore = useDebugStore(); const debugStore = useDebugStore();
const elideFromDebugging = debugStore.isDebugMode && !debuggable; const elideFromDebugging = debugStore.isDebugMode && !debuggable;
const { blockLabel: urlBlockLabel } = useParams(); const { blockLabel: urlBlockLabel } = useParams();
@@ -61,7 +68,12 @@ function ValidationNode({ id, data, type }: NodeProps<ValidationNode>) {
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id }); const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
useEffect(() => {
setFacing(data.showCode ? "back" : "front");
}, [data.showCode]);
return ( return (
<Flippable facing={facing} preserveFrontsideHeight={true}>
<div> <div>
<Handle <Handle
type="source" type="source"
@@ -153,7 +165,9 @@ function ValidationNode({ id, data, type }: NodeProps<ValidationNode>) {
Error Messages Error Messages
</Label> </Label>
<HelpTooltip <HelpTooltip
content={helpTooltips["validation"]["errorCodeMapping"]} content={
helpTooltips["validation"]["errorCodeMapping"]
}
/> />
</div> </div>
<Checkbox <Checkbox
@@ -166,7 +180,11 @@ function ValidationNode({ id, data, type }: NodeProps<ValidationNode>) {
handleChange( handleChange(
"errorCodeMapping", "errorCodeMapping",
checked checked
? JSON.stringify(errorMappingExampleValue, null, 2) ? JSON.stringify(
errorMappingExampleValue,
null,
2,
)
: "null", : "null",
); );
}} }}
@@ -196,7 +214,9 @@ function ValidationNode({ id, data, type }: NodeProps<ValidationNode>) {
Continue on Failure Continue on Failure
</Label> </Label>
<HelpTooltip <HelpTooltip
content={helpTooltips["validation"]["continueOnFailure"]} content={
helpTooltips["validation"]["continueOnFailure"]
}
/> />
</div> </div>
<div className="w-52"> <div className="w-52">
@@ -217,6 +237,8 @@ function ValidationNode({ id, data, type }: NodeProps<ValidationNode>) {
</Accordion> </Accordion>
</div> </div>
</div> </div>
<BlockCodeEditor blockLabel={label} blockType={type} script={script} />
</Flippable>
); );
} }

View File

@@ -17,6 +17,7 @@ import { useDebugSessionQuery } from "@/routes/workflows/hooks/useDebugSessionQu
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery"; import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
import { import {
debuggableWorkflowBlockTypes, debuggableWorkflowBlockTypes,
scriptableWorkflowBlockTypes,
type WorkflowBlockType, type WorkflowBlockType,
type WorkflowApiResponse, type WorkflowApiResponse,
} from "@/routes/workflows/types/workflowTypes"; } from "@/routes/workflows/types/workflowTypes";
@@ -154,6 +155,7 @@ function NodeHeader({
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const location = useLocation(); const location = useLocation();
const isDebuggable = debuggableWorkflowBlockTypes.has(type); const isDebuggable = debuggableWorkflowBlockTypes.has(type);
const isScriptable = scriptableWorkflowBlockTypes.has(type);
const { data: workflowRun } = useWorkflowRunQuery(); const { data: workflowRun } = useWorkflowRunQuery();
const workflowRunIsRunningOrQueued = const workflowRunIsRunningOrQueued =
workflowRun && statusIsRunningOrQueued(workflowRun); workflowRun && statusIsRunningOrQueued(workflowRun);
@@ -414,6 +416,7 @@ function NodeHeader({
})} })}
> >
<NodeActionMenu <NodeActionMenu
isScriptable={isScriptable}
onDelete={() => { onDelete={() => {
deleteNodeCallback(nodeId); deleteNodeCallback(nodeId);
}} }}

View File

@@ -228,6 +228,17 @@ export const debuggableWorkflowBlockTypes: Set<WorkflowBlockType> = new Set([
"validation", "validation",
]); ]);
export const scriptableWorkflowBlockTypes: Set<WorkflowBlockType> = new Set([
"action",
"extraction",
"file_download",
"goto_url",
"login",
"navigation",
"task",
"validation",
]);
export function isTaskVariantBlock(item: { export function isTaskVariantBlock(item: {
block_type: WorkflowBlockType; block_type: WorkflowBlockType;
}): boolean { }): boolean {