diff --git a/CHANGELOG.md b/CHANGELOG.md index 075148316..6770f3158 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +#### 0.19.3: Maintenance Release + + - Split node - fix complete to send msg for k/v object + - Remove unused Join node merged object key typed input + - Set the JavaScript editor to full-screen + - Filter global modules installed locally + - Add svg to permitted icon extension list + - Debug node - indicate status all the time if selected to do so + - pi nodes - increase test coverage slightly + - TCP-request node - only write payload + - JSON schema: perform validation when obj -> obj or str -> str + - JSON schema: add draft-06 support (via $schema keyword) + - Mqtt proxy configuration for websocket connection, #1651. + - Allows MQTT Shared Subscriptions for MQTT-In core node + - Fix use of HTML tag or CSS class specification as icon of typedInput + #### 0.19.2: Maintenance Release - Ensure node default colour is used if palette.theme has no match diff --git a/editor/js/ui/editors/js.js b/editor/js/ui/editors/js.js index cfb5a61c1..cc50ead2b 100644 --- a/editor/js/ui/editors/js.js +++ b/editor/js/ui/editors/js.js @@ -32,7 +32,7 @@ RED.editor.types._js = (function() { var trayOptions = { title: options.title, - width: "inherit", + width: options.width||"inherit", buttons: [ { id: "node-dialog-cancel", diff --git a/nodes/core/core/58-debug.js b/nodes/core/core/58-debug.js index cfd20b6ea..0d57ca491 100644 --- a/nodes/core/core/58-debug.js +++ b/nodes/core/core/58-debug.js @@ -20,7 +20,7 @@ module.exports = function(RED) { this.severity = n.severity || 40; this.active = (n.active === null || typeof n.active === "undefined") || n.active; if (this.tostatus) { - this.oldStatus = {fill:"grey", shape:this.active?"dot":"ring"}; + this.oldStatus = {fill:"grey", shape:"ring"}; this.status(this.oldStatus); } else { this.status({}); } @@ -131,7 +131,7 @@ module.exports = function(RED) { node.active = false; res.sendStatus(201); if (node.tostatus && node.hasOwnProperty("oldStatus")) { - node.oldStatus.shape = "ring"; + node.oldStatus.shape = "dot"; node.status(node.oldStatus); } } else { diff --git a/nodes/core/core/80-function.html b/nodes/core/core/80-function.html index 13ef20db0..913be7c41 100644 --- a/nodes/core/core/80-function.html +++ b/nodes/core/core/80-function.html @@ -128,6 +128,7 @@ var value = that.editor.getValue(); RED.editor.editJavaScript({ value: value, + width: "Infinity", cursor: that.editor.getCursorPosition(), complete: function(v,cursor) { that.editor.setValue(v, -1); diff --git a/nodes/core/logic/17-split.html b/nodes/core/logic/17-split.html index 9c2866b9c..fccb8c0d0 100644 --- a/nodes/core/logic/17-split.html +++ b/nodes/core/logic/17-split.html @@ -295,7 +295,8 @@ For object outputs, once this count has been reached, the node can be configured to send a message for each subsequent message received.

A timeout can be set to trigger sending the new message using whatever has been received so far.

-

If a message is received with the msg.complete property set, the output message is sent.

+

If a message is received with the msg.complete property set, the output message is finalised and sent. + This resets any part counts.

Reduce Sequence mode

