From 50136dd339cc10f2d1c6534bb7dee565734b94d8 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 18 Aug 2025 15:48:38 +0800 Subject: [PATCH] fix(ui): node validation error --- .../forms/BranchBlockNodeConfigForm.tsx | 68 ++++++++++++++++--- ...nchBlockNodeConfigFormExpressionEditor.tsx | 13 +++- .../designer/nodes/BizDeployNodeRegistry.tsx | 5 +- .../workflow/designer/nodes/ConditionNode.tsx | 33 +++++++++ 4 files changed, 107 insertions(+), 12 deletions(-) diff --git a/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigForm.tsx b/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigForm.tsx index 3ac7a234..a3929f5e 100644 --- a/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigForm.tsx +++ b/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigForm.tsx @@ -5,7 +5,16 @@ import { type AnchorProps, Form, type FormInstance } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; -import { type WorkflowNodeConfigForBranchBlock, defaultNodeConfigForBranchBlock } from "@/domain/workflow"; +import { + type Expr, + type ExprComparisonOperator, + type ExprLogicalOperator, + ExprType, + type ExprValueType, + type WorkflowNodeConfigForBranchBlock, + defaultNodeConfigForBranchBlock, +} from "@/domain/workflow"; + import { useAntdForm } from "@/hooks"; import { NodeFormContextProvider } from "./_context"; @@ -34,11 +43,13 @@ const BranchBlockNodeConfigForm = ({ node, ...props }: BranchBlockNodeConfigForm try { await exprEditorRef.current!.validate(); } catch { - ctx.addIssue({ - code: "custom", - message: t("workflow_node.branch_block.form.expression.errmsg.invalid"), - path: ["expression"], - }); + if (!ctx.issues.some((issue) => issue.path?.[0] === "expression")) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.branch_block.form.expression.errmsg.invalid"), + path: ["expression"], + }); + } } } }); @@ -79,10 +90,51 @@ const getInitialValues = (): Nullish>> => { }; const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => { - const { t: _ } = i18n; + const { t } = i18n; + + const exprSchema: z.ZodType = z.lazy(() => + z.discriminatedUnion("type", [ + z.object({ + type: z.literal(ExprType.Constant), + value: z.string(), + valueType: z.string(), + }), + + z.object({ + type: z.literal(ExprType.Variant), + selector: z.object({ + id: z.string(), + name: z.string(), + type: z.string(), + }), + }), + + z.object({ + type: z.literal(ExprType.Comparison), + operator: z.string(), + left: exprSchema, + right: exprSchema, + }), + + z.object({ + type: z.literal(ExprType.Logical), + operator: z.string(), + left: exprSchema, + right: exprSchema, + }), + + z.object({ + type: z.literal(ExprType.Not), + expr: exprSchema, + }), + ]) + ); return z.object({ - expression: z.any().nullish(), + expression: z + .any() + .nullish() + .refine((v) => v == null || exprSchema.safeParse(v).success, t("workflow_node.branch_block.form.expression.errmsg.invalid")), }); }; diff --git a/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigFormExpressionEditor.tsx b/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigFormExpressionEditor.tsx index 53a7474b..4240bd03 100644 --- a/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigFormExpressionEditor.tsx +++ b/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigFormExpressionEditor.tsx @@ -6,8 +6,15 @@ import { useControllableValue } from "ahooks"; import { Button, Form, Input, Radio, Select, theme } from "antd"; import Show from "@/components/Show"; -import type { Expr, ExprComparisonOperator, ExprLogicalOperator, ExprValue, ExprValueSelector, ExprValueType } from "@/domain/workflow"; -import { ExprType } from "@/domain/workflow"; +import { + type Expr, + type ExprComparisonOperator, + type ExprLogicalOperator, + ExprType, + type ExprValue, + type ExprValueSelector, + type ExprValueType, +} from "@/domain/workflow"; import { useAntdFormName } from "@/hooks"; import { useNodeFormContext } from "./_context"; @@ -146,7 +153,7 @@ const BranchBlockNodeConfigFormExpressionEditor = forwardRef(); - const formName = useAntdFormName({ form: formInst, name: "workflowNodeConditionConfigFormExpressionEditorForm" }); + const formName = useAntdFormName({ form: formInst, name: "workflowNodeBranchBlockConfigFormExpressionEditorForm" }); const [formModel, setFormModel] = useState(getInitialValues()); useEffect(() => { diff --git a/ui/src/components/workflow/designer/nodes/BizDeployNodeRegistry.tsx b/ui/src/components/workflow/designer/nodes/BizDeployNodeRegistry.tsx index a731a7b7..cd36aad7 100644 --- a/ui/src/components/workflow/designer/nodes/BizDeployNodeRegistry.tsx +++ b/ui/src/components/workflow/designer/nodes/BizDeployNodeRegistry.tsx @@ -39,7 +39,10 @@ export const BizDeployNodeRegistry: NodeRegistry = { } }, ["config.certificateOutputNodeId"]: ({ value, context: { node } }) => { - if (!getAllPreviousNodes(node).includes(value)) { + if (value == null) return; + + const prevNodeIds = getAllPreviousNodes(node).map((e) => e.id); + if (!prevNodeIds.includes(value)) { return { message: "Invalid input", level: FeedbackLevel.Error, diff --git a/ui/src/components/workflow/designer/nodes/ConditionNode.tsx b/ui/src/components/workflow/designer/nodes/ConditionNode.tsx index c8920a02..ebb12e44 100644 --- a/ui/src/components/workflow/designer/nodes/ConditionNode.tsx +++ b/ui/src/components/workflow/designer/nodes/ConditionNode.tsx @@ -5,6 +5,7 @@ import { Typography } from "antd"; import { type Expr, ExprType, newNode } from "@/domain/workflow"; +import { getAllPreviousNodes } from "../_util"; import { BaseNode, BranchNode } from "./_shared"; import { NodeKindType, type NodeRegistry, NodeType } from "./typings"; import BranchBlockNodeConfigForm from "../forms/BranchBlockNodeConfigForm"; @@ -72,6 +73,38 @@ export const BranchBlockNodeRegistry: NodeRegistry = { }; } }, + ["config.expression"]: ({ value, context: { node } }) => { + if (value == null) return; + + const prevNodeIds = getAllPreviousNodes(node).map((e) => e.id); + const deepValidate = (expr: Expr) => { + if ("selector" in expr) { + if (!prevNodeIds.includes(expr.selector.id)) { + return false; + } + } + + if ("left" in expr) { + if (!deepValidate(expr.left)) { + return false; + } + } + + if ("right" in expr) { + if (!deepValidate(expr.right)) { + return false; + } + } + + return true; + }; + if (!deepValidate(value)) { + return { + message: "Invalid input", + level: FeedbackLevel.Error, + }; + } + }, }, render: () => {