From ab2d3bfd8006d4c5889b020b81a37b4eb2374286 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Sep 2021 10:43:41 +0100 Subject: [PATCH 1/6] Fix RED.nodes.filterNodes when searching full node set --- .../@node-red/editor-client/src/js/nodes.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 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 da3bdb729..a19830709 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 @@ -448,12 +448,18 @@ RED.nodes = (function() { doZFilter = true; } } + var objectLookup = false; if (searchSet === null) { - searchSet = nodes; + searchSet = Object.keys(nodes); + objectLookup = true; } + for (var n=0;n Date: Wed, 29 Sep 2021 10:44:09 +0100 Subject: [PATCH 2/6] Add 'radio' option to treeList --- .../src/js/ui/common/treeList.js | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js index 19d65c591..baab6acda 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js @@ -41,6 +41,7 @@ * sublabel: 'Local', // a sub-label for the item * icon: 'fa fa-rocket', // (optional) icon for the item * checkbox: true/false, // (optional) if present, display checkbox accordingly + * radio: 'group-name', // (optional) if present, display radio box - using group-name to set radio group * selected: true/false, // (optional) whether the item is selected or not * children: [] | function(done,item) // (optional) an array of child items, or a function * // that will call the `done` callback with an array @@ -640,6 +641,41 @@ } } selectWrapper.appendTo(label) + } else if (item.radio) { + var selectWrapper = $(''); + var cb = $('').prop('name', item.radio).prop('checked',item.selected).appendTo(selectWrapper); + cb.on('click', function(e) { + e.stopPropagation(); + }); + cb.on('change', function(e) { + item.selected = this.checked; + that._selected.forEach(function(selectedItem) { + if (selectedItem.radio === item.radio) { + selectedItem.treeList.label.removeClass("selected"); + selectedItem.selected = false; + that._selected.delete(selectedItem); + } + }) + if (item.selected) { + that._selected.add(item); + } else { + that._selected.delete(item); + } + label.toggleClass("selected",this.checked); + that._trigger("select",e,item); + }) + if (!item.children) { + label.on("click", function(e) { + e.stopPropagation(); + cb.trigger("click"); + }) + } + item.treeList.select = function(v) { + if (v !== item.selected) { + cb.trigger("click"); + } + } + selectWrapper.appendTo(label) } else { label.on("click", function(e) { if (!that.options.multi) { From b01fd24e153c1ada17d9644d984538bfa0d4c3b2 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Sep 2021 10:45:00 +0100 Subject: [PATCH 3/6] Add link-call node and add return mode for link-out node --- .../editor-client/src/js/ui/editor.js | 2 +- .../src/js/ui/editors/panes/appearance.js | 17 +-- .../editor-client/src/js/ui/view-tools.js | 6 +- .../@node-red/editor-client/src/js/ui/view.js | 8 +- .../src/sass/ui/common/treeList.scss | 3 +- .../@node-red/nodes/core/common/60-link.html | 110 +++++++++++++++--- .../@node-red/nodes/core/common/60-link.js | 63 +++++++++- .../@node-red/nodes/icons/link-call.svg | 5 + .../@node-red/nodes/icons/link-return.svg | 5 + .../nodes/locales/en-US/messages.json | 8 +- 10 files changed, 192 insertions(+), 35 deletions(-) create mode 100644 packages/node_modules/@node-red/nodes/icons/link-call.svg create mode 100644 packages/node_modules/@node-red/nodes/icons/link-return.svg diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index b6c11e5f3..8dd0ee0d5 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -151,7 +151,7 @@ RED.editor = (function() { valid = definition[property].hasOwnProperty("required") && !definition[property].required; } else { var configNode = RED.nodes.node(value); - valid = (configNode !== null && (configNode.valid == null || configNode.valid)); + valid = (configNode && (configNode.valid == null || configNode.valid)); } } return valid; diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js index e910ede04..046141b52 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js @@ -93,17 +93,20 @@ } + var showLabel = node._def.hasOwnProperty("showLabel")?node._def.showLabel:true; + if (!$("#node-input-show-label").prop('checked')) { // Not checked - hide label - if (!/^link (in|out)$/.test(node.type)) { - // Not a link node - default state is true + + if (showLabel) { + // Default to show label if (node.l !== false) { editState.changes.l = node.l editState.changed = true; } node.l = false; } else { - // A link node - default state is false + // Node has showLabel:false (eg link nodes) if (node.hasOwnProperty('l') && node.l) { editState.changes.l = node.l editState.changed = true; @@ -112,8 +115,8 @@ } } else { // Checked - show label - if (!/^link (in|out)$/.test(node.type)) { - // Not a link node - default state is true + if (showLabel) { + // Default to show label if (node.hasOwnProperty('l') && !node.l) { editState.changes.l = node.l editState.changed = true; @@ -204,8 +207,8 @@ }) if (!node.hasOwnProperty("l")) { - // Show label if type not link - node.l = !/^link (in|out)$/.test(node._def.type); + // Show label unless def.showLabel set to false + node.l = node._def.hasOwnProperty("showLabel")?node._def.showLabel:true; } $("#node-input-show-label").prop("checked",node.l).trigger("change"); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index 2fba37e88..9b3977448 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -159,15 +159,15 @@ RED.view.tools = (function() { nodes.forEach(function(n) { var modified = false; var oldValue = n.l === undefined?true:n.l; - var isLink = /^link (in|out)$/.test(n._def.type); + var showLabel = n._def.hasOwnProperty("showLabel")?n._def.showLabel:true; if (labelShown) { - if (n.l === false || (isLink && !n.hasOwnProperty('l'))) { + if (n.l === false || (!showLabel && !n.hasOwnProperty('l'))) { n.l = true; modified = true; } } else { - if ((!isLink && (!n.hasOwnProperty('l') || n.l === true)) || (isLink && n.l === true) ) { + if ((showLabel && (!n.hasOwnProperty('l') || n.l === true)) || (!showLabel && n.l === true) ) { n.l = false; modified = true; } 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 8ccbf6fe4..42455055b 100755 --- 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 @@ -413,7 +413,7 @@ RED.view = (function() { var nn = result.node; var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label"); - if (showLabel !== undefined && !/^link (in|out)$/.test(nn._def.type) && !nn._def.defaults.hasOwnProperty("l")) { + if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) { nn.l = showLabel; } @@ -1088,7 +1088,7 @@ RED.view = (function() { nn.x = point[0]; nn.y = point[1]; var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label"); - if (showLabel !== undefined && !/^link (in|out)$/.test(nn._def.type) && !nn._def.defaults.hasOwnProperty("l")) { + if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) { nn.l = showLabel; } if (quickAddLink) { @@ -1991,7 +1991,7 @@ RED.view = (function() { activeLinkNodes = {}; for (var i=0;i + + diff --git a/packages/node_modules/@node-red/nodes/core/common/60-link.js b/packages/node_modules/@node-red/nodes/core/common/60-link.js index 5470ed434..939191fbf 100644 --- a/packages/node_modules/@node-red/nodes/core/common/60-link.js +++ b/packages/node_modules/@node-red/nodes/core/common/60-link.js @@ -17,6 +17,8 @@ module.exports = function(RED) { "use strict"; + const crypto = require("crypto"); + function LinkInNode(n) { RED.nodes.createNode(this,n); var node = this; @@ -40,13 +42,70 @@ module.exports = function(RED) { function LinkOutNode(n) { RED.nodes.createNode(this,n); var node = this; + var mode = n.mode || "link"; + var event = "node:"+n.id; this.on("input", function(msg, send, done) { msg._event = event; RED.events.emit(event,msg) - send(msg); - done(); + + if (mode === "return") { + if (Array.isArray(msg._linkSource) && msg._linkSource.length > 0) { + var messageEvent = msg._linkSource.pop(); + var returnNode = RED.nodes.getNode(messageEvent.node); + if (returnNode && returnNode.returnLinkMessage) { + returnNode.returnLinkMessage(messageEvent.id, msg); + } else { + node.warn("Return target not a link-call node") + } + } else { + node.warn("No call return target") + } + done(); + } else if (mode === "link") { + send(msg); + done(); + } }); } RED.nodes.registerType("link out",LinkOutNode); + + + function LinkCallNode(n) { + RED.nodes.createNode(this,n); + const node = this; + const target = n.links[0]; + const messageEvents = {}; + + this.on("input", function(msg, send, done) { + msg._linkSource = msg._linkSource || []; + const messageEvent = { + id: crypto.randomBytes(14).toString('hex'), + node: node.id, + } + messageEvents[messageEvent.id] = { send, done }; + msg._linkSource.push(messageEvent); + var targetNode = RED.nodes.getNode(target); + if (targetNode) { + targetNode.receive(msg); + } + }); + + this.returnLinkMessage = function(eventId, msg) { + if (Array.isArray(msg._linkSource) && msg._linkSource.length === 0) { + delete msg._linkSource; + } + const messageEvent = messageEvents[eventId]; + if (messageEvent) { + delete messageEvents[eventId]; + messageEvent.send(msg); + messageEvent.done(); + } else { + node.warn("Unrecognised message returned") + } + } + } + RED.nodes.registerType("link call",LinkCallNode); + + } diff --git a/packages/node_modules/@node-red/nodes/icons/link-call.svg b/packages/node_modules/@node-red/nodes/icons/link-call.svg new file mode 100644 index 000000000..a3dcbdd22 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/icons/link-call.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/node_modules/@node-red/nodes/icons/link-return.svg b/packages/node_modules/@node-red/nodes/icons/link-return.svg new file mode 100644 index 000000000..e82ee92f6 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/icons/link-return.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index a2e63a046..d9d94ef59 100755 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -159,7 +159,13 @@ }, "link": { "linkIn": "link in", - "linkOut": "link out" + "linkOut": "link out", + "linkCall": "link call", + "linkOutReturn": "link return", + "outMode": "Mode", + "sendToAll": "Send to all connected link nodes", + "returnToCaller": "Return to calling link node" + }, "tls": { "tls": "TLS configuration", From 1931395fdbe5a0ea81283a5cf425bcf399b067b6 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Sep 2021 13:49:55 +0100 Subject: [PATCH 4/6] Add basic link-call/return tests --- .../@node-red/nodes/core/common/60-link.js | 6 +- .../nodes/locales/en-US/messages.json | 6 +- test/nodes/core/common/60-link_spec.js | 65 +++++++++++++++++++ 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/common/60-link.js b/packages/node_modules/@node-red/nodes/core/common/60-link.js index 939191fbf..d5eda733f 100644 --- a/packages/node_modules/@node-red/nodes/core/common/60-link.js +++ b/packages/node_modules/@node-red/nodes/core/common/60-link.js @@ -56,10 +56,10 @@ module.exports = function(RED) { if (returnNode && returnNode.returnLinkMessage) { returnNode.returnLinkMessage(messageEvent.id, msg); } else { - node.warn("Return target not a link-call node") + node.warn(RED._("link.error.missingReturn")) } } else { - node.warn("No call return target") + node.warn(RED._("link.error.missingReturn")) } done(); } else if (mode === "link") { @@ -101,7 +101,7 @@ module.exports = function(RED) { messageEvent.send(msg); messageEvent.done(); } else { - node.warn("Unrecognised message returned") + node.send(msg); } } } diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index d9d94ef59..cbb344ed3 100755 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -164,8 +164,10 @@ "linkOutReturn": "link return", "outMode": "Mode", "sendToAll": "Send to all connected link nodes", - "returnToCaller": "Return to calling link node" - + "returnToCaller": "Return to calling link node", + "error": { + "missingReturn": "Missing return node information" + } }, "tls": { "tls": "TLS configuration", diff --git a/test/nodes/core/common/60-link_spec.js b/test/nodes/core/common/60-link_spec.js index 50072d761..5314eed15 100644 --- a/test/nodes/core/common/60-link_spec.js +++ b/test/nodes/core/common/60-link_spec.js @@ -119,4 +119,69 @@ describe('link Node', function() { }); }); + describe("link-call node", function() { + it('should call link-in node and get response', function(done) { + var flow = [{id:"link-in-1", type:"link in", wires: [[ "func"]]}, + {id:"func", type:"helper", wires: [["link-out-1"]]}, + {id:"link-out-1", type:"link out", mode: "return"}, + {id:"link-call", type:"link call", links:["link-in-1"], wires:[["n4"]]}, + {id:"n4", type:"helper"} ]; + helper.load(linkNode, flow, function() { + var func = helper.getNode("func"); + func.on("input", function(msg, send, done) { + msg.payload = "123"; + send(msg); + done(); + }) + var n1 = helper.getNode("link-call"); + var n4 = helper.getNode("n4"); + n4.on("input", function(msg) { + try { + msg.should.have.property('payload', '123'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"hello"}); + }); + }) + }); + + it('should allow nested link-call flows', function(done) { + var flow = [/** Multiply by 2 link flow **/ + {id:"li1", type:"link in", wires: [[ "m2"]]}, + {id:"m2", type:"helper", wires: [["lo1"]]}, + {id:"lo1", type:"link out", mode: "return"}, + /** Multiply by 3 link flow **/ + {id:"li2", type:"link in", wires: [[ "m3"]]}, + {id:"m3", type:"helper", wires: [["lo2"]]}, + {id:"lo2", type:"link out", mode: "return"}, + /** Multiply by 6 link flow **/ + {id:"li3", type:"link in", wires: [[ "link-call-1"]]}, + {id:"link-call-1", type:"link call", links:["m2"], wires:[["link-call-2"]]}, + {id:"link-call-2", type:"link call", links:["m3"], wires:[["lo3"]]}, + {id:"lo3", type:"link out", mode: "return"}, + /** Test Flow Entry **/ + {id:"link-call", type:"link call", links:["li3"], wires:[["n4"]]}, + {id:"n4", type:"helper"} ]; + helper.load(linkNode, flow, function() { + var m2 = helper.getNode("m2"); + m2.on("input", function(msg, send, done) { msg.payload *= 2 ; send(msg); done(); }) + var m3 = helper.getNode("m3"); + m3.on("input", function(msg, send, done) { msg.payload *= 3 ; send(msg); done(); }) + + var n1 = helper.getNode("link-call"); + var n4 = helper.getNode("n4"); + n4.on("input", function(msg) { + try { + msg.should.have.property('payload', 24); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:4}); + }); + }) }); From dfd9364061d29e9a4bf2e20e9abecb8b7a4fc237 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Sep 2021 14:28:12 +0100 Subject: [PATCH 5/6] Add timeout option to link-call node --- .../@node-red/nodes/core/common/60-link.html | 7 +++++- .../@node-red/nodes/core/common/60-link.js | 23 ++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/common/60-link.html b/packages/node_modules/@node-red/nodes/core/common/60-link.html index 8f65ad935..05fa8b429 100644 --- a/packages/node_modules/@node-red/nodes/core/common/60-link.html +++ b/packages/node_modules/@node-red/nodes/core/common/60-link.html @@ -28,6 +28,10 @@ +
+ + +
@@ -240,7 +244,8 @@ color:"#ddd",//"#87D8CF", defaults: { name: {value:""}, - links: { value: [], type:"link in[]"} + links: { value: [], type:"link in[]"}, + timeout: { value: "30", validate:RED.validators.number(true) } }, inputs: 1, outputs: 1, diff --git a/packages/node_modules/@node-red/nodes/core/common/60-link.js b/packages/node_modules/@node-red/nodes/core/common/60-link.js index d5eda733f..53404e446 100644 --- a/packages/node_modules/@node-red/nodes/core/common/60-link.js +++ b/packages/node_modules/@node-red/nodes/core/common/60-link.js @@ -76,6 +76,10 @@ module.exports = function(RED) { const node = this; const target = n.links[0]; const messageEvents = {}; + let timeout = parseFloat(n.timeout || "30")*1000; + if (isNaN(timeout)) { + timeout = 30000; + } this.on("input", function(msg, send, done) { msg._linkSource = msg._linkSource || []; @@ -83,7 +87,14 @@ module.exports = function(RED) { id: crypto.randomBytes(14).toString('hex'), node: node.id, } - messageEvents[messageEvent.id] = { send, done }; + messageEvents[messageEvent.id] = { + msg: RED.util.cloneMessage(msg), + send, + done, + ts: setTimeout(function() { + timeoutMessage(messageEvent.id) + }, timeout ) + }; msg._linkSource.push(messageEvent); var targetNode = RED.nodes.getNode(target); if (targetNode) { @@ -97,6 +108,7 @@ module.exports = function(RED) { } const messageEvent = messageEvents[eventId]; if (messageEvent) { + clearTimeout(messageEvent.ts); delete messageEvents[eventId]; messageEvent.send(msg); messageEvent.done(); @@ -104,6 +116,15 @@ module.exports = function(RED) { node.send(msg); } } + + function timeoutMessage(eventId) { + const messageEvent = messageEvents[eventId]; + if (messageEvent) { + delete messageEvents[eventId]; + node.error("timeout",messageEvent.msg); + } + } + } RED.nodes.registerType("link call",LinkCallNode); From f1cbca8d768ebb05b35c564e56f503a8933ba9ac Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Sep 2021 14:46:09 +0100 Subject: [PATCH 6/6] Add link-call help --- .../nodes/locales/en-US/common/60-link.html | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/common/60-link.html b/packages/node_modules/@node-red/nodes/locales/en-US/common/60-link.html index 3d986ee9f..6bd6305f1 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/common/60-link.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/common/60-link.html @@ -28,10 +28,23 @@ + +