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:
* - generateIds - whether to replace all node ids
@ -1776,119 +1803,131 @@ RED.nodes = (function() {
* - id:copy - import with new id
* - id:replace - import over the top of existing
*/
function importNodes(newNodesObj,options) { // createNewIds,createMissingWorkspace) {
const defOpts = { generateIds: false, addFlow: false, markChanged: false, reimport: false, importMap: {} }
options = Object.assign({}, defOpts, options)
options.importMap = options.importMap || {}
function importNodes(newNodes, options) {
const defaultOptions = { generateIds: false, addFlow: false, markChanged: false, reimport: false, importMap: {} };
options = Object.assign({}, defaultOptions, options);
const createNewIds = options.generateIds;
const reimport = (!createNewIds && !!options.reimport)
const createMissingWorkspace = options.addFlow;
var i;
var n;
var newNodes;
var nodeZmap = {};
var recoveryWorkspace;
if (typeof newNodesObj === "string") {
if (newNodesObj === "") {
return;
}
const reimport = (!createNewIds && !!options.reimport);
// Checks and converts the flow into an Array if necessary
if (typeof newNodes === "string") {
if (newNodes === "") { return; }
try {
newNodes = JSON.parse(newNodesObj);
newNodes = JSON.parse(newNodes);
} catch(err) {
var e = new Error(RED._("clipboard.invalidFlow",{message:err.message}));
e.code = "NODE_RED";
throw e;
const error = new Error(RED._("clipboard.invalidFlow", { message: err.message }));
error.code = "NODE_RED";
throw error;
}
} else {
newNodes = newNodesObj;
}
if (!Array.isArray(newNodes)) {
newNodes = [newNodes];
}
// Scan for any duplicate nodes and remove them. This is a temporary
// fix to help resolve corrupted flows caused by 0.20.0 where multiple
// copies of the flow would get loaded at the same time.
// If the user hit deploy they would have saved those duplicates.
var seenIds = {};
var existingNodes = [];
var nodesToReplace = [];
const seenIds = new Set();
const existingNodes = [];
const nodesToReplace = [];
// Checks if the imported flow contains duplicates or existing nodes.
newNodes = newNodes.filter(function(node) {
const id = node.id;
newNodes = newNodes.filter(function(n) {
var id = n.id;
if (seenIds[n.id]) {
return false;
}
seenIds[n.id] = true;
// This is a temporary fix to help resolve corrupted flows caused by 0.20.0 where multiple
// copies of the flow would get loaded at the same time.
// NOTE: Generally it is the last occurrence which is kept but not here.
if (seenIds.has(id)) { return false; }
seenIds.add(id);
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]) {
// No conflict resolution for this node
var existing = allNodes.getNode(id) || configNodes[id] || workspaces[id] || subflows[id] || groups[id] || junctions[id];
if (existing) {
existingNodes.push({existing:existing, imported:n});
const existingNode = allNodes.getNode(id) || configNodes[id] || workspaces[id] || subflows[id] || groups[id] || junctions[id];
if (existingNode) {
existingNodes.push({ existing: existingNode, imported: node });
}
} else if (options.importMap[id] === "replace") {
nodesToReplace.push(n);
nodesToReplace.push(node);
return false;
}
}
return true;
})
});
if (existingNodes.length > 0) {
var errorMessage = RED._("clipboard.importDuplicate",{count:existingNodes.length});
var nodeList = $("<ul>");
var existingNodesCount = Math.min(5,existingNodes.length);
for (var i=0;i<existingNodesCount;i++) {
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);
// If some nodes already exists, ask the user for each node to choose between copying it or replacing it
if (existingNodes.length) {
const errorMessage = RED._("clipboard.importDuplicate",{ count: existingNodes.length });
const maxItemCount = 5; // Max 5 items in the list
const nodeList = $("<ul>");
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.importConfig = identifyImportConflicts(newNodes);
throw existingNodesError;
}
var removedNodes;
if (nodesToReplace.length > 0) {
var replaceResult = replaceNodes(nodesToReplace);
removedNodes = replaceResult.removedNodes;
// TODO: check the z of the subflow instance and check _that_ if it exists
// TODO: Handled activeWorkspace = 0
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) {
isInitialLoad = true;
initialLoad = JSON.parse(JSON.stringify(newNodes));
}
var unknownTypes = [];
for (i=0;i<newNodes.length;i++) {
n = newNodes[i];
var id = n.id;
// TODO: remove workspace in next release+1
if (n.type != "workspace" &&
n.type != "tab" &&
n.type != "subflow" &&
n.type != "group" &&
n.type != 'junction' &&
!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) {
const unknownTypes = identifyUnknowType(newNodes, { emitNotification: isInitialLoad });
const nodeZmap = {};
let recoveryWorkspace;
for (let node of newNodes) {
if (node.z) {
nodeZmap[node.z] = nodeZmap[node.z] || [];
nodeZmap[node.z].push(node);
} else if (isInitialLoad && node.hasOwnProperty('x') && node.hasOwnProperty('y') && !node.z) {
// 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) {
@ -1899,50 +1938,21 @@ RED.nodes = (function() {
label: RED._("clipboard.recoveredNodes"),
info: RED._("clipboard.recoveredNodesInfo"),
env: []
}
};
addWorkspace(recoveryWorkspace);
RED.workspaces.add(recoveryWorkspace);
nodeZmap[recoveryWorkspace.id] = [];
}
n.z = recoveryWorkspace.id;
nodeZmap[recoveryWorkspace.id].push(n);
}
}
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;
}
}
node.z = recoveryWorkspace.id;
nodeZmap[recoveryWorkspace.id].push(node);
}
}
var i;
var n;
var new_workspaces = [];
var workspace_map = {};
var new_subflows = [];