fix(ui): node validation error

This commit is contained in:
Fu Diwei 2025-08-18 15:48:38 +08:00
parent 089d66de04
commit 50136dd339
4 changed files with 107 additions and 12 deletions

View File

@ -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<z.infer<ReturnType<typeof getSchema>>> => {
};
const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }) => {
const { t: _ } = i18n;
const { t } = i18n;
const exprSchema: z.ZodType<Expr> = z.lazy(() =>
z.discriminatedUnion("type", [
z.object({
type: z.literal(ExprType.Constant),
value: z.string(),
valueType: z.string<ExprValueType>(),
}),
z.object({
type: z.literal(ExprType.Variant),
selector: z.object({
id: z.string(),
name: z.string(),
type: z.string<ExprValueType>(),
}),
}),
z.object({
type: z.literal(ExprType.Comparison),
operator: z.string<ExprComparisonOperator>(),
left: exprSchema,
right: exprSchema,
}),
z.object({
type: z.literal(ExprType.Logical),
operator: z.string<ExprLogicalOperator>(),
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")),
});
};

View File

@ -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<BranchBlockNodeConf
const { node } = useNodeFormContext();
const [formInst] = Form.useForm<ConditionFormValues>();
const formName = useAntdFormName({ form: formInst, name: "workflowNodeConditionConfigFormExpressionEditorForm" });
const formName = useAntdFormName({ form: formInst, name: "workflowNodeBranchBlockConfigFormExpressionEditorForm" });
const [formModel, setFormModel] = useState<ConditionFormValues>(getInitialValues());
useEffect(() => {

View File

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

View File

@ -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: () => {