Initial commit of function importNodes refactoring

This commit is contained in:
GogoVega 2024-06-07 15:09:48 +02:00
parent 61b12f6bbe
commit b495c18694
No known key found for this signature in database
GPG Key ID: E1E048B63AC5AC2B

View File

@ -1764,6 +1764,33 @@ RED.nodes = (function() {
} }
function identifyUnknowType(nodes, options = {}) {
const unknownTypes = [];
for (let node of nodes) {
// TODO: remove workspace in next release+1
const knowTypes = ["workspace", "tab", "subflow", "group", "junction"];
if (!knowTypes.includes(node.type) &&
node.type.substring(0, 8) != "subflow:" &&
!registry.getNodeType(node.type) &&
!unknownTypes.includes(node.type)) {
unknownTypes.push(node.type);
}
}
if (options.emitNotification && unknownTypes.length) {
const typeList = $("<ul>");
unknownTypes.forEach(function(type) {
$("<li>").text(type).appendTo(typeList);
});
RED.notify(
"<p>" + RED._("clipboard.importUnrecognised", {count: unknownTypes.length }) + "</p>" + typeList[0].outerHTML,
"error", false, 10000);
}
return unknownTypes;
}
/** /**
* Options: * Options:
* - generateIds - whether to replace all node ids * - generateIds - whether to replace all node ids
@ -1776,119 +1803,131 @@ RED.nodes = (function() {
* - id:copy - import with new id * - id:copy - import with new id
* - id:replace - import over the top of existing * - id:replace - import over the top of existing
*/ */
function importNodes(newNodesObj,options) { // createNewIds,createMissingWorkspace) { function importNodes(newNodes, options) {
const defOpts = { generateIds: false, addFlow: false, markChanged: false, reimport: false, importMap: {} } const defaultOptions = { generateIds: false, addFlow: false, markChanged: false, reimport: false, importMap: {} };
options = Object.assign({}, defOpts, options) options = Object.assign({}, defaultOptions, options);
options.importMap = options.importMap || {}
const createNewIds = options.generateIds; const createNewIds = options.generateIds;
const reimport = (!createNewIds && !!options.reimport)
const createMissingWorkspace = options.addFlow; const createMissingWorkspace = options.addFlow;
var i; const reimport = (!createNewIds && !!options.reimport);
var n;
var newNodes; // Checks and converts the flow into an Array if necessary
var nodeZmap = {}; if (typeof newNodes === "string") {
var recoveryWorkspace; if (newNodes === "") { return; }
if (typeof newNodesObj === "string") {
if (newNodesObj === "") {
return;
}
try { try {
newNodes = JSON.parse(newNodesObj); newNodes = JSON.parse(newNodes);
} catch(err) { } catch(err) {
var e = new Error(RED._("clipboard.invalidFlow",{message:err.message})); const error = new Error(RED._("clipboard.invalidFlow", { message: err.message }));
e.code = "NODE_RED"; error.code = "NODE_RED";
throw e; throw error;
} }
} else {
newNodes = newNodesObj;
} }
if (!Array.isArray(newNodes)) { if (!Array.isArray(newNodes)) {
newNodes = [newNodes]; newNodes = [newNodes];
} }
// Scan for any duplicate nodes and remove them. This is a temporary const seenIds = new Set();
// fix to help resolve corrupted flows caused by 0.20.0 where multiple const existingNodes = [];
// copies of the flow would get loaded at the same time. const nodesToReplace = [];
// If the user hit deploy they would have saved those duplicates. // Checks if the imported flow contains duplicates or existing nodes.
var seenIds = {}; newNodes = newNodes.filter(function(node) {
var existingNodes = []; const id = node.id;
var nodesToReplace = [];
newNodes = newNodes.filter(function(n) { // This is a temporary fix to help resolve corrupted flows caused by 0.20.0 where multiple
var id = n.id; // copies of the flow would get loaded at the same time.
if (seenIds[n.id]) { // NOTE: Generally it is the last occurrence which is kept but not here.
return false; if (seenIds.has(id)) { return false; }
} seenIds.add(id);
seenIds[n.id] = true;
if (!options.generateIds) { // Checks if the node already exists - the user will choose between copying the node or replacing it
if (!createNewIds) {
if (!options.importMap[id]) { if (!options.importMap[id]) {
// No conflict resolution for this node // No conflict resolution for this node
var existing = allNodes.getNode(id) || configNodes[id] || workspaces[id] || subflows[id] || groups[id] || junctions[id]; const existingNode = allNodes.getNode(id) || configNodes[id] || workspaces[id] || subflows[id] || groups[id] || junctions[id];
if (existing) { if (existingNode) {
existingNodes.push({existing:existing, imported:n}); existingNodes.push({ existing: existingNode, imported: node });
} }
} else if (options.importMap[id] === "replace") { } else if (options.importMap[id] === "replace") {
nodesToReplace.push(n); nodesToReplace.push(node);
return false; return false;
} }
} }
return true; return true;
}) });
if (existingNodes.length > 0) { // If some nodes already exists, ask the user for each node to choose between copying it or replacing it
var errorMessage = RED._("clipboard.importDuplicate",{count:existingNodes.length}); if (existingNodes.length) {
var nodeList = $("<ul>"); const errorMessage = RED._("clipboard.importDuplicate",{ count: existingNodes.length });
var existingNodesCount = Math.min(5,existingNodes.length); const maxItemCount = 5; // Max 5 items in the list
for (var i=0;i<existingNodesCount;i++) { const nodeList = $("<ul>");
var conflict = existingNodes[i];
$("<li>").text(
conflict.existing.id+
" [ "+conflict.existing.type+ ((conflict.imported.type !== conflict.existing.type)?" | "+conflict.imported.type:"")+" ]").appendTo(nodeList)
}
if (existingNodesCount !== existingNodes.length) {
$("<li>").text(RED._("deploy.confirm.plusNMore",{count:existingNodes.length-existingNodesCount})).appendTo(nodeList)
}
var wrapper = $("<p>").append(nodeList);
var existingNodesError = new Error(errorMessage+wrapper.html()); 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.code = "import_conflict";
existingNodesError.importConfig = identifyImportConflicts(newNodes); existingNodesError.importConfig = identifyImportConflicts(newNodes);
throw existingNodesError; throw existingNodesError;
} }
var removedNodes;
if (nodesToReplace.length > 0) { // TODO: check the z of the subflow instance and check _that_ if it exists
var replaceResult = replaceNodes(nodesToReplace); // TODO: Handled activeWorkspace = 0
removedNodes = replaceResult.removedNodes; let activeWorkspace = RED.workspaces.active();
const activeSubflow = getSubflow(activeWorkspace);
for (let node of newNodes) {
const group = /^subflow:(.+)$/.exec(node.type);
if (group) {
const subflowId = group[1];
if (activeSubflow) {
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";
throw error;
}
}
}
} }
const removedNodes = [];
// Now that the user has made his choice, replace the nodes that need to be replaced.
if (nodesToReplace.length > 0) {
const result = replaceNodes(nodesToReplace);
removedNodes.concat(result.removedNodes);
}
var isInitialLoad = false; let isInitialLoad = false;
if (!initialLoad) { if (!initialLoad) {
isInitialLoad = true; isInitialLoad = true;
initialLoad = JSON.parse(JSON.stringify(newNodes)); initialLoad = JSON.parse(JSON.stringify(newNodes));
} }
var unknownTypes = [];
for (i=0;i<newNodes.length;i++) { const unknownTypes = identifyUnknowType(newNodes, { emitNotification: isInitialLoad });
n = newNodes[i];
var id = n.id; const nodeZmap = {};
// TODO: remove workspace in next release+1 let recoveryWorkspace;
if (n.type != "workspace" && for (let node of newNodes) {
n.type != "tab" && if (node.z) {
n.type != "subflow" && nodeZmap[node.z] = nodeZmap[node.z] || [];
n.type != "group" && nodeZmap[node.z].push(node);
n.type != 'junction' && } else if (isInitialLoad && node.hasOwnProperty('x') && node.hasOwnProperty('y') && !node.z) {
!registry.getNodeType(n.type) &&
n.type.substring(0,8) != "subflow:" &&
unknownTypes.indexOf(n.type)==-1) {
unknownTypes.push(n.type);
}
if (n.z) {
nodeZmap[n.z] = nodeZmap[n.z] || [];
nodeZmap[n.z].push(n);
} else if (isInitialLoad && n.hasOwnProperty('x') && n.hasOwnProperty('y') && !n.z) {
// Hit the rare issue where node z values get set to 0. // Hit the rare issue where node z values get set to 0.
// Repair the flow - but we really need to track that down. // Repair the flow - but we really need to track that down.
if (!recoveryWorkspace) { if (!recoveryWorkspace) {
@ -1899,50 +1938,21 @@ RED.nodes = (function() {
label: RED._("clipboard.recoveredNodes"), label: RED._("clipboard.recoveredNodes"),
info: RED._("clipboard.recoveredNodesInfo"), info: RED._("clipboard.recoveredNodesInfo"),
env: [] env: []
} };
addWorkspace(recoveryWorkspace); addWorkspace(recoveryWorkspace);
RED.workspaces.add(recoveryWorkspace); RED.workspaces.add(recoveryWorkspace);
nodeZmap[recoveryWorkspace.id] = []; nodeZmap[recoveryWorkspace.id] = [];
} }
n.z = recoveryWorkspace.id; node.z = recoveryWorkspace.id;
nodeZmap[recoveryWorkspace.id].push(n); nodeZmap[recoveryWorkspace.id].push(node);
}
}
if (!isInitialLoad && unknownTypes.length > 0) {
var typeList = $("<ul>");
unknownTypes.forEach(function(t) {
$("<li>").text(t).appendTo(typeList);
})
typeList = typeList[0].outerHTML;
RED.notify("<p>"+RED._("clipboard.importUnrecognised",{count:unknownTypes.length})+"</p>"+typeList,"error",false,10000);
}
var activeWorkspace = RED.workspaces.active();
//TODO: check the z of the subflow instance and check _that_ if it exists
var activeSubflow = getSubflow(activeWorkspace);
for (i=0;i<newNodes.length;i++) {
var m = /^subflow:(.+)$/.exec(newNodes[i].type);
if (m) {
var subflowId = m[1];
var parent = getSubflow(activeWorkspace);
if (parent) {
var err;
if (subflowId === parent.id) {
err = new Error(RED._("notification.errors.cannotAddSubflowToItself"));
}
if (subflowContains(subflowId,parent.id)) {
err = new Error(RED._("notification.errors.cannotAddCircularReference"));
}
if (err) {
// TODO: standardise error codes
err.code = "NODE_RED";
throw err;
}
}
} }
} }
var i;
var n;
var new_workspaces = []; var new_workspaces = [];
var workspace_map = {}; var workspace_map = {};
var new_subflows = []; var new_subflows = [];