Add wait block (#1265)

This commit is contained in:
Shuchang Zheng
2024-11-26 07:24:47 -08:00
committed by GitHub
parent 1de4df2f31
commit fcc83f6fe4
7 changed files with 182 additions and 3 deletions

View File

@@ -0,0 +1,96 @@
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useDeleteNodeCallback } from "@/routes/workflows/hooks/useDeleteNodeCallback";
import { useNodeLabelChangeHandler } from "@/routes/workflows/hooks/useLabelChangeHandler";
import { StopwatchIcon } from "@radix-ui/react-icons";
import { Handle, NodeProps, Position, useReactFlow } from "@xyflow/react";
import { useState } from "react";
import { EditableNodeTitle } from "../components/EditableNodeTitle";
import { NodeActionMenu } from "../NodeActionMenu";
import type { WaitNode } from "./types";
import { HelpTooltip } from "@/components/HelpTooltip";
function WaitNode({ id, data }: NodeProps<WaitNode>) {
const { updateNodeData } = useReactFlow();
const { editable } = data;
const [label, setLabel] = useNodeLabelChangeHandler({
id,
initialValue: data.label,
});
const [inputs, setInputs] = useState({
waitInSeconds: data.waitInSeconds,
});
const deleteNodeCallback = useDeleteNodeCallback();
function handleChange(key: string, value: unknown) {
if (!editable) {
return;
}
setInputs({ ...inputs, [key]: value });
updateNodeData(id, { [key]: value });
}
return (
<div>
<Handle
type="source"
position={Position.Bottom}
id="a"
className="opacity-0"
/>
<Handle
type="target"
position={Position.Top}
id="b"
className="opacity-0"
/>
<div className="w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4">
<header className="flex h-[2.75rem] justify-between">
<div className="flex gap-2">
<div className="flex h-[2.75rem] w-[2.75rem] items-center justify-center rounded border border-slate-600">
<StopwatchIcon className="size-6" />
</div>
<div className="flex flex-col gap-1">
<EditableNodeTitle
value={label}
editable={editable}
onChange={setLabel}
titleClassName="text-base"
inputClassName="text-base"
/>
<span className="text-xs text-slate-400">Wait Block</span>
</div>
</div>
<NodeActionMenu
onDelete={() => {
deleteNodeCallback(id);
}}
/>
</header>
<div className="space-y-2">
<div className="flex gap-2">
<Label className="text-xs text-slate-300">
Wait Time (in seconds)
</Label>
<HelpTooltip content="Specify a number for how many seconds to wait. Value must be between 0 and 300 seconds." />
</div>
<Input
type="number"
min="0"
max="300"
value={inputs.waitInSeconds}
onChange={(event) => {
if (!editable) {
return;
}
handleChange("waitInSeconds", Number(event.target.value));
}}
className="nopan text-xs"
/>
</div>
</div>
</div>
);
}
export { WaitNode };

View File

@@ -0,0 +1,19 @@
import type { Node } from "@xyflow/react";
import { NodeBaseData } from "../types";
export type WaitNodeData = NodeBaseData & {
waitInSeconds: number;
};
export type WaitNode = Node<WaitNodeData, "wait">;
export const waitNodeDefaultData: WaitNodeData = {
label: "",
continueOnFailure: false,
editable: true,
waitInSeconds: 0,
};
export function isWaitNode(node: Node): node is WaitNode {
return node.type === "wait";
}

View File

@@ -29,6 +29,8 @@ import { ExtractionNode } from "./ExtractionNode/types";
import { ExtractionNode as ExtractionNodeComponent } from "./ExtractionNode/ExtractionNode";
import { LoginNode } from "./LoginNode/types";
import { LoginNode as LoginNodeComponent } from "./LoginNode/LoginNode";
import { WaitNode } from "./WaitNode/types";
import { WaitNode as WaitNodeComponent } from "./WaitNode/WaitNode";
export type UtilityNode = StartNode | NodeAdderNode;
@@ -45,7 +47,8 @@ export type WorkflowBlockNode =
| ActionNode
| NavigationNode
| ExtractionNode
| LoginNode;
| LoginNode
| WaitNode;
export function isUtilityNode(node: AppNode): node is UtilityNode {
return node.type === "nodeAdder" || node.type === "start";
@@ -73,4 +76,5 @@ export const nodeTypes = {
navigation: memo(NavigationNodeComponent),
extraction: memo(ExtractionNodeComponent),
login: memo(LoginNodeComponent),
wait: memo(WaitNodeComponent),
} as const;

View File

@@ -8,6 +8,7 @@ import {
ListBulletIcon,
LockOpen1Icon,
PlusIcon,
StopwatchIcon,
UpdateIcon,
UploadIcon,
} from "@radix-ui/react-icons";
@@ -104,6 +105,12 @@ const nodeLibraryItems: Array<{
title: "Login Block",
description: "Login to a website",
},
{
nodeType: "wait",
icon: <StopwatchIcon className="size-6" />,
title: "Wait Block",
description: "Wait for some time",
},
];
type Props = {

View File

@@ -27,6 +27,7 @@ import {
WorkflowCreateYAMLRequest,
ExtractionBlockYAML,
LoginBlockYAML,
WaitBlockYAML,
} from "../types/workflowYamlTypes";
import {
EMAIL_BLOCK_SENDER,
@@ -71,6 +72,7 @@ import {
isExtractionNode,
} from "./nodes/ExtractionNode/types";
import { loginNodeDefaultData } from "./nodes/LoginNode/types";
import { waitNodeDefaultData } from "./nodes/WaitNode/types";
export const NEW_NODE_LABEL_PREFIX = "block_";
@@ -273,6 +275,17 @@ function convertToNode(
},
};
}
case "wait": {
return {
...identifiers,
...common,
type: "wait",
data: {
...commonData,
waitInSeconds: block.wait_sec ?? 0,
},
};
}
case "code": {
return {
...identifiers,
@@ -618,6 +631,17 @@ function createNode(
},
};
}
case "wait": {
return {
...identifiers,
...common,
type: "wait",
data: {
...waitNodeDefaultData,
label,
},
};
}
case "loop": {
return {
...identifiers,
@@ -832,6 +856,13 @@ function getWorkflowBlock(node: WorkflowBlockNode): BlockYAML {
cache_actions: node.data.cacheActions,
};
}
case "wait": {
return {
...base,
block_type: "wait",
wait_sec: node.data.waitInSeconds,
};
}
case "sendEmail": {
return {
...base,
@@ -1385,6 +1416,14 @@ function convertBlocksToBlockYAML(
};
return blockYaml;
}
case "wait": {
const blockYaml: WaitBlockYAML = {
...base,
block_type: "wait",
wait_sec: block.wait_sec,
};
return blockYaml;
}
case "for_loop": {
const blockYaml: ForLoopBlockYAML = {
...base,

View File

@@ -114,7 +114,8 @@ export type WorkflowBlock =
| ActionBlock
| NavigationBlock
| ExtractionBlock
| LoginBlock;
| LoginBlock
| WaitBlock;
export const WorkflowBlockType = {
Task: "task",
@@ -130,6 +131,7 @@ export const WorkflowBlockType = {
Navigation: "navigation",
Extraction: "extraction",
Login: "login",
Wait: "wait",
} as const;
export type WorkflowBlockType =
@@ -275,6 +277,11 @@ export type LoginBlock = WorkflowBlockBase & {
cache_actions: boolean;
};
export type WaitBlock = WorkflowBlockBase & {
block_type: "wait";
wait_sec?: number;
};
export type WorkflowDefinition = {
parameters: Array<Parameter>;
blocks: Array<WorkflowBlock>;

View File

@@ -80,6 +80,7 @@ const BlockTypes = {
NAVIGATION: "navigation",
EXTRACTION: "extraction",
LOGIN: "login",
WAIT: "wait",
} as const;
export type BlockType = (typeof BlockTypes)[keyof typeof BlockTypes];
@@ -97,7 +98,8 @@ export type BlockYAML =
| ActionBlockYAML
| NavigationBlockYAML
| ExtractionBlockYAML
| LoginBlockYAML;
| LoginBlockYAML
| WaitBlockYAML;
export type BlockYAMLBase = {
block_type: BlockType;
@@ -188,6 +190,11 @@ export type LoginBlockYAML = BlockYAMLBase & {
cache_actions: boolean;
};
export type WaitBlockYAML = BlockYAMLBase & {
block_type: "wait";
wait_sec?: number;
};
export type CodeBlockYAML = BlockYAMLBase & {
block_type: "code";
code: string;