Cleanup and remove old logic when importing identical subflows

This commit is contained in:
GogoVega 2024-06-17 23:00:06 +02:00
parent 507038947d
commit 4f20cd5c0a
No known key found for this signature in database
GPG Key ID: E1E048B63AC5AC2B

View File

@ -1386,7 +1386,7 @@ RED.nodes = (function() {
var wires = links.filter(function(d) { return d.source === p });
for (var i=0;i<wires.length;i++) {
var w = wires[i];
if (w.target.type != "subflow") {
if (w?.target && w.target.type != "subflow") {
nIn.wires.push({id:w.target.id})
}
}
@ -1716,7 +1716,7 @@ RED.nodes = (function() {
// Remove the old subflow definition - but leave the instances in place
var removalResult = RED.subflow.removeSubflow(n.id, true);
// Create the list of nodes for the new subflow def
var subflowNodes = [n].concat(zMap[n.id]);
var subflowNodes = [n].concat(zMap[n.id]).filter((s) => !!s);
// Import the new subflow - no clashes should occur as we've removed
// the old version
var result = importNodes(subflowNodes);
@ -1801,6 +1801,30 @@ RED.nodes = (function() {
return unknownTypes;
}
function emitExistingNodesNotification(existingNodes, importedNodes) {
const errorMessage = RED._("clipboard.importDuplicate", { count: existingNodes.length });
const maxItemCount = 5; // Max 5 items in the list
const nodeList = $("<ul>");
let itemCount = 0;
for (const { existing, imported } of existingNodes) {
if (itemCount >= maxItemCount) { break; }
const conflictType = (imported.type !== existing.type) ? " | " + imported.type : "";
$("<li>").text(existing.id + " [ " + existing.type + conflictType + " ]").appendTo(nodeList);
itemCount++;
}
if (existingNodes.length > maxItemCount) {
$("<li>").text(RED._("deploy.confirm.plusNMore", { count: (existingNodes.length - maxItemCount) })).appendTo(nodeList);
}
const wrapper = $("<p>").append(nodeList);
const existingNodesError = new Error(errorMessage + wrapper.html());
existingNodesError.code = "import_conflict";
existingNodesError.importConfig = identifyImportConflicts(importedNodes);
throw existingNodesError;
}
function copyConfigNode(configNode, def, options = {}) {
const newNode = {
id: configNode.id,
@ -1906,8 +1930,8 @@ RED.nodes = (function() {
newNode.h = 0;
} else if (node.type.substring(0, 7) === "subflow") {
newNode.name = node.name;
newNode.inputs = node.inputs;
newNode.outputs = node.outputs;
newNode.inputs = node.inputs ?? 0;
newNode.outputs = node.outputs ?? 0;
newNode.env = node.env;
} else {
newNode._config.x = node.x;
@ -2029,44 +2053,27 @@ RED.nodes = (function() {
// If some nodes already exists, ask the user for each node to choose
// between copying it, replacing it or not importing it.
// NOTE: Stops the import here - throws an error.
if (existingNodes.length) {
const errorMessage = RED._("clipboard.importDuplicate", { count: existingNodes.length });
const maxItemCount = 5; // Max 5 items in the list
const nodeList = $("<ul>");
let itemCount = 0;
for (let { existing, imported } of existingNodes) {
if (itemCount >= maxItemCount) { break; }
const conflictType = (imported.type !== existing.type) ? " | " + imported.type : "";
$("<li>").text(existing.id + " [ " + existing.type + conflictType + " ]").appendTo(nodeList);
itemCount++;
}
if (existingNodes.length > maxItemCount) {
$("<li>").text(RED._("deploy.confirm.plusNMore", { count: (existingNodes.length - maxItemCount) })).appendTo(nodeList);
}
const wrapper = $("<p>").append(nodeList);
const existingNodesError = new Error(errorMessage + wrapper.html());
existingNodesError.code = "import_conflict";
existingNodesError.importConfig = identifyImportConflicts(originalNodes);
throw existingNodesError;
emitExistingNodesNotification(existingNodes, originalNodes);
}
// NOTE: activeWorkspace can be equal to 0 if it's the initial load
let activeWorkspace = RED.workspaces.active();
const activeSubflow = getSubflow(activeWorkspace);
for (const node of originalNodes) {
const group = /^subflow:(.+)$/.exec(node.type);
if (group) {
const subflowId = group[1];
// NOTE: activeWorkspace can be equal to 0 if it's the initial load
if (activeSubflow) {
if (activeSubflow) {
for (const node of originalNodes) {
const group = /^subflow:(.+)$/.exec(node.type);
if (group) {
const subflowId = group[1];
let error;
if (subflowId === activeSubflow.id) {
error = new Error(RED._("notification.errors.cannotAddSubflowToItself"));
} else if (subflowContains(subflowId, activeSubflow.id)) {
error = new Error(RED._("notification.errors.cannotAddCircularReference"));
}
if (error) {
// TODO: standardise error codes
error.code = "NODE_RED";
@ -2089,53 +2096,13 @@ RED.nodes = (function() {
initialLoad = JSON.parse(JSON.stringify(originalNodes));
}
// Identifies unknown nodes and can emit a notification to warn the user.
const unknownTypes = identifyUnknowType(originalNodes, { emitNotification: !isInitialLoad });
const nodeZmap = {};
let recoveryWorkspace = null;
// If it's the initial load, create a recovery workspace if any nodes don't have `node.z` and assign to it.
for (const node of originalNodes) {
if (node.z) {
nodeZmap[node.z] = nodeZmap[node.z] || [];
nodeZmap[node.z].push(node);
} else if (isInitialLoad && node.hasOwnProperty("x") && node.hasOwnProperty("y")) {
// Hit the rare issue where node z values get set to 0.
// Repair the flow - but we really need to track that down.
if (!recoveryWorkspace) {
recoveryWorkspace = {
id: getID(),
type: "tab",
disabled: false,
label: RED._("clipboard.recoveredNodes"),
info: RED._("clipboard.recoveredNodesInfo"),
env: []
};
addWorkspace(recoveryWorkspace);
RED.workspaces.add(recoveryWorkspace);
nodeZmap[recoveryWorkspace.id] = [];
}
node.z = recoveryWorkspace.id;
nodeZmap[node.z].push(node);
}
}
const newWorkspaces = [];
if (recoveryWorkspace) {
newWorkspaces.push(recoveryWorkspace);
const notification = RED.notify(RED._("clipboard.recoveredNodesNotification", { flowName: RED._("clipboard.recoveredNodes") }), {
type: "warning",
fixed: true,
buttons: [
{ text: RED._("common.label.close"), click: function () { notification.close(); } }
]
});
}
const subflowMap = {};
const workspaceMap = {};
const newSubflows = [];
const subflowDenyList = {};
const newWorkspaces = [];
// Find all tabs and subflow tabs and add it to workspace
// NOTE: Subflow tab not the instance
for (const node of originalNodes) {
@ -2163,43 +2130,32 @@ RED.nodes = (function() {
newWorkspaces.push(node);
RED.workspaces.add(node);
} else if (node.type === "subflow") {
let matchingSubflow;
// TODO: Si l'id est différent on devrait l'importer
if (!options.importMap[node.id]) {
matchingSubflow = checkForMatchingSubflow(node, nodeZmap[node.id]);
console.log("SUBFLOW", matchingSubflow)
node.in.forEach(function(input, i) {
input.type = "subflow";
input.direction = "in";
input.z = node.id;
input.i = i;
input.id = getID();
});
node.out.forEach(function(output, i) {
output.type = "subflow";
output.direction = "out";
output.z = node.id;
output.i = i;
output.id = getID();
});
if (node.status) {
node.status.type = "subflow";
node.status.direction = "status";
node.status.z = node.id;
node.status.id = getID();
}
if (matchingSubflow) {
subflowDenyList[node.id] = matchingSubflow;
} else {
node.in.forEach(function(input, i) {
input.type = "subflow";
input.direction = "in";
input.z = node.id;
input.i = i;
input.id = getID();
});
node.out.forEach(function(output, i) {
output.type = "subflow";
output.direction = "out";
output.z = node.id;
output.i = i;
output.id = getID();
});
if (node.status) {
node.status.type = "subflow";
node.status.direction = "status";
node.status.z = node.id;
node.status.id = getID();
}
if (createNewIds || options.importMap[node.id] === "copy") {
node.id = getID();
}
subflowMap[oldId] = node;
newSubflows.push(node);
addSubflow(node, (createNewIds || options.importMap[node.id] === "copy"));
if (createNewIds || options.importMap[node.id] === "copy") {
node.id = getID();
}
subflowMap[oldId] = node;
newSubflows.push(node);
addSubflow(node, (createNewIds || options.importMap[node.id] === "copy"));
}
}
@ -2212,11 +2168,90 @@ RED.nodes = (function() {
activeWorkspace = defaultWorkspace.id;
}
let newWorkspace = null;
let recoveryWorkspace = null;
// Correct or update the z property of each node
for (const node of originalNodes) {
const isConfigNode = !node.x && !node.y;
// If it's the initial load, create a recovery workspace if any nodes don't have `node.z` and assign to it.
if (!node.z && isInitialLoad && node.hasOwnProperty("x") && node.hasOwnProperty("y")) {
// Hit the rare issue where node z values get set to 0.
// Repair the flow - but we really need to track that down.
if (!recoveryWorkspace) {
recoveryWorkspace = {
id: getID(),
type: "tab",
disabled: false,
label: RED._("clipboard.recoveredNodes"),
info: RED._("clipboard.recoveredNodesInfo"),
env: []
};
}
node.z = recoveryWorkspace.id;
continue;
}
// TODO: remove workspace in next release+1
if (node.type === "workspace" || node.type === "tab" || node.type === "subflow") { continue; }
// Fix `node.z` for not found/new one case
if (createNewIds || options.importMap[node.id] === "copy") {
// Config Node can have an undefined `node.z`
if (!isConfigNode || (isConfigNode && node.z)) {
if (subflowMap[node.z]) {
node.z = subflowMap[node.z].id;
} else {
node.z = workspaceMap[node.z];
if (!workspaces[node.z]) {
node.z = activeWorkspace;
if (createNewWorkspace) {
if (newWorkspace === null) {
newWorkspace = RED.workspaces.add(null, true);
newWorkspaces.push(newWorkspace);
}
node.z = newWorkspace.id;
}
}
}
}
} else {
const keepNodesCurrentZ = reimport && node.z && (RED.workspaces.contains(node.z) || RED.nodes.subflow(node.z));
if (isConfigNode && !keepNodesCurrentZ && node.z && !workspaceMap[node.z] && !subflowMap[node.z]) {
node.z = activeWorkspace;
} else if (!isConfigNode && !keepNodesCurrentZ && (node.z == null || (!workspaceMap[node.z] && !subflowMap[node.z]))) {
node.z = activeWorkspace;
if (createNewWorkspace) {
if (!newWorkspace) {
newWorkspace = RED.workspaces.add(null,true);
newWorkspaces.push(newWorkspace);
}
node.z = newWorkspace.id;
}
}
}
}
// Add the recovery tab if used and warn the user
if (recoveryWorkspace) {
addWorkspace(recoveryWorkspace);
RED.workspaces.add(recoveryWorkspace);
// Put the recovery workspace at the first position
newWorkspaces.splice(0, 1, recoveryWorkspace, newWorkspaces[0]);
const notification = RED.notify(RED._("clipboard.recoveredNodesNotification", { flowName: RED._("clipboard.recoveredNodes") }), {
type: "warning",
fixed: true,
buttons: [
{ text: RED._("common.label.close"), click: function () { notification.close(); } }
]
});
}
const nodeMap = {};
const newNodes = [];
const newGroups = [];
const newJunctions = [];
let newWorkspace = null;
// Find all Config Nodes, Groups, Junctions, Nodes and Subflow instances and add them
// NOTE: Replaced Config Nodes and Subflow instances no longer appear below
for (const node of originalNodes) {
@ -2257,53 +2292,12 @@ RED.nodes = (function() {
const isConfigNode = def?.category === "config";
// Fix `node.z` for undefined/not found/new one case
if (createNewIds || options.importMap[oldId] === "copy") {
// Config Node can have an undefined `node.z`
if (!isConfigNode || (isConfigNode && node.z)) {
if (subflowDenyList[node.z]) {
continue;
} else if (subflowMap[node.z]) {
node.z = subflowMap[node.z].id;
} else {
node.z = workspaceMap[node.z];
if (!workspaces[node.z]) {
if (createNewWorkspace) {
if (newWorkspace === null) {
newWorkspace = RED.workspaces.add(null, true);
newWorkspaces.push(newWorkspace);
}
node.z = newWorkspace.id;
} else {
node.z = activeWorkspace;
}
}
}
}
} else {
const keepNodesCurrentZ = reimport && node.z && (RED.workspaces.contains(node.z) || RED.nodes.subflow(node.z));
if (isConfigNode && !keepNodesCurrentZ && node.z && !workspaceMap[node.z] && !subflowMap[node.z]) {
node.z = activeWorkspace;
} else if (!isConfigNode && !keepNodesCurrentZ && (node.z == null || (!workspaceMap[node.z] && !subflowMap[node.z]))) {
if (createNewWorkspace) {
if (newWorkspace === null) {
newWorkspace = RED.workspaces.add(null,true);
newWorkspaces.push(newWorkspace);
}
node.z = newWorkspace.id;
} else {
node.z = activeWorkspace;
}
}
}
// Update the node definition for subflow instance
if (!isUnknownNode && node.type.substring(0, 7) === "subflow") {
const parentId = node.type.split(":")[1];
const subflow = subflowDenyList[parentId] || subflowMap[parentId] || getSubflow(parentId);
const subflow = subflowMap[parentId] || getSubflow(parentId);
if (subflowDenyList[parentId] || createNewIds || options.importMap[node.id] === "copy") {
if (createNewIds || options.importMap[node.id] === "copy") {
node.type = "subflow:" + subflow.id;
def = registry.getNodeType(node.type);
}
@ -2312,36 +2306,10 @@ RED.nodes = (function() {
node.outputs = subflow.out.length;
}
// Now the properties have been corrected, copy the node properties:
// Now the properties have been fixed, copy the node properties:
// Config Node
if (isConfigNode) {
// TODO: A quoi sert ce bloc ? Replace?
let existingConfigNode;
if (createNewIds || options.importMap[oldId] === "copy") {
if (options.importMap[oldId] !== "copy") {
existingConfigNode = RED.nodes.node(oldId);
if (existingConfigNode) {
if (node.z && existingConfigNode.z !== node.z) {
existingConfigNode = null;
// Check the config nodes on n.z
// TODO: pourquoi utiliser z au lieu de l'id ?
for (var cn in configNodes) {
if (configNodes.hasOwnProperty(cn)) {
if (configNodes[cn].z === node.z && compareNodes(configNodes[cn], node, false)) {
existingConfigNode = configNodes[cn];
nodeMap[oldId] = configNodes[cn];
break;
}
}
}
}
}
}
}
if (!existingConfigNode || existingConfigNode._def.exclusive) { //} || !compareNodes(existingConfigNode,n,true) || existingConfigNode.z !== n.z) {
nodeMap[oldId] = copyConfigNode(node, def, options);
}
nodeMap[oldId] = copyConfigNode(node, def, options);
// Node, Group, Junction or Subflow
} else {
nodeMap[oldId] = copyNode(node, def, options);
@ -2385,7 +2353,7 @@ RED.nodes = (function() {
}
});
allNodes.eachNode(function (node) {
// The event will be triggered after the import
// TODO: The event will be triggered after the import - Quid?
updateConfigNodeUsers(node, { emitEvent: false });
});
@ -2397,7 +2365,6 @@ RED.nodes = (function() {
const wires = Array.isArray(wiresGroup) ? wiresGroup : [wiresGroup];
wires.forEach(function (wire) {
// Skip if the wire is clinked to a non-existent node
// TODO: Add warning message?
if (!nodeMap.hasOwnProperty(wire)) { return; }
if (node.z === nodeMap[wire].z) {
const link = { source: node, sourcePort: node.wires.indexOf(wiresGroup), target: nodeMap[wire] };
@ -2438,6 +2405,7 @@ RED.nodes = (function() {
const n = nodeMap[id];
if (n) {
if (n._def.category === "config") {
// TODO: addNode calls updateConfigNodeUsers so remove here
if (n.users.indexOf(node) === -1) {
n.users.push(node);
}
@ -2454,6 +2422,7 @@ RED.nodes = (function() {
// Add Links to Workspace
for (const subflow of newSubflows) {
// TODO: Handled missing node
subflow.in.forEach(function (input) {
input.wires.forEach(function (wire) {
const link = { source: input, sourcePort: 0, target: nodeMap[wire.id] };