+ {({ field: { value } }) => (
+ <>
+ {value != null ? `${value} ${t("workflow_node.delay.form.wait.unit")}` : t("workflow.detail.design.editor.placeholder")}
+ >
+ )}
+
+ }
+ />
+ );
+ },
+ },
+
+ onAdd() {
+ return newNode(NodeType.Delay, { i18n: getI18n() });
+ },
+};
diff --git a/ui/src/components/workflow/designer/nodes/index.ts b/ui/src/components/workflow/designer/nodes/index.ts
index 3a6e48a2a..586bd01fc 100644
--- a/ui/src/components/workflow/designer/nodes/index.ts
+++ b/ui/src/components/workflow/designer/nodes/index.ts
@@ -4,6 +4,7 @@ import { BizMonitorNodeRegistry } from "./BizMonitorNodeRegistry";
import { BizNotifyNodeRegistry } from "./BizNotifyNodeRegistry";
import { BizUploadNodeRegistry } from "./BizUploadNodeRegistry";
import { BranchBlockNodeRegistry, ConditionNodeRegistry } from "./ConditionNode";
+import { DelayNodeRegistry } from "./DelayNode";
import { EndNodeRegistry } from "./EndNode";
import { StartNodeRegistry } from "./StartNode";
import { CatchBlockNodeRegistry, TryCatchNodeRegistry } from "./TryCatchNode";
@@ -12,6 +13,7 @@ export const getAllNodeRegistries = () => {
return [
StartNodeRegistry,
EndNodeRegistry,
+ DelayNodeRegistry,
BizApplyNodeRegistry,
BizUploadNodeRegistry,
BizMonitorNodeRegistry,
diff --git a/ui/src/components/workflow/designer/nodes/typings.ts b/ui/src/components/workflow/designer/nodes/typings.ts
index 5199e90f4..e3dceae1b 100644
--- a/ui/src/components/workflow/designer/nodes/typings.ts
+++ b/ui/src/components/workflow/designer/nodes/typings.ts
@@ -13,6 +13,7 @@ import { WORKFLOW_NODE_TYPES, type WorkflowNode } from "@/domain/workflow";
export enum NodeType {
Start = "start",
End = "end",
+ Delay = "delay",
Condition = "condition",
BranchBlock = "branchBlock",
TryCatch = "tryCatch",
@@ -28,6 +29,7 @@ export enum NodeType {
/* TYPE GUARD, PLEASE DO NOT REMOVE THESE! */
console.assert(NodeType.Start === WORKFLOW_NODE_TYPES.START);
console.assert(NodeType.End === WORKFLOW_NODE_TYPES.END);
+console.assert(NodeType.Delay === WORKFLOW_NODE_TYPES.DELAY);
console.assert(NodeType.Condition === WORKFLOW_NODE_TYPES.CONDITION);
console.assert(NodeType.BranchBlock === WORKFLOW_NODE_TYPES.BRANCHBLOCK);
console.assert(NodeType.TryCatch === WORKFLOW_NODE_TYPES.TRYCATCH);
diff --git a/ui/src/domain/workflow.ts b/ui/src/domain/workflow.ts
index a2cea54e4..48c4b9738 100644
--- a/ui/src/domain/workflow.ts
+++ b/ui/src/domain/workflow.ts
@@ -37,6 +37,7 @@ export type WorkflowTriggerType = (typeof WORKFLOW_TRIGGERS)[keyof typeof WORKFL
export const WORKFLOW_NODE_TYPES = Object.freeze({
START: "start",
END: "end",
+ DELAY: "delay",
CONDITION: "condition",
BRANCHBLOCK: "branchBlock",
TRYCATCH: "tryCatch",
@@ -73,6 +74,14 @@ export const defaultNodeConfigForStart = (): Partial
};
};
+export type WorkflowNodeConfigForDelay = {
+ wait?: number;
+};
+
+export const defaultNodeConfigForDelay = (): Partial => {
+ return {};
+};
+
export type WorkflowNodeConfigForBranchBlock = {
expression?: Expr;
};
@@ -189,6 +198,16 @@ export const newNode = (type: WorkflowNodeType, { i18n = getI18n() }: { i18n?: R
},
};
+ case WORKFLOW_NODE_TYPES.DELAY:
+ return {
+ id: newNodeId(),
+ type: type,
+ data: {
+ name: t("workflow_node.delay.default_name"),
+ config: defaultNodeConfigForDelay(),
+ },
+ };
+
case WORKFLOW_NODE_TYPES.CONDITION: {
const branch1 = newNode(WORKFLOW_NODE_TYPES.BRANCHBLOCK, { i18n });
branch1.data.name = `${branch1.data.name} 1`;
diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json
index 5646b884d..c1c620987 100644
--- a/ui/src/i18n/locales/en/nls.workflow.nodes.json
+++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json
@@ -1025,6 +1025,14 @@
"workflow_node.notify.form.skip_on_all_prev_skipped.switch.on": "skip",
"workflow_node.notify.form.skip_on_all_prev_skipped.switch.off": "not skip",
+ "workflow_node.delay.label": "Delay",
+ "workflow_node.delay.help": "Pause the execution, and wait for a certain amount of time.",
+ "workflow_node.delay.default_name": "Delay",
+ "workflow_node.delay.form_anchor.parameters.tab": "Parameters",
+ "workflow_node.delay.form.wait.label": "Waiting time",
+ "workflow_node.delay.form.wait.placeholder": "Please enter waiting time",
+ "workflow_node.delay.form.wait.unit": "seconds",
+
"workflow_node.condition.label": "Parallel/Conditional branch",
"workflow_node.condition.help": "When the specified conditions are met, enter the corresponding branch. The failure of a node in a certain branch does not affect the continuation of parallel branch execution.",
"workflow_node.condition.default_name": "Parallel",
diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json
index b098eea67..45069defa 100644
--- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json
+++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json
@@ -1023,6 +1023,14 @@
"workflow_node.notify.form.skip_on_all_prev_skipped.switch.on": "跳过",
"workflow_node.notify.form.skip_on_all_prev_skipped.switch.off": "不跳过",
+ "workflow_node.delay.label": "延迟等待",
+ "workflow_node.delay.help": "暂停执行工作流并等待一段时间。这在某些需要降低调用执行频率的场景很有用。",
+ "workflow_node.delay.default_name": "延迟",
+ "workflow_node.delay.form_anchor.parameters.tab": "参数设置",
+ "workflow_node.delay.form.wait.label": "等待时间",
+ "workflow_node.delay.form.wait.placeholder": "请输入等待时间",
+ "workflow_node.delay.form.wait.unit": "秒",
+
"workflow_node.condition.label": "并行/条件分支",
"workflow_node.condition.help": "当满足指定的条件时,进入相应分支。某一分支中的节点执行失败不影响平行分支继续执行。",
"workflow_node.condition.default_name": "并行",
diff --git a/ui/src/pages/workflows/WorkflowDetailDesign.tsx b/ui/src/pages/workflows/WorkflowDetailDesign.tsx
index 5a9c92e49..2fe36e5ce 100644
--- a/ui/src/pages/workflows/WorkflowDetailDesign.tsx
+++ b/ui/src/pages/workflows/WorkflowDetailDesign.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useMemo, useRef, useState } from "react";
+import { useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { IconArrowBackUp, IconDots } from "@tabler/icons-react";
import { useDeepCompareEffect } from "ahooks";
@@ -7,7 +7,6 @@ import { debounce } from "radash";
import Show from "@/components/Show";
import { WorkflowDesigner, type WorkflowDesignerInstance, WorkflowNodeDrawer, WorkflowToolbar } from "@/components/workflow/designer";
-import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun";
import { useZustandShallowSelector } from "@/hooks";
import { useWorkflowStore } from "@/stores/workflow";
import { getErrMsg } from "@/utils/error";
@@ -20,16 +19,8 @@ const WorkflowDetailDesign = () => {
const { workflow, ...workflowStore } = useWorkflowStore(useZustandShallowSelector(["workflow", "orchestrate", "publish", "rollback"]));
- const [workflowRunDisabled, setWorkflowRunDisabled] = useState(false);
- const workflowRollbackDisabled = useMemo(
- () => workflowRunDisabled || !workflow.hasDraft || !workflow.hasContent,
- [workflowRunDisabled, workflow.hasDraft, workflow.hasContent]
- );
- const workflowPublishDisabled = useMemo(() => workflowRunDisabled || !workflow.hasDraft, [workflowRunDisabled, workflow.hasDraft]);
- useEffect(() => {
- const running = workflow.lastRunStatus === WORKFLOW_RUN_STATUSES.PENDING || workflow.lastRunStatus === WORKFLOW_RUN_STATUSES.PROCESSING;
- setWorkflowRunDisabled(running);
- }, [workflow.lastRunStatus]);
+ const workflowRollbackDisabled = useMemo(() => !workflow.hasDraft || !workflow.hasContent, [workflow.hasDraft, workflow.hasContent]);
+ const workflowPublishDisabled = useMemo(() => !workflow.hasDraft, [workflow.hasDraft]);
const designerRef = useRef(null);
const designerPending = useRef(false); // 保存中时阻止刷新画布