Merge pull request #425 from usual2970/feat/result_branch

添加执行结果分支节点
This commit is contained in:
Yoan.liu 2025-01-20 09:46:50 +08:00 committed by GitHub
commit 6bdcfaaef0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 342 additions and 49 deletions

View File

@ -26,13 +26,16 @@ type Workflow struct {
type WorkflowNodeType string type WorkflowNodeType string
const ( const (
WorkflowNodeTypeStart = WorkflowNodeType("start") WorkflowNodeTypeStart = WorkflowNodeType("start")
WorkflowNodeTypeEnd = WorkflowNodeType("end") WorkflowNodeTypeEnd = WorkflowNodeType("end")
WorkflowNodeTypeApply = WorkflowNodeType("apply") WorkflowNodeTypeApply = WorkflowNodeType("apply")
WorkflowNodeTypeDeploy = WorkflowNodeType("deploy") WorkflowNodeTypeDeploy = WorkflowNodeType("deploy")
WorkflowNodeTypeNotify = WorkflowNodeType("notify") WorkflowNodeTypeNotify = WorkflowNodeType("notify")
WorkflowNodeTypeBranch = WorkflowNodeType("branch") WorkflowNodeTypeBranch = WorkflowNodeType("branch")
WorkflowNodeTypeCondition = WorkflowNodeType("condition") WorkflowNodeTypeCondition = WorkflowNodeType("condition")
WorkflowNodeTypeExecuteResultBranch = WorkflowNodeType("execute_result_branch")
WorkflowNodeTypeExecuteSuccess = WorkflowNodeType("execute_success")
WorkflowNodeTypeExecuteFailure = WorkflowNodeType("execute_failure")
) )
type WorkflowTriggerType string type WorkflowTriggerType string

View File

@ -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
}

View File

@ -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
}

View File

