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 bcd1f8c5a..c639b1bca 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 @@ -1038,23 +1038,34 @@ RED.nodes = (function() { return {nodes:removedNodes,links:removedLinks, groups: removedGroups, junctions: removedJunctions}; } + /** + * Add a Subflow to the Workspace + * + * @param {object} sf The Subflow to add. + * @param {boolean|undefined} createNewIds Whether to update the name. + */ function addSubflow(sf, createNewIds) { if (createNewIds) { - var subflowNames = Object.keys(subflows).map(function(sfid) { - return subflows[sfid].name; - }); + // Update the Subflow name to highlight that this is a copy + const subflowNames = Object.keys(subflows).map(function (sfid) { + return subflows[sfid].name || ""; + }) + subflowNames.sort() - subflowNames.sort(); - var copyNumber = 1; - var subflowName = sf.name; + let copyNumber = 1; + let subflowName = sf.name; subflowNames.forEach(function(name) { if (subflowName == name) { + subflowName = sf.name + " (" + copyNumber + ")"; copyNumber++; - subflowName = sf.name+" ("+copyNumber+")"; } }); + sf.name = subflowName; } + + sf.instances = []; + subflows[sf.id] = sf; allNodes.addTab(sf.id); linkTabMap[sf.id] = []; @@ -1107,7 +1118,7 @@ RED.nodes = (function() { module: "node-red" } }); - sf.instances = []; + sf._def = RED.nodes.getType("subflow:"+sf.id); RED.events.emit("subflows:add",sf); } @@ -1749,7 +1760,8 @@ 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]); + // Need to sort the list in order to remove missing nodes + 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); @@ -2029,6 +2041,8 @@ RED.nodes = (function() { if (matchingSubflow) { subflow_denylist[n.id] = matchingSubflow; } else { + const oldId = n.id; + subflow_map[n.id] = n; if (createNewIds || options.importMap[n.id] === "copy") { nid = getID(); @@ -2056,7 +2070,7 @@ RED.nodes = (function() { n.status.id = getID(); } new_subflows.push(n); - addSubflow(n,createNewIds || options.importMap[n.id] === "copy"); + addSubflow(n,createNewIds || options.importMap[oldId] === "copy"); } } } @@ -2176,7 +2190,7 @@ RED.nodes = (function() { x:parseFloat(n.x || 0), y:parseFloat(n.y || 0), z:n.z, - type:0, + type: n.type, info: n.info, changed:false, _config:{} @@ -2237,7 +2251,6 @@ RED.nodes = (function() { } } } - node.type = n.type; node._def = def; if (node.type === "group") { node._def = RED.group.def; @@ -2267,6 +2280,15 @@ RED.nodes = (function() { outputs: n.outputs|| (n.wires && n.wires.length) || 0, set: registry.getNodeSet("node-red/unknown") } + var orig = {}; + for (var p in n) { + if (n.hasOwnProperty(p) && p!="x" && p!="y" && p!="z" && p!="id" && p!="wires") { + orig[p] = n[p]; + } + } + node._orig = orig; + node.name = n.type; + node.type = "unknown"; } else { if (subflow_denylist[parentId] || createNewIds || options.importMap[n.id] === "copy") { parentId = subflow.id; @@ -2464,9 +2486,11 @@ RED.nodes = (function() { n = new_subflows[i]; n.in.forEach(function(input) { input.wires.forEach(function(wire) { - var link = {source:input, sourcePort:0, target:node_map[wire.id]}; - addLink(link); - new_links.push(link); + if (node_map.hasOwnProperty(wire.id)) { + var link = {source:input, sourcePort:0, target:node_map[wire.id]}; + addLink(link); + new_links.push(link); + } }); delete input.wires; }); @@ -2475,11 +2499,13 @@ RED.nodes = (function() { var link; if (subflow_map[wire.id] && subflow_map[wire.id].id == n.id) { link = {source:n.in[wire.port], sourcePort:wire.port,target:output}; - } else { + } else if (node_map.hasOwnProperty(wire.id) || subflow_map.hasOwnProperty(wire.id)) { link = {source:node_map[wire.id]||subflow_map[wire.id], sourcePort:wire.port,target:output}; } - addLink(link); - new_links.push(link); + if (link) { + addLink(link); + new_links.push(link); + } }); delete output.wires; }); @@ -2488,11 +2514,13 @@ RED.nodes = (function() { var link; if (subflow_map[wire.id] && subflow_map[wire.id].id == n.id) { link = {source:n.in[wire.port], sourcePort:wire.port,target:n.status}; - } else { + } else if (node_map.hasOwnProperty(wire.id) || subflow_map.hasOwnProperty(wire.id)) { link = {source:node_map[wire.id]||subflow_map[wire.id], sourcePort:wire.port,target:n.status}; } - addLink(link); - new_links.push(link); + if (link) { + addLink(link); + new_links.push(link); + } }); delete n.status.wires; } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index ff0091e65..245b6db75 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -288,7 +288,7 @@ RED.view = (function() { } selectedLinks.clearUnselected() }, - length: () => groups.length, + length: () => groups.size, forEach: (func) => { groups.forEach(func) }, toArray: () => [...groups], clear: function () { @@ -2689,22 +2689,21 @@ RED.view = (function() { addToRemovedLinks(reconnectResult.removedLinks) } - var startDirty = RED.nodes.dirty(); - var startChanged = false; - var selectedGroups = []; + const startDirty = RED.nodes.dirty(); + let movingSelectedGroups = []; if (movingSet.length() > 0) { for (var i=0;i=0; i--) { - var g = selectedGroups[i]; + for (i = movingSelectedGroups.length-1; i>=0; i--) { + var g = movingSelectedGroups[i]; removedGroups.push(g); RED.nodes.removeGroup(g); } diff --git a/packages/node_modules/@node-red/nodes/core/function/89-delay.js b/packages/node_modules/@node-red/nodes/core/function/89-delay.js index b0fc2ed86..8f9964115 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-delay.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-delay.js @@ -253,7 +253,13 @@ module.exports = function(RED) { if (node.allowrate && m.hasOwnProperty("rate") && !isNaN(parseFloat(m.rate))) { node.rate = m.rate; } - send(m); + if (msg.hasOwnProperty("reset")) { + if (msg.hasOwnProperty("flush")) { + node.buffer.push({msg: m, send: send, done: done}); + } + } + else { send(m); } + node.reportDepth(); node.intervalID = setInterval(sendMsgFromBuffer, node.rate); done(); @@ -303,7 +309,8 @@ module.exports = function(RED) { node.droppedMsgs++; } } - } else { + } + else { if (node.allowrate && msg.hasOwnProperty("rate") && !isNaN(parseFloat(msg.rate))) { node.rate = msg.rate; } diff --git a/packages/node_modules/@node-red/runtime/lib/flows/util.js b/packages/node_modules/@node-red/runtime/lib/flows/util.js index fa25a26d0..6b7f659b9 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/util.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/util.js @@ -113,6 +113,10 @@ async function evaluateEnvProperties(flow, env, credentials) { resolve() }); })) + } else if (type === "conf-type" && /^\${[^}]+}$/.test(value)) { + // Get the config node from the parent subflow + const name = value.substring(2, value.length - 1); + value = flow.getSetting(name); } else { try { value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null); diff --git a/test/nodes/core/function/89-delay_spec.js b/test/nodes/core/function/89-delay_spec.js index 46b0037bc..4fbf3df54 100644 --- a/test/nodes/core/function/89-delay_spec.js +++ b/test/nodes/core/function/89-delay_spec.js @@ -1009,6 +1009,29 @@ describe('delay Node', function() { }); }); + it('sending a msg with reset to empty queue doesnt send anything', function(done) { + this.timeout(2000); + var flow = [{"id":"delayNode1","type":"delay","name":"delayNode","pauseType":"rate","timeout":1,"timeoutUnits":"seconds","rate":2,"rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"wires":[["helperNode1"]]}, + {id:"helperNode1", type:"helper", wires:[]}]; + helper.load(delayNode, flow, function() { + var delayNode1 = helper.getNode("delayNode1"); + var helperNode1 = helper.getNode("helperNode1"); + var t = Date.now(); + var c = 0; + helperNode1.on("input", function(msg) { + console.log("Shold not get here") + done(e); + }); + + setTimeout( function() { + if (c === 0) { done(); } + }, 250); + + // send test messages + delayNode1.receive({payload:1,topic:"foo",reset:true}); // send something with blank topic + }); + }); + /* Messaging API support */ function mapiDoneTestHelper(done, pauseType, drop, msgAndTimings) { const completeNode = require("nr-test-utils").require("@node-red/nodes/core/common/24-complete.js");