When configured to join in reduce mode, an expression is applied to each @@ -439,10 +440,7 @@ $("#node-input-joiner").typedInput({ default: 'str', typeField: $("#node-input-joinerType"), - types:[ - 'str', - 'bin' - ] + types:['str', 'bin'] }); $("#node-input-property").typedInput({ @@ -451,7 +449,7 @@ }); $("#node-input-key").typedInput({ - types:['msg', {value:"merge", label:"", hasValue:false}] + types:['msg'] }); $("#node-input-build").change(); diff --git a/nodes/core/logic/17-split.js b/nodes/core/logic/17-split.js index 3170ba584..f5ad199ca 100644 --- a/nodes/core/logic/17-split.js +++ b/nodes/core/logic/17-split.js @@ -586,7 +586,10 @@ module.exports = function(RED) { } else { if (msg.hasOwnProperty('complete')) { - completeSend(partId); + if (inflight[partId]) { + inflight[partId].msg.complete = msg.complete; + completeSend(partId); + } } else { node.warn("Message missing key property 'msg."+node.key+"' - cannot add to object") @@ -594,6 +597,7 @@ module.exports = function(RED) { } return; } + if (!inflight.hasOwnProperty(partId)) { if (payloadType === 'object' || payloadType === 'merged') { inflight[partId] = { @@ -604,19 +608,6 @@ module.exports = function(RED) { msg:RED.util.cloneMessage(msg) }; } - else if (node.accumulate === true) { - if (msg.hasOwnProperty("reset")) { delete inflight[partId]; } - inflight[partId] = inflight[partId] || { - currentCount:0, - payload:{}, - targetCount:targetCount, - type:payloadType, - msg:RED.util.cloneMessage(msg) - } - if (payloadType === 'string' || payloadType === 'array' || payloadType === 'buffer') { - inflight[partId].payload = []; - } - } else { inflight[partId] = { currentCount:0, diff --git a/nodes/core/logic/18-sort.js b/nodes/core/logic/18-sort.js index 124392014..c330801e2 100644 --- a/nodes/core/logic/18-sort.js +++ b/nodes/core/logic/18-sort.js @@ -196,9 +196,10 @@ module.exports = function(RED) { }).catch(err => { node.error(err,msg); }); + return; } var parts = msg.parts; - if (!parts.hasOwnProperty("id") || !parts.hasOwnProperty("index")) { + if (!parts || !parts.hasOwnProperty("id") || !parts.hasOwnProperty("index")) { return; } var gid = parts.id; @@ -242,7 +243,8 @@ module.exports = function(RED) { delete pending[key]; } } - pending_count = 0; }) + pending_count = 0; + }); } RED.nodes.registerType("sort", SortNode); diff --git a/package.json b/package.json index de004cac2..59e6161c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "0.19.2", + "version": "0.19.3", "description": "A visual tool for wiring the Internet of Things", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -42,7 +42,7 @@ "cookie": "0.3.1", "cookie-parser": "1.4.3", "cors": "2.8.4", - "cron": "1.3.0", + "cron": "1.4.1", "denque": "1.3.0", "express": "4.16.3", "express-session": "1.15.6", @@ -57,9 +57,9 @@ "jsonata": "1.5.4", "media-typer": "0.3.0", "memorystore": "1.6.0", - "mqtt": "2.18.5", + "mqtt": "2.18.8", "multer": "1.3.1", - "mustache": "2.3.1", + "mustache": "2.3.2", "node-red-node-email": "0.1.*", "node-red-node-feedparser": "^0.1.12", "node-red-node-rbe": "0.2.*", @@ -74,7 +74,7 @@ "request": "2.88.0", "semver": "5.5.1", "sentiment": "2.1.0", - "uglify-js": "3.4.8", + "uglify-js": "3.4.9", "when": "3.7.8", "ws": "1.1.5", "xml2js": "0.4.19" @@ -86,7 +86,7 @@ "chromedriver": "^2.41.0", "grunt": "~1.0.3", "grunt-chmod": "~1.1.1", - "grunt-cli": "~1.2.0", + "grunt-cli": "~1.3.1", "grunt-concurrent": "~2.3.1", "grunt-contrib-clean": "~1.1.0", "grunt-contrib-compress": "~1.4.0", diff --git a/test/nodes/core/logic/17-split_spec.js b/test/nodes/core/logic/17-split_spec.js index bafdb0281..b30a62d67 100644 --- a/test/nodes/core/logic/17-split_spec.js +++ b/test/nodes/core/logic/17-split_spec.js @@ -603,14 +603,14 @@ describe('JOIN node', function() { }); it('should accumulate a merged object', function(done) { - var flow = [{id:"n1", type:"join", wires:[["n2"]], build:"merged",mode:"custom",accumulate:true, count:1}, + var flow = [{id:"n1", type:"join", wires:[["n2"]], build:"merged",mode:"custom",accumulate:true, count:3}, {id:"n2", type:"helper"}]; helper.load(joinNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); var c = 0; n2.on("input", function(msg) { - if (c === 5) { + if (c === 3) { try { msg.should.have.property("payload"); msg.payload.should.have.property("a",3); @@ -632,14 +632,14 @@ describe('JOIN node', function() { }); it('should be able to reset an accumulation', function(done) { - var flow = [{id:"n1", type:"join", wires:[["n2"]], build:"merged",accumulate:true,mode:"custom", count:1}, + var flow = [{id:"n1", type:"join", wires:[["n2"]], build:"merged",accumulate:true,mode:"custom", count:3}, {id:"n2", type:"helper"}]; helper.load(joinNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); var c = 0; n2.on("input", function(msg) { - if (c === 3) { + if (c === 1) { try { msg.should.have.property("payload"); msg.payload.should.have.property("a",1); @@ -649,11 +649,20 @@ describe('JOIN node', function() { } catch(e) { done(e) } } - if (c === 5) { + if (c === 2) { try { msg.should.have.property("payload"); - msg.payload.should.have.property("b",2); - msg.payload.should.have.property("c",1); + msg.payload.should.have.property("e",2); + msg.payload.should.have.property("f",1); + } + catch(e) { done(e) } + } + if (c === 3) { + try { + msg.should.have.property("payload"); + msg.payload.should.have.property("g",2); + msg.payload.should.have.property("h",1); + msg.payload.should.have.property("i",3); done(); } catch(e) { done(e) } @@ -664,8 +673,11 @@ describe('JOIN node', function() { n1.receive({payload:{b:2}, topic:"b"}); n1.receive({payload:{c:3}, topic:"c"}); n1.receive({payload:{d:4}, topic:"d", complete:true}); - n1.receive({payload:{b:2}, topic:"e"}); - n1.receive({payload:{c:1}, topic:"f"}); + n1.receive({payload:{e:2}, topic:"e"}); + n1.receive({payload:{f:1}, topic:"f", complete:true}); + n1.receive({payload:{g:2}, topic:"g"}); + n1.receive({payload:{h:1}, topic:"h"}); + n1.receive({payload:{i:3}, topic:"i"}); }); }); diff --git a/test/nodes/core/logic/18-sort_spec.js b/test/nodes/core/logic/18-sort_spec.js index 993cec8d2..e513a9f8a 100644 --- a/test/nodes/core/logic/18-sort_spec.js +++ b/test/nodes/core/logic/18-sort_spec.js @@ -66,13 +66,25 @@ describe('SORT node', function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { - msg.should.have.property(target); - var data = msg[target]; - data.length.should.equal(data_out.length); - for(var i = 0; i < data_out.length; i++) { - data[i].should.equal(data_out[i]); + try { + msg.should.have.property(target); + var data = msg[target]; + data.length.should.equal(data_out.length); + for(var i = 0; i < data_out.length; i++) { + var data0 = data[i]; + var data1 = data_out[i]; + if (typeof data0 === "object") { + data0.should.deepEqual(data1); + } + else { + data0.should.equal(data1); + } + } + done(); + } + catch(e) { + console.log(e); } - done(); }); var msg = {}; msg[target] = data_in; @@ -93,6 +105,34 @@ describe('SORT node', function() { } function check_sort1(flow, key, key_type, data_in, data_out, done) { + function equals(v0, v1) { + var k0 = Object.keys(v0); + var k1 = Object.keys(v1); + + if (k0.length === k1.length) { + for (var i = 0; i < k0.length; i++) { + var k = k0[i]; + if (!v1.hasOwnProperty(k) || + (v0[k] !== v1[k])) { + return false; + } + } + return true; + } + return false; + } + function indexOf(a, v) { + for(var i = 0; i < a.length; i++) { + var av = a[i]; + if ((typeof v === 'object') && equals(v, av)) { + return i; + } + else if (v === av) { + return i; + } + } + return -1; + } var sort = flow[0]; var prop = (key_type === "msg") ? key : "payload"; sort.targetType = "seq"; @@ -107,7 +147,7 @@ describe('SORT node', function() { msg.should.have.property("parts"); msg.parts.should.have.property("count", data_out.length); var data = msg[prop]; - var index = data_out.indexOf(data); + var index = indexOf(data_out, data); msg.parts.should.have.property("index", index); count++; if (count === data_out.length) { @@ -136,7 +176,6 @@ describe('SORT node', function() { check_sort1(flow, exp, "jsonata", data_in, data_out, done); } - (function() { var flow = [{id:"n1", type:"sort", order:"ascending", as_num:false, wires:[["n2"]]}, {id:"n2", type:"helper"}]; @@ -239,6 +278,19 @@ describe('SORT node', function() { }); })(); + (function() { + var flow = [{id:"n1", type:"sort", order:"ascending", as_num:true, wires:[["n2"]]}, + {id:"n2", type:"helper"}]; + var conv = function(x) { + return x.map(function(v) { return { val:v }; }); + }; + var data_in = conv([ "200", "4", "30", "1000" ]); + var data_out = conv([ "4", "30", "200", "1000" ]); + it('should sort payload of objects', function(done) { + check_sort0C(flow, "val", data_in, data_out, done); + }); + })(); + it('should sort payload by context (exp, not number, ascending)', function(done) { var flow = [{id:"n1", type:"sort", target:"data", targetType:"msg", msgKey:"$flowContext($)", msgKeyType:"jsonata", order:"ascending", as_num:false, wires:[["n2"]],z:"flow"}, {id:"n2", type:"helper",z:"flow"}, @@ -268,7 +320,7 @@ describe('SORT node', function() { }); it('should sort message group by context (exp, not number, ascending)', function(done) { - var flow = [{id:"n1", type:"sort", target:"data", targetType:"msg", msgKey:"$globalContext(payload)", msgKeyType:"jsonata", order:"ascending", as_num:false, wires:[["n2"]],z:"flow"}, + var flow = [{id:"n1", type:"sort", target:"data", targetType:"seq", seqKey:"$globalContext(payload)", seqKeyType:"jsonata", order:"ascending", as_num:false, wires:[["n2"]],z:"flow"}, {id:"n2", type:"helper",z:"flow"}, {id:"flow", type:"tab"}]; var data_in = [ "first", "second", "third", "fourth" ]; @@ -282,15 +334,20 @@ describe('SORT node', function() { n1.context()["global"].set("third","3"); n1.context()["global"].set("fourth","2"); n2.on("input", function(msg) { - msg.should.have.property("payload"); - msg.should.have.property("parts"); - msg.parts.should.have.property("count", data_out.length); - var data = msg["payload"]; - var index = data_out.indexOf(data); - msg.parts.should.have.property("index", index); - count++; - if (count === data_out.length) { - done(); + try { + msg.should.have.property("payload"); + msg.should.have.property("parts"); + msg.parts.should.have.property("count", data_out.length); + var data = msg["payload"]; + var index = data_out.indexOf(data); + msg.parts.should.have.property("index", index); + count++; + if (count === data_out.length) { + done(); + } + } + catch(e) { + done(e); } }); var len = data_in.length; @@ -332,7 +389,7 @@ describe('SORT node', function() { }); it('should sort message group by persistable context (exp, not number, descending)', function(done) { - var flow = [{id:"n1", type:"sort", target:"data", targetType:"msg", msgKey:"$flowContext(payload,\"memory\")", msgKeyType:"jsonata", order:"descending", as_num:false, wires:[["n2"]],z:"flow"}, + var flow = [{id:"n1", type:"sort", target:"data", targetType:"seq", seqKey:"$flowContext(payload,\"memory\")", seqKeyType:"jsonata", order:"descending", as_num:false, wires:[["n2"]],z:"flow"}, {id:"n2", type:"helper",z:"flow"}, {id:"flow", type:"tab"}]; var data_in = [ "first", "second", "third", "fourth" ];