@ -8,7 +8,7 @@ import (
"github.com/usual2970/certimate/internal/domain" "github.com/usual2970/certimate/internal/domain"
) )
type nodeProcessor interface { type NodeProcessor interface {
Run(ctx context.Context) error Run(ctx context.Context) error
Log(ctx context.Context) *domain.WorkflowRunLog Log(ctx context.Context) *domain.WorkflowRunLog
AddOutput(ctx context.Context, title, content string, err ...string) AddOutput(ctx context.Context, title, content string, err ...string)
@ -58,7 +58,7 @@ func (l *nodeLogger) AddOutput(ctx context.Context, title, content string, err .
l.log.Outputs = append(l.log.Outputs, output) l.log.Outputs = append(l.log.Outputs, output)
} }
func GetProcessor(node *domain.WorkflowNode) (nodeProcessor, error) { func GetProcessor(node *domain.WorkflowNode) (NodeProcessor, error) {
switch node.Type { switch node.Type {
case domain.WorkflowNodeTypeStart: case domain.WorkflowNodeTypeStart:
return NewStartNode(node), nil return NewStartNode(node), nil
@ -70,6 +70,10 @@ func GetProcessor(node *domain.WorkflowNode) (nodeProcessor, error) {
return NewDeployNode(node), nil return NewDeployNode(node), nil
case domain.WorkflowNodeTypeNotify: case domain.WorkflowNodeTypeNotify:
return NewNotifyNode(node), nil return NewNotifyNode(node), nil
case domain.WorkflowNodeTypeExecuteSuccess:
return NewExecuteSuccessNode(node), nil
case domain.WorkflowNodeTypeExecuteFailure:
return NewExecuteFailureNode(node), nil
} }
return nil, errors.New("not implemented") return nil, errors.New("not implemented")
} }

View File

@ -31,7 +31,7 @@ func (w *workflowProcessor) Run(ctx context.Context) error {
func (w *workflowProcessor) processNode(ctx context.Context, node *domain.WorkflowNode) error { func (w *workflowProcessor) processNode(ctx context.Context, node *domain.WorkflowNode) error {
current := node current := node
for current != nil { for current != nil {
if current.Type == domain.WorkflowNodeTypeBranch { if current.Type == domain.WorkflowNodeTypeBranch || current.Type == domain.WorkflowNodeTypeExecuteResultBranch {
for _, branch := range current.Branches { for _, branch := range current.Branches {
if err := w.processNode(ctx, &branch); err != nil { if err := w.processNode(ctx, &branch); err != nil {
continue continue
@ -39,24 +39,37 @@ func (w *workflowProcessor) processNode(ctx context.Context, node *domain.Workfl
} }
} }
if current.Type != domain.WorkflowNodeTypeBranch { var runErr error
processor, err := nodes.GetProcessor(current) var processor nodes.NodeProcessor
if err != nil { for {
return err if current.Type != domain.WorkflowNodeTypeBranch && current.Type != domain.WorkflowNodeTypeExecuteResultBranch {
} processor, runErr = nodes.GetProcessor(current)
if runErr != nil {
break
}
err = processor.Run(ctx) runErr = processor.Run(ctx)
log := processor.Log(ctx) log := processor.Log(ctx)
if log != nil { if log != nil {
w.logs = append(w.logs, *log) w.logs = append(w.logs, *log)
} }
if runErr != nil {
if err != nil { break
return err }
} }
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 return nil
@ -65,3 +78,16 @@ func (w *workflowProcessor) processNode(ctx context.Context, node *domain.Workfl
func setContextWorkflowId(ctx context.Context, id string) context.Context { func setContextWorkflowId(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, "workflow_id", id) return context.WithValue(ctx, "workflow_id", id)
} }
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
}

View File

@ -7,6 +7,8 @@ import BranchNode from "./node/BranchNode";
import ConditionNode from "./node/ConditionNode"; import ConditionNode from "./node/ConditionNode";
import DeployNode from "./node/DeployNode"; import DeployNode from "./node/DeployNode";
import EndNode from "./node/EndNode"; import EndNode from "./node/EndNode";
import ExecuteResultBranchNode from "./node/ExecuteResultBranchNode";
import ExecuteResultNode from "./node/ExecuteResultNode";
import NotifyNode from "./node/NotifyNode"; import NotifyNode from "./node/NotifyNode";
import StartNode from "./node/StartNode"; import StartNode from "./node/StartNode";
@ -35,6 +37,13 @@ const WorkflowElement = ({ node, disabled, branchId, branchIndex }: WorkflowElem
case WorkflowNodeType.Branch: case WorkflowNodeType.Branch:
return <BranchNode node={node} disabled={disabled} />; return <BranchNode node={node} disabled={disabled} />;
case WorkflowNodeType.ExecuteResultBranch:
return <ExecuteResultBranchNode node={node} disabled={disabled} />;
case WorkflowNodeType.ExecuteSuccess:
case WorkflowNodeType.ExecuteFailure:
return <ExecuteResultNode node={node} disabled={disabled} branchId={branchId!} branchIndex={branchIndex!} />;
case WorkflowNodeType.Condition: case WorkflowNodeType.Condition:
return <ConditionNode node={node} disabled={disabled} branchId={branchId!} branchIndex={branchIndex!} />; return <ConditionNode node={node} disabled={disabled} branchId={branchId!} branchIndex={branchIndex!} />;

View File

@ -27,20 +27,29 @@ const AddNode = ({ node, disabled }: AddNodeProps) => {
[WorkflowNodeType.Apply, "workflow_node.apply.label", <SolutionOutlinedIcon />], [WorkflowNodeType.Apply, "workflow_node.apply.label", <SolutionOutlinedIcon />],
[WorkflowNodeType.Deploy, "workflow_node.deploy.label", <CloudUploadOutlinedIcon />], [WorkflowNodeType.Deploy, "workflow_node.deploy.label", <CloudUploadOutlinedIcon />],
[WorkflowNodeType.Branch, "workflow_node.branch.label", <SisternodeOutlinedIcon />], [WorkflowNodeType.Branch, "workflow_node.branch.label", <SisternodeOutlinedIcon />],
[WorkflowNodeType.ExecuteResultBranch, "workflow_node.execute_result_branch.label", <SisternodeOutlinedIcon />],
[WorkflowNodeType.Notify, "workflow_node.notify.label", <SendOutlinedIcon />], [WorkflowNodeType.Notify, "workflow_node.notify.label", <SendOutlinedIcon />],
].map(([type, label, icon]) => { ]
return { .filter(([type]) => {
key: type as string, if (node.type !== WorkflowNodeType.Apply && node.type !== WorkflowNodeType.Deploy && type === WorkflowNodeType.ExecuteResultBranch) {
disabled: disabled, return false;
label: t(label as string), }
icon: icon,
onClick: () => { return true;
const nextNode = newNode(type as WorkflowNodeType); })
addNode(nextNode, node.id); .map(([type, label, icon]) => {
}, return {
}; key: type as string,
}); disabled: disabled,
}, [node.id, 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 ( return (
<div className="relative py-6 before:absolute before:left-1/2 before:top-0 before:h-full before:w-[2px] before:-translate-x-1/2 before:bg-stone-200 before:content-['']"> <div className="relative py-6 before:absolute before:left-1/2 before:top-0 before:h-full before:w-[2px] before:-translate-x-1/2 before:bg-stone-200 before:content-['']">

View File

@ -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(<WorkflowElement key={current.id} node={current} disabled={disabled} branchId={branchNodeId} branchIndex={branchIndex} />);
current = current.next;
}
return elements;
};
return (
<>
<div
className="relative flex gap-x-16 before:absolute before:inset-x-[128px] before:top-0 before:h-[2px] before:bg-stone-200 before:content-[''] after:absolute after:inset-x-[128px] after:bottom-0 after:h-[2px] after:bg-stone-200 after:content-['']"
style={{
backgroundColor: token.token.colorBgContainer,
}}
>
{node.branches?.map((branch, index) => (
<div
key={branch.id}
className="relative flex flex-col items-center before:absolute before:left-1/2 before:top-0 before:h-full before:w-[2px] before:-translate-x-1/2 before:bg-stone-200 before:content-['']"
>
{index == 0 && (
<>
<div
className="absolute -left-px -top-1 h-2 w-1/2"
style={{
backgroundColor: token.token.colorBgContainer,
}}
></div>
<div
className="absolute -bottom-1 -left-px z-50 h-2 w-1/2"
style={{
backgroundColor: token.token.colorBgContainer,
}}
></div>
</>
)}
{node.branches && index == node.branches.length - 1 && (
<>
<div
className="absolute -right-px -top-1 h-2 w-1/2"
style={{
backgroundColor: token.token.colorBgContainer,
}}
></div>
<div
className="absolute -bottom-1 -right-px z-50 h-2 w-1/2"
style={{
backgroundColor: token.token.colorBgContainer,
}}
></div>
</>
)}
<div className="relative flex flex-col items-center">{renderBranch(branch, node.id, index)}</div>
</div>
))}
</div>
<AddNode node={node} disabled={disabled} />
</>
);
};
export default memo(ExecuteResultBranchNode);

View File

@ -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 (
<>
<Popover
arrow={false}
content={
<SharedNode.Menu
node={node}
branchId={branchId}
branchIndex={branchIndex}
disabled={disabled}
trigger={<Button color="primary" icon={<MoreOutlinedIcon />} variant="text" />}
/>
}
overlayClassName="shadow-md"
overlayInnerStyle={{ padding: 0 }}
placement="rightTop"
>
<Card className="relative z-[1] mt-10 w-[256px] shadow-md" styles={{ body: { padding: 0 } }} hoverable>
<div className="flex h-[48px] flex-col items-center justify-center truncate px-4 py-2">
<div className="flex items-center space-x-2">
{node.type === WorkflowNodeType.ExecuteSuccess ? (
<>
<CheckCircleIcon size={18} className="text-green-500" />
<div>{t("workflow_node.execute_success.label")}</div>
</>
) : (
<>
<XCircleIcon size={18} className="text-red-500" />
<div>{t("workflow_node.execute_failure.label")}</div>
</>
)}
</div>
</div>
</Card>
</Popover>
<AddNode node={node} disabled={disabled} />
</>
);
};
export default memo(ExecuteResultNode);

View File

@ -91,7 +91,13 @@ const SharedNodeMenu = ({ trigger, node, disabled, branchId, branchIndex, afterU
}; };
const handleDeleteClick = async () => { 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!); await removeBranch(branchId!, branchIndex!);
} else { } else {
await removeNode(node.id); await removeNode(node.id);
@ -151,7 +157,11 @@ const SharedNodeMenu = ({ trigger, node, disabled, branchId, branchIndex, afterU
key: "remove", key: "remove",
disabled: disabled || node.type === WorkflowNodeType.Start, disabled: disabled || node.type === WorkflowNodeType.Start,
label: 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_branch")
: t("workflow_node.action.remove_node"), : t("workflow_node.action.remove_node"),
icon: <CloseCircleOutlinedIcon />, icon: <CloseCircleOutlinedIcon />,

View File

@ -30,6 +30,9 @@ export enum WorkflowNodeType {
Start = "start", Start = "start",
End = "end", End = "end",
Branch = "branch", Branch = "branch",
ExecuteResultBranch = "execute_result_branch",
ExecuteSuccess = "execute_success",
ExecuteFailure = "execute_failure",
Condition = "condition", Condition = "condition",
Apply = "apply", Apply = "apply",
Deploy = "deploy", Deploy = "deploy",
@ -41,6 +44,9 @@ const workflowNodeTypeDefaultNames: Map<WorkflowNodeType, string> = new Map([
[WorkflowNodeType.Start, i18n.t("workflow_node.start.label")], [WorkflowNodeType.Start, i18n.t("workflow_node.start.label")],
[WorkflowNodeType.End, i18n.t("workflow_node.end.label")], [WorkflowNodeType.End, i18n.t("workflow_node.end.label")],
[WorkflowNodeType.Branch, i18n.t("workflow_node.branch.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.Condition, i18n.t("workflow_node.condition.label")],
[WorkflowNodeType.Apply, i18n.t("workflow_node.apply.label")], [WorkflowNodeType.Apply, i18n.t("workflow_node.apply.label")],
[WorkflowNodeType.Deploy, i18n.t("workflow_node.deploy.label")], [WorkflowNodeType.Deploy, i18n.t("workflow_node.deploy.label")],
@ -214,6 +220,17 @@ export const newNode = (nodeType: WorkflowNodeType, options: NewNodeOptions = {}
node.branches = [newNode(WorkflowNodeType.Condition, { branchIndex: 0 }), newNode(WorkflowNodeType.Condition, { branchIndex: 1 })]; node.branches = [newNode(WorkflowNodeType.Condition, { branchIndex: 0 }), newNode(WorkflowNodeType.Condition, { branchIndex: 1 })];
} }
break; 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; return node;
@ -227,7 +244,7 @@ export const updateNode = (node: WorkflowNode, targetNode: WorkflowNode) => {
Object.assign(current, targetNode); Object.assign(current, targetNode);
break; 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.branches = current.branches!.map((branch) => updateNode(branch, targetNode));
} }
current = current.next as WorkflowNode; current = current.next as WorkflowNode;
@ -240,16 +257,16 @@ export const addNode = (node: WorkflowNode, preId: string, targetNode: WorkflowN
return produce(node, (draft) => { return produce(node, (draft) => {
let current = draft; let current = draft;
while (current) { 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; targetNode.next = current.next;
current.next = targetNode; current.next = targetNode;
break; 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; targetNode.branches![0].next = current.next;
current.next = targetNode; current.next = targetNode;
break; 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.branches = current.branches!.map((branch) => addNode(branch, preId, targetNode));
} }
current = current.next as WorkflowNode; current = current.next as WorkflowNode;
@ -273,7 +290,7 @@ export const addBranch = (node: WorkflowNode, branchNodeId: string) => {
); );
break; 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.branches = current.branches!.map((branch) => addBranch(branch, branchNodeId));
} }
current = current.next as WorkflowNode; current = current.next as WorkflowNode;
@ -290,7 +307,7 @@ export const removeNode = (node: WorkflowNode, targetNodeId: string) => {
current.next = current.next.next; current.next = current.next.next;
break; 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.branches = current.branches!.map((branch) => removeNode(branch, targetNodeId));
} }
current = current.next as WorkflowNode; current = current.next as WorkflowNode;
@ -310,7 +327,7 @@ export const removeBranch = (node: WorkflowNode, branchNodeId: string, branchInd
}; };
while (current && last) { while (current && last) {
if (current.id === branchNodeId) { if (current.id === branchNodeId) {
if (current.type !== WorkflowNodeType.Branch) { if (current.type !== WorkflowNodeType.Branch && current.type !== WorkflowNodeType.ExecuteResultBranch) {
return draft; return draft;
} }
current.branches!.splice(branchIndex, 1); current.branches!.splice(branchIndex, 1);
@ -332,7 +349,7 @@ export const removeBranch = (node: WorkflowNode, branchNodeId: string, branchInd
break; 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.branches = current.branches!.map((branch) => removeBranch(branch, branchNodeId, branchIndex));
} }
current = current.next as WorkflowNode; current = current.next as WorkflowNode;
@ -354,6 +371,11 @@ export const getWorkflowOutputBeforeId = (node: WorkflowNode, id: string, type:
return true; 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)) { if (current.type !== WorkflowNodeType.Branch && current.outputs && current.outputs.some((io) => io.type === type)) {
output.push({ output.push({
...current, ...current,
@ -361,7 +383,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; const currentLength = output.length;
for (const branch of current.branches!) { for (const branch of current.branches!) {
if (traverse(branch, output)) { if (traverse(branch, output)) {
@ -384,7 +406,7 @@ export const getWorkflowOutputBeforeId = (node: WorkflowNode, id: string, type:
export const isAllNodesValidated = (node: WorkflowNode): boolean => { export const isAllNodesValidated = (node: WorkflowNode): boolean => {
let current = node as typeof node | undefined; let current = node as typeof node | undefined;
while (current) { while (current) {
if (current.type === WorkflowNodeType.Branch) { if (current.type === WorkflowNodeType.Branch || current.type === WorkflowNodeType.ExecuteResultBranch) {
for (const branch of current.branches!) { for (const branch of current.branches!) {
if (!isAllNodesValidated(branch)) { if (!isAllNodesValidated(branch)) {
return false; return false;

View File

@ -381,5 +381,11 @@
"workflow_node.branch.label": "Branch", "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" "workflow_node.condition.label": "Condition"
} }

View File

@ -381,5 +381,11 @@
"workflow_node.branch.label": "分支", "workflow_node.branch.label": "分支",
"workflow_node.execute_result_branch.label": "执行结果分支",
"workflow_node.execute_success.label": "执行成功",
"workflow_node.execute_failure.label": "执行失败",
"workflow_node.condition.label": "条件" "workflow_node.condition.label": "条件"
} }