mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Cleanup and remove old logic when importing identical subflows
This commit is contained in:
parent
507038947d
commit
4f20cd5c0a
@ -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] };
|
||||
|
Loading…
x
Reference in New Issue
Block a user