From e6e964aa8cc0e28317918527f229aa06c6dd2ced Mon Sep 17 00:00:00 2001
From: yoan <536464346@qq.com>
Date: Sun, 19 Jan 2025 17:01:02 +0800
Subject: [PATCH] add execute result branch
---
internal/domain/workflow.go | 17 ++--
.../node-processor/execute_failure_node.go | 27 ++++++
.../node-processor/execute_success_node.go | 27 ++++++
internal/workflow/node-processor/processor.go | 4 +
.../node-processor/workflow_processor.go | 52 ++++++++----
.../components/workflow/WorkflowElement.tsx | 9 ++
ui/src/components/workflow/node/AddNode.tsx | 35 +++++---
.../workflow/node/ExecuteResultBranchNode.tsx | 84 +++++++++++++++++++
.../workflow/node/ExecuteResultNode.tsx | 60 +++++++++++++
.../components/workflow/node/_SharedNode.tsx | 14 +++-
ui/src/domain/workflow.ts | 42 +++++++---
.../i18n/locales/en/nls.workflow.nodes.json | 6 ++
.../i18n/locales/zh/nls.workflow.nodes.json | 6 ++
13 files changed, 336 insertions(+), 47 deletions(-)
create mode 100644 internal/workflow/node-processor/execute_failure_node.go
create mode 100644 internal/workflow/node-processor/execute_success_node.go
create mode 100644 ui/src/components/workflow/node/ExecuteResultBranchNode.tsx
create mode 100644 ui/src/components/workflow/node/ExecuteResultNode.tsx
diff --git a/internal/domain/workflow.go b/internal/domain/workflow.go
index 59474a7b..5ad8d0fe 100644
--- a/internal/domain/workflow.go
+++ b/internal/domain/workflow.go
@@ -9,13 +9,16 @@ import (
type WorkflowNodeType string
const (
- WorkflowNodeTypeStart = WorkflowNodeType("start")
- WorkflowNodeTypeEnd = WorkflowNodeType("end")
- WorkflowNodeTypeApply = WorkflowNodeType("apply")
- WorkflowNodeTypeDeploy = WorkflowNodeType("deploy")
- WorkflowNodeTypeNotify = WorkflowNodeType("notify")
- WorkflowNodeTypeBranch = WorkflowNodeType("branch")
- WorkflowNodeTypeCondition = WorkflowNodeType("condition")
+ WorkflowNodeTypeStart = WorkflowNodeType("start")
+ WorkflowNodeTypeEnd = WorkflowNodeType("end")
+ WorkflowNodeTypeApply = WorkflowNodeType("apply")
+ WorkflowNodeTypeDeploy = WorkflowNodeType("deploy")
+ WorkflowNodeTypeNotify = WorkflowNodeType("notify")
+ WorkflowNodeTypeBranch = WorkflowNodeType("branch")
+ WorkflowNodeTypeCondition = WorkflowNodeType("condition")
+ WorkflowNodeTypeExecuteResultBranch = WorkflowNodeType("execute_result_branch")
+ WorkflowNodeTypeExecuteSuccess = WorkflowNodeType("execute_success")
+ WorkflowNodeTypeExecuteFailure = WorkflowNodeType("execute_failure")
)
type WorkflowTriggerType string
diff --git a/internal/workflow/node-processor/execute_failure_node.go b/internal/workflow/node-processor/execute_failure_node.go
new file mode 100644
index 00000000..d1ff0034
--- /dev/null
+++ b/internal/workflow/node-processor/execute_failure_node.go
@@ -0,0 +1,27 @@
+package nodeprocessor
+
+import (
+ "context"
+
+ "github.com/usual2970/certimate/internal/domain"
+)
+
+type executeFailureNode struct {
+ node *domain.WorkflowNode
+ *nodeLogger
+}
+
+func NewExecuteFailureNode(node *domain.WorkflowNode) *executeFailureNode {
+ return &executeFailureNode{
+ node: node,
+ nodeLogger: NewNodeLogger(node),
+ }
+}
+
+func (e *executeFailureNode) Run(ctx context.Context) error {
+ e.AddOutput(ctx,
+ e.node.Name,
+ "进入执行失败分支",
+ )
+ return nil
+}
diff --git a/internal/workflow/node-processor/execute_success_node.go b/internal/workflow/node-processor/execute_success_node.go
new file mode 100644
index 00000000..d8d4139f
--- /dev/null
+++ b/internal/workflow/node-processor/execute_success_node.go
@@ -0,0 +1,27 @@
+package nodeprocessor
+
+import (
+ "context"
+
+ "github.com/usual2970/certimate/internal/domain"
+)
+
+type executeSuccessNode struct {
+ node *domain.WorkflowNode
+ *nodeLogger
+}
+
+func NewExecuteSuccessNode(node *domain.WorkflowNode) *executeSuccessNode {
+ return &executeSuccessNode{
+ node: node,
+ nodeLogger: NewNodeLogger(node),
+ }
+}
+
+func (e *executeSuccessNode) Run(ctx context.Context) error {
+ e.AddOutput(ctx,
+ e.node.Name,
+ "进入执行成功分支",
+ )
+ return nil
+}
diff --git a/internal/workflow/node-processor/processor.go b/internal/workflow/node-processor/processor.go
index 128022ac..c9dd01c6 100644
--- a/internal/workflow/node-processor/processor.go
+++ b/internal/workflow/node-processor/processor.go
@@ -57,6 +57,10 @@ func GetProcessor(node *domain.WorkflowNode) (nodeProcessor, error) {
return NewDeployNode(node), nil
case domain.WorkflowNodeTypeNotify:
return NewNotifyNode(node), nil
+ case domain.WorkflowNodeTypeExecuteSuccess:
+ return NewExecuteSuccessNode(node), nil
+ case domain.WorkflowNodeTypeExecuteFailure:
+ return NewExecuteFailureNode(node), nil
}
return nil, errors.New("not implemented")
}
diff --git a/internal/workflow/node-processor/workflow_processor.go b/internal/workflow/node-processor/workflow_processor.go
index 20a53315..d4286578 100644
--- a/internal/workflow/node-processor/workflow_processor.go
+++ b/internal/workflow/node-processor/workflow_processor.go
@@ -30,7 +30,7 @@ func (w *workflowProcessor) Run(ctx context.Context) error {
func (w *workflowProcessor) runNode(ctx context.Context, node *domain.WorkflowNode) error {
current := node
for current != nil {
- if current.Type == domain.WorkflowNodeTypeBranch {
+ if current.Type == domain.WorkflowNodeTypeBranch || current.Type == domain.WorkflowNodeTypeExecuteResultBranch {
for _, branch := range current.Branches {
if err := w.runNode(ctx, &branch); err != nil {
continue
@@ -38,24 +38,37 @@ func (w *workflowProcessor) runNode(ctx context.Context, node *domain.WorkflowNo
}
}
- if current.Type != domain.WorkflowNodeTypeBranch {
- processor, err := GetProcessor(current)
- if err != nil {
- return err
- }
+ var runErr error
+ var processor nodeProcessor
+ for {
+ if current.Type != domain.WorkflowNodeTypeBranch && current.Type != domain.WorkflowNodeTypeExecuteResultBranch {
+ processor, runErr = GetProcessor(current)
+ if runErr != nil {
+ break
+ }
- err = processor.Run(ctx)
+ runErr = processor.Run(ctx)
- log := processor.Log(ctx)
- if log != nil {
- w.logs = append(w.logs, *log)
- }
-
- if err != nil {
- return err
+ log := processor.Log(ctx)
+ if log != nil {
+ w.logs = append(w.logs, *log)
+ }
+ if runErr != nil {
+ break
+ }
}
+ break
+ }
+
+ if runErr != nil && current.Next != nil && current.Next.Type != domain.WorkflowNodeTypeExecuteResultBranch {
+ return runErr
+ } else if runErr != nil && current.Next != nil && current.Next.Type == domain.WorkflowNodeTypeExecuteResultBranch {
+ current = getBranchByType(current.Next.Branches, domain.WorkflowNodeTypeExecuteFailure)
+ } else if runErr == nil && current.Next != nil && current.Next.Type == domain.WorkflowNodeTypeExecuteResultBranch {
+ current = getBranchByType(current.Next.Branches, domain.WorkflowNodeTypeExecuteSuccess)
+ } else {
+ current = current.Next
}
- current = current.Next
}
return nil
@@ -68,3 +81,12 @@ func WithWorkflowId(ctx context.Context, id string) context.Context {
func GetWorkflowId(ctx context.Context) string {
return ctx.Value("workflow_id").(string)
}
+
+func getBranchByType(branches []domain.WorkflowNode, nodeType domain.WorkflowNodeType) *domain.WorkflowNode {
+ for _, branch := range branches {
+ if branch.Type == nodeType {
+ return &branch
+ }
+ }
+ return nil
+}
diff --git a/ui/src/components/workflow/WorkflowElement.tsx b/ui/src/components/workflow/WorkflowElement.tsx
index 758699b6..289689dd 100644
--- a/ui/src/components/workflow/WorkflowElement.tsx
+++ b/ui/src/components/workflow/WorkflowElement.tsx
@@ -7,6 +7,8 @@ import BranchNode from "./node/BranchNode";
import ConditionNode from "./node/ConditionNode";
import DeployNode from "./node/DeployNode";
import EndNode from "./node/EndNode";
+import ExecuteResultBranchNode from "./node/ExecuteResultBranchNode";
+import ExecuteResultNode from "./node/ExecuteResultNode";
import NotifyNode from "./node/NotifyNode";
import StartNode from "./node/StartNode";
@@ -35,6 +37,13 @@ const WorkflowElement = ({ node, disabled, branchId, branchIndex }: WorkflowElem
case WorkflowNodeType.Branch:
return ;
+ case WorkflowNodeType.ExecuteResultBranch:
+ return ;
+
+ case WorkflowNodeType.ExecuteSuccess:
+ case WorkflowNodeType.ExecuteFailure:
+ return ;
+
case WorkflowNodeType.Condition:
return ;
diff --git a/ui/src/components/workflow/node/AddNode.tsx b/ui/src/components/workflow/node/AddNode.tsx
index df3f9d1e..a4fa451f 100644
--- a/ui/src/components/workflow/node/AddNode.tsx
+++ b/ui/src/components/workflow/node/AddNode.tsx
@@ -27,20 +27,29 @@ const AddNode = ({ node, disabled }: AddNodeProps) => {
[WorkflowNodeType.Apply, "workflow_node.apply.label", ],
[WorkflowNodeType.Deploy, "workflow_node.deploy.label", ],
[WorkflowNodeType.Branch, "workflow_node.branch.label", ],
+ [WorkflowNodeType.ExecuteResultBranch, "workflow_node.execute_result_branch.label", ],
[WorkflowNodeType.Notify, "workflow_node.notify.label", ],
- ].map(([type, label, icon]) => {
- return {
- key: type as string,
- disabled: disabled,
- label: t(label as string),
- icon: icon,
- onClick: () => {
- const nextNode = newNode(type as WorkflowNodeType);
- addNode(nextNode, node.id);
- },
- };
- });
- }, [node.id, disabled]);
+ ]
+ .filter(([type]) => {
+ if (node.type !== WorkflowNodeType.Apply && node.type !== WorkflowNodeType.Deploy && type === WorkflowNodeType.ExecuteResultBranch) {
+ return false;
+ }
+
+ return true;
+ })
+ .map(([type, label, icon]) => {
+ return {
+ key: type as string,
+ disabled: disabled,
+ label: t(label as string),
+ icon: icon,
+ onClick: () => {
+ const nextNode = newNode(type as WorkflowNodeType);
+ addNode(nextNode, node.id);
+ },
+ };
+ });
+ }, [node.id, disabled, node.type]);
return (
diff --git a/ui/src/components/workflow/node/ExecuteResultBranchNode.tsx b/ui/src/components/workflow/node/ExecuteResultBranchNode.tsx
new file mode 100644
index 00000000..10501cf9
--- /dev/null
+++ b/ui/src/components/workflow/node/ExecuteResultBranchNode.tsx
@@ -0,0 +1,84 @@
+import { memo } from "react";
+import { theme } from "antd";
+
+import { type WorkflowNode } from "@/domain/workflow";
+
+import AddNode from "./AddNode";
+import WorkflowElement from "../WorkflowElement";
+import { type SharedNodeProps } from "./_SharedNode";
+
+const { useToken } = theme;
+
+export type BrandNodeProps = SharedNodeProps;
+
+const ExecuteResultBranchNode = ({ node, disabled }: BrandNodeProps) => {
+ const token = useToken();
+
+ const renderBranch = (node: WorkflowNode, branchNodeId?: string, branchIndex?: number) => {
+ const elements: JSX.Element[] = [];
+
+ let current = node as typeof node | undefined;
+ while (current) {
+ elements.push(
);
+ current = current.next;
+ }
+
+ return elements;
+ };
+
+ return (
+ <>
+
+ {node.branches?.map((branch, index) => (
+
+ {index == 0 && (
+ <>
+
+
+ >
+ )}
+ {node.branches && index == node.branches.length - 1 && (
+ <>
+
+
+ >
+ )}
+
{renderBranch(branch, node.id, index)}
+
+ ))}
+
+
+
+ >
+ );
+};
+
+export default memo(ExecuteResultBranchNode);
diff --git a/ui/src/components/workflow/node/ExecuteResultNode.tsx b/ui/src/components/workflow/node/ExecuteResultNode.tsx
new file mode 100644
index 00000000..a90b7970
--- /dev/null
+++ b/ui/src/components/workflow/node/ExecuteResultNode.tsx
@@ -0,0 +1,60 @@
+import { memo } from "react";
+import { useTranslation } from "react-i18next";
+import { MoreOutlined as MoreOutlinedIcon } from "@ant-design/icons";
+import { Button, Card, Popover } from "antd";
+
+import { CheckCircleIcon, XCircleIcon } from "lucide-react";
+import { WorkflowNodeType } from "@/domain/workflow";
+import AddNode from "./AddNode";
+import SharedNode, { type SharedNodeProps } from "./_SharedNode";
+
+export type ConditionNodeProps = SharedNodeProps & {
+ branchId: string;
+ branchIndex: number;
+};
+
+const ExecuteResultNode = ({ node, disabled, branchId, branchIndex }: ConditionNodeProps) => {
+ const { t } = useTranslation();
+
+ return (
+ <>
+
} variant="text" />}
+ />
+ }
+ overlayClassName="shadow-md"
+ overlayInnerStyle={{ padding: 0 }}
+ placement="rightTop"
+ >
+
+
+
+ {node.type === WorkflowNodeType.ExecuteSuccess ? (
+ <>
+
+
{t("workflow_node.execute_success.label")}
+ >
+ ) : (
+ <>
+
+
{t("workflow_node.execute_failure.label")}
+ >
+ )}
+
+
+
+
+
+
+ >
+ );
+};
+
+export default memo(ExecuteResultNode);
diff --git a/ui/src/components/workflow/node/_SharedNode.tsx b/ui/src/components/workflow/node/_SharedNode.tsx
index c6ddfe50..39bd6119 100644
--- a/ui/src/components/workflow/node/_SharedNode.tsx
+++ b/ui/src/components/workflow/node/_SharedNode.tsx
@@ -90,7 +90,13 @@ const SharedNodeMenu = ({ trigger, node, disabled, branchId, branchIndex, afterU
};
const handleDeleteClick = async () => {
- if (node.type === WorkflowNodeType.Branch || node.type === WorkflowNodeType.Condition) {
+ if (
+ node.type === WorkflowNodeType.Branch ||
+ node.type === WorkflowNodeType.Condition ||
+ node.type === WorkflowNodeType.ExecuteResultBranch ||
+ node.type === WorkflowNodeType.ExecuteSuccess ||
+ node.type === WorkflowNodeType.ExecuteFailure
+ ) {
await removeBranch(branchId!, branchIndex!);
} else {
await removeNode(node.id);
@@ -148,7 +154,11 @@ const SharedNodeMenu = ({ trigger, node, disabled, branchId, branchIndex, afterU
key: "remove",
disabled: disabled || node.type === WorkflowNodeType.Start,
label:
- node.type === WorkflowNodeType.Branch || node.type === WorkflowNodeType.Condition
+ node.type === WorkflowNodeType.Branch ||
+ node.type === WorkflowNodeType.Condition ||
+ node.type === WorkflowNodeType.ExecuteResultBranch ||
+ node.type === WorkflowNodeType.ExecuteSuccess ||
+ node.type === WorkflowNodeType.ExecuteFailure
? t("workflow_node.action.remove_branch")
: t("workflow_node.action.remove_node"),
icon:
,
diff --git a/ui/src/domain/workflow.ts b/ui/src/domain/workflow.ts
index c86078ac..a7ff010e 100644
--- a/ui/src/domain/workflow.ts
+++ b/ui/src/domain/workflow.ts
@@ -30,6 +30,9 @@ export enum WorkflowNodeType {
Start = "start",
End = "end",
Branch = "branch",
+ ExecuteResultBranch = "execute_result_branch",
+ ExecuteSuccess = "execute_success",
+ ExecuteFailure = "execute_failure",
Condition = "condition",
Apply = "apply",
Deploy = "deploy",
@@ -41,6 +44,9 @@ const workflowNodeTypeDefaultNames: Map
= new Map([
[WorkflowNodeType.Start, i18n.t("workflow_node.start.label")],
[WorkflowNodeType.End, i18n.t("workflow_node.end.label")],
[WorkflowNodeType.Branch, i18n.t("workflow_node.branch.label")],
+ [WorkflowNodeType.ExecuteResultBranch, i18n.t("workflow_node.execute_result_branch.label")],
+ [WorkflowNodeType.ExecuteSuccess, i18n.t("workflow_node.execute_success.label")],
+ [WorkflowNodeType.ExecuteFailure, i18n.t("workflow_node.execute_failure.label")],
[WorkflowNodeType.Condition, i18n.t("workflow_node.condition.label")],
[WorkflowNodeType.Apply, i18n.t("workflow_node.apply.label")],
[WorkflowNodeType.Deploy, i18n.t("workflow_node.deploy.label")],
@@ -210,6 +216,17 @@ export const newNode = (nodeType: WorkflowNodeType, options: NewNodeOptions = {}
node.branches = [newNode(WorkflowNodeType.Condition, { branchIndex: 0 }), newNode(WorkflowNodeType.Condition, { branchIndex: 1 })];
}
break;
+ case WorkflowNodeType.ExecuteResultBranch:
+ {
+ node.branches = [newNode(WorkflowNodeType.ExecuteSuccess), newNode(WorkflowNodeType.ExecuteFailure)];
+ }
+ break;
+ case WorkflowNodeType.ExecuteSuccess:
+ case WorkflowNodeType.ExecuteFailure:
+ {
+ node.validated = true;
+ }
+ break;
}
return node;
@@ -223,7 +240,7 @@ export const updateNode = (node: WorkflowNode, targetNode: WorkflowNode) => {
Object.assign(current, targetNode);
break;
}
- if (current.type === WorkflowNodeType.Branch) {
+ if (current.type === WorkflowNodeType.Branch || current.type === WorkflowNodeType.ExecuteResultBranch) {
current.branches = current.branches!.map((branch) => updateNode(branch, targetNode));
}
current = current.next as WorkflowNode;
@@ -236,16 +253,16 @@ export const addNode = (node: WorkflowNode, preId: string, targetNode: WorkflowN
return produce(node, (draft) => {
let current = draft;
while (current) {
- if (current.id === preId && targetNode.type !== WorkflowNodeType.Branch) {
+ if (current.id === preId && targetNode.type !== WorkflowNodeType.Branch && targetNode.type !== WorkflowNodeType.ExecuteResultBranch) {
targetNode.next = current.next;
current.next = targetNode;
break;
- } else if (current.id === preId && targetNode.type === WorkflowNodeType.Branch) {
+ } else if (current.id === preId && (targetNode.type === WorkflowNodeType.Branch || targetNode.type === WorkflowNodeType.ExecuteResultBranch)) {
targetNode.branches![0].next = current.next;
current.next = targetNode;
break;
}
- if (current.type === WorkflowNodeType.Branch) {
+ if (current.type === WorkflowNodeType.Branch || current.type === WorkflowNodeType.ExecuteResultBranch) {
current.branches = current.branches!.map((branch) => addNode(branch, preId, targetNode));
}
current = current.next as WorkflowNode;
@@ -269,7 +286,7 @@ export const addBranch = (node: WorkflowNode, branchNodeId: string) => {
);
break;
}
- if (current.type === WorkflowNodeType.Branch) {
+ if (current.type === WorkflowNodeType.Branch || current.type === WorkflowNodeType.ExecuteResultBranch) {
current.branches = current.branches!.map((branch) => addBranch(branch, branchNodeId));
}
current = current.next as WorkflowNode;
@@ -286,7 +303,7 @@ export const removeNode = (node: WorkflowNode, targetNodeId: string) => {
current.next = current.next.next;
break;
}
- if (current.type === WorkflowNodeType.Branch) {
+ if (current.type === WorkflowNodeType.Branch || current.type === WorkflowNodeType.ExecuteResultBranch) {
current.branches = current.branches!.map((branch) => removeNode(branch, targetNodeId));
}
current = current.next as WorkflowNode;
@@ -306,7 +323,7 @@ export const removeBranch = (node: WorkflowNode, branchNodeId: string, branchInd
};
while (current && last) {
if (current.id === branchNodeId) {
- if (current.type !== WorkflowNodeType.Branch) {
+ if (current.type !== WorkflowNodeType.Branch && current.type !== WorkflowNodeType.ExecuteResultBranch) {
return draft;
}
current.branches!.splice(branchIndex, 1);
@@ -328,7 +345,7 @@ export const removeBranch = (node: WorkflowNode, branchNodeId: string, branchInd
break;
}
- if (current.type === WorkflowNodeType.Branch) {
+ if (current.type === WorkflowNodeType.Branch || current.type === WorkflowNodeType.ExecuteResultBranch) {
current.branches = current.branches!.map((branch) => removeBranch(branch, branchNodeId, branchIndex));
}
current = current.next as WorkflowNode;
@@ -350,6 +367,11 @@ export const getWorkflowOutputBeforeId = (node: WorkflowNode, id: string, type:
return true;
}
+ // 如果当前节点是execute_failure,清除execute_result_branch节点前一个节点的输出
+ if (current.type === WorkflowNodeType.ExecuteFailure) {
+ output.splice(output.length - 1);
+ }
+
if (current.type !== WorkflowNodeType.Branch && current.outputs && current.outputs.some((io) => io.type === type)) {
output.push({
...current,
@@ -357,7 +379,7 @@ export const getWorkflowOutputBeforeId = (node: WorkflowNode, id: string, type:
});
}
- if (current.type === WorkflowNodeType.Branch) {
+ if (current.type === WorkflowNodeType.Branch || current.type === WorkflowNodeType.ExecuteResultBranch) {
const currentLength = output.length;
for (const branch of current.branches!) {
if (traverse(branch, output)) {
@@ -380,7 +402,7 @@ export const getWorkflowOutputBeforeId = (node: WorkflowNode, id: string, type:
export const isAllNodesValidated = (node: WorkflowNode): boolean => {
let current = node as typeof node | undefined;
while (current) {
- if (current.type === WorkflowNodeType.Branch) {
+ if (current.type === WorkflowNodeType.Branch || current.type === WorkflowNodeType.ExecuteResultBranch) {
for (const branch of current.branches!) {
if (!isAllNodesValidated(branch)) {
return false;
diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json
index 89d8ae71..9fa6e336 100644
--- a/ui/src/i18n/locales/en/nls.workflow.nodes.json
+++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json
@@ -362,5 +362,11 @@
"workflow_node.branch.label": "Branch",
+ "workflow_node.execute_result_branch.label": "Execute result branch",
+
+ "workflow_node.execute_success.label": "Execute success",
+
+ "workflow_node.execute_failure.label": "Execute failure",
+
"workflow_node.condition.label": "Condition"
}
diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json
index 9ea8c7dc..74974795 100644
--- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json
+++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json
@@ -362,5 +362,11 @@
"workflow_node.branch.label": "分支",
+ "workflow_node.execute_result_branch.label": "执行结果分支",
+
+ "workflow_node.execute_success.label": "执行成功",
+
+ "workflow_node.execute_failure.label": "执行失败",
+
"workflow_node.condition.label": "条件"
}