1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Merge branch '0.18' into projects

This commit is contained in:
Nick O'Leary 2018-01-24 23:06:27 +00:00 committed by GitHub
commit e250a91f09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 647 additions and 888 deletions

View File

@ -272,10 +272,19 @@ RED.nodes = (function() {
if (updatedConfigNode) { if (updatedConfigNode) {
RED.workspaces.refresh(); RED.workspaces.refresh();
} }
try {
if (node._def.oneditdelete) {
node._def.oneditdelete.call(node);
}
} catch(err) {
console.log("oneditdelete",node.id,node.type,err.toString());
}
RED.events.emit('nodes:remove',node); RED.events.emit('nodes:remove',node);
} }
} }
if (node && node._def.onremove) { if (node && node._def.onremove) {
// Deprecated: never documented but used by some early nodes
console.log("Deprecated API warning: node type ",node.type," has an onremove function - should be oneditremove - please report");
node._def.onremove.call(n); node._def.onremove.call(n);
} }
return {links:removedLinks,nodes:removedNodes}; return {links:removedLinks,nodes:removedNodes};

View File

@ -779,6 +779,16 @@ RED.editor = (function() {
var fileName = selectIconFile.val(); var fileName = selectIconFile.val();
iconFileHidden.val(fileName); iconFileHidden.val(fileName);
}); });
var clear = $('<button class="editor-button editor-button-small"><i class="fa fa-times"></i></button>').appendTo(iconForm);
clear.click(function(evt) {
evt.preventDefault();
var iconPath = RED.utils.getDefaultNodeIcon(node._def, node);
selectIconModule.val(iconPath.module);
moduleChange(selectIconModule, selectIconFile, iconModuleHidden, iconFileHidden, iconSets, true);
selectIconFile.removeClass("input-error");
selectIconFile.val(iconPath.file);
iconFileHidden.val(iconPath.file);
});
moduleChange(selectIconModule, selectIconFile, iconModuleHidden, iconFileHidden, iconSets, false); moduleChange(selectIconModule, selectIconFile, iconModuleHidden, iconFileHidden, iconSets, false);
var iconFileList = iconSets[selectIconModule.val()]; var iconFileList = iconSets[selectIconModule.val()];

View File

