From b495c1869423be16176431fc6d21849077b68907 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Fri, 7 Jun 2024 15:09:48 +0200 Subject: [PATCH 01/15] Initial commit of function `importNodes` refactoring --- .../@node-red/editor-client/src/js/nodes.js | 240 +++++++++--------- 1 file changed, 125 insertions(+), 115 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 54c89157a..e1086884e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -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 = $("
" + RED._("clipboard.importUnrecognised", {count: unknownTypes.length }) + "
" + 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 = $("").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 = $("
").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 "+RED._("clipboard.importUnrecognised",{count:unknownTypes.length})+"");
- unknownTypes.forEach(function(t) {
- $("