Spaces:
Build error
Build error
File size: 4,787 Bytes
d9494a5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | import { isDefined } from '@/utils';
import { WorkflowActionType } from '@/workflow/types/WorkflowActionType';
import {
type IfElseStepInput,
type IteratorStepInput,
type ValidatableWorkflow,
type ValidatableWorkflowStep,
type WorkflowValidationIssue,
} from '@/workflow/validation/types/workflow-validation.type';
import { type WorkflowGraph } from '@/workflow/validation/utils/build-workflow-graph.util';
import { getStepInput } from '@/workflow/validation/utils/get-step-outgoing-step-ids.util';
export const validateWorkflowGraph = ({
workflow,
graph,
}: {
workflow: ValidatableWorkflow;
graph: WorkflowGraph;
}): WorkflowValidationIssue[] => {
const issues: WorkflowValidationIssue[] = [];
const steps = workflow.steps ?? [];
const stepIds = new Set(steps.map((step) => step.id));
const seenStepIds = new Set<string>();
for (const step of steps) {
if (seenStepIds.has(step.id)) {
issues.push({
severity: 'error',
code: 'DUPLICATE_STEP_ID',
message: `Duplicate step id "${step.id}". Every step id must be unique within the workflow.`,
stepId: step.id,
});
}
seenStepIds.add(step.id);
}
const triggerNextStepIds = (workflow.trigger?.nextStepIds ?? []).filter(
isDefined,
);
if (steps.length > 0 && triggerNextStepIds.length === 0) {
issues.push({
severity: 'error',
code: 'TRIGGER_HAS_NO_NEXT_STEP',
message: `The trigger is not connected to any step. The trigger must have a "nextStepIds" array pointing to the first step (e.g. nextStepIds: ["${steps[0].id}"]). If you used edges, also set trigger.nextStepIds.`,
});
}
for (const triggerNextStepId of triggerNextStepIds) {
if (!stepIds.has(triggerNextStepId)) {
issues.push({
severity: 'error',
code: 'DANGLING_REFERENCE',
message: `The trigger references a non-existent step "${triggerNextStepId}".`,
});
}
}
for (const step of steps) {
for (const outgoingStepId of graph.childrenByStepId.get(step.id) ?? []) {
if (!stepIds.has(outgoingStepId)) {
issues.push({
severity: 'error',
code: 'DANGLING_REFERENCE',
message: `Step "${step.name ?? step.id}" references a non-existent step "${outgoingStepId}".`,
stepId: step.id,
});
}
}
issues.push(...validateBranchingStep(step));
}
for (const step of steps) {
if (!graph.reachableFromTrigger.has(step.id)) {
issues.push({
severity: 'error',
code: 'UNREACHABLE_STEP',
message: `Step "${step.name ?? step.id}" is not reachable from the trigger. Ensure a chain of nextStepIds connects the trigger to this step. Check that the preceding step includes this step's id ("${step.id}") in its nextStepIds array.`,
stepId: step.id,
});
}
}
return issues;
};
const validateBranchingStep = (
step: ValidatableWorkflowStep,
): WorkflowValidationIssue[] => {
const issues: WorkflowValidationIssue[] = [];
const input = getStepInput(step);
if (step.type === WorkflowActionType.IF_ELSE) {
const branchList = (input as Partial<IfElseStepInput> | undefined)
?.branches;
const branches = Array.isArray(branchList) ? branchList : [];
if (branches.length < 2) {
issues.push({
severity: 'warning',
code: 'IF_ELSE_INSUFFICIENT_BRANCHES',
message: `If/Else step "${step.name ?? step.id}" should have at least two branches (a condition branch and an else branch).`,
stepId: step.id,
});
}
for (const branch of branches) {
const branchNextStepIds = branch?.nextStepIds;
if (!Array.isArray(branchNextStepIds) || branchNextStepIds.length === 0) {
issues.push({
severity: 'error',
code: 'IF_ELSE_BRANCH_HAS_NO_NEXT_STEP',
message: `A branch of If/Else step "${step.name ?? step.id}" is not connected to any step.`,
stepId: step.id,
});
}
}
}
if (step.type === WorkflowActionType.ITERATOR) {
const iteratorInput = input as Partial<IteratorStepInput> | undefined;
const items = iteratorInput?.items;
const hasConfiguredItems =
(typeof items === 'string' && items.length > 0) ||
(Array.isArray(items) && items.length > 0);
const initialLoopStepIds = iteratorInput?.initialLoopStepIds;
const hasLoopBody =
Array.isArray(initialLoopStepIds) && initialLoopStepIds.length > 0;
if (hasConfiguredItems && !hasLoopBody) {
issues.push({
severity: 'error',
code: 'ITERATOR_MISSING_LOOP_BODY',
message: `Iterator step "${step.name ?? step.id}" has items to iterate over but no steps inside the loop.`,
stepId: step.id,
});
}
}
return issues;
};
|