@ -412,16 +412,17 @@ RED.palette = (function() {
for (var j=0;j<nodeSet.types.length;j++) { for (var j=0;j<nodeSet.types.length;j++) {
showNodeType(nodeSet.types[j]); showNodeType(nodeSet.types[j]);
var def = RED.nodes.getType(nodeSet.types[j]); var def = RED.nodes.getType(nodeSet.types[j]);
if (def.onpaletteadd && typeof def.onpaletteadd === "function") { if (def && def.onpaletteadd && typeof def.onpaletteadd === "function") {
def.onpaletteadd.call(def); def.onpaletteadd.call(def);
} }
} }
}); });
RED.events.on('registry:node-set-disabled', function(nodeSet) { RED.events.on('registry:node-set-disabled', function(nodeSet) {
console.log(nodeSet);
for (var j=0;j<nodeSet.types.length;j++) { for (var j=0;j<nodeSet.types.length;j++) {
hideNodeType(nodeSet.types[j]); hideNodeType(nodeSet.types[j]);
var def = RED.nodes.getType(nodeSet.types[j]); var def = RED.nodes.getType(nodeSet.types[j]);
if (def.onpaletteremove && typeof def.onpaletteremove === "function") { if (def && def.onpaletteremove && typeof def.onpaletteremove === "function") {
def.onpaletteremove.call(def); def.onpaletteremove.call(def);
} }
} }
@ -431,7 +432,7 @@ RED.palette = (function() {
for (var j=0;j<nodeSet.types.length;j++) { for (var j=0;j<nodeSet.types.length;j++) {
removeNodeType(nodeSet.types[j]); removeNodeType(nodeSet.types[j]);
var def = RED.nodes.getType(nodeSet.types[j]); var def = RED.nodes.getType(nodeSet.types[j]);
if (def.onpaletteremove && typeof def.onpaletteremove === "function") { if (def && def.onpaletteremove && typeof def.onpaletteremove === "function") {
def.onpaletteremove.call(def); def.onpaletteremove.call(def);
} }
} }

View File

@ -19,6 +19,7 @@ RED.typeSearch = (function() {
function search(val) { function search(val) {
activeFilter = val.toLowerCase(); activeFilter = val.toLowerCase();
var visible = searchResults.editableList('filter'); var visible = searchResults.editableList('filter');
searchResults.editableList('sort');
setTimeout(function() { setTimeout(function() {
selected = 0; selected = 0;
searchResults.children().removeClass('selected'); searchResults.children().removeClass('selected');
@ -101,6 +102,23 @@ RED.typeSearch = (function() {
} }
return (activeFilter==="")||(data.index.indexOf(activeFilter) > -1); return (activeFilter==="")||(data.index.indexOf(activeFilter) > -1);
}, },
sort: function(A,B) {
if (activeFilter === "") {
return A.i - B.i;
}
var Ai = A.index.indexOf(activeFilter);
var Bi = B.index.indexOf(activeFilter);
if (Ai === -1) {
return 1;
}
if (Bi === -1) {
return -1;
}
if (Ai === Bi) {
return sortTypeLabels(A,B);
}
return Ai-Bi;
},
addItem: function(container,i,object) { addItem: function(container,i,object) {
var def = object.def; var def = object.def;
object.index = object.type.toLowerCase(); object.index = object.type.toLowerCase();
@ -225,7 +243,17 @@ RED.typeSearch = (function() {
} }
return label; return label;
} }
function sortTypeLabels(a,b) {
var al = a.label.toLowerCase();
var bl = b.label.toLowerCase();
if (al < bl) {
return -1;
} else if (al === bl) {
return 0;
} else {
return 1;
}
}
function refreshTypeList() { function refreshTypeList() {
var i; var i;
searchResults.editableList('empty'); searchResults.editableList('empty');
@ -250,27 +278,19 @@ RED.typeSearch = (function() {
items.push({type:t,def: def, label:getTypeLabel(t,def)}); items.push({type:t,def: def, label:getTypeLabel(t,def)});
} }
}); });
items.sort(function(a,b) { items.sort(sortTypeLabels);
var al = a.label.toLowerCase();
var bl = b.label.toLowerCase();
if (al < bl) {
return -1;
} else if (al === bl) {
return 0;
} else {
return 1;
}
})
var commonCount = 0; var commonCount = 0;
var item; var item;
var index = 0;
for(i=0;i<common.length;i++) { for(i=0;i<common.length;i++) {
var itemDef = RED.nodes.getType(common[i]); var itemDef = RED.nodes.getType(common[i]);
if (itemDef) { if (itemDef) {
item = { item = {
type: common[i], type: common[i],
common: true, common: true,
def: itemDef def: itemDef,
i: index++
}; };
item.label = getTypeLabel(item.type,item.def); item.label = getTypeLabel(item.type,item.def);
if (i === common.length-1) { if (i === common.length-1) {
@ -283,7 +303,8 @@ RED.typeSearch = (function() {
item = { item = {
type:recentlyUsed[i], type:recentlyUsed[i],
def: RED.nodes.getType(recentlyUsed[i]), def: RED.nodes.getType(recentlyUsed[i]),
recent: true recent: true,
i: index++
}; };
item.label = getTypeLabel(item.type,item.def); item.label = getTypeLabel(item.type,item.def);
if (i === recentlyUsed.length-1) { if (i === recentlyUsed.length-1) {
@ -292,6 +313,7 @@ RED.typeSearch = (function() {
searchResults.editableList('addItem', item); searchResults.editableList('addItem', item);
} }
for (i=0;i<items.length;i++) { for (i=0;i<items.length;i++) {
items[i].i = index++;
searchResults.editableList('addItem', items[i]); searchResults.editableList('addItem', items[i]);
} }
setTimeout(function() { setTimeout(function() {

View File

@ -1,20 +1,9 @@
<!--
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.
-->
<script type="text/x-red" data-template-name="sentiment"> <script type="text/x-red" data-template-name="sentiment">
<div class="form-row">
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="node-red:common.label.property"></span></label>
<input type="text" id="node-input-property" style="width:70%;"/>
</div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
@ -22,7 +11,7 @@
</script> </script>
<script type="text/x-red" data-help-name="sentiment"> <script type="text/x-red" data-help-name="sentiment">
<p>Analyses the <code>payload</code> and adds a <code>sentiment</code> object.</p> <p>Analyses the chosen property, default <code>payload</code>, and adds a <code>sentiment</code> object.</p>
<h3>Outputs</h3> <h3>Outputs</h3>
<dl class="message-properties"> <dl class="message-properties">
<dt>sentiment <span class="property-type">object</span></dt> <dt>sentiment <span class="property-type">object</span></dt>
@ -47,6 +36,7 @@
color:"#E6E0F8", color:"#E6E0F8",
defaults: { defaults: {
name: {value:""}, name: {value:""},
property: {value:"payload",required:true}
}, },
inputs:1, inputs:1,
outputs:1, outputs:1,
@ -56,6 +46,12 @@
}, },
labelStyle: function() { labelStyle: function() {
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
},
oneditprepare: function() {
if (this.property === undefined) {
$("#node-input-property").val("payload");
}
$("#node-input-property").typedInput({default:'msg',types:['msg']});
} }
}); });
</script> </script>

View File

@ -1,18 +1,3 @@
/**
* 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.
**/
module.exports = function(RED) { module.exports = function(RED) {
"use strict"; "use strict";
@ -20,16 +5,18 @@ module.exports = function(RED) {
function SentimentNode(n) { function SentimentNode(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
this.property = n.property||"payload";
var node = this; var node = this;
this.on("input", function(msg) { this.on("input", function(msg) {
if (msg.hasOwnProperty("payload")) { var value = RED.util.getMessageProperty(msg,node.property);
sentiment(msg.payload, msg.overrides || null, function (err, result) { if (value !== undefined) {
sentiment(value, msg.overrides || null, function (err, result) {
msg.sentiment = result; msg.sentiment = result;
node.send(msg); node.send(msg);
}); });
} }
else { node.send(msg); } // If no payload - just pass it on. else { node.send(msg); } // If no matching property - just pass it on.
}); });
} }
RED.nodes.registerType("sentiment",SentimentNode); RED.nodes.registerType("sentiment",SentimentNode);

View File

@ -5,7 +5,8 @@
"topic": "Topic", "topic": "Topic",
"name": "Name", "name": "Name",
"username": "Username", "username": "Username",
"password": "Password" "password": "Password",
"property": "Property"
}, },
"status": { "status": {
"connected": "connected", "connected": "connected",
@ -542,7 +543,7 @@
"label": { "label": {
"property": "Property", "property": "Property",
"rule": "rule", "rule": "rule",
"repair" : "repair sequence (reconstruct parts property of outgoing messages)" "repair" : "recreate message sequences"
}, },
"and": "and", "and": "and",
"checkall": "checking all rules", "checkall": "checking all rules",
@ -558,7 +559,7 @@
"nnull":"is not null", "nnull":"is not null",
"head":"head", "head":"head",
"tail":"tail", "tail":"tail",
"index":"is between", "index":"index between",
"exp":"JSONata exp", "exp":"JSONata exp",
"else":"otherwise" "else":"otherwise"
}, },
@ -608,7 +609,7 @@
"maxout": "e.g. 255" "maxout": "e.g. 255"
}, },
"scale": { "scale": {
"payload": "Scale msg.payload", "payload": "Scale the message property",
"limit": "Scale and limit to the target range", "limit": "Scale and limit to the target range",
"wrap": "Scale and wrap within the target range" "wrap": "Scale and wrap within the target range"
}, },
@ -846,7 +847,7 @@
"mode":{ "mode":{
"mode":"Mode", "mode":"Mode",
"auto":"automatic", "auto":"automatic",
"merge":"merge sequence", "merge":"merge sequences",
"reduce":"reduce sequence", "reduce":"reduce sequence",
"custom":"manual" "custom":"manual"
}, },
@ -881,8 +882,8 @@
"exp": "Reduce exp", "exp": "Reduce exp",
"exp-value": "exp", "exp-value": "exp",
"init": "Initial value", "init": "Initial value",
"right": "Evaluate in reverse order (right to left)", "right": "Evaluate in reverse order (last to first)",
"fixup": "Fixup exp" "fixup": "Fix-up exp"
}, },
"errors": { "errors": {
"invalid-expr": "Invalid JSONata expression: __error__" "invalid-expr": "Invalid JSONata expression: __error__"
@ -904,20 +905,19 @@
"batch" : { "batch" : {
"mode": { "mode": {
"label" : "Mode", "label" : "Mode",
"num-msgs" : "number of messages", "num-msgs" : "Group by number of messages",
"interval" : "interval in seconds", "interval" : "Group by time interval",
"concat" : "concatenate sequences" "concat" : "Concatenate sequences"
}, },
"count": { "count": {
"label" : "Number of msgs", "label" : "Number of messages",
"overwrap" : "Overwrap", "overlap" : "Overlap",
"count" : "count", "count" : "count",
"invalid" : "Invalid count and overwrap" "invalid" : "Invalid count and overlap"
}, },
"interval": { "interval": {
"label" : "Interval (sec)", "label" : "Interval",
"seconds" : "seconds", "seconds" : "seconds",
"sec" : "sec",
"empty" : "send empty message when no message arrives" "empty" : "send empty message when no message arrives"
}, },
"concat": { "concat": {

View File

@ -40,7 +40,7 @@
</script> </script>
<script type="text/x-red" data-help-name="switch"> <script type="text/x-red" data-help-name="switch">
<p>Route messages based on their property values.</p> <p>Route messages based on their property values or sequence position.</p>
<h3>Details</h3> <h3>Details</h3>
<p>When a message arrives, the node will evaluate each of the defined rules <p>When a message arrives, the node will evaluate each of the defined rules
and forward the message to the corresponding outputs of any matching rules.</p> and forward the message to the corresponding outputs of any matching rules.</p>
@ -48,115 +48,25 @@
that matches.</p> that matches.</p>
<p>The rules can be evaluated against an individual message property, a flow or global <p>The rules can be evaluated against an individual message property, a flow or global
context property or the result of a JSONata expression.</p> context property or the result of a JSONata expression.</p>
<h3>Rules</h3> <h4>Rules</h4>
<p>Routing rules are categorized into three:</p> <p>There are four types of rule:</p>
<dl> <ol>
<dt>value rules</dt> <li><b>Value</b> rules are evaluated against the configured property</li>
<dd> <li><b>Sequence</b> rules can be used on message sequences, such as those
<table> generated by the Split node</li>
<tr> <li>A JSONata <b>Expression</b> can be provided that will be evaluated
<th>operator</th> against the whole message and will match if the expression returns
<th>description</th> a true value.</li>
</tr> <li>An <b>Otherwise</b> rule can be used to match if none of the preceeding
<tr> rules have matched.</li>
<td><b>==</b></td> </ol>
<td>property value is equals to specified value</td> <h4>Handling message sequences</h4>
</tr> <p>By default, the node does not modify the <code>msg.parts</code> property of messages
<tr> that are part of a sequence.</p>
<td><b>!=</b></td> <p>The <b>recreate message sequences</b> option can be enabled to generate new message sequences
<td>property value is not equals to specified value</td> for each rule that matches. In this mode, the node will buffer the entire incoming
</tr> sequence before sending the new sequences on. The runtime setting `nodeMessageBufferMaxLength`
<tr> can be used to limit how many messages nodes will buffer.</p>
<td><b>&lt;</b></td>
<td>property value is less than specified value</td>
</tr>
<tr>
<td><b>&lt;=</b></td>
<td>property value is less than or equals to specified value</td>
</tr>
<tr>
<td><b>&gt;</b></td>
<td>property value is greater than specified value</td>
</tr>
<tr>
<td><b>&gt;=</b></td>
<td>property value is greater than or equals to specified value</td>
</tr>
<tr>
<td><b>is between</b></td>
<td>property value is between specified values</td>
</tr>
<tr>
<td><b>contains</b></td>
<td>property string value contains specified string value</td>
</tr>
<tr>
<td><b>matches regex</b></td>
<td>property string value matches specified regex</td>
</tr>
<tr>
<td><b>is true</b></td>
<td>property value is true</td>
</tr>
<tr>
<td><b>is false</b></td>
<td>property value is false</td>
</tr>
<tr>
<td><b>is null</b></td>
<td>property value is null</td>
</tr>
<tr>
<td><b>is not null</b></td>
<td>property value is not null</td>
</tr>
</table>
</dd>
<dt>sequence rules</dt>
<dd>
<table>
<tr>
<th>operator</th>
<th>description</th>
</tr>
<tr>
<td><b>head</b></td>
<td>message is included in the first specified number of messages in a sequence</td>
</tr>
<tr>
<td><b>tail</b></td>
<td>message is included in the last specified number of messages in a sequence</td>
</tr>
<tr>
<td><b>pos. between</b></td>
<td>message is included between specified positions in a sequence</td>
</tr>
</table>
</dd>
<dt>other rules</dt>
<dd>
<table>
<tr>
<th>operator</th>
<th>description</th>
</tr>
<tr>
<td><b>JSONata exp</b></td>
<td>specified JSONata expression evaluate to true. In JSONata expression, $I represents <code>msg.parts.index</code> and $N represents <code>msg.parts.count</code> respectively if exists.</td>
</tr>
<tr>
<td><b>otherwise</b></td>
<td>no matching rule found</td>
</tr>
</table>
</dd>
</dl>
<h3>Repair Sequence</h3>
<p>If <b>repair sequence (reconstruct parts property of outgoing messages)</b> checkbox is selected, <code>msg.parts</code> property are reconstructed for each port to make forks of valid sequences. Otherwise, <code>msg.parts</code> property of incoming messages are passed through.</p>
<h3>Note:</h3>
<p>This node internally keeps messages for its operation if <b>repair sequence</b> checkbox is ON. In order to prevent unexpected memory usage, maximum number of messages kept can be specified by <code>switchMaxKeptMsgsCount</code> property in <b>settings.js</b>.</p>
</script> </script>
<script type="text/javascript"> <script type="text/javascript">
@ -221,7 +131,6 @@
} }
} }
if ((rule.t === 'btwn') || (rule.t === 'index')) { if ((rule.t === 'btwn') || (rule.t === 'index')) {
console.log(`; ${label}/${JSON.stringify(rule)}`);
label += " "+getValueLabel(rule.vt,rule.v)+" & "+getValueLabel(rule.v2t,rule.v2); label += " "+getValueLabel(rule.vt,rule.v)+" & "+getValueLabel(rule.v2t,rule.v2);
} else if (rule.t !== 'true' && rule.t !== 'false' && rule.t !== 'null' && rule.t !== 'nnull' && rule.t !== 'else' ) { } else if (rule.t !== 'true' && rule.t !== 'false' && rule.t !== 'null' && rule.t !== 'nnull' && rule.t !== 'else' ) {
label += " "+getValueLabel(rule.vt,rule.v); label += " "+getValueLabel(rule.vt,rule.v);

View File

@ -53,7 +53,7 @@ module.exports = function(RED) {
function max_kept_msgs_count(node) { function max_kept_msgs_count(node) {
if (_max_kept_msgs_count === undefined) { if (_max_kept_msgs_count === undefined) {
var name = "switchMaxKeptMsgsCount"; var name = "nodeMessageBufferMaxLength";
if (RED.settings.hasOwnProperty(name)) { if (RED.settings.hasOwnProperty(name)) {
_max_kept_msgs_count = RED.settings[name]; _max_kept_msgs_count = RED.settings[name];
} }

View File

@ -1,5 +1,9 @@
<script type="text/x-red" data-template-name="range"> <script type="text/x-red" data-template-name="range">
<div class="form-row">
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="common.label.property"></span></label>
<input type="text" id="node-input-property" style="width:calc(70% - 1px)"/>
</div>
<div class="form-row"> <div class="form-row">
<label for="node-input-action"><i class="fa fa-dot-circle-o"></i> <span data-i18n="range.label.action"></span></label> <label for="node-input-action"><i class="fa fa-dot-circle-o"></i> <span data-i18n="range.label.action"></span></label>
<select id="node-input-action" style="width:70%;"> <select id="node-input-action" style="width:70%;">
@ -65,6 +69,7 @@
maxout: {value:"",required:true,validate:RED.validators.number()}, maxout: {value:"",required:true,validate:RED.validators.number()},
action: {value:"scale"}, action: {value:"scale"},
round: {value:false}, round: {value:false},
property: {value:"payload",required:true},
name: {value:""} name: {value:""}
}, },
inputs: 1, inputs: 1,
@ -75,6 +80,12 @@
}, },
labelStyle: function() { labelStyle: function() {
return this.name ? "node_label_italic" : ""; return this.name ? "node_label_italic" : "";
},
oneditprepare: function() {
if (this.property === undefined) {
$("#node-input-property").val("payload");
}
$("#node-input-property").typedInput({default:'msg',types:['msg']});
} }
}); });
</script> </script>

View File

@ -24,11 +24,13 @@ module.exports = function(RED) {
this.maxin = Number(n.maxin); this.maxin = Number(n.maxin);
this.minout = Number(n.minout); this.minout = Number(n.minout);
this.maxout = Number(n.maxout); this.maxout = Number(n.maxout);
this.property = n.property||"payload";
var node = this; var node = this;
this.on('input', function (msg) { this.on('input', function (msg) {
if (msg.hasOwnProperty("payload")) { var value = RED.util.getMessageProperty(msg,node.property);
var n = Number(msg.payload); if (value !== undefined) {
var n = Number(value);
if (!isNaN(n)) { if (!isNaN(n)) {
if (node.action == "clamp") { if (node.action == "clamp") {
if (n < node.minin) { n = node.minin; } if (n < node.minin) { n = node.minin; }
@ -38,11 +40,12 @@ module.exports = function(RED) {
var divisor = node.maxin - node.minin; var divisor = node.maxin - node.minin;
n = ((n - node.minin) % divisor + divisor) % divisor + node.minin; n = ((n - node.minin) % divisor + divisor) % divisor + node.minin;
} }
msg.payload = ((n - node.minin) / (node.maxin - node.minin) * (node.maxout - node.minout)) + node.minout; value = ((n - node.minin) / (node.maxin - node.minin) * (node.maxout - node.minout)) + node.minout;
if (node.round) { msg.payload = Math.round(msg.payload); } if (node.round) { value = Math.round(value); }
RED.util.setMessageProperty(msg,node.property,value);
node.send(msg); node.send(msg);
} }
else { node.log(RED._("range.errors.notnumber")+": "+msg.payload); } else { node.log(RED._("range.errors.notnumber")+": "+value); }
} }
else { node.send(msg); } // If no payload - just pass it on. else { node.send(msg); } // If no payload - just pass it on.
}); });

View File

@ -90,11 +90,9 @@
a message and send each complete segment. If there is a partial segment at the end, a message and send each complete segment. If there is a partial segment at the end,
the node will hold on to it and prepend it to the next message that is received. the node will hold on to it and prepend it to the next message that is received.
</p> </p>
<p>When operating in this mode, the node will not set the `msg.parts.count` <p>When operating in this mode, the node will not set the <code>msg.parts.count</code>
property as it does not know how many messages to expect in the stream. This property as it does not know how many messages to expect in the stream. This
means it cannot be used with the <b>join</b> node in its automatic mode</p> means it cannot be used with the <b>join</b> node in its automatic mode</p>
</script> </script>
<script type="text/javascript"> <script type="text/javascript">
@ -170,7 +168,6 @@
<select id="node-input-mode" style="width:200px;"> <select id="node-input-mode" style="width:200px;">
<option value="auto" data-i18n="join.mode.auto"></option> <option value="auto" data-i18n="join.mode.auto"></option>
<option value="custom" data-i18n="join.mode.custom"></option> <option value="custom" data-i18n="join.mode.custom"></option>
<option value="merge" data-i18n="join.mode.merge"></option>
<option value="reduce" data-i18n="join.mode.reduce"></option> <option value="reduce" data-i18n="join.mode.reduce"></option>
</select> </select>
</div> </div>
@ -219,18 +216,6 @@
</ul> </ul>
</div> </div>
</div> </div>
<div class="node-row-merge">
<div class="form-row">
<label data-i18n="join.merge.topics-label"></label>
<div class="form-row node-input-topics-container-row">
<ol id="node-input-topics-container"></ol>
</div>
</div>
<div class="form-row">
<input type="checkbox" id="node-input-mergeOnChange" style="margin-left:10px; vertical-align:top; width:auto;">
<label for="node-input-mergeOnChange" style="width:auto;" data-i18n="join.merge.on-change"></label>
</div>
</div>
<div class="node-row-reduce"> <div class="node-row-reduce">
<div class="form-row"> <div class="form-row">
<label for="node-input-reduceExp" data-i18n="join.reduce.exp" style="margin-left:10px;"></label> <label for="node-input-reduceExp" data-i18n="join.reduce.exp" style="margin-left:10px;"></label>
@ -259,16 +244,15 @@
</script> </script>
<script type="text/x-red" data-help-name="join"> <script type="text/x-red" data-help-name="join">
<p>Joins sequences of messages into a single message. This node provides four mode for message combination:</p> <p>Joins sequences of messages into a single message.</p>
<p>There are three modes available:</p>
<dl> <dl>
<dt>automatic</dt> <dt>automatic</dt>
<dd>When paired with the <b>split</b> node, it will automatically join the messages to reverse the split that was performed.</dd> <dd>When paired with the <b>split</b> node, it will automatically join the messages to reverse the split that was performed.</dd>
<dt>manual</dt> <dt>manual</dt>
<dd>It will join sequences of messages in a variety of ways.</dd> <dd>Join sequences of messages in a variety of ways.</dd>
<dt>merge sequence</dt>
<dd>It will merge incoming messages into single message using <code>topic</code> property.</dd>
<dt>reduce sequence</dt> <dt>reduce sequence</dt>
<dd>When paired with the <b>split</b> node, it will reduce the message sequence into single message.</dd> <dd>Apply an expression against all messages in a sequence to reduce it to a single message.</dd>
</dl> </dl>
<h3>Inputs</h3> <h3>Inputs</h3>
<dl class="message-properties"> <dl class="message-properties">
@ -292,11 +276,13 @@
<h3>Details</h3> <h3>Details</h3>
<h4>Automatic mode</h4> <h4>Automatic mode</h4>
<p>When configured to join in manual mode, the node is able to join sequences of messages using <code>parts</code> property of incoming messages.</p> <p>Automatic mode uses the <code>parts</code> property of incoming messages to
determine how the sequence should be joined. This allows it to automatically
reverse the action of a <b>split</b> node.
<h4>Manual mode</h4> <h4>Manual mode</h4>
<p>When configured to join in manual mode, the node is able to join sequences <p>When configured to join in manual mode, the node is able to join sequences
of messages in a variety of ways.</p> of messages into a number of different results:</p>
<ul> <ul>
<li>a <b>string</b> or <b>buffer</b> - created by joining the selected property of each message with the specified join characters or buffer.</li> <li>a <b>string</b> or <b>buffer</b> - created by joining the selected property of each message with the specified join characters or buffer.</li>
<li>an <b>array</b> - created by adding each selected property, or entire message, to the output array.</li> <li>an <b>array</b> - created by adding each selected property, or entire message, to the output array.</li>
@ -311,49 +297,48 @@
<p>A <i>timeout</i> can be set to trigger sending the new message using whatever has been received so far.</p> <p>A <i>timeout</i> can be set to trigger sending the new message using whatever has been received so far.</p>
<p>If a message is received with the <b>msg.complete</b> property set, the output message is sent.</p> <p>If a message is received with the <b>msg.complete</b> property set, the output message is sent.</p>
<h4>Merge Sequence mode</h4>
<p>When configured to join in merge mode, the join node can create a message based on <code>topic</code> value.</p>
<p>Input messages are merged in order specified by <b>Topics</b> value.
<p>For example, if value of <b>Topics</b> is <b>x,x,y</b>, two input messages with topic <b>x</b> and one input message with topic <b>y</b> are merged into a new message in order of arrival.</p>
<p>If "Send merged message on arrival of a new topic" check box is selected, the last messages with each topic is kept internally and output message is sent when a message with new topics arrives.</p>
<p>The merged message contains <code>payload</code> property and properties for each topic. The <code>payload</code> property represents ordered array of payload value of input messages for each topic. The property for each topic represents a payload value for single occurrence of topic or array of payload values for multiple occurrences of the topic.</p>
<h4>Reduce Sequence mode</h4> <h4>Reduce Sequence mode</h4>
<p>When configured to join in reduce sequence mode, following values can be specified:</p> <p>When configured to join in reduce mode, an expression is applied to each
message in a sequence and the result accumulated to produce a single message.</p>
<dl class="message-properties"> <dl class="message-properties">
<dt>Reduce exp</dt>
<dd>JSONata expression for reducing message group. This expression represents the accumulated result. In the expression, following special variables can be used:
<ul>
<li><code>$A</code> accumulated value, </li>
<li><code>$I</code> index of the message in a group, </li>
<li><code>$N</code> number of messages of a group.</li>
</ul>
</dd>
<dt>Initial value</dt> <dt>Initial value</dt>
<dd> <dd>The initial value of the accumulated value (<code>$A</code>).</dd>
initial value of reduction. <dt>Reduce expression</dt>
</dd> <dd>A JSONata expression that is called for each message in the sequence.
<dt>Fixup exp</dt> The result is passed to the next call of the expression as the accumulated value.
<dd> In the expression, the following special variables can be used:
JSONata expression applied after reduction of a message group completed. In the expression, following special variables can be used:
<ul> <ul>
<li><code>$A</code> accumulated value, </li> <li><code>$A</code>: the accumulated value, </li>
<li><code>$N</code> number of messages of a group.</li> <li><code>$I</code>: index of the message in the sequence, </li>
<li><code>$N</code>: number of messages in the sequence.</li>
</ul> </ul>
</dd> </dd>
<p>Order of reduction on a message group can be specified by checkbox (<b>Evaluate in reverse order (right to left)</b>).</p> <dt>Fix-up expression</dt>
</dl> <dd>An optional JSONata expression that is applied after the reduce expression
<p><b>Example:</b> Join node outputs an average for each input message group with the following setting: has been applied to all messages in the sequence.
In the expression, following special variables can be used:
<ul> <ul>
<li><b>Reduce exp</b>: <code>$A+payload</code></li> <li><code>$A</code>: the accumulated value, </li>
<li><code>$N</code>: number of messages in the sequence.</li>
</ul>
</dd>
<p>By default, the reduce expression is applied in order, from the first
to the last message of the sequence. It can optionally be applied in
reverse order.</p>
</dl>
<p><b>Example:</b> the following settings, given a sequence of numeric values,
calculates the average value:
<ul>
<li><b>Reduce expression</b>: <code>$A+payload</code></li>
<li><b>Initial value</b>: <code>0</code></li> <li><b>Initial value</b>: <code>0</code></li>
<li><b>Fixup exp</b>: <code>$A/$N</code></li> <li><b>Fix-up expression</b>: <code>$A/$N</code></li>
</ul> </ul>
</p> </p>
<h4>Storing messages</h4>
<h4>Note:</h4> <p>This node will buffer messages internally in order to work across sequences. The
<p>This node internally keeps messages for its operation. In order to prevent unexpected memory usage, maximum number of messages kept can be specified by <code>joinMaxKeptMsgsCount</code> property in <b>settings.js</b>.</p> runtime setting `nodeMessageBufferMaxLength` can be used to limit how many messages nodes
will buffer.</p>
</script> </script>
<script type="text/javascript"> <script type="text/javascript">
@ -372,8 +357,6 @@
accumulate: { value:"false" }, accumulate: { value:"false" },
timeout: {value:""}, timeout: {value:""},
count: {value:""}, count: {value:""},
topics: {value:[{topic:""}]},
mergeOnChange: {value:false},
reduceRight: {value:false}, reduceRight: {value:false},
reduceExp: {value:undefined}, reduceExp: {value:undefined},
reduceInit: {value:undefined}, reduceInit: {value:undefined},
@ -391,41 +374,10 @@
}, },
oneditprepare: function() { oneditprepare: function() {
var node = this; var node = this;
var topic_str = node._("join.merge.topic");
function resizeTopics(topic) {
var newWidth = topic.width();
topic.find('.red-ui-typedInput')
.typedInput("width",newWidth-15);
}
$("#node-input-topics-container")
.css('min-height','250px').css('min-width','420px')
.editableList({
addItem: function(container,i,opt) {
if (!opt.hasOwnProperty('topic')) {
opt.topic = "";
}
var row = $('<div/>').appendTo(container);
var valueField = $('<input/>',{
class:"node-input-topic-value",
type:"text",
style:"margin-left: 5px;"
}).appendTo(row)
.typedInput({default:'str', types:['str']});
valueField.typedInput('value', opt.topic);
valueField.typedInput('type', 'str');
valueField.attr('placeholder', topic_str);
resizeTopics(container);
},
resizeItem: resizeTopics,
sortable: true,
removable: true
});
$("#node-input-mode").change(function(e) { $("#node-input-mode").change(function(e) {
var val = $(this).val(); var val = $(this).val();
$(".node-row-custom").toggle(val==='custom'); $(".node-row-custom").toggle(val==='custom');
$(".node-row-merge").toggle(val==='merge');
$(".node-row-reduce").toggle(val==='reduce'); $(".node-row-reduce").toggle(val==='reduce');
$(".form-tips-auto").toggle((val==='auto') || (val==='reduce')); $(".form-tips-auto").toggle((val==='auto') || (val==='reduce'));
if (val === "auto") { if (val === "auto") {
@ -434,15 +386,6 @@
else if (val === "custom") { else if (val === "custom") {
$("#node-input-build").change(); $("#node-input-build").change();
} }
else if (val === "merge") {
var topics = node.topics;
var container = $("#node-input-topics-container");
container.editableList('empty');
for (var i=0;i<topics.length;i++) {
var topic = topics[i];
container.editableList('addItem', topic);
}
}
else if (val === "reduce") { else if (val === "reduce") {
var jsonata_or_empty = { var jsonata_or_empty = {
value: "jsonata", value: "jsonata",
@ -519,27 +462,6 @@
if (build !== 'object' && build !== 'merged') { if (build !== 'object' && build !== 'merged') {
$("#node-input-accumulate").prop("checked",false); $("#node-input-accumulate").prop("checked",false);
} }
var topics = $("#node-input-topics-container").editableList('items');
var node = this;
node.topics = [];
topics.each(function(i) {
var topicData = $(this).data('data');
var topic = $(this);
var vf = topic.find(".node-input-topic-value");
var value = vf.typedInput('value');
var r = {topic:value};
node.topics.push(r);
});
},
oneditresize: function(size) {
var rows = $("#dialog-form>div:not(.node-input-topics-container-row)");
var height = size.height;
for (var i=0;i<rows.size();i++) {
height -= $(rows[i]).outerHeight(true);
}
var editorRow = $("#dialog-form>div.node-input-topics-container-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-topics-container").editableList('height',height);
} }
}); });
</script> </script>

View File

@ -236,7 +236,7 @@ module.exports = function(RED) {
function max_kept_msgs_count(node) { function max_kept_msgs_count(node) {
if (_max_kept_msgs_count === undefined) { if (_max_kept_msgs_count === undefined) {
var name = "joinMaxKeptMsgsCount"; var name = "nodeMessageBufferMaxLength";
if (RED.settings.hasOwnProperty(name)) { if (RED.settings.hasOwnProperty(name)) {
_max_kept_msgs_count = RED.settings[name]; _max_kept_msgs_count = RED.settings[name];
} }
@ -247,89 +247,6 @@ module.exports = function(RED) {
return _max_kept_msgs_count; return _max_kept_msgs_count;
} }
function add_to_topic(node, pending, topic, msg) {
var merge_on_change = node.merge_on_change;
if (!pending.hasOwnProperty(topic)) {
pending[topic] = [];
}
var topics = pending[topic];
topics.push(msg);
if (merge_on_change) {
var counts = node.topic_counts;
if (topics.length > counts[topic]) {
topics.shift();
node.pending_count--;
}
}
}
function compute_topic_counts(topics) {
var counts = {};
for (var topic of topics) {
counts[topic] = (counts.hasOwnProperty(topic) ? counts[topic] : 0) +1;
}
return counts;
}
function try_merge(node, pending, merge_on_change) {
var topics = node.topics;
var counts = node.topic_counts;
for(var topic of topics) {
if(!pending.hasOwnProperty(topic) ||
(pending[topic].length < counts[topic])) {
return;
}
}
var merge_on_change = node.merge_on_change;
var msgs = [];
var vals = [];
var new_msg = {payload: vals};
for (var topic of topics) {
var pmsgs = pending[topic];
var msg = pmsgs.shift();
if (merge_on_change) {
pmsgs.push(msg);
}
var pval = msg.payload;
var val = new_msg[topic];
msgs.push(msg);
vals.push(pval);
if (val instanceof Array) {
new_msg[topic].push(pval);
}
else if (val === undefined) {
new_msg[topic] = pval;
}
else {
new_msg[topic] = [val, pval]
}
}
node.send(new_msg);
if (!merge_on_change) {
node.pending_count -= topics.length;
}
}
function merge_msg(node, msg) {
var topics = node.topics;
var topic = msg.topic;
if(node.topics.indexOf(topic) >= 0) {
var pending = node.pending;
if(node.topic_counts == undefined) {
node.topic_counts = compute_topic_counts(topics)
}
add_to_topic(node, pending, topic, msg);
node.pending_count++;
var max_msgs = max_kept_msgs_count(node);
if ((max_msgs > 0) && (node.pending_count > max_msgs)) {
node.pending = {};
node.pending_count = 0;
node.error(RED._("join.too-many"), msg);
}
try_merge(node, pending);
}
}
function apply_r(exp, accum, msg, index, count) { function apply_r(exp, accum, msg, index, count) {
exp.assign("I", index); exp.assign("I", index);
exp.assign("N", count); exp.assign("N", count);
@ -381,8 +298,8 @@ module.exports = function(RED) {
var pending = node.pending; var pending = node.pending;
var pending_count = node.pending_count; var pending_count = node.pending_count;
var gid = msg.parts.id; var gid = msg.parts.id;
var count;
if(!pending.hasOwnProperty(gid)) { if(!pending.hasOwnProperty(gid)) {
var count = undefined;
if(parts.hasOwnProperty('count')) { if(parts.hasOwnProperty('count')) {
count = msg.parts.count; count = msg.parts.count;
} }
@ -568,10 +485,6 @@ module.exports = function(RED) {
node.warn("Message missing msg.parts property - cannot join in 'auto' mode") node.warn("Message missing msg.parts property - cannot join in 'auto' mode")
return; return;
} }
if (node.mode === 'merge' && !msg.hasOwnProperty("topic")) {
node.warn("Message missing msg.topic property - cannot join in 'merge' mode");
return;
}
if (node.propertyType == "full") { if (node.propertyType == "full") {
property = msg; property = msg;
@ -602,10 +515,6 @@ module.exports = function(RED) {
arrayLen = msg.parts.len; arrayLen = msg.parts.len;
propertyIndex = msg.parts.index; propertyIndex = msg.parts.index;
} }
else if (node.mode === 'merge') {
merge_msg(node, msg);
return;
}
else if (node.mode === 'reduce') { else if (node.mode === 'reduce') {
reduce_msg(node, msg); reduce_msg(node, msg);
return; return;

View File

@ -81,7 +81,7 @@
</p> </p>
<p><b>Note:</b> This node internally keeps messages for its operation. In order to prevent unexpected memory usage, maximum number of messages kept can be specified. Default is no limit on number of messages. <p><b>Note:</b> This node internally keeps messages for its operation. In order to prevent unexpected memory usage, maximum number of messages kept can be specified. Default is no limit on number of messages.
<ul> <ul>
<li><code>maxKeptMsgsCount</code> property set in <b>settings.js</b>.</li> <li><code>nodeMessageBufferMaxLength</code> property set in <b>settings.js</b>.</li>
</ul> </ul>
</p> </p>
</script> </script>

View File

@ -21,7 +21,7 @@ module.exports = function(RED) {
function max_kept_msgs_count(node) { function max_kept_msgs_count(node) {
if (_max_kept_msgs_count === undefined) { if (_max_kept_msgs_count === undefined) {
var name = "maxKeptMsgsCount"; var name = "nodeMessageBufferMaxLength";
if (RED.settings.hasOwnProperty(name)) { if (RED.settings.hasOwnProperty(name)) {
_max_kept_msgs_count = RED.settings[name]; _max_kept_msgs_count = RED.settings[name];
} }

View File

@ -18,7 +18,7 @@
<script type="text/x-red" data-template-name="batch"> <script type="text/x-red" data-template-name="batch">
<div class="form-row"> <div class="form-row">
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> <span data-i18n="batch.mode.label"></span></label> <label for="node-input-mode"><span data-i18n="batch.mode.label"></span></label>
<select type="text" id="node-input-mode" style="width: 300px;"> <select type="text" id="node-input-mode" style="width: 300px;">
<option value="count" data-i18n="batch.mode.num-msgs"></option> <option value="count" data-i18n="batch.mode.num-msgs"></option>
<option value="interval" data-i18n="batch.mode.interval"></option> <option value="interval" data-i18n="batch.mode.interval"></option>
@ -28,26 +28,26 @@
<div class="node-row-msg-count"> <div class="node-row-msg-count">
<div class="form-row node-row-count"> <div class="form-row node-row-count">
<label for="node-input-count" data-i18n="batch.count.label"></label> <label style="margin-left: 10px; width: 175px;" for="node-input-count" data-i18n="batch.count.label"></label>
<input type="text" id="node-input-count" data-i18n="[placeholder]batch.count.count" style="width: 50px;"> <input type="text" id="node-input-count" style="width: 50px;">
</div> </div>
</div> </div>
<div class="node-row-msg-overwrap"> <div class="node-row-msg-overlap">
<div class="form-row node-row-overwrap"> <div class="form-row node-row-overlap">
<label for="node-input-count" data-i18n="batch.count.overwrap"></label> <label style="margin-left: 10px; width: 175px;" for="node-input-overlap" data-i18n="batch.count.overlap"></label>
<input type="text" id="node-input-overwrap" data-i18n="[placeholder]batch.count.count" style="width: 50px;"> <input type="text" id="node-input-overlap" style="width: 50px;">
</div> </div>
</div> </div>
<div class="node-row-msg-interval"> <div class="node-row-msg-interval">
<div class="form-row node-row-interval"> <div class="form-row node-row-interval">
<label for="node-input-interval"> <span data-i18n="batch.interval.label"></span></label> <label style="margin-left: 10px; width: 175px;" for="node-input-interval"> <span data-i18n="batch.interval.label"></span></label>
<input type="text" id="node-input-interval" data-i18n="[placeholder]batch.interval.seconds" style="width: 50px;"> <input type="text" id="node-input-interval" style="width: 50px;">
<span data-i18n="batch.interval.sec"></span> <span data-i18n="batch.interval.seconds"></span>
</div> </div>
<div class="form-row"> <div class="form-row">
<input type="checkbox" id="node-input-allowEmptySequence" style="margin-left:10px; vertical-align:top; width:auto;"> <input type="checkbox" id="node-input-allowEmptySequence" style="margin-left:20px; margin-right: 10px; vertical-align:top; width:auto;">
<label for="node-input-allowEmptySequence" style="width:auto;" data-i18n="batch.interval.empty"></label> <label for="node-input-allowEmptySequence" style="width:auto;" data-i18n="batch.interval.empty"></label>
</div> </div>
</div> </div>
@ -69,20 +69,31 @@
</script> </script>
<script type="text/x-red" data-help-name="batch"> <script type="text/x-red" data-help-name="batch">
<p>A function that divides input messages into multiple sequences of messages or concatenates multiple sequences of messages into a single messages sequence.</p> <p>Creates sequences of messages based on various rules.</p>
<h3>Details</h3> <h3>Details</h3>
<h4>group by number of messages</h4> <p>There are three modes for creating message sequences:</p>
<p>groups incoming messages into sequences of messages with specified counts. The output message group can be overwrapped.</p> <dl>
<h4>group by interval in seconds</h4> <dt>Number of messages</dt>
<p>groups incoming messages received withn specified interval into sequences of messages. </p> <dd>groups messages into sequences of a given length. The <b>overlap</b>
<h4>concatenate message groups</h4> option specifies how many messages and the end of one sequence should be
<p>creates a message sequence based on <code>topic</code> value of incoming message sequences.</p> repeated at the start of the next sequence.</dd>
<p>Target and order of concatenated sequences are specified by <code>Topics</code> value. Selection of concatenated message groups is based on arrival of first message of the group.</p>
<p><b>Note:</b> This node internally keeps messages for its operation. In order to prevent unexpected memory usage, maximum number of messages kept can be specified. Default is no limit on number of messages. <dt>Time interval</dt>
<ul> <dd>groups messages that arrive within the specified interval. If no messages
<li><code>batchMaxKeptMsgsCount</code> property set in <b>settings.js</b>.</li> arrive within the interval, the node can optionally send on an empty message.</dd>
</ul>
</p> <dt>Concatenate Sequences</dt>
<dd>creates a message sequence by concatenating incoming sequences. Each sequence
must have a <code>msg.topic</code> property to identify it. The node is
configured with a list of <code>topic</code> values to identify the order
sequences are concatenated.
</dd>
</dd>
</dl>
<h4>Storing messages</h4>
<p>This node will buffer messages internally in order to work across sequences. The
runtime setting `nodeMessageBufferMaxLength` can be used to limit how many messages nodes
will buffer.</p>
</script> </script>
<script type="text/javascript"> <script type="text/javascript">
@ -93,7 +104,7 @@
name: {value:""}, name: {value:""},
mode: {value:"count"}, mode: {value:"count"},
count: {value:10}, count: {value:10},
overwrap: {value:0}, overlap: {value:0},
interval: {value:10}, interval: {value:10},
allowEmptySequence: {value:false}, allowEmptySequence: {value:false},
topics: {value:[{topic:""}]} topics: {value:[{topic:""}]}
@ -143,14 +154,14 @@
$("#node-input-count").spinner({ $("#node-input-count").spinner({
}); });
$("#node-input-overwrap").spinner({ $("#node-input-overlap").spinner({
}); });
$("#node-input-interval").spinner({ $("#node-input-interval").spinner({
}); });
$("#node-input-mode").change(function(e) { $("#node-input-mode").change(function(e) {
var val = $(this).val(); var val = $(this).val();
$(".node-row-msg-count").toggle(val==="count"); $(".node-row-msg-count").toggle(val==="count");
$(".node-row-msg-overwrap").toggle(val==="count"); $(".node-row-msg-overlap").toggle(val==="count");
$(".node-row-msg-interval").toggle(val==="interval"); $(".node-row-msg-interval").toggle(val==="interval");
$(".node-row-msg-concat").toggle(val==="concat"); $(".node-row-msg-concat").toggle(val==="concat");
if (val==="concat") { if (val==="concat") {

View File

@ -21,7 +21,7 @@ module.exports = function(RED) {
function max_kept_msgs_count(node) { function max_kept_msgs_count(node) {
if (_max_kept_msgs_count === undefined) { if (_max_kept_msgs_count === undefined) {
var name = "batchMaxKeptMsgsCount"; var name = "nodeMessageBufferMaxLength";
if (RED.settings.hasOwnProperty(name)) { if (RED.settings.hasOwnProperty(name)) {
_max_kept_msgs_count = RED.settings[name]; _max_kept_msgs_count = RED.settings[name];
} }
@ -171,9 +171,9 @@ module.exports = function(RED) {
node.pending_count = 0; node.pending_count = 0;
if (mode === "count") { if (mode === "count") {
var count = Number(n.count || 1); var count = Number(n.count || 1);
var overwrap = Number(n.overwrap || 0); var overlap = Number(n.overlap || 0);
var is_overwrap = (overwrap > 0); var is_overlap = (overlap > 0);
if (count <= overwrap) { if (count <= overlap) {
node.error(RED._("batch.count.invalid")); node.error(RED._("batch.count.invalid"));
return; return;
} }
@ -183,9 +183,9 @@ module.exports = function(RED) {
queue.push(msg); queue.push(msg);
node.pending_count++; node.pending_count++;
if (queue.length === count) { if (queue.length === count) {
send_msgs(node, queue, is_overwrap); send_msgs(node, queue, is_overlap);
node.pending = node.pending =
(overwrap === 0) ? [] : queue.slice(-overwrap); (overlap === 0) ? [] : queue.slice(-overlap);
node.pending_count = 0; node.pending_count = 0;
} }
var max_msgs = max_kept_msgs_count(node); var max_msgs = max_kept_msgs_count(node);

View File

@ -1,9 +1,5 @@
<script type="text/x-red" data-template-name="json"> <script type="text/x-red" data-template-name="json">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-row"> <div class="form-row">
<label for="node-input-action"><span data-i18n="json.label.action"></span></label> <label for="node-input-action"><span data-i18n="json.label.action"></span></label>
<select style="width:70%" id="node-input-action"> <select style="width:70%" id="node-input-action">
@ -14,11 +10,13 @@
</div> </div>
<div class="form-row"> <div class="form-row">
<label data-i18n="json.label.property"></label> <label data-i18n="json.label.property"></label>
<input type="text" id="node-input-property" style="width: 70%"/> <input type="text" id="node-input-property" style="width:70%;"/>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<hr align="middle"/> <hr align="middle"/>
<div class="form-row node-json-to-json-options"> <div class="form-row node-json-to-json-options">
<label style="width:100%; border-bottom: 1px solid #eee;"><span data-i18n="json.label.o2j"></span></label> <label style="width:100%; border-bottom: 1px solid #eee;"><span data-i18n="json.label.o2j"></span></label>
</div> </div>
@ -55,7 +53,6 @@
receives a String, no further checks will be made of the property. It will receives a String, no further checks will be made of the property. It will
not check the String is valid JSON nor will it reformat it if the format option not check the String is valid JSON nor will it reformat it if the format option
is selected.</p> is selected.</p>
</script> </script>
<script type="text/javascript"> <script type="text/javascript">
@ -64,8 +61,8 @@
color:"#DEBD5C", color:"#DEBD5C",
defaults: { defaults: {
name: {value:""}, name: {value:""},
property: { value:"payload" }, property: {value:"payload",required:true},
action: { value:"" }, action: {value:""},
pretty: {value:false} pretty: {value:false}
}, },
inputs:1, inputs:1,

View File

@ -1,5 +1,9 @@
<script type="text/x-red" data-template-name="xml"> <script type="text/x-red" data-template-name="xml">
<div class="form-row">
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="node-red:common.label.property"></span></label>
<input type="text" id="node-input-property" style="width:70%;"/>
</div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
@ -58,6 +62,7 @@
color:"#DEBD5C", color:"#DEBD5C",
defaults: { defaults: {
name: {value:""}, name: {value:""},
property: {value:"payload",required:true},
attr: {value:""}, attr: {value:""},
chr: {value:""} chr: {value:""}
}, },
@ -69,6 +74,12 @@
}, },
labelStyle: function() { labelStyle: function() {
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
},
oneditprepare: function() {
if (this.property === undefined) {
$("#node-input-property").val("payload");
}
$("#node-input-property").typedInput({default:'msg',types:['msg']});
} }
}); });
</script> </script>

View File

@ -1,18 +1,3 @@
/**
* 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.
**/
module.exports = function(RED) { module.exports = function(RED) {
"use strict"; "use strict";
@ -23,35 +8,39 @@ module.exports = function(RED) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
this.attrkey = n.attr; this.attrkey = n.attr;
this.charkey = n.chr; this.charkey = n.chr;
this.property = n.property||"payload";
var node = this; var node = this;
this.on("input", function(msg) { this.on("input", function(msg) {
if (msg.hasOwnProperty("payload")) { var value = RED.util.getMessageProperty(msg,node.property);
if (value !== undefined) {
var options; var options;
if (typeof msg.payload === "object") { if (typeof value === "object") {
options = {renderOpts:{pretty:false}}; options = {renderOpts:{pretty:false}};
if (msg.hasOwnProperty("options") && typeof msg.options === "object") { options = msg.options; } if (msg.hasOwnProperty("options") && typeof msg.options === "object") { options = msg.options; }
options.async = false; options.async = false;
var builder = new xml2js.Builder(options); var builder = new xml2js.Builder(options);
msg.payload = builder.buildObject(msg.payload, options); value = builder.buildObject(value, options);
RED.util.setMessageProperty(msg,node.property,value);
node.send(msg); node.send(msg);
} }
else if (typeof msg.payload == "string") { else if (typeof value == "string") {
options = {}; options = {};
if (msg.hasOwnProperty("options") && typeof msg.options === "object") { options = msg.options; } if (msg.hasOwnProperty("options") && typeof msg.options === "object") { options = msg.options; }
options.async = true; options.async = true;
options.attrkey = node.attrkey || options.attrkey || '$'; options.attrkey = node.attrkey || options.attrkey || '$';
options.charkey = node.charkey || options.charkey || '_'; options.charkey = node.charkey || options.charkey || '_';
parseString(msg.payload, options, function (err, result) { parseString(value, options, function (err, result) {
if (err) { node.error(err, msg); } if (err) { node.error(err, msg); }
else { else {
msg.payload = result; value = result;
RED.util.setMessageProperty(msg,node.property,value);
node.send(msg); node.send(msg);
} }
}); });
} }
else { node.warn(RED._("xml.errors.xml_js")); } else { node.warn(RED._("xml.errors.xml_js")); }
} }
else { node.send(msg); } // If no payload - just pass it on. else { node.send(msg); } // If no property - just pass it on.
}); });
} }
RED.nodes.registerType("xml",XMLNode); RED.nodes.registerType("xml",XMLNode);

View File

@ -1,5 +1,9 @@
<script type="text/x-red" data-template-name="yaml"> <script type="text/x-red" data-template-name="yaml">
<div class="form-row">
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="common.label.property"></span></label>
<input type="text" id="node-input-property" style="width:calc(70% - 1px);"/>
</div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
@ -30,6 +34,7 @@
category: 'function', category: 'function',
color:"#DEBD5C", color:"#DEBD5C",
defaults: { defaults: {
property: {value:"payload",required:true},
name: {value:""} name: {value:""}
}, },
inputs:1, inputs:1,
@ -40,6 +45,12 @@
}, },
labelStyle: function() { labelStyle: function() {
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
},
oneditprepare: function() {
if (this.property === undefined) {
$("#node-input-property").val("payload");
}
$("#node-input-property").typedInput({default:'msg',types:['msg']});
} }
}); });
</script> </script>

View File

@ -4,20 +4,24 @@ module.exports = function(RED) {
var yaml = require('js-yaml'); var yaml = require('js-yaml');
function YAMLNode(n) { function YAMLNode(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
this.property = n.property||"payload";
var node = this; var node = this;
this.on("input", function(msg) { this.on("input", function(msg) {
if (msg.hasOwnProperty("payload")) { var value = RED.util.getMessageProperty(msg,node.property);
if (typeof msg.payload === "string") { if (value !== undefined) {
if (typeof value === "string") {
try { try {
msg.payload = yaml.load(msg.payload); value = yaml.load(value);
RED.util.setMessageProperty(msg,node.property,value);
node.send(msg); node.send(msg);
} }
catch(e) { node.error(e.message,msg); } catch(e) { node.error(e.message,msg); }
} }
else if (typeof msg.payload === "object") { else if (typeof value === "object") {
if (!Buffer.isBuffer(msg.payload)) { if (!Buffer.isBuffer(value)) {
try { try {
msg.payload = yaml.dump(msg.payload); value = yaml.dump(value);
RED.util.setMessageProperty(msg,node.property,value);
node.send(msg); node.send(msg);
} }
catch(e) { catch(e) {

View File

@ -14,8 +14,6 @@
* limitations under the License. * limitations under the License.
**/ **/
var when = require("when");
var clients = [ var clients = [
{id:"node-red-editor",secret:"not_available"}, {id:"node-red-editor",secret:"not_available"},
{id:"node-red-admin",secret:"not_available"} {id:"node-red-admin",secret:"not_available"}
@ -25,9 +23,9 @@ module.exports = {
get: function(id) { get: function(id) {
for (var i=0;i<clients.length;i++) { for (var i=0;i<clients.length;i++) {
if (clients[i].id == id) { if (clients[i].id == id) {
return when.resolve(clients[i]); return Promise.resolve(clients[i]);
} }
} }
return when.resolve(null); return Promise.resolve(null);
} }
} }

View File

@ -135,21 +135,8 @@ function completeVerify(profile,done) {
}); });
} }
module.exports = {
init: init, function genericStrategy(adminApp,strategy) {
needsPermission: needsPermission,
ensureClientSecret: ensureClientSecret,
authenticateClient: authenticateClient,
getToken: getToken,
errorHandler: function(err,req,res,next) {
//TODO: audit log statment
//console.log(err.stack);
//log.log({level:"audit",type:"auth",msg:err.toString()});
return server.errorHandler()(err,req,res,next);
},
login: login,
revoke: revoke,
genericStrategy: function(adminApp,strategy) {
var crypto = require("crypto") var crypto = require("crypto")
var session = require('express-session') var session = require('express-session')
var MemoryStore = require('memorystore')(session) var MemoryStore = require('memorystore')(session)
@ -174,7 +161,7 @@ module.exports = {
function() { function() {
var originalDone = arguments[arguments.length-1]; var originalDone = arguments[arguments.length-1];
if (options.verify) { if (options.verify) {
var args = Array.prototype.slice.call(arguments); var args = Array.from(arguments);
args[args.length-1] = function(err,profile) { args[args.length-1] = function(err,profile) {
if (err) { if (err) {
return originalDone(err); return originalDone(err);
@ -191,16 +178,36 @@ module.exports = {
} }
)); ));
adminApp.get('/auth/strategy', passport.authenticate(strategy.name)); adminApp.get('/auth/strategy',
passport.authenticate(strategy.name, {session:false, failureRedirect: settings.httpAdminRoot }),
completeGenerateStrategyAuth
);
adminApp.get('/auth/strategy/callback', adminApp.get('/auth/strategy/callback',
passport.authenticate(strategy.name, {session:false, failureRedirect: settings.httpAdminRoot }), passport.authenticate(strategy.name, {session:false, failureRedirect: settings.httpAdminRoot }),
function(req, res) { completeGenerateStrategyAuth
);
}
function completeGenerateStrategyAuth(req,res) {
var tokens = req.user.tokens; var tokens = req.user.tokens;
delete req.user.tokens; delete req.user.tokens;
// Successful authentication, redirect home. // Successful authentication, redirect home.
res.redirect(settings.httpAdminRoot + '?access_token='+tokens.accessToken); res.redirect(settings.httpAdminRoot + '?access_token='+tokens.accessToken);
} }
);
module.exports = {
} init: init,
needsPermission: needsPermission,
ensureClientSecret: ensureClientSecret,
authenticateClient: authenticateClient,
getToken: getToken,
errorHandler: function(err,req,res,next) {
//TODO: audit log statment
//console.log(err.stack);
//log.log({level:"audit",type:"auth",msg:err.toString()});
return server.errorHandler()(err,req,res,next);
},
login: login,
revoke: revoke,
genericStrategy: genericStrategy
} }

View File

@ -14,8 +14,6 @@
* limitations under the License. * limitations under the License.
**/ **/
var when = require("when");
function generateToken(length) { function generateToken(length) {
var c = "ABCDEFGHIJKLMNOPQRSTUZWXYZabcdefghijklmnopqrstuvwxyz1234567890"; var c = "ABCDEFGHIJKLMNOPQRSTUZWXYZabcdefghijklmnopqrstuvwxyz1234567890";
var token = []; var token = [];
@ -49,7 +47,7 @@ function expireSessions() {
if (modified) { if (modified) {
return storage.saveSessions(sessions); return storage.saveSessions(sessions);
} else { } else {
return when.resolve(); return Promise.resolve();
} }
} }
function loadSessions() { function loadSessions() {
@ -69,7 +67,7 @@ module.exports = {
// At this point, storage will not have been initialised, so defer loading // At this point, storage will not have been initialised, so defer loading
// the sessions until there's a request for them. // the sessions until there's a request for them.
loadedSessions = null; loadedSessions = null;
return when.resolve(); return Promise.resolve();
}, },
get: function(token) { get: function(token) {
return loadSessions().then(function() { return loadSessions().then(function() {
@ -78,7 +76,7 @@ module.exports = {
return expireSessions().then(function() { return null }); return expireSessions().then(function() { return null });
} }
} }
return when.resolve(sessions[token]); return Promise.resolve(sessions[token]);
}); });
}, },
create: function(user,client,scope) { create: function(user,client,scope) {

View File

@ -14,13 +14,12 @@
* limitations under the License. * limitations under the License.
**/ **/
var when = require("when");
var util = require("util"); var util = require("util");
var clone = require("clone");
var bcrypt; var bcrypt;
try { bcrypt = require('bcrypt'); } try { bcrypt = require('bcrypt'); }
catch(e) { bcrypt = require('bcryptjs'); } catch(e) { bcrypt = require('bcryptjs'); }
var users = {}; var users = {};
var passwords = {};
var defaultUser = null; var defaultUser = null;
function authenticate() { function authenticate() {
@ -28,31 +27,33 @@ function authenticate() {
if (typeof username !== 'string') { if (typeof username !== 'string') {
username = username.username; username = username.username;
} }
var user = users[username]; const args = Array.from(arguments);
return api.get(username).then(function(user) {
if (user) { if (user) {
if (arguments.length === 2) { if (args.length === 2) {
// Username/password authentication // Username/password authentication
var password = arguments[1]; var password = args[1];
return when.promise(function(resolve,reject) { return new Promise(function(resolve,reject) {
bcrypt.compare(password, passwords[username], function(err, res) { bcrypt.compare(password, user.password, function(err, res) {
resolve(res?user:null); resolve(res?cleanUser(user):null);
}); });
}); });
} else { } else {
// Try to extract common profile information // Try to extract common profile information
if (arguments[0].hasOwnProperty('photos') && arguments[0].photos.length > 0) { if (args[0].hasOwnProperty('photos') && args[0].photos.length > 0) {
user.image = arguments[0].photos[0].value; user.image = args[0].photos[0].value;
} }
return when.resolve(user); return cleanUser(user);
} }
} }
return when.resolve(null); return null;
});
} }
function get(username) { function get(username) {
return when.resolve(users[username]); return Promise.resolve(users[username]);
} }
function getDefaultUser() { function getDefaultUser() {
return when.resolve(null); return Promise.resolve(null);
} }
var api = { var api = {
@ -63,7 +64,6 @@ var api = {
function init(config) { function init(config) {
users = {}; users = {};
passwords = {};
defaultUser = null; defaultUser = null;
if (config.type == "credentials" || config.type == "strategy") { if (config.type == "credentials" || config.type == "strategy") {
if (config.users) { if (config.users) {
@ -77,11 +77,7 @@ function init(config) {
} }
for (var i=0;i<us.length;i++) { for (var i=0;i<us.length;i++) {
var u = us[i]; var u = us[i];
users[u.username] = { users[u.username] = clone(u);
"username":u.username,
"permissions":u.permissions
};
passwords[u.username] = u.password;
} }
} }
} }
@ -96,7 +92,7 @@ function init(config) {
api.default = config.default; api.default = config.default;
} else { } else {
api.default = function() { api.default = function() {
return when.resolve({ return Promise.resolve({
"anonymous": true, "anonymous": true,
"permissions":config.default.permissions "permissions":config.default.permissions
}); });
@ -106,10 +102,17 @@ function init(config) {
api.default = getDefaultUser; api.default = getDefaultUser;
} }
} }
function cleanUser(user) {
if (user && user.hasOwnProperty('password')) {
user = clone(user);
delete user.password;
}
return user;
}
module.exports = { module.exports = {
init: init, init: init,
get: function(username) { return api.get(username) }, get: function(username) { return api.get(username).then(cleanUser)},
authenticate: function() { return api.authenticate.apply(null, arguments) }, authenticate: function() { return api.authenticate.apply(null, arguments) },
default: function() { return api.default(); } default: function() { return api.default(); }
}; };

View File

@ -29,8 +29,7 @@ var npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
var paletteEditorEnabled = false; var paletteEditorEnabled = false;
var settings; var settings;
var moduleRe = /^(@[^/]+?[/])?[^/]+?$/;
var moduleRe = /^[^/]+$/;
var slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/; var slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/;
function init(_settings) { function init(_settings) {
@ -71,7 +70,6 @@ function checkExistingModule(module,version) {
} }
return false; return false;
} }
function installModule(module,version) { function installModule(module,version) {
//TODO: ensure module is 'safe' //TODO: ensure module is 'safe'
return when.promise(function(resolve,reject) { return when.promise(function(resolve,reject) {
@ -99,24 +97,30 @@ function installModule(module,version) {
} }
var installDir = settings.userDir || process.env.NODE_RED_HOME || "."; var installDir = settings.userDir || process.env.NODE_RED_HOME || ".";
var args = ['install','--save','--save-prefix="~"','--production',installName]; var args = ['install','--save','--save-prefix="~"','--production',installName];
log.trace(npmCommand + JSON.stringify(args)); log.trace(npmCommand + JSON.stringify(args));
var child = child_process.execFile(npmCommand,args, var child = child_process.spawn(npmCommand,args,{
{ cwd: installDir,
cwd: installDir shell: true
}, });
function(err, stdin, stdout) { var output = "";
if (err) { child.stdout.on('data', (data) => {
output += data;
});
child.stderr.on('data', (data) => {
output += data;
});
child.on('close', (code) => {
if (code !== 0) {
var e; var e;
var lookFor404 = new RegExp(" 404 .*"+module+"$","m"); var lookFor404 = new RegExp(" 404 .*"+module,"m");
var lookForVersionNotFound = new RegExp("version not found: "+module+"@"+version,"m"); var lookForVersionNotFound = new RegExp("version not found: "+module+"@"+version,"m");
if (lookFor404.test(stdout)) { if (lookFor404.test(output)) {
log.warn(log._("server.install.install-failed-not-found",{name:module})); log.warn(log._("server.install.install-failed-not-found",{name:module}));
e = new Error("Module not found"); e = new Error("Module not found");
e.code = 404; e.code = 404;
reject(e); reject(e);
} else if (isUpgrade && lookForVersionNotFound.test(stdout)) { } else if (isUpgrade && lookForVersionNotFound.test(output)) {
log.warn(log._("server.install.upgrade-failed-not-found",{name:module})); log.warn(log._("server.install.upgrade-failed-not-found",{name:module}));
e = new Error("Module not found"); e = new Error("Module not found");
e.code = 404; e.code = 404;
@ -124,7 +128,7 @@ function installModule(module,version) {
} else { } else {
log.warn(log._("server.install.install-failed-long",{name:module})); log.warn(log._("server.install.install-failed-long",{name:module}));
log.warn("------------------------------------------"); log.warn("------------------------------------------");
log.warn(err.toString()); log.warn(output);
log.warn("------------------------------------------"); log.warn("------------------------------------------");
reject(new Error(log._("server.install.install-failed"))); reject(new Error(log._("server.install.install-failed")));
} }
@ -138,8 +142,7 @@ function installModule(module,version) {
resolve(require("./registry").setModulePendingUpdated(module,version)); resolve(require("./registry").setModulePendingUpdated(module,version));
} }
} }
} });
);
}); });
} }

View File

@ -115,12 +115,26 @@ function getLocalNodeFiles(dir) {
function scanDirForNodesModules(dir,moduleName) { function scanDirForNodesModules(dir,moduleName) {
var results = []; var results = [];
var scopeName;
try { try {
var files = fs.readdirSync(dir); var files = fs.readdirSync(dir);
if (moduleName) {
var m = /^(?:(@[^/]+)[/])?([^@/]+)/.exec(moduleName);
if (m) {
scopeName = m[1];
moduleName = m[2];
}
}
for (var i=0;i<files.length;i++) { for (var i=0;i<files.length;i++) {
var fn = files[i]; var fn = files[i];
if (/^@/.test(fn)) { if (/^@/.test(fn)) {
if (scopeName && scopeName === fn) {
// Looking for a specific scope/module
results = results.concat(scanDirForNodesModules(path.join(dir,fn),moduleName)); results = results.concat(scanDirForNodesModules(path.join(dir,fn),moduleName));
break;
} else {
results = results.concat(scanDirForNodesModules(path.join(dir,fn),moduleName));
}
} else { } else {
if (isIncluded(fn) && !isExcluded(fn) && (!moduleName || fn == moduleName)) { if (isIncluded(fn) && !isExcluded(fn) && (!moduleName || fn == moduleName)) {
var pkgfn = path.join(dir,fn,"package.json"); var pkgfn = path.join(dir,fn,"package.json");

View File

@ -47,9 +47,10 @@ module.exports = {
// The maximum length, in characters, of any message sent to the debug sidebar tab // The maximum length, in characters, of any message sent to the debug sidebar tab
debugMaxLength: 1000, debugMaxLength: 1000,
// The maximum number of messages kept internally in nodes. // The maximum number of messages nodes will buffer internally as part of their
// Zero or undefined value means not restricting number of messages. // operation. This applies across a range of nodes that operate on message sequences.
//maxKeptMsgsCount: 0, // defaults to no limit. A value of 0 also means no limit is applied.
//nodeMaxMessageBufferLength: 0,
// To disable the option for using local files for storing keys and certificates in the TLS configuration // To disable the option for using local files for storing keys and certificates in the TLS configuration
// node, set this to true // node, set this to true

View File

@ -75,6 +75,28 @@ describe('sentiment Node', function() {
}); });
}); });
it('should add a positive score for good words - alternative property', function(done) {
var flow = [{id:"jn1",type:"sentiment",property:"foo",wires:[["jn2"]]},
{id:"jn2", type:"helper"}];
helper.load(sentimentNode, flow, function() {
var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2");
jn2.on("input", function(msg) {
try {
msg.should.have.property('sentiment');
msg.sentiment.should.have.property('score');
msg.sentiment.score.should.be.a.Number();
msg.sentiment.score.should.be.above(10);
done();
} catch(err) {
done(err);
}
});
var testString = 'good, great, best, brilliant';
jn1.receive({foo:testString});
});
});
it('should add a negative score for bad words', function(done) { it('should add a negative score for bad words', function(done) {
var flow = [{id:"jn1",type:"sentiment",wires:[["jn2"]]}, var flow = [{id:"jn1",type:"sentiment",wires:[["jn2"]]},
{id:"jn2", type:"helper"}]; {id:"jn2", type:"helper"}];
@ -93,6 +115,24 @@ describe('sentiment Node', function() {
}); });
}); });
it('should add a negative score for bad words - alternative property', function(done) {
var flow = [{id:"jn1",type:"sentiment",property:"foo",wires:[["jn2"]]},
{id:"jn2", type:"helper"}];
helper.load(sentimentNode, flow, function() {
var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2");
jn2.on("input", function(msg) {
msg.should.have.property('sentiment');
msg.sentiment.should.have.property('score');
msg.sentiment.score.should.be.a.Number();
msg.sentiment.score.should.be.below(-10);
done();
});
var testString = 'bad, horrible, negative, awful';
jn1.receive({foo:testString});
});
});
it('should allow you to override word scoring', function(done) { it('should allow you to override word scoring', function(done) {
var flow = [{id:"jn1",type:"sentiment",wires:[["jn2"]]}, var flow = [{id:"jn1",type:"sentiment",wires:[["jn2"]]},
{id:"jn2", type:"helper"}]; {id:"jn2", type:"helper"}];
@ -112,4 +152,23 @@ describe('sentiment Node', function() {
}); });
}); });
it('should allow you to override word scoring - alternative property', function(done) {
var flow = [{id:"jn1",type:"sentiment",property:"foo",wires:[["jn2"]]},
{id:"jn2", type:"helper"}];
helper.load(sentimentNode, flow, function() {
var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2");
jn2.on("input", function(msg) {
msg.should.have.property('sentiment');
msg.sentiment.should.have.property('score');
msg.sentiment.score.should.be.a.Number();
msg.sentiment.score.should.equal(20);
done();
});
var testString = 'sick, wicked';
var overrides = {'sick': 10, 'wicked': 10 };
jn1.receive({foo:testString,overrides:overrides});
});
});
}); });

View File

@ -29,7 +29,7 @@ describe('switch Node', function() {
afterEach(function(done) { afterEach(function(done) {
helper.unload(); helper.unload();
helper.stopServer(done); helper.stopServer(done);
RED.settings.switchMaxKeptMsgsCount = 0; RED.settings.nodeMessageBufferMaxLength = 0;
}); });
it('should be loaded with some defaults', function(done) { it('should be loaded with some defaults', function(done) {
@ -692,7 +692,7 @@ describe('switch Node', function() {
]; ];
helper.load(switchNode, flow, function() { helper.load(switchNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
RED.settings.switchMaxKeptMsgsCount = 2; RED.settings.nodeMessageBufferMaxLength = 2;
setTimeout(function() { setTimeout(function() {
var logEvents = helper.log().args.filter(function (evt) { var logEvents = helper.log().args.filter(function (evt) {
return evt[0].type == "switch"; return evt[0].type == "switch";

View File

@ -270,7 +270,7 @@ describe('JOIN node', function() {
afterEach(function() { afterEach(function() {
helper.unload(); helper.unload();
RED.settings.joinMaxKeptMsgsCount = 0; RED.settings.nodeMessageBufferMaxLength = 0;
}); });
it('should be loaded', function(done) { it('should be loaded', function(done) {
@ -731,197 +731,6 @@ describe('JOIN node', function() {
}); });
}); });
it('should merge messages with topics (single)', function(done) {
var flow = [{id:"n1", type:"join", mode:"merge",
topics:[{topic:"TA"}, {topic:"TB"}],
wires:[["n2"]]},
{id:"n2", type:"helper"}];
helper.load(joinNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var count = 0;
n2.on("input", function(msg) {
try {
msg.should.have.property("TA");
msg.should.have.property("TB");
msg.should.have.property("payload");
msg.payload.should.be.an.Array();
msg.payload.length.should.equal(2);
count++;
if (count === 1) {
msg.TA.should.equal("a");
msg.TB.should.equal("b");
msg.payload[0].should.equal("a");
msg.payload[1].should.equal("b");
}
if (count === 2) {
msg.TA.should.equal("d");
msg.TB.should.equal("c");
msg.payload[0].should.equal("d");
msg.payload[1].should.equal("c");
done();
}
}
catch(e) { done(e); }
});
n1.receive({payload:"a", topic:"TA"});
n1.receive({payload:"b", topic:"TB"});
n1.receive({payload:"c", topic:"TB"});
n1.receive({payload:"d", topic:"TA"});
});
});
it('should merge messages with topics (multiple)', function(done) {
var flow = [{id:"n1", type:"join", mode:"merge",
topics:[{topic:"TA"}, {topic:"TB"}, {topic:"TA"}],
wires:[["n2"]]},
{id:"n2", type:"helper"}];
helper.load(joinNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var count = 0;
n2.on("input", function(msg) {
try {
msg.should.have.property("TA");
msg.TA.should.be.an.Array();
msg.TA.length.should.equal(2);
msg.should.have.property("TB");
msg.should.have.property("payload");
msg.payload.should.be.an.Array();
msg.payload.length.should.equal(3);
count++;
if (count === 1) {
msg.TA[0].should.equal("a");
msg.TA[1].should.equal("d");
msg.TB.should.equal("b");
msg.payload[0].should.equal("a");
msg.payload[1].should.equal("b");
msg.payload[2].should.equal("d");
}
if (count === 2) {
msg.TA[0].should.equal("e");
msg.TA[1].should.equal("f");
msg.TB.should.equal("c");
msg.payload[0].should.equal("e");
msg.payload[1].should.equal("c");
msg.payload[2].should.equal("f");
done();
}
}
catch(e) { done(e); }
});
n1.receive({payload:"a", topic:"TA"});
n1.receive({payload:"b", topic:"TB"});
n1.receive({payload:"c", topic:"TB"});
n1.receive({payload:"d", topic:"TA"});
n1.receive({payload:"e", topic:"TA"});
n1.receive({payload:"f", topic:"TA"});
});
});
it('should merge messages with topics (single, send on new topic)', function(done) {
var flow = [{id:"n1", type:"join", mode:"merge",
topics:[{topic:"TA"}, {topic:"TB"}],
mergeOnChange:true,
wires:[["n2"]]},
{id:"n2", type:"helper"}];
helper.load(joinNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var count = 0;
n2.on("input", function(msg) {
try {
msg.should.have.property("TA");
msg.should.have.property("TB");
msg.should.have.property("payload");
msg.payload.should.be.an.Array();
msg.payload.length.should.equal(2);
count++;
if (count === 1) {
msg.TA.should.equal("a");
msg.TB.should.equal("b");
msg.payload[0].should.equal("a");
msg.payload[1].should.equal("b");
}
if (count === 2) {
msg.TA.should.equal("a");
msg.TB.should.equal("c");
msg.payload[0].should.equal("a");
msg.payload[1].should.equal("c");
}
if (count === 3) {
msg.TA.should.equal("d");
msg.TB.should.equal("c");
msg.payload[0].should.equal("d");
msg.payload[1].should.equal("c");
done();
}
}
catch(e) { done(e); }
});
n1.receive({payload:"a", topic:"TA"});
n1.receive({payload:"b", topic:"TB"});
n1.receive({payload:"c", topic:"TB"});
n1.receive({payload:"d", topic:"TA"});
});
});
it('should merge messages with topics (multiple, send on new topic)', function(done) {
var flow = [{id:"n1", type:"join", mode:"merge",
topics:[{topic:"TA"}, {topic:"TB"}, {topic:"TA"}],
mergeOnChange:true,
wires:[["n2"]]},
{id:"n2", type:"helper"}];
helper.load(joinNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var count = 0;
n2.on("input", function(msg) {
try {
msg.should.have.property("TA");
msg.TA.should.be.an.Array();
msg.TA.length.should.equal(2);
msg.should.have.property("TB");
msg.should.have.property("payload");
msg.payload.should.be.an.Array();
msg.payload.length.should.equal(3);
count++;
if (count === 1) {
msg.TA[0].should.equal("a");
msg.TA[1].should.equal("c");
msg.TB.should.equal("b");
msg.payload[0].should.equal("a");
msg.payload[1].should.equal("b");
msg.payload[2].should.equal("c");
}
if (count === 2) {
msg.TA[0].should.equal("c");
msg.TA[1].should.equal("d");
msg.TB.should.equal("b");
msg.payload[0].should.equal("c");
msg.payload[1].should.equal("b");
msg.payload[2].should.equal("d");
}
if (count === 3) {
msg.TA[0].should.equal("c");
msg.TA[1].should.equal("d");
msg.TB.should.equal("e");
msg.payload[0].should.equal("c");
msg.payload[1].should.equal("e");
msg.payload[2].should.equal("d");
done();
}
}
catch(e) { done(e); }
});
n1.receive({payload:"a", topic:"TA"});
n1.receive({payload:"b", topic:"TB"});
n1.receive({payload:"c", topic:"TA"});
n1.receive({payload:"d", topic:"TA"});
n1.receive({payload:"e", topic:"TB"});
});
});
it('should redece messages', function(done) { it('should redece messages', function(done) {
var flow = [{id:"n1", type:"join", mode:"reduce", var flow = [{id:"n1", type:"join", mode:"reduce",
reduceRight:false, reduceRight:false,
@ -1065,31 +874,6 @@ describe('JOIN node', function() {
}); });
}); });
it('should handle too many pending messages for merge mode', function(done) {
var flow = [{id:"n1", type:"join", mode:"merge",
topics:[{topic:"TA"}, {topic:"TA"}, {topic:"TB"}],
wires:[["n2"]]},
{id:"n2", type:"helper"}];
helper.load(joinNode, flow, function() {
var n1 = helper.getNode("n1");
RED.settings.joinMaxKeptMsgsCount = 2;
setTimeout(function() {
var logEvents = helper.log().args.filter(function (evt) {
return evt[0].type == "join";
});
var evt = logEvents[0][0];
evt.should.have.property('id', "n1");
evt.should.have.property('type', "join");
evt.should.have.property('msg', "join.too-many");
done();
}, 150);
n1.receive({payload:"a", topic:"TA"});
n1.receive({payload:"b", topic:"TB"});
n1.receive({payload:"c", topic:"TB"});
n1.receive({payload:"d", topic:"TA"});
});
});
it('should handle too many pending messages for reduce mode', function(done) { it('should handle too many pending messages for reduce mode', function(done) {
var flow = [{id:"n1", type:"join", mode:"reduce", var flow = [{id:"n1", type:"join", mode:"reduce",
reduceRight:false, reduceRight:false,
@ -1101,7 +885,7 @@ describe('JOIN node', function() {
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
helper.load(joinNode, flow, function() { helper.load(joinNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
RED.settings.joinMaxKeptMsgsCount = 2; RED.settings.nodeMessageBufferMaxLength = 2;
setTimeout(function() { setTimeout(function() {
var logEvents = helper.log().args.filter(function (evt) { var logEvents = helper.log().args.filter(function (evt) {
return evt[0].type == "join"; return evt[0].type == "join";

View File

@ -27,7 +27,7 @@ describe('SORT node', function() {
afterEach(function() { afterEach(function() {
helper.unload(); helper.unload();
RED.settings.maxKeptMsgsCount = 0; RED.settings.nodeMessageBufferMaxLength = 0;
}); });
it('should be loaded', function(done) { it('should be loaded', function(done) {
@ -254,7 +254,7 @@ describe('SORT node', function() {
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
helper.load(sortNode, flow, function() { helper.load(sortNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
RED.settings.maxKeptMsgsCount = 2; RED.settings.nodeMessageBufferMaxLength = 2;
setTimeout(function() { setTimeout(function() {
var logEvents = helper.log().args.filter(function (evt) { var logEvents = helper.log().args.filter(function (evt) {
return evt[0].type == "sort"; return evt[0].type == "sort";

View File

@ -28,7 +28,7 @@ describe('BATCH node', function() {
afterEach(function() { afterEach(function() {
helper.unload(); helper.unload();
RED.settings.batchMaxKeptMsgsCount = 0; RED.settings.nodeMessageBufferMaxLength = 0;
}); });
it('should be loaded with defaults', function(done) { it('should be loaded with defaults', function(done) {
@ -144,7 +144,7 @@ describe('BATCH node', function() {
describe('mode: count', function() { describe('mode: count', function() {
it('should create seq. with count', function(done) { it('should create seq. with count', function(done) {
var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 2, overwrap: 0, interval: 10, allowEmptySequence: false, topics: [], wires:[["n2"]]}, var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 2, overlap: 0, interval: 10, allowEmptySequence: false, topics: [], wires:[["n2"]]},
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
var results = [ var results = [
[0, 1], [0, 1],
@ -154,8 +154,8 @@ describe('BATCH node', function() {
check_count(flow, results, done); check_count(flow, results, done);
}); });
it('should create seq. with count and overwrap', function(done) { it('should create seq. with count and overlap', function(done) {
var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 3, overwrap: 2, interval: 10, allowEmptySequence: false, topics: [], wires:[["n2"]]}, var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 3, overlap: 2, interval: 10, allowEmptySequence: false, topics: [], wires:[["n2"]]},
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
var results = [ var results = [
[0, 1, 2], [0, 1, 2],
@ -167,12 +167,12 @@ describe('BATCH node', function() {
}); });
it('should handle too many pending messages', function(done) { it('should handle too many pending messages', function(done) {
var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 5, overwrap: 0, interval: 10, allowEmptySequence: false, topics: [], wires:[["n2"]]}, var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 5, overlap: 0, interval: 10, allowEmptySequence: false, topics: [], wires:[["n2"]]},
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
helper.load(batchNode, flow, function() { helper.load(batchNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
RED.settings.batchMaxKeptMsgsCount = 2; RED.settings.nodeMessageBufferMaxLength = 2;
setTimeout(function() { setTimeout(function() {
var logEvents = helper.log().args.filter(function (evt) { var logEvents = helper.log().args.filter(function (evt) {
return evt[0].type == "batch"; return evt[0].type == "batch";
@ -194,7 +194,7 @@ describe('BATCH node', function() {
describe('mode: interval', function() { describe('mode: interval', function() {
it('should create seq. with interval', function(done) { it('should create seq. with interval', function(done) {
var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overwrap: 0, interval: 1, allowEmptySequence: false, topics: [], wires:[["n2"]]}, var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [], wires:[["n2"]]},
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
var results = [ var results = [
[0, 1], [0, 1],
@ -204,7 +204,7 @@ describe('BATCH node', function() {
}); });
it('should create seq. with interval (in float)', function(done) { it('should create seq. with interval (in float)', function(done) {
var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overwrap: 0, interval: 0.5, allowEmptySequence: false, topics: [], wires:[["n2"]]}, var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overlap: 0, interval: 0.5, allowEmptySequence: false, topics: [], wires:[["n2"]]},
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
var results = [ var results = [
[0, 1], [0, 1],
@ -214,7 +214,7 @@ describe('BATCH node', function() {
}); });
it('should create seq. with interval & not send empty seq', function(done) { it('should create seq. with interval & not send empty seq', function(done) {
var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overwrap: 0, interval: 1, allowEmptySequence: false, topics: [], wires:[["n2"]]}, var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [], wires:[["n2"]]},
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
var results = [ var results = [
// 1300, 2600, 3900, 5200, // 1300, 2600, 3900, 5200,
@ -224,7 +224,7 @@ describe('BATCH node', function() {
}); });
it('should create seq. with interval & send empty seq', function(done) { it('should create seq. with interval & send empty seq', function(done) {
var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overwrap: 0, interval: 1, allowEmptySequence: true, topics: [], wires:[["n2"]]}, var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overlap: 0, interval: 1, allowEmptySequence: true, topics: [], wires:[["n2"]]},
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
var results = [ var results = [
// 1300, 2600, 3900, 5200, // 1300, 2600, 3900, 5200,
@ -234,12 +234,12 @@ describe('BATCH node', function() {
}); });
it('should handle too many pending messages', function(done) { it('should handle too many pending messages', function(done) {
var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overwrap: 0, interval: 1, allowEmptySequence: false, topics: [], wires:[["n2"]]}, var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [], wires:[["n2"]]},
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
helper.load(batchNode, flow, function() { helper.load(batchNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
RED.settings.batchMaxKeptMsgsCount = 2; RED.settings.nodeMessageBufferMaxLength = 2;
setTimeout(function() { setTimeout(function() {
var logEvents = helper.log().args.filter(function (evt) { var logEvents = helper.log().args.filter(function (evt) {
return evt[0].type == "batch"; return evt[0].type == "batch";
@ -261,7 +261,7 @@ describe('BATCH node', function() {
describe('mode: concat', function() { describe('mode: concat', function() {
it('should concat two seq. (series)', function(done) { it('should concat two seq. (series)', function(done) {
var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overwrap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TB"}], wires:[["n2"]]}, var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TB"}], wires:[["n2"]]},
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
var results = [ var results = [
[2, 3, 0, 1] [2, 3, 0, 1]
@ -276,7 +276,7 @@ describe('BATCH node', function() {
}); });
it('should concat two seq. (mixed)', function(done) { it('should concat two seq. (mixed)', function(done) {
var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overwrap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TB"}], wires:[["n2"]]}, var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TB"}], wires:[["n2"]]},
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
var results = [ var results = [
[2, 3, 0, 1] [2, 3, 0, 1]
@ -291,7 +291,7 @@ describe('BATCH node', function() {
}); });
it('should concat three seq.', function(done) { it('should concat three seq.', function(done) {
var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overwrap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TB"}, {topic: "TC"}], wires:[["n2"]]}, var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TB"}, {topic: "TC"}], wires:[["n2"]]},
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
var results = [ var results = [
[2, 3, 0, 1, 4] [2, 3, 0, 1, 4]
@ -307,12 +307,12 @@ describe('BATCH node', function() {
}); });
it('should handle too many pending messages', function(done) { it('should handle too many pending messages', function(done) {
var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overwrap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TB"}], wires:[["n2"]]}, var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TB"}], wires:[["n2"]]},
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
helper.load(batchNode, flow, function() { helper.load(batchNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
RED.settings.batchMaxKeptMsgsCount = 2; RED.settings.nodeMessageBufferMaxLength = 2;
setTimeout(function() { setTimeout(function() {
var logEvents = helper.log().args.filter(function (evt) { var logEvents = helper.log().args.filter(function (evt) {
return evt[0].type == "batch"; return evt[0].type == "batch";

View File

@ -57,6 +57,26 @@ describe('XML node', function() {
}); });
}); });
it('should convert a valid xml string to a javascript object - alternative property', function(done) {
var flow = [{id:"n1",type:"xml",property:"foo",wires:[["n2"]],func:"return msg;"},
{id:"n2", type:"helper"}];
helper.load(xmlNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.foo.should.have.property('employees');
msg.foo.employees.should.have.property('firstName');
should.equal(msg.foo.employees.firstName[0], 'John');
msg.foo.employees.should.have.property('lastName');
should.equal(msg.foo.employees.lastName[0], 'Smith');
done();
});
var string = '<employees><firstName>John</firstName><lastName>Smith</lastName></employees>';
n1.receive({foo:string,topic: "bar"});
});
});
it('should convert a valid xml string to a javascript object with options', function(done) { it('should convert a valid xml string to a javascript object with options', function(done) {
var flow = [{id:"n1",type:"xml",wires:[["n2"]],func:"return msg;"}, var flow = [{id:"n1",type:"xml",wires:[["n2"]],func:"return msg;"},
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
@ -94,20 +114,20 @@ describe('XML node', function() {
}); });
}); });
it('should convert a javascript object to an xml string with options', function(done) { it('should convert a javascript object to an xml string with options - alternative property', function(done) {
var flow = [{id:"n1",type:"xml",wires:[["n2"]],func:"return msg;"}, var flow = [{id:"n1",type:"xml",property:"foo",wires:[["n2"]],func:"return msg;"},
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
helper.load(xmlNode, flow, function() { helper.load(xmlNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on("input", function(msg) { n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar'); msg.should.have.property('topic', 'bar');
var index = msg.payload.indexOf('<employees>\n <firstName>John</firstName>\n <lastName>Smith</lastName>\n</employees>'); var index = msg.foo.indexOf('<employees>\n <firstName>John</firstName>\n <lastName>Smith</lastName>\n</employees>');
index.should.be.above(-1); index.should.be.above(-1);
done(); done();
}); });
var obj = {"employees":{"firstName":["John"],"lastName":["Smith"] }}; var obj = {"employees":{"firstName":["John"],"lastName":["Smith"] }};
n1.receive({payload:obj, topic:"bar", options:{headless:true}}); n1.receive({foo:obj, topic:"bar", options:{headless:true}});
}); });
}); });

View File

@ -55,6 +55,24 @@ describe('YAML node', function() {
}); });
}); });
it('should convert a valid yaml string to a javascript object - using another property', function(done) {
var flow = [{id:"yn1",type:"yaml",property:"foo",wires:[["yn2"]],func:"return msg;"},
{id:"yn2", type:"helper"}];
helper.load(yamlNode, flow, function() {
var yn1 = helper.getNode("yn1");
var yn2 = helper.getNode("yn2");
yn2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.foo.should.have.property('employees');
msg.foo.employees[0].should.have.property('firstName', 'John');
msg.foo.employees[0].should.have.property('lastName', 'Smith');
done();
});
var yamlString = "employees:\n - firstName: John\n lastName: Smith\n";
yn1.receive({foo:yamlString,topic: "bar"});
});
});
it('should convert a javascript object to a yaml string', function(done) { it('should convert a javascript object to a yaml string', function(done) {
var flow = [{id:"yn1",type:"yaml",wires:[["yn2"]],func:"return msg;"}, var flow = [{id:"yn1",type:"yaml",wires:[["yn2"]],func:"return msg;"},
{id:"yn2", type:"helper"}]; {id:"yn2", type:"helper"}];
@ -70,6 +88,21 @@ describe('YAML node', function() {
}); });
}); });
it('should convert a javascript object to a yaml string - using another property', function(done) {
var flow = [{id:"yn1",type:"yaml",property:"foo",wires:[["yn2"]],func:"return msg;"},
{id:"yn2", type:"helper"}];
helper.load(yamlNode, flow, function() {
var yn1 = helper.getNode("yn1");
var yn2 = helper.getNode("yn2");
yn2.on("input", function(msg) {
should.equal(msg.foo, "employees:\n - firstName: John\n lastName: Smith\n");
done();
});
var obj = {employees:[{firstName:"John", lastName:"Smith"}]};
yn1.receive({foo:obj});
});
});
it('should convert an array to a yaml string', function(done) { it('should convert an array to a yaml string', function(done) {
var flow = [{id:"yn1",type:"yaml",wires:[["yn2"]],func:"return msg;"}, var flow = [{id:"yn1",type:"yaml",wires:[["yn2"]],func:"return msg;"},
{id:"yn2", type:"helper"}]; {id:"yn2", type:"helper"}];

View File

@ -19,6 +19,7 @@ var sinon = require("sinon");
var when = require("when"); var when = require("when");
var path = require("path"); var path = require("path");
var fs = require('fs'); var fs = require('fs');
var EventEmitter = require('events');
var child_process = require('child_process'); var child_process = require('child_process');
var installer = require("../../../../../red/runtime/nodes/registry/installer"); var installer = require("../../../../../red/runtime/nodes/registry/installer");
@ -31,6 +32,9 @@ describe('nodes/registry/installer', function() {
installer.init({}); installer.init({});
}); });
afterEach(function() { afterEach(function() {
if (child_process.spawn.restore) {
child_process.spawn.restore();
}
if (child_process.execFile.restore) { if (child_process.execFile.restore) {
child_process.execFile.restore(); child_process.execFile.restore();
} }
@ -58,8 +62,15 @@ describe('nodes/registry/installer', function() {
describe("installs module", function() { describe("installs module", function() {
it("rejects when npm returns a 404", function(done) { it("rejects when npm returns a 404", function(done) {
sinon.stub(child_process,"execFile",function(cmd,args,opt,cb) { sinon.stub(child_process,"spawn",function(cmd,args,opt) {
cb(new Error(),""," 404 this_wont_exist"); var ee = new EventEmitter();
ee.stdout = new EventEmitter();
ee.stderr = new EventEmitter();
setTimeout(function() {
ee.stderr.emit('data'," 404 this_wont_exist");
ee.emit('close',1);
},10)
return ee;
}); });
installer.installModule("this_wont_exist").otherwise(function(err) { installer.installModule("this_wont_exist").otherwise(function(err) {
@ -68,8 +79,15 @@ describe('nodes/registry/installer', function() {
}); });
}); });
it("rejects when npm does not find specified version", function(done) { it("rejects when npm does not find specified version", function(done) {
sinon.stub(child_process,"execFile",function(cmd,args,opt,cb) { sinon.stub(child_process,"spawn",function(cmd,args,opt) {
cb(new Error(),""," version not found: this_wont_exist@0.1.2"); var ee = new EventEmitter();
ee.stdout = new EventEmitter();
ee.stderr = new EventEmitter();
setTimeout(function() {
ee.stderr.emit('data'," version not found: this_wont_exist@0.1.2");
ee.emit('close',1);
},10)
return ee;
}); });
sinon.stub(typeRegistry,"getModuleInfo", function() { sinon.stub(typeRegistry,"getModuleInfo", function() {
return { return {
@ -94,8 +112,15 @@ describe('nodes/registry/installer', function() {
}); });
}); });
it("rejects with generic error", function(done) { it("rejects with generic error", function(done) {
sinon.stub(child_process,"execFile",function(cmd,args,opt,cb) { sinon.stub(child_process,"spawn",function(cmd,args,opt,cb) {
cb(new Error("test_error"),"",""); var ee = new EventEmitter();
ee.stdout = new EventEmitter();
ee.stderr = new EventEmitter();
setTimeout(function() {
ee.stderr.emit('data'," kaboom!");
ee.emit('close',1);
},10)
return ee;
}); });
installer.installModule("this_wont_exist").then(function() { installer.installModule("this_wont_exist").then(function() {
@ -106,8 +131,14 @@ describe('nodes/registry/installer', function() {
}); });
it("succeeds when module is found", function(done) { it("succeeds when module is found", function(done) {
var nodeInfo = {nodes:{module:"foo",types:["a"]}}; var nodeInfo = {nodes:{module:"foo",types:["a"]}};
sinon.stub(child_process,"execFile",function(cmd,args,opt,cb) { sinon.stub(child_process,"spawn",function(cmd,args,opt) {
cb(null,"",""); var ee = new EventEmitter();
ee.stdout = new EventEmitter();
ee.stderr = new EventEmitter();
setTimeout(function() {
ee.emit('close',0);
},10)
return ee;
}); });
var addModule = sinon.stub(registry,"addModule",function(md) { var addModule = sinon.stub(registry,"addModule",function(md) {
return when.resolve(nodeInfo); return when.resolve(nodeInfo);
@ -146,8 +177,14 @@ describe('nodes/registry/installer', function() {
return when.resolve(nodeInfo); return when.resolve(nodeInfo);
}); });
var resourcesDir = path.resolve(path.join(__dirname,"..","resources","local","TestNodeModule","node_modules","TestNodeModule")); var resourcesDir = path.resolve(path.join(__dirname,"..","resources","local","TestNodeModule","node_modules","TestNodeModule"));
sinon.stub(child_process,"execFile",function(cmd,args,opt,cb) { sinon.stub(child_process,"spawn",function(cmd,args,opt) {
cb(null,"",""); var ee = new EventEmitter();
ee.stdout = new EventEmitter();
ee.stderr = new EventEmitter();
setTimeout(function() {
ee.emit('close',0);
},10)
return ee;
}); });
installer.installModule(resourcesDir).then(function(info) { installer.installModule(resourcesDir).then(function(info) {
info.should.eql(nodeInfo); info.should.eql(nodeInfo);