Files
Automata/packages/core/src/PartialExecutionUtils/findSubgraph.ts

114 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { INode } from 'n8n-workflow';
import type { GraphConnection } from './DirectedGraph';
import { DirectedGraph } from './DirectedGraph';
function findSubgraphRecursive(
graph: DirectedGraph,
destinationNode: INode,
current: INode,
trigger: INode,
newGraph: DirectedGraph,
currentBranch: GraphConnection[],
) {
// If the current node is the chosen trigger keep this branch.
if (current === trigger) {
for (const connection of currentBranch) {
newGraph.addNodes(connection.from, connection.to);
newGraph.addConnection(connection);
}
return;
}
let parentConnections = graph.getDirectParents(current);
// If the current node has no parents, dont keep this branch.
if (parentConnections.length === 0) {
return;
}
// If the current node is the destination node again, dont keep this branch.
const isCycleWithDestinationNode =
current === destinationNode && currentBranch.some((c) => c.to === destinationNode);
if (isCycleWithDestinationNode) {
return;
}
// If the current node was already visited, keep this branch.
const isCycleWithCurrentNode = currentBranch.some((c) => c.to === current);
if (isCycleWithCurrentNode) {
// TODO: write function that adds nodes when adding connections
for (const connection of currentBranch) {
newGraph.addNodes(connection.from, connection.to);
newGraph.addConnection(connection);
}
return;
}
// If the current node is disabled, dont keep this node, but keep the
// branch.
// Take every incoming connection and connect it to every node that is
// connected to the current nodes first output
if (current.disabled) {
const incomingConnections = graph.getDirectParents(current);
const outgoingConnections = graph
.getDirectChildren(current)
// NOTE: When a node is disabled only the first output gets data
.filter((connection) => connection.outputIndex === 0);
parentConnections = [];
for (const incomingConnection of incomingConnections) {
for (const outgoingConnection of outgoingConnections) {
const newConnection = {
...incomingConnection,
to: outgoingConnection.to,
inputIndex: outgoingConnection.inputIndex,
};
parentConnections.push(newConnection);
currentBranch.pop();
currentBranch.push(newConnection);
}
}
}
// Recurse on each parent.
for (const parentConnection of parentConnections) {
findSubgraphRecursive(graph, destinationNode, parentConnection.from, trigger, newGraph, [
...currentBranch,
parentConnection,
]);
}
}
/**
* Find all nodes that can lead from the trigger to the destination node,
* ignoring disabled nodes.
*
* The algorithm is:
* Start with Destination Node
*
* 1. if the current node is the chosen trigger keep this branch
* 2. if the current node has no parents, dont keep this branch
* 3. if the current node is the destination node again, dont keep this
* branch
* 4. if the current node was already visited, keep this branch
* 5. if the current node is disabled, dont keep this node, but keep the
* branch
* - take every incoming connection and connect it to every node that is
* connected to the current nodes first output
* 6. Recurse on each parent
*/
export function findSubgraph(
graph: DirectedGraph,
destinationNode: INode,
trigger: INode,
): DirectedGraph {
const newGraph = new DirectedGraph();
findSubgraphRecursive(graph, destinationNode, destinationNode, trigger, newGraph, []);
return newGraph;
}