From a413f3cded4f4fe37de0726b4ac2ae2553ee42ba Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Sat, 26 Jan 2019 23:15:20 +0900 Subject: [PATCH] Add support of subflow env var --- .../editor-client/locales/en-US/editor.json | 3 +- .../editor-client/locales/ja/editor.json | 3 +- .../@node-red/editor-client/src/js/nodes.js | 7 +- .../editor-client/src/js/ui/editor.js | 135 +++++- .../editor-client/src/js/ui/subflow.js | 8 +- .../@node-red/nodes/core/core/80-function.js | 4 +- .../@node-red/nodes/core/logic/15-change.js | 2 +- .../@node-red/runtime/lib/nodes/Node.js | 19 + .../@node-red/runtime/lib/nodes/flows/Flow.js | 4 + .../runtime/lib/nodes/flows/Subflow.js | 72 +++- .../node_modules/@node-red/util/lib/util.js | 38 +- test/nodes/subflow/subflow_spec.js | 408 ++++++++++++++++++ .../runtime/lib/nodes/flows/Subflow_spec.js | 156 +++++++ 13 files changed, 835 insertions(+), 24 deletions(-) create mode 100644 test/nodes/subflow/subflow_spec.js diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index 1fff8b87e..a4a2a5485 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -901,6 +901,7 @@ "editor-tab": { "properties": "Properties", "description": "Description", - "appearance": "Appearance" + "appearance": "Appearance", + "env": "Env Var" } } diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index 23c957426..2d412cb0d 100755 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -901,6 +901,7 @@ "editor-tab": { "properties": "プロパティ", "description": "説明", - "appearance": "外観" + "appearance": "外観", + "env": "環境変数" } } 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 d9ef6ab44..0965cbdb9 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 @@ -358,7 +358,10 @@ RED.nodes = (function() { } subflows[sf.id] = sf; RED.nodes.registerType("subflow:"+sf.id, { - defaults:{name:{value:""}}, + defaults:{ + name:{value:""}, + env:{value:[]} + }, icon: function() { return sf.icon||"subflow.png" }, category: sf.category || "subflows", inputs: sf.in.length, @@ -535,6 +538,7 @@ RED.nodes = (function() { node.category = n.category; node.in = []; node.out = []; + node.env = n.env; n.in.forEach(function(p) { var nIn = {x:p.x,y:p.y,wires:[]}; @@ -1018,6 +1022,7 @@ RED.nodes = (function() { node.name = n.name; node.outputs = subflow.out.length; node.inputs = subflow.in.length; + node.env = n.env; } else { if (!node._def) { if (node.x && node.y) { 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 443f3844e..c69b4e216 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 @@ -540,7 +540,113 @@ RED.editor = (function() { return label; } - function buildEditForm(container,formId,type,ns) { + function buildEnvForm(container, node) { + var env_container = $('#node-input-env-container'); + env_container + .css('min-height','250px').css('min-width','450px') + .editableList({ + addItem: function(container, i, opt) { + var row = $('
').appendTo(container); + var nameField = $('', { + class: "node-input-env-name", + type: "text", + style: "margin-length: 5px; width: 32%;" + }).appendTo(row); + var valueField = $('',{ + class: "node-input-env-value", + type: "text", + style: "margin-left: 5px; width: 65%;" + }).appendTo(row) + valueField.typedInput({default:'str', + types:['str','num','bool','json','bin','re','date'] + }); + if (opt) { + nameField.val(opt.name); + valueField.typedInput('type', opt.type); + valueField.typedInput('value', opt.value); + } + }, + sortable: true, + removable: true + }); + var envs = node.env; + if (envs) { + for (var i = 0; i < envs.length; i++) { + var env = envs[i]; + env_container.editableList('addItem', env); + } + } + } + + function convEnv(editable_list) { + if (editable_list) { + var env = []; + editable_list.each(function(i) { + var entry = $(this); + var nf = entry.find(".node-input-env-name"); + var vf = entry.find(".node-input-env-value"); + var name = nf.val(); + var value = vf.typedInput("value"); + var type = vf.typedInput("type"); + var info = { + name: name, + label: "", + value: value, + type: type, + target_type: "env var", + target: name + }; + var item = { + name: name, + type: type, + value: value, + info: info + }; + env.push(item); + }); + return env; + } + return null; + } + + function isSameEnv(env0, env1) { + function isSameInfo(info0, info1) { + if (info0 && info1 && + (info0.name === info1.name) && + (info0.label === info1.label) && + (info0.value === info1.value) && + (info0.type === info1.type) && + (info0.target_type === info1.target_type) && + (info0.target === info1.target)) { + return true; + } + return false; + } + function inEnv(env, item) { + for(var i = 0; i < env.length; i++) { + var item1 = env[i]; + if ((item1.name !== item.name) || + (item1.type !== item.type) || + (item1.value !== item.value) || + !isSameInfo(item1.info, item.info)) { + return false; + } + } + return true; + } + if (env0 && env1 && (env0.length === env1.length)) { + for (var i = 0; i < env0.length; i++) { + var item0 = env0[i]; + if (!inEnv(env1, item0)) { + return false; + } + } + return true; + } + return false; + } + + function buildEditForm(container,formId,type,ns,node) { var dialogForm = $('
').appendTo(container); dialogForm.html($("script[data-template-name='"+type+"']").html()); ns = ns||"node-red"; @@ -561,6 +667,11 @@ RED.editor = (function() { } $(this).attr("data-i18n",keys.join(";")); }); + + if ((type === "subflow") || (type === "subflow-template")) { + buildEnvForm(dialogForm, node); + } + // Add dummy fields to prevent 'Enter' submitting the form in some // cases, and also prevent browser auto-fill of password // Add in reverse order as they are prepended... @@ -1268,6 +1379,16 @@ RED.editor = (function() { } } + if (type === "subflow") { + var old_env = editing_node.env; + var new_env = convEnv($("#node-input-env-container").editableList("items")); + if (!isSameEnv(old_env, new_env)) { + editing_node.env = new_env; + changes.env = editing_node.env; + changed = true; + } + } + if (changed) { var wasChanged = editing_node.changed; editing_node.changed = true; @@ -1373,7 +1494,7 @@ RED.editor = (function() { content: $('
', {class:"editor-tray-content"}).appendTo(editorContent).hide(), iconClass: "fa fa-cog" }; - buildEditForm(nodePropertiesTab.content,"dialog-form",type,ns); + buildEditForm(nodePropertiesTab.content,"dialog-form",type,ns,node); editorTabs.addTab(nodePropertiesTab); if (!node._def.defaults || !node._def.defaults.hasOwnProperty('info')) { @@ -1986,6 +2107,14 @@ RED.editor = (function() { changed = true; } + var old_env = editing_node.env; + var new_env = convEnv($("#node-input-env-container").editableList("items")); + if (!isSameEnv(old_env, new_env)) { + editing_node.env = new_env; + changes.env = editing_node.env; + changed = true; + } + RED.palette.refresh(); if (changed) { @@ -2063,7 +2192,7 @@ RED.editor = (function() { content: $('
', {class:"editor-tray-content"}).appendTo(editorContent).hide(), iconClass: "fa fa-cog" }; - buildEditForm(nodePropertiesTab.content,"dialog-form","subflow-template"); + buildEditForm(nodePropertiesTab.content,"dialog-form","subflow-template", undefined, editing_node); editorTabs.addTab(nodePropertiesTab); var descriptionTab = { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js index 2acbd88b5..3cfd51a5d 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js @@ -17,10 +17,16 @@ RED.subflow = (function() { - var _subflowEditTemplate = ''; + var _subflowEditTemplate = ''; var _subflowTemplateEditTemplate = ''; diff --git a/packages/node_modules/@node-red/nodes/core/core/80-function.js b/packages/node_modules/@node-red/nodes/core/core/80-function.js index 0e1efbdcd..e8454d79e 100644 --- a/packages/node_modules/@node-red/nodes/core/core/80-function.js +++ b/packages/node_modules/@node-red/nodes/core/core/80-function.js @@ -158,9 +158,7 @@ module.exports = function(RED) { }, env: { get: function(envVar) { - // For now, just return the env var. This will eventually - // also return project settings and subflow instance properties - return process.env[envVar] + return node.getenv(envVar); } }, setTimeout: function () { diff --git a/packages/node_modules/@node-red/nodes/core/logic/15-change.js b/packages/node_modules/@node-red/nodes/core/logic/15-change.js index 3faf5c7aa..8d100233e 100644 --- a/packages/node_modules/@node-red/nodes/core/logic/15-change.js +++ b/packages/node_modules/@node-red/nodes/core/logic/15-change.js @@ -94,7 +94,7 @@ module.exports = function(RED) { this.error(RED._("change.errors.invalid-expr",{error:e.message})); } } else if (rule.tot === 'env') { - rule.to = RED.util.evaluateNodeProperty(rule.to,'env'); + rule.to = RED.util.evaluateNodeProperty(rule.to,'env',node); } } diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/Node.js b/packages/node_modules/@node-red/runtime/lib/nodes/Node.js index a0825f7b1..02562ca99 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/Node.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/Node.js @@ -34,6 +34,9 @@ function Node(n) { if (n._alias) { this._alias = n._alias; } + if (n.env) { + this.env = n.env; + } if (n._flow) { // Make this a non-enumerable property as it may cause // circular references. Any existing code that tries to JSON serialise @@ -236,6 +239,22 @@ Node.prototype.receive = function(msg) { } }; +Node.prototype.getenv = function(name) { + var flow = this._flow; + if (flow) { + var val = flow.getSetting(name); + return val; + } + return undefined; +}; + +Node.prototype.setenv = function(name, val) { + var flow = this._flow; + if (flow) { + flow.setSetting(name, val); + } +}; + function log_helper(self, level, msg) { var o = { level: level, diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js index 20399f19d..8af2185ff 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js @@ -461,6 +461,10 @@ class Flow { } console.log("==================") } + + getenv(name) { + return process.env[name]; + } } /** diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js index cf393868e..883a47af0 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js @@ -22,6 +22,35 @@ const flowUtil = require("./util"); var Log; + +/** + * Convert env var definition of subflow template and instance to env var dic + * @param {Hash} env1 env var definition of template + * @param {Hash} env2 env var definition of instance + * @return {Hash} env var dic + */ +function env2Dic(env0, env1) { + var dic = {}; + function add(env) { + for(var i = 0; i < env.length; i++) { + var item = env[i]; + var name = item.name; + var info = Object.assign({}, item.info); + info.name = name; + dic[name] = item; + dic[name+"_type"] = { value: item.type }; + dic[name+"_info"] = { value: info }; + } + } + if (env0) { + add(env0); + } + if (env1) { + add(env1); + } + return dic; +} + /** * This class represents a subflow - which is handled as a special type of Flow */ @@ -104,12 +133,14 @@ class Subflow extends Flow { var self = this; // Create a subflow node to accept inbound messages and route appropriately var Node = require("../Node"); + var env = env2Dic(this.subflowDef.env, this.subflowInstance.env); var subflowInstanceConfig = { id: this.subflowInstance.id, type: this.subflowInstance.type, z: this.subflowInstance.z, name: this.subflowInstance.name, wires: [], + env: env, _flow: this } if (this.subflowDef.in) { @@ -164,7 +195,7 @@ class Subflow extends Flow { self.node._updateWires(subflowInstanceConfig.wires); } } - } + }; // Wire the subflow outputs if (this.subflowDef.out) { @@ -172,7 +203,7 @@ class Subflow extends Flow { for (var i=0;i'); expr._legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(value); diff --git a/test/nodes/subflow/subflow_spec.js b/test/nodes/subflow/subflow_spec.js new file mode 100644 index 000000000..86b7e33e0 --- /dev/null +++ b/test/nodes/subflow/subflow_spec.js @@ -0,0 +1,408 @@ +/** +* Copyright JS Foundation and other contributors, http://js.foundation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +var should = require("should"); +var functionNode = require("nr-test-utils").require("@node-red/nodes/core/core/80-function.js"); +var helper = require("node-red-node-test-helper"); + +// Notice: +// - nodes should have x, y, z property when defining subflow. + +describe('subflow', function() { + + before(function(done) { + helper.startServer(done); + }); + + after(function(done) { + helper.stopServer(done); + }); + + afterEach(function() { + helper.unload(); + }); + + it('should define subflow', function(done) { + var flow = [ + {id:"t1", type:"tab"}, + {id:"n1", z:"t1", type:"subflow:s1", wires:[["n2"]]}, + {id:"n2", z:"t1", type:"helper", wires:[]}, + // Subflow + {id:"s1", type:"subflow", name:"Subflow", info:"", + in:[{wires:[ {id:"s1-n1"} ]}], + out:[{wires:[ {id:"s1-n1", port:0} ]}]}, + {id:"s1-n1", z:"s1", type:"function", + func:"return msg;", wires:[]} + ]; + helper.load(functionNode, flow, function() { + done(); + }); + }); + + it('should pass data to/from subflow', function(done) { + var flow = [ + {id:"t0", type:"tab", label:"", disabled:false, info:""}, + {id:"n1", x:10, y:10, z:"t0", type:"subflow:s1", wires:[["n2"]]}, + {id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]}, + // Subflow + {id:"s1", type:"subflow", name:"Subflow", info:"", + in:[{ + x:10, y:10, + wires:[ {id:"s1-n1"} ] + }], + out:[{ + x:10, y:10, + wires:[ {id:"s1-n1", port:0} ] + }] + }, + {id:"s1-n1", x:10, y:10, z:"s1", type:"function", + func:"msg.payload = msg.payload+'bar'; return msg;", wires:[]} + ]; + helper.load(functionNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property("payload", "foobar"); + done(); + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should pass data to/from nested subflow', function(done) { + var flow = [ + {id:"t0", type:"tab", label:"", disabled:false, info:""}, + {id:"n1", x:10, y:10, z:"t0", type:"subflow:s1", wires:[["n2"]]}, + {id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]}, + // Subflow1 + {id:"s1", type:"subflow", name:"Subflow1", info:"", + in:[{ + x:10, y:10, + wires:[ {id:"s1-n1"} ] + }], + out:[{ + x:10, y:10, + wires:[ {id:"s1-n2", port:0} ] + }] + }, + {id:"s1-n1", x:10, y:10, z:"s1", type:"subflow:s2", + wires:[["s1-n2"]]}, + {id:"s1-n2", x:10, y:10, z:"s1", type:"function", + func:"msg.payload = msg.payload+'baz'; return msg;", wires:[]}, + // Subflow2 + {id:"s2", type:"subflow", name:"Subflow2", info:"", + in:[{ + x:10, y:10, + wires:[ {id:"s2-n1"} ] + }], + out:[{ + x:10, y:10, + wires:[ {id:"s2-n1", port:0} ] + }] + }, + {id:"s2-n1", x:10, y:10, z:"s2", type:"function", + func:"msg.payload=msg.payload+'bar'; return msg;", wires:[]} + ]; + helper.load(functionNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property("payload", "foobarbaz"); + done(); + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should access env var of subflow template', function(done) { + var flow = [ + {id:"t0", type:"tab", label:"", disabled:false, info:""}, + {id:"n1", x:10, y:10, z:"t0", type:"subflow:s1", wires:[["n2"]]}, + {id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]}, + // Subflow + {id:"s1", type:"subflow", name:"Subflow", info:"", + env: [ + {name: "K", type: "T", value: "V", + info: { + name: "K", + label: "", + value: "V", + type: "T", + target_type: "env var", + "target": "K" + }} + ], + in:[{ + x:10, y:10, + wires:[ {id:"s1-n1"} ] + }], + out:[{ + x:10, y:10, + wires:[ {id:"s1-n1", port:0} ] + }] + }, + {id:"s1-n1", x:10, y:10, z:"s1", type:"function", + func:"msg.V = env.get('K'); msg.T = env.get('K_type'); msg.I = env.get('K_info'); return msg;", + wires:[]} + ]; + helper.load(functionNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property("V", "V"); + msg.should.have.property("T", "T"); + msg.should.have.property("I"); + done(); + } + catch (e) { + console.log(e); + done(e); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should access env var of subflow instance', function(done) { + var flow = [ + {id:"t0", type:"tab", label:"", disabled:false, info:""}, + {id:"n1", x:10, y:10, z:"t0", type:"subflow:s1", + env: [ + {name: "K", type: "T", value: "V", + info: { + name: "K", + label: "", + value: "V", + type: "T", + target_type: "env var", + "target": "K" + }} + ], + wires:[["n2"]]}, + {id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]}, + // Subflow + {id:"s1", type:"subflow", name:"Subflow", info:"", + in:[{ + x:10, y:10, + wires:[ {id:"s1-n1"} ] + }], + out:[{ + x:10, y:10, + wires:[ {id:"s1-n1", port:0} ] + }] + }, + {id:"s1-n1", x:10, y:10, z:"s1", type:"function", + func:"msg.V = env.get('K'); msg.T = env.get('K_type'); msg.I = env.get('K_info'); return msg;", + wires:[]} + ]; + helper.load(functionNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property("V", "V"); + msg.should.have.property("T", "T"); + msg.should.have.property("I"); + done(); + } + catch (e) { + console.log(e); + done(e); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should overwrite env var of subflow template by env var of subflow instance', function(done) { + var flow = [ + {id:"t0", type:"tab", label:"", disabled:false, info:""}, + {id:"n1", x:10, y:10, z:"t0", type:"subflow:s1", + env: [ + {name: "K", type: "T", value: "V", + info: { + name: "K", + label: "", + value: "V", + type: "T", + target_type: "env var", + "target": "K" + }} + ], + wires:[["n2"]]}, + {id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]}, + // Subflow + {id:"s1", type:"subflow", name:"Subflow", info:"", + env: [ + {name: "K", type: "TT", value: "TV", + info: { + name: "K", + label: "", + value: "TV", + type: "TT", + target_type: "env var", + "target": "K" + }} + ], + in:[{ + x:10, y:10, + wires:[ {id:"s1-n1"} ] + }], + out:[{ + x:10, y:10, + wires:[ {id:"s1-n1", port:0} ] + }] + }, + {id:"s1-n1", x:10, y:10, z:"s1", type:"function", + func:"msg.V = env.get('K'); msg.T = env.get('K_type'); msg.I = env.get('K_info'); return msg;", + wires:[]} + ]; + helper.load(functionNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property("V", "V"); + msg.should.have.property("T", "T"); + msg.should.have.property("I"); + done(); + } + catch (e) { + console.log(e); + done(e); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should access env var of parent subflow template', function(done) { + var flow = [ + {id:"t0", type:"tab", label:"", disabled:false, info:""}, + {id:"n1", x:10, y:10, z:"t0", type:"subflow:s1", wires:[["n2"]]}, + {id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]}, + // Subflow1 + {id:"s1", type:"subflow", name:"Subflow1", info:"", + env: [ + {name: "K", type: "T", value: "V", + info: { + name: "K", + label: "", + value: "V", + type: "T", + target_type: "env var", + "target": "K" + }} + ], + in:[{ + x:10, y:10, + wires:[ {id:"s1-n1"} ] + }], + out:[{ + x:10, y:10, + wires:[ {id:"s1-n2", port:0} ] + }] + }, + {id:"s1-n1", x:10, y:10, z:"s1", type:"subflow:s2", + wires:[["s1-n2"]]}, + {id:"s1-n2", x:10, y:10, z:"s1", type:"function", + func:"return msg;", wires:[]}, + // Subflow2 + {id:"s2", type:"subflow", name:"Subflow2", info:"", + in:[{ + x:10, y:10, + wires:[ {id:"s2-n1"} ] + }], + out:[{ + x:10, y:10, + wires:[ {id:"s2-n1", port:0} ] + }] + }, + {id:"s2-n1", x:10, y:10, z:"s2", type:"function", + func:"msg.V = env.get('K'); return msg;", + wires:[]} + ]; + helper.load(functionNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property("V", "V"); + done(); + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should access env var of parent subflow instance', function(done) { + var flow = [ + {id:"t0", type:"tab", label:"", disabled:false, info:""}, + {id:"n1", x:10, y:10, z:"t0", type:"subflow:s1", + env: [ + {name: "K", type: "T", value: "V", + info: { + name: "K", + label: "", + value: "V", + type: "T", + target_type: "env var", + "target": "K" + }} + ], + wires:[["n2"]]}, + {id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]}, + // Subflow1 + {id:"s1", type:"subflow", name:"Subflow1", info:"", + in:[{ + x:10, y:10, + wires:[ {id:"s1-n1"} ] + }], + out:[{ + x:10, y:10, + wires:[ {id:"s1-n2", port:0} ] + }] + }, + {id:"s1-n1", x:10, y:10, z:"s1", type:"subflow:s2", + wires:[["s1-n2"]]}, + {id:"s1-n2", x:10, y:10, z:"s1", type:"function", + func:"return msg;", wires:[]}, + // Subflow2 + {id:"s2", type:"subflow", name:"Subflow2", info:"", + in:[{ + x:10, y:10, + wires:[ {id:"s2-n1"} ] + }], + out:[{ + x:10, y:10, + wires:[ {id:"s2-n1", port:0} ] + }] + }, + {id:"s2-n1", x:10, y:10, z:"s2", type:"function", + func:"msg.V = env.get('K'); return msg;", + wires:[]} + ]; + helper.load(functionNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property("V", "V"); + done(); + }); + n1.receive({payload:"foo"}); + }); + }); + +}); diff --git a/test/unit/@node-red/runtime/lib/nodes/flows/Subflow_spec.js b/test/unit/@node-red/runtime/lib/nodes/flows/Subflow_spec.js index 732c406f7..bb4c7f372 100644 --- a/test/unit/@node-red/runtime/lib/nodes/flows/Subflow_spec.js +++ b/test/unit/@node-red/runtime/lib/nodes/flows/Subflow_spec.js @@ -67,10 +67,12 @@ describe('Subflow', function() { this.foo = n.foo; this.handled = 0; this.stopped = false; + this.received = null; currentNodes[node.id] = node; this.on('input',function(msg) { // console.log(this.id,msg.payload); node.handled++; + node.received = msg.payload; node.send(msg); }); this.on('close',function() { @@ -170,6 +172,34 @@ describe('Subflow', function() { } util.inherits(TestAsyncNode,Node); + var TestEnvNode = function(n) { + Node.call(this,n); + this._index = createCount++; + this.scope = n.scope; + this.foo = n.foo; + var node = this; + this.stopped = false; + this.received = null; + currentNodes[node.id] = node; + this.on('input',function(msg) { + var val = node.getenv("__KEY__"); + node.received = val; + node.send({payload: val}); + }); + this.on('close',function() { + node.stopped = true; + stoppedNodes[node.id] = node; + delete currentNodes[node.id]; + }); + this.__updateWires = this.updateWires; + this.updateWires = function(newWires) { + rewiredNodes[node.id] = node; + node.newWires = newWires; + node.__updateWires(newWires); + }; + } + util.inherits(TestEnvNode,Node); + before(function() { getType = sinon.stub(typeRegistry,"get",function(type) { if (type=="test") { @@ -178,6 +208,8 @@ describe('Subflow', function() { return TestErrorNode; } else if (type=="testStatus"){ return TestStatusNode; + } else if (type=="testEnv"){ + return TestEnvNode; } else { return TestAsyncNode; } @@ -533,4 +565,128 @@ describe('Subflow', function() { }); + describe("#env var", function() { + it("can access process env var", function(done) { + var config = flowUtils.parseConfig([ + {id:"t1",type:"tab"}, + {id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]}, + {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, + {id:"3",x:10,y:10,z:"t1",type:"test",foo:"t1.3",wires:[]}, + {id:"sf1",type:"subflow",name:"Subflow 2",info:"", + "in":[ {wires:[{id:"sf1-1"}]} ], + "out":[ {wires:[{id:"sf1-2",port:0}]} ]}, + {id:"sf1-1",type:"test",z:"sf1",foo:"sf1.1",x:166,y:99,wires:[["sf1-2"]]}, + {id:"sf1-2",type:"testEnv",z:"sf1",foo:"sf1-cn",x:166,y:99,wires:[[]]} + ]); + var flow = Flow.create({ + getSetting: k=> process.env[k], + handleError: (a,b,c) => { console.log(a,b,c); } + },config,config.flows["t1"]); + + flow.start(); + + process.env["__KEY__"] = "__VAL__"; + + currentNodes["1"].receive({payload: "test"}); + currentNodes["3"].should.have.a.property("received", "__VAL__"); + + flow.stop().then(function() { + done(); + }); + }); + + it("can access subflow env var", function(done) { + var config = flowUtils.parseConfig([ + {id:"t1",type:"tab"}, + {id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]}, + {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, + {id:"3",x:10,y:10,z:"t1",type:"test",foo:"t1.3",wires:[]}, + {id:"sf1",type:"subflow",name:"Subflow 2",info:"", + "in":[ {wires:[{id:"sf1-1"}]} ], + "out":[ {wires:[{id:"sf1-2",port:0}]} ]}, + {id:"sf1-1",type:"test",z:"sf1",foo:"sf1.1",x:166,y:99,wires:[["sf1-2"]]}, + {id:"sf1-2",type:"testEnv",z:"sf1",foo:"sf1.2",x:166,y:99,wires:[[]]} + ]); + var flow = Flow.create({ + getSetting: k=> process.env[k], + handleError: (a,b,c) => { console.log(a,b,c); } + },config,config.flows["t1"]); + + flow.start(); + + var testenv_node = null; + for (var n in currentNodes) { + var node = currentNodes[n]; + if (node.type === "testEnv") { + testenv_node = node; + break; + } + } + process.env["__KEY__"] = "__VAL0__"; + testenv_node.setenv("__KEY__", "__VAL1__"); + + currentNodes["1"].receive({payload: "test"}); + currentNodes["3"].should.have.a.property("received", "__VAL1__"); + + flow.stop().then(function() { + done(); + }); + }); + + it("can access nested subflow env var", function(done) { + var config = flowUtils.parseConfig([ + {id:"t1",type:"tab"}, + {id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]}, + {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, + {id:"3",x:10,y:10,z:"t1",type:"test",foo:"t1.3",wires:[]}, + {id:"sf1",type:"subflow",name:"Subflow 1",info:"", + in:[{wires:[{id:"sf1-1"}]}], + out:[{wires:[{id:"sf1-2",port:0}]}]}, + {id:"sf2",type:"subflow",name:"Subflow 2",info:"", + in:[{wires:[{id:"sf2-1"}]}], + out:[{wires:[{id:"sf2-2",port:0}]}]}, + {id:"sf1-1",type:"test",z:"sf1",foo:"sf1.1",x:166,y:99,wires:[["sf1-2"]]}, + {id:"sf1-2",type:"subflow:sf2",z:"sf1",x:166,y:99,wires:[[]]}, + {id:"sf2-1",type:"test",z:"sf2",foo:"sf2.1",x:166,y:99,wires:[["sf2-2"]]}, + {id:"sf2-2",type:"testEnv",z:"sf2",foo:"sf2.2",x:166,y:99,wires:[[]]}, + ]); + var flow = Flow.create({ + getSetting: k=> process.env[k], + handleError: (a,b,c) => { console.log(a,b,c); } + },config,config.flows["t1"]); + + flow.start(); + + var node_sf1_1 = null; + var node_sf2_1 = null; + var testenv_node = null; + for (var n in currentNodes) { + var node = currentNodes[n]; + if (node.foo === "sf1.1") { + node_sf1_1 = node; + } + if (node.foo === "sf2.1") { + node_sf2_1 = node; + } + } + + process.env["__KEY__"] = "__VAL0__"; + currentNodes["1"].receive({payload: "test"}); + currentNodes["3"].should.have.a.property("received", "__VAL0__"); + + node_sf1_1.setenv("__KEY__", "__VAL1__"); + currentNodes["1"].receive({payload: "test"}); + currentNodes["3"].should.have.a.property("received", "__VAL1__"); + + node_sf2_1.setenv("__KEY__", "__VAL2__"); + currentNodes["1"].receive({payload: "test"}); + currentNodes["3"].should.have.a.property("received", "__VAL2__"); + + flow.stop().then(function() { + done(); + }); + }); + + }); + });