Add wait block (#1265)
This commit is contained in:
@@ -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 };
|
||||||
@@ -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";
|
||||||
|
}
|
||||||
@@ -29,6 +29,8 @@ import { ExtractionNode } from "./ExtractionNode/types";
|
|||||||
import { ExtractionNode as ExtractionNodeComponent } from "./ExtractionNode/ExtractionNode";
|
import { ExtractionNode as ExtractionNodeComponent } from "./ExtractionNode/ExtractionNode";
|
||||||
import { LoginNode } from "./LoginNode/types";
|
import { LoginNode } from "./LoginNode/types";
|
||||||
import { LoginNode as LoginNodeComponent } from "./LoginNode/LoginNode";
|
import { LoginNode as LoginNodeComponent } from "./LoginNode/LoginNode";
|
||||||
|
import { WaitNode } from "./WaitNode/types";
|
||||||
|
import { WaitNode as WaitNodeComponent } from "./WaitNode/WaitNode";
|
||||||
|
|
||||||
export type UtilityNode = StartNode | NodeAdderNode;
|
export type UtilityNode = StartNode | NodeAdderNode;
|
||||||
|
|
||||||
@@ -45,7 +47,8 @@ export type WorkflowBlockNode =
|
|||||||
| ActionNode
|
| ActionNode
|
||||||
| NavigationNode
|
| NavigationNode
|
||||||
| ExtractionNode
|
| ExtractionNode
|
||||||
| LoginNode;
|
| LoginNode
|
||||||
|
| WaitNode;
|
||||||
|
|
||||||
export function isUtilityNode(node: AppNode): node is UtilityNode {
|
export function isUtilityNode(node: AppNode): node is UtilityNode {
|
||||||
return node.type === "nodeAdder" || node.type === "start";
|
return node.type === "nodeAdder" || node.type === "start";
|
||||||
@@ -73,4 +76,5 @@ export const nodeTypes = {
|
|||||||
navigation: memo(NavigationNodeComponent),
|
navigation: memo(NavigationNodeComponent),
|
||||||
extraction: memo(ExtractionNodeComponent),
|
extraction: memo(ExtractionNodeComponent),
|
||||||
login: memo(LoginNodeComponent),
|
login: memo(LoginNodeComponent),
|
||||||
|
wait: memo(WaitNodeComponent),
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
ListBulletIcon,
|
ListBulletIcon,
|
||||||
LockOpen1Icon,
|
LockOpen1Icon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
|
StopwatchIcon,
|
||||||
UpdateIcon,
|
UpdateIcon,
|
||||||
UploadIcon,
|
UploadIcon,
|
||||||
} from "@radix-ui/react-icons";
|
} from "@radix-ui/react-icons";
|
||||||
@@ -104,6 +105,12 @@ const nodeLibraryItems: Array<{
|
|||||||
title: "Login Block",
|
title: "Login Block",
|
||||||
description: "Login to a website",
|
description: "Login to a website",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
nodeType: "wait",
|
||||||
|
icon: <StopwatchIcon className="size-6" />,
|
||||||
|
title: "Wait Block",
|
||||||
|
description: "Wait for some time",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
WorkflowCreateYAMLRequest,
|
WorkflowCreateYAMLRequest,
|
||||||
ExtractionBlockYAML,
|
ExtractionBlockYAML,
|
||||||
LoginBlockYAML,
|
LoginBlockYAML,
|
||||||
|
WaitBlockYAML,
|
||||||
} from "../types/workflowYamlTypes";
|
} from "../types/workflowYamlTypes";
|
||||||
import {
|
import {
|
||||||
EMAIL_BLOCK_SENDER,
|
EMAIL_BLOCK_SENDER,
|
||||||
@@ -71,6 +72,7 @@ import {
|
|||||||
isExtractionNode,
|
isExtractionNode,
|
||||||
} 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";
|
||||||
|
|
||||||
export const NEW_NODE_LABEL_PREFIX = "block_";
|
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": {
|
case "code": {
|
||||||
return {
|
return {
|
||||||
...identifiers,
|
...identifiers,
|
||||||
@@ -618,6 +631,17 @@ function createNode(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case "wait": {
|
||||||
|
return {
|
||||||
|
...identifiers,
|
||||||
|
...common,
|
||||||
|
type: "wait",
|
||||||
|
data: {
|
||||||
|
...waitNodeDefaultData,
|
||||||
|
label,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
case "loop": {
|
case "loop": {
|
||||||
return {
|
return {
|
||||||
...identifiers,
|
...identifiers,
|
||||||
@@ -832,6 +856,13 @@ function getWorkflowBlock(node: WorkflowBlockNode): BlockYAML {
|
|||||||
cache_actions: node.data.cacheActions,
|
cache_actions: node.data.cacheActions,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case "wait": {
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
block_type: "wait",
|
||||||
|
wait_sec: node.data.waitInSeconds,
|
||||||
|
};
|
||||||
|
}
|
||||||
case "sendEmail": {
|
case "sendEmail": {
|
||||||
return {
|
return {
|
||||||
...base,
|
...base,
|
||||||
@@ -1385,6 +1416,14 @@ function convertBlocksToBlockYAML(
|
|||||||
};
|
};
|
||||||
return blockYaml;
|
return blockYaml;
|
||||||
}
|
}
|
||||||
|
case "wait": {
|
||||||
|
const blockYaml: WaitBlockYAML = {
|
||||||
|
...base,
|
||||||
|
block_type: "wait",
|
||||||
|
wait_sec: block.wait_sec,
|
||||||
|
};
|
||||||
|
return blockYaml;
|
||||||
|
}
|
||||||
case "for_loop": {
|
case "for_loop": {
|
||||||
const blockYaml: ForLoopBlockYAML = {
|
const blockYaml: ForLoopBlockYAML = {
|
||||||
...base,
|
...base,
|
||||||
|
|||||||
@@ -114,7 +114,8 @@ export type WorkflowBlock =
|
|||||||
| ActionBlock
|
| ActionBlock
|
||||||
| NavigationBlock
|
| NavigationBlock
|
||||||
| ExtractionBlock
|
| ExtractionBlock
|
||||||
| LoginBlock;
|
| LoginBlock
|
||||||
|
| WaitBlock;
|
||||||
|
|
||||||
export const WorkflowBlockType = {
|
export const WorkflowBlockType = {
|
||||||
Task: "task",
|
Task: "task",
|
||||||
@@ -130,6 +131,7 @@ export const WorkflowBlockType = {
|
|||||||
Navigation: "navigation",
|
Navigation: "navigation",
|
||||||
Extraction: "extraction",
|
Extraction: "extraction",
|
||||||
Login: "login",
|
Login: "login",
|
||||||
|
Wait: "wait",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type WorkflowBlockType =
|
export type WorkflowBlockType =
|
||||||
@@ -275,6 +277,11 @@ export type LoginBlock = WorkflowBlockBase & {
|
|||||||
cache_actions: boolean;
|
cache_actions: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type WaitBlock = WorkflowBlockBase & {
|
||||||
|
block_type: "wait";
|
||||||
|
wait_sec?: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type WorkflowDefinition = {
|
export type WorkflowDefinition = {
|
||||||
parameters: Array<Parameter>;
|
parameters: Array<Parameter>;
|
||||||
blocks: Array<WorkflowBlock>;
|
blocks: Array<WorkflowBlock>;
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ const BlockTypes = {
|
|||||||
NAVIGATION: "navigation",
|
NAVIGATION: "navigation",
|
||||||
EXTRACTION: "extraction",
|
EXTRACTION: "extraction",
|
||||||
LOGIN: "login",
|
LOGIN: "login",
|
||||||
|
WAIT: "wait",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type BlockType = (typeof BlockTypes)[keyof typeof BlockTypes];
|
export type BlockType = (typeof BlockTypes)[keyof typeof BlockTypes];
|
||||||
@@ -97,7 +98,8 @@ export type BlockYAML =
|
|||||||
| ActionBlockYAML
|
| ActionBlockYAML
|
||||||
| NavigationBlockYAML
|
| NavigationBlockYAML
|
||||||
| ExtractionBlockYAML
|
| ExtractionBlockYAML
|
||||||
| LoginBlockYAML;
|
| LoginBlockYAML
|
||||||
|
| WaitBlockYAML;
|
||||||
|
|
||||||
export type BlockYAMLBase = {
|
export type BlockYAMLBase = {
|
||||||
block_type: BlockType;
|
block_type: BlockType;
|
||||||
@@ -188,6 +190,11 @@ export type LoginBlockYAML = BlockYAMLBase & {
|
|||||||
cache_actions: boolean;
|
cache_actions: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type WaitBlockYAML = BlockYAMLBase & {
|
||||||
|
block_type: "wait";
|
||||||
|
wait_sec?: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type CodeBlockYAML = BlockYAMLBase & {
|
export type CodeBlockYAML = BlockYAMLBase & {
|
||||||
block_type: "code";
|
block_type: "code";
|
||||||
code: string;
|
code: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user