fix: implemented cycle check on isValidConnection (#8716)

* Implemented cycle check on isValidConnection

* Implemented loop check if the loop done results in a loopcomponent loop
This commit is contained in:
Lucas Oliveira 2025-06-26 11:13:11 -03:00 • committed by GitHub
commit 39af1ded5f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -329,10 +329,11 @@ export function unselectAllNodesEdges(nodes: Node[], edges: Edge[]) {
} }
export function isValidConnection( export function isValidConnection(
{ source, target, sourceHandle, targetHandle }: Connection, connection: Connection,
nodes?: AllNodeType[], nodes?: AllNodeType[],
edges?: EdgeType[], edges?: EdgeType[],
): boolean { ): boolean {
const { source, target, sourceHandle, targetHandle } = connection;
if (source === target) { if (source === target) {
return false; return false;
} }
@ -342,6 +343,33 @@ export function isValidConnection(
const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!); const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!);
const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!); const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!);
// Helper to find the edge between two nodes
function findEdgeBetween(srcId: string, tgtId: string) {
return edgesArray.find((e) => e.source === srcId && e.target === tgtId);
}
// Modified hasCycle to return the path of edges forming the loop
const findCyclePath = (
node: AllNodeType,
visited = new Set(),
path: EdgeType[] = [],
): EdgeType[] | null => {
if (visited.has(node.id)) return null;
visited.add(node.id);
for (const outgoer of getOutgoers(node, nodesArray, edgesArray)) {
const edge = findEdgeBetween(node.id, outgoer.id);
if (!edge) continue;
if (outgoer.id === source) {
// This edge would close the loop
return [...path, edge];
}
const result = findCyclePath(outgoer, visited, [...path, edge]);
if (result) return result;
}
return null;
};
if ( if (
targetHandleObject.inputTypes?.some( targetHandleObject.inputTypes?.some(
(n) => n === sourceHandleObject.dataType, (n) => n === sourceHandleObject.dataType,
@ -359,22 +387,43 @@ export function isValidConnection(
t === targetHandleObject.type, t === targetHandleObject.type,
) )
) { ) {
let targetNode = nodesArray.find((node) => node.id === target!)?.data?.node; let targetNode = nodesArray.find((node) => node.id === target!);
if (!targetNode) { let targetNodeDataNode = targetNode?.data?.node;
if (!edgesArray.find((e) => e.targetHandle === targetHandle)) { if (
(!targetNodeDataNode &&
!edgesArray.find((e) => e.targetHandle === targetHandle)) ||
(targetNodeDataNode &&
targetHandleObject.output_types &&
!edgesArray.find((e) => e.targetHandle === targetHandle)) ||
(targetNodeDataNode &&
!targetHandleObject.output_types &&
((!targetNodeDataNode.template[targetHandleObject.fieldName].list &&
!edgesArray.find((e) => e.targetHandle === targetHandle)) ||
targetNodeDataNode.template[targetHandleObject.fieldName].list))
) {
// If the current target handle is a loop component, allow connection immediately
if (targetHandleObject.output_types) {
return true; return true;
} }
} else if ( // Check for loop and if any edge in the loop is a loop component
targetHandleObject.output_types && let cyclePath: EdgeType[] | null = null;
!edgesArray.find((e) => e.targetHandle === targetHandle) if (targetNode) {
) { cyclePath = findCyclePath(targetNode);
return true; }
} else if ( if (cyclePath) {
!targetHandleObject.output_types && // Check if any edge in the cycle path is a loop component
((!targetNode.template[targetHandleObject.fieldName].list && const hasLoopComponent = cyclePath.some((edge) => {
!edgesArray.find((e) => e.targetHandle === targetHandle)) || try {
targetNode.template[targetHandleObject.fieldName].list) const th = scapeJSONParse(edge.targetHandle!);
) { return !!th.output_types;
} catch {
return false;
}
});
if (!hasLoopComponent) {
return false;
}
}
return true; return true;
} }
} }