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-22 23:17:28 +00:00
commit 84711beec0
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
34 changed files with 3191 additions and 346 deletions

BIN
editor/icons/batch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

View File

@ -170,7 +170,7 @@ RED.editor = (function() {
} }
} }
} }
if (!node._def.defaults.hasOwnProperty("icon") && node.icon) { if (node._def.hasOwnProperty("defaults") && !node._def.defaults.hasOwnProperty("icon") && node.icon) {
var iconPath = RED.utils.separateIconPath(node.icon); var iconPath = RED.utils.separateIconPath(node.icon);
var iconSets = RED.nodes.getIconSets(); var iconSets = RED.nodes.getIconSets();
var iconFileList = iconSets[iconPath.module]; var iconFileList = iconSets[iconPath.module];

View File

@ -81,6 +81,9 @@ RED.view = (function() {
.style("cursor","crosshair") .style("cursor","crosshair")
.on("mousedown", function() { .on("mousedown", function() {
focusView(); focusView();
})
.on("contextmenu", function(){
d3.event.preventDefault();
}); });
var vis = outer var vis = outer
@ -1392,7 +1395,7 @@ RED.view = (function() {
function portMouseUp(d,portType,portIndex) { function portMouseUp(d,portType,portIndex) {
var i; var i;
if (mouse_mode === RED.state.QUICK_JOINING) { if (mouse_mode === RED.state.QUICK_JOINING && drag_lines.length > 0) {
if (drag_lines[0].node===d) { if (drag_lines[0].node===d) {
return return
} }

View File

@ -18,7 +18,7 @@
<div class="form-row"> <div class="form-row">
<label data-i18n="trigger.send" for="node-input-op1"></label> <label data-i18n="trigger.send" for="node-input-op1"></label>
<input type="hidden" id="node-input-op1type"> <input type="hidden" id="node-input-op1type">
<input style="width: 70%" type="text" id="node-input-op1"> <input style="width: 70%" type="text" id="node-input-op1" placeholder="1">
</div> </div>
<div class="form-row"> <div class="form-row">
<label data-i18n="trigger.then"></label> <label data-i18n="trigger.then"></label>
@ -45,7 +45,7 @@
<div class="form-row node-type-wait"> <div class="form-row node-type-wait">
<label data-i18n="trigger.then-send"></label> <label data-i18n="trigger.then-send"></label>
<input type="hidden" id="node-input-op2type"> <input type="hidden" id="node-input-op2type">
<input style="width: 70%" type="text" id="node-input-op2"> <input style="width: 70%" type="text" id="node-input-op2" placeholder="0">
</div> </div>
<div class="form-row"> <div class="form-row">
<label data-i18n="trigger.label.reset" style="width:auto"></label> <label data-i18n="trigger.label.reset" style="width:auto"></label>

View File

@ -80,7 +80,7 @@ module.exports = function(RED) {
var topic = msg.topic || "_none"; var topic = msg.topic || "_none";
if (node.bytopic === "all") { topic = "_none"; } if (node.bytopic === "all") { topic = "_none"; }
node.topics[topic] = node.topics[topic] || {}; node.topics[topic] = node.topics[topic] || {};
if (msg.hasOwnProperty("reset") || ((node.reset !== '') && (msg.payload == node.reset)) ) { if (msg.hasOwnProperty("reset") || ((node.reset !== '') && msg.hasOwnProperty("payload") && (msg.payload !== null) && msg.payload.toString && (msg.payload.toString() == node.reset)) ) {
if (node.loop === true) { clearInterval(node.topics[topic].tout); } if (node.loop === true) { clearInterval(node.topics[topic].tout); }
else { clearTimeout(node.topics[topic].tout); } else { clearTimeout(node.topics[topic].tout); }
delete node.topics[topic]; delete node.topics[topic];
@ -104,7 +104,9 @@ module.exports = function(RED) {
if (node.duration === 0) { node.topics[topic].tout = 0; } if (node.duration === 0) { node.topics[topic].tout = 0; }
else if (node.loop === true) { else if (node.loop === true) {
/* istanbul ignore else */
if (node.topics[topic].tout) { clearInterval(node.topics[topic].tout); } if (node.topics[topic].tout) { clearInterval(node.topics[topic].tout); }
/* istanbul ignore else */
if (node.op1type !== "nul") { if (node.op1type !== "nul") {
var msg2 = RED.util.cloneMessage(msg); var msg2 = RED.util.cloneMessage(msg);
node.topics[topic].tout = setInterval(function() { node.send(RED.util.cloneMessage(msg2)); }, node.duration); node.topics[topic].tout = setInterval(function() { node.send(RED.util.cloneMessage(msg2)); }, node.duration);
@ -128,7 +130,9 @@ module.exports = function(RED) {
node.status({fill:"blue",shape:"dot",text:" "}); node.status({fill:"blue",shape:"dot",text:" "});
} }
else if ((node.extend === "true" || node.extend === true) && (node.duration > 0)) { else if ((node.extend === "true" || node.extend === true) && (node.duration > 0)) {
/* istanbul ignore else */
if (node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } if (node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
/* istanbul ignore else */
if (node.topics[topic].tout) { clearTimeout(node.topics[topic].tout); } if (node.topics[topic].tout) { clearTimeout(node.topics[topic].tout); }
node.topics[topic].tout = setTimeout(function() { node.topics[topic].tout = setTimeout(function() {
var msg2 = null; var msg2 = null;
@ -153,6 +157,7 @@ module.exports = function(RED) {
}); });
this.on("close", function() { this.on("close", function() {
for (var t in node.topics) { for (var t in node.topics) {
/* istanbul ignore else */
if (node.topics[t]) { if (node.topics[t]) {
if (node.loop === true) { clearInterval(node.topics[t].tout); } if (node.loop === true) { clearInterval(node.topics[t].tout); }
else { clearTimeout(node.topics[t].tout); } else { clearTimeout(node.topics[t].tout); }

View File

@ -541,7 +541,8 @@
"switch": { "switch": {
"label": { "label": {
"property": "Property", "property": "Property",
"rule": "rule" "rule": "rule",
"repair" : "repair sequence (reconstruct parts property of outgoing messages)"
}, },
"and": "and", "and": "and",
"checkall": "checking all rules", "checkall": "checking all rules",
@ -555,10 +556,15 @@
"false":"is false", "false":"is false",
"null":"is null", "null":"is null",
"nnull":"is not null", "nnull":"is not null",
"head":"head",
"tail":"tail",
"index":"is between",
"exp":"JSONata exp",
"else":"otherwise" "else":"otherwise"
}, },
"errors": { "errors": {
"invalid-expr": "Invalid JSONata expression: __error__" "invalid-expr": "Invalid JSONata expression: __error__",
"too-many" : "too many pending messages in switch node"
} }
}, },
"change": { "change": {
@ -840,6 +846,8 @@
"mode":{ "mode":{
"mode":"Mode", "mode":"Mode",
"auto":"automatic", "auto":"automatic",
"merge":"merge sequence",
"reduce":"reduce sequence",
"custom":"manual" "custom":"manual"
}, },
"combine":"Combine each", "combine":"Combine each",
@ -861,19 +869,63 @@
"afterTimeout":"After a timeout following the first message", "afterTimeout":"After a timeout following the first message",
"seconds":"seconds", "seconds":"seconds",
"complete":"After a message with the <code>msg.complete</code> property set", "complete":"After a message with the <code>msg.complete</code> property set",
"tip":"This mode assumes this node is either paired with a <i>split</i> node or the received messages will have a properly configured <code>msg.parts</code> property." "tip":"This mode assumes this node is either paired with a <i>split</i> node or the received messages will have a properly configured <code>msg.parts</code> property.",
"too-many" : "too many pending messages in join node",
"merge": {
"topics-label":"Merged Topics",
"topics":"topics",
"topic" : "topic",
"on-change":"Send merged message on arrival of a new topic"
},
"reduce": {
"exp": "Reduce exp",
"exp-value": "exp",
"init": "Initial value",
"right": "Evaluate in reverse order (right to left)",
"fixup": "Fixup exp"
},
"errors": {
"invalid-expr": "Invalid JSONata expression: __error__"
}
}, },
"sort" : { "sort" : {
"key-type" : "Key type", "target" : "Sort",
"payload" : "payload or element", "seq" : "message sequence",
"exp" : "expression", "key" : "Key",
"key-exp" : "Key exp.", "elem" : "element value",
"order" : "Order", "order" : "Order",
"ascending" : "ascending", "ascending" : "ascending",
"descending" : "descending", "descending" : "descending",
"as-number" : "as number", "as-number" : "as number",
"invalid-exp" : "invalid JSONata expression in sort node", "invalid-exp" : "invalid JSONata expression in sort node",
"too-many" : "too many pending messages in sort node", "too-many" : "too many pending messages in sort node",
"clear" : "clear pending message in sort node" "clear" : "clear pending message in sort node"
},
"batch" : {
"mode": {
"label" : "Mode",
"num-msgs" : "number of messages",
"interval" : "interval in seconds",
"concat" : "concatenate sequences"
},
"count": {
"label" : "Number of msgs",
"overwrap" : "Overwrap",
"count" : "count",
"invalid" : "Invalid count and overwrap"
},
"interval": {
"label" : "Interval (sec)",
"seconds" : "seconds",
"sec" : "sec",
"empty" : "send empty message when no message arrives"
},
"concat": {
"topics-label": "Topics",
"topic" : "topic"
},
"too-many" : "too many pending messages in batch node",
"unexpected" : "unexpected mode",
"no-parts" : "no parts property in message"
} }
} }

View File

@ -33,6 +33,10 @@
<option value="false" data-i18n="switch.stopfirst"></option> <option value="false" data-i18n="switch.stopfirst"></option>
</select> </select>
</div> </div>
<div class="form-row">
<input type="checkbox" id="node-input-repair" style="display: inline-block; width: auto; vertical-align: top;">
<label style="width: auto;" for="node-input-repair"><span data-i18n="switch.label.repair"></span></label></input>
</div>
</script> </script>
<script type="text/x-red" data-help-name="switch"> <script type="text/x-red" data-help-name="switch">
@ -44,25 +48,138 @@
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>
<p>Routing rules are categorized into three:</p>
<dl>
<dt>value rules</dt>
<dd>
<table>
<tr>
<th>operator</th>
<th>description</th>
</tr>
<tr>
<td><b>==</b></td>
<td>property value is equals to specified value</td>
</tr>
<tr>
<td><b>!=</b></td>
<td>property value is not equals to specified value</td>
</tr>
<tr>
<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">
(function() { (function() {
var operators = [ var operators = [
{v:"eq",t:"=="}, {v:"eq",t:"==",kind:'V'},
{v:"neq",t:"!="}, {v:"neq",t:"!=",kind:'V'},
{v:"lt",t:"<"}, {v:"lt",t:"<",kind:'V'},
{v:"lte",t:"<="}, {v:"lte",t:"<=",kind:'V'},
{v:"gt",t:">"}, {v:"gt",t:">",kind:'V'},
{v:"gte",t:">="}, {v:"gte",t:">=",kind:'V'},
{v:"btwn",t:"switch.rules.btwn"}, {v:"btwn",t:"switch.rules.btwn",kind:'V'},
{v:"cont",t:"switch.rules.cont"}, {v:"cont",t:"switch.rules.cont",kind:'V'},
{v:"regex",t:"switch.rules.regex"}, {v:"regex",t:"switch.rules.regex",kind:'V'},
{v:"true",t:"switch.rules.true"}, {v:"true",t:"switch.rules.true",kind:'V'},
{v:"false",t:"switch.rules.false"}, {v:"false",t:"switch.rules.false",kind:'V'},
{v:"null",t:"switch.rules.null"}, {v:"null",t:"switch.rules.null",kind:'V'},
{v:"nnull",t:"switch.rules.nnull"}, {v:"nnull",t:"switch.rules.nnull",kind:'V'},
{v:"else",t:"switch.rules.else"} {v:"head",t:"switch.rules.head",kind:'S'},
{v:"index",t:"switch.rules.index",kind:'S'},
{v:"tail",t:"switch.rules.tail",kind:'S'},
{v:"jsonata_exp",t:"switch.rules.exp",kind:'O'},
{v:"else",t:"switch.rules.else",kind:'O'}
]; ];
function clipValueLength(v) { function clipValueLength(v) {
@ -88,6 +205,7 @@
propertyType: { value:"msg" }, propertyType: { value:"msg" },
rules: {value:[{t:"eq", v:"", vt:"str"}]}, rules: {value:[{t:"eq", v:"", vt:"str"}]},
checkall: {value:"true", required:true}, checkall: {value:"true", required:true},
repair: {value:false},
outputs: {value:1} outputs: {value:1}
}, },
inputs: 1, inputs: 1,
@ -102,7 +220,8 @@
break; break;
} }
} }
if (rule.t === 'btwn') { 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);
@ -132,6 +251,8 @@
var selectField = rule.find("select"); var selectField = rule.find("select");
var type = selectField.val()||""; var type = selectField.val()||"";
var valueField = rule.find(".node-input-rule-value"); var valueField = rule.find(".node-input-rule-value");
var numField = rule.find(".node-input-rule-num-value");
var expField = rule.find(".node-input-rule-exp-value");
var btwnField1 = rule.find(".node-input-rule-btwn-value"); var btwnField1 = rule.find(".node-input-rule-btwn-value");
var btwnField2 = rule.find(".node-input-rule-btwn-value2"); var btwnField2 = rule.find(".node-input-rule-btwn-value2");
var selectWidth; var selectWidth;
@ -143,9 +264,13 @@
selectWidth = 120; selectWidth = 120;
} }
selectField.width(selectWidth); selectField.width(selectWidth);
if (type === "btwn") { if ((type === "btwn") || (type === "index")) {
btwnField1.typedInput("width",(newWidth-selectWidth-70)); btwnField1.typedInput("width",(newWidth-selectWidth-70));
btwnField2.typedInput("width",(newWidth-selectWidth-70)); btwnField2.typedInput("width",(newWidth-selectWidth-70));
} else if ((type === "head") || (type === "tail")) {
numField.typedInput("width",(newWidth-selectWidth-70));
} else if (type === "jsonata_exp") {
expField.typedInput("width",(newWidth-selectWidth-70));
} else { } else {
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") { if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") {
// valueField.hide(); // valueField.hide();
@ -171,10 +296,26 @@
var row2 = $('<div/>',{style:"padding-top: 5px; padding-left: 175px;"}).appendTo(container); var row2 = $('<div/>',{style:"padding-top: 5px; padding-left: 175px;"}).appendTo(container);
var row3 = $('<div/>',{style:"padding-top: 5px; padding-left: 102px;"}).appendTo(container); var row3 = $('<div/>',{style:"padding-top: 5px; padding-left: 102px;"}).appendTo(container);
var selectField = $('<select/>',{style:"width:120px; margin-left: 5px; text-align: center;"}).appendTo(row); var selectField = $('<select/>',{style:"width:120px; margin-left: 5px; text-align: center;"}).appendTo(row);
var group0 = $('<optgroup/>', { label: "value rules" }).appendTo(selectField);
for (var d in operators) { for (var d in operators) {
selectField.append($("<option></option>").val(operators[d].v).text(/^switch/.test(operators[d].t)?node._(operators[d].t):operators[d].t)); if(operators[d].kind === 'V') {
group0.append($("<option></option>").val(operators[d].v).text(/^switch/.test(operators[d].t)?node._(operators[d].t):operators[d].t));
}
}
var group1 = $('<optgroup/>', { label: "sequence rules" }).appendTo(selectField);
for (var d in operators) {
if(operators[d].kind === 'S') {
group1.append($("<option></option>").val(operators[d].v).text(/^switch/.test(operators[d].t)?node._(operators[d].t):operators[d].t));
}
}
for (var d in operators) {
if(operators[d].kind === 'O') {
selectField.append($("<option></option>").val(operators[d].v).text(/^switch/.test(operators[d].t)?node._(operators[d].t):operators[d].t));
}
} }
var valueField = $('<input/>',{class:"node-input-rule-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'str',types:['msg','flow','global','str','num','jsonata',previousValueType]}); var valueField = $('<input/>',{class:"node-input-rule-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'str',types:['msg','flow','global','str','num','jsonata',previousValueType]});
var numValueField = $('<input/>',{class:"node-input-rule-num-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'num',types:['flow','global','num','jsonata']});
var expValueField = $('<input/>',{class:"node-input-rule-exp-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'jsonata',types:['jsonata']});
var btwnValueField = $('<input/>',{class:"node-input-rule-btwn-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'num',types:['msg','flow','global','str','num','jsonata',previousValueType]}); var btwnValueField = $('<input/>',{class:"node-input-rule-btwn-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'num',types:['msg','flow','global','str','num','jsonata',previousValueType]});
var btwnAndLabel = $('<span/>',{class:"node-input-rule-btwn-label"}).text(" "+andLabel+" ").appendTo(row3); var btwnAndLabel = $('<span/>',{class:"node-input-rule-btwn-label"}).text(" "+andLabel+" ").appendTo(row3);
var btwnValue2Field = $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"margin-left:2px;"}).appendTo(row3).typedInput({default:'num',types:['msg','flow','global','str','num','jsonata',previousValueType]}); var btwnValue2Field = $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"margin-left:2px;"}).appendTo(row3).typedInput({default:'num',types:['msg','flow','global','str','num','jsonata',previousValueType]});
@ -185,11 +326,27 @@
selectField.change(function() { selectField.change(function() {
resizeRule(container); resizeRule(container);
var type = selectField.val(); var type = selectField.val();
if (type === "btwn") { if ((type === "btwn") || (type === "index")) {
valueField.typedInput('hide'); valueField.typedInput('hide');
expValueField.typedInput('hide');
numValueField.typedInput('hide');
btwnValueField.typedInput('show'); btwnValueField.typedInput('show');
} else if ((type === "head") || (type === "tail")) {
btwnValueField.typedInput('hide');
btwnValue2Field.typedInput('hide');
expValueField.typedInput('hide');
numValueField.typedInput('show');
valueField.typedInput('hide');
} else if (type === "jsonata_exp") {
btwnValueField.typedInput('hide');
btwnValue2Field.typedInput('hide');
expValueField.typedInput('show');
numValueField.typedInput('hide');
valueField.typedInput('hide');
} else { } else {
btwnValueField.typedInput('hide'); btwnValueField.typedInput('hide');
expValueField.typedInput('hide');
numValueField.typedInput('hide');
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") { if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") {
valueField.typedInput('hide'); valueField.typedInput('hide');
} else { } else {
@ -199,7 +356,7 @@
if (type === "regex") { if (type === "regex") {
row2.show(); row2.show();
row3.hide(); row3.hide();
} else if (type === "btwn") { } else if ((type === "btwn") || (type === "index")) {
row2.hide(); row2.hide();
row3.show(); row3.show();
btwnValue2Field.typedInput('show'); btwnValue2Field.typedInput('show');
@ -209,11 +366,17 @@
} }
}); });
selectField.val(rule.t); selectField.val(rule.t);
if (rule.t == "btwn") { if ((rule.t == "btwn") || (rule.t == "index")) {
btwnValueField.typedInput('value',rule.v); btwnValueField.typedInput('value',rule.v);
btwnValueField.typedInput('type',rule.vt||'num'); btwnValueField.typedInput('type',rule.vt||'num');
btwnValue2Field.typedInput('value',rule.v2); btwnValue2Field.typedInput('value',rule.v2);
btwnValue2Field.typedInput('type',rule.v2t||'num'); btwnValue2Field.typedInput('type',rule.v2t||'num');
} else if ((rule.t === "head") || (rule.t === "tail")) {
numValueField.typedInput('value',rule.v);
numValueField.typedInput('type',rule.vt||'num');
} else if (rule.t === "jsonata_exp") {
expValueField.typedInput('value',rule.v);
expValueField.typedInput('type',rule.vt||'jsonata');
} else if (typeof rule.v != "undefined") { } else if (typeof rule.v != "undefined") {
valueField.typedInput('value',rule.v); valueField.typedInput('value',rule.v);
valueField.typedInput('type',rule.vt||'str'); valueField.typedInput('type',rule.vt||'str');
@ -275,11 +438,17 @@
var type = rule.find("select").val(); var type = rule.find("select").val();
var r = {t:type}; var r = {t:type};
if (!(type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else")) { if (!(type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else")) {
if (type === "btwn") { if ((type === "btwn") || (type === "index")) {
r.v = rule.find(".node-input-rule-btwn-value").typedInput('value'); r.v = rule.find(".node-input-rule-btwn-value").typedInput('value');
r.vt = rule.find(".node-input-rule-btwn-value").typedInput('type'); r.vt = rule.find(".node-input-rule-btwn-value").typedInput('type');
r.v2 = rule.find(".node-input-rule-btwn-value2").typedInput('value'); r.v2 = rule.find(".node-input-rule-btwn-value2").typedInput('value');
r.v2t = rule.find(".node-input-rule-btwn-value2").typedInput('type'); r.v2t = rule.find(".node-input-rule-btwn-value2").typedInput('type');
} else if ((type === "head") || (type === "tail")) {
r.v = rule.find(".node-input-rule-num-value").typedInput('value');
r.vt = rule.find(".node-input-rule-num-value").typedInput('type');
} else if (type === "jsonata_exp") {
r.v = rule.find(".node-input-rule-exp-value").typedInput('value');
r.vt = rule.find(".node-input-rule-exp-value").typedInput('type');
} else { } else {
r.v = rule.find(".node-input-rule-value").typedInput('value'); r.v = rule.find(".node-input-rule-value").typedInput('value');
r.vt = rule.find(".node-input-rule-value").typedInput('type'); r.vt = rule.find(".node-input-rule-value").typedInput('type');

View File

@ -31,9 +31,39 @@ module.exports = function(RED) {
'false': function(a) { return a === false; }, 'false': function(a) { return a === false; },
'null': function(a) { return (typeof a == "undefined" || a === null); }, 'null': function(a) { return (typeof a == "undefined" || a === null); },
'nnull': function(a) { return (typeof a != "undefined" && a !== null); }, 'nnull': function(a) { return (typeof a != "undefined" && a !== null); },
'head': function(a, b, c, d, parts) {
var count = Number(b);
return (parts.index < count);
},
'tail': function(a, b, c, d, parts) {
var count = Number(b);
return (parts.count -count <= parts.index);
},
'index': function(a, b, c, d, parts) {
var min = Number(b);
var max = Number(c);
var index = parts.index;
return ((min <= index) && (index <= max));
},
'jsonata_exp': function(a, b) { return (b === true); },
'else': function(a) { return a === true; } 'else': function(a) { return a === true; }
}; };
var _max_kept_msgs_count = undefined;
function max_kept_msgs_count(node) {
if (_max_kept_msgs_count === undefined) {
var name = "switchMaxKeptMsgsCount";
if (RED.settings.hasOwnProperty(name)) {
_max_kept_msgs_count = RED.settings[name];
}
else {
_max_kept_msgs_count = 0;
}
}
return _max_kept_msgs_count;
}
function SwitchNode(n) { function SwitchNode(n) {
RED.nodes.createNode(this, n); RED.nodes.createNode(this, n);
this.rules = n.rules || []; this.rules = n.rules || [];
@ -53,8 +83,11 @@ module.exports = function(RED) {
this.previousValue = null; this.previousValue = null;
var node = this; var node = this;
var valid = true; var valid = true;
var needs_count = false;
var repair = n.repair;
for (var i=0; i<this.rules.length; i+=1) { for (var i=0; i<this.rules.length; i+=1) {
var rule = this.rules[i]; var rule = this.rules[i];
needs_count = needs_count || ((rule.t === "tail") || (rule.t === "jsonata_exp"));
if (!rule.vt) { if (!rule.vt) {
if (!isNaN(Number(rule.v))) { if (!isNaN(Number(rule.v))) {
rule.vt = 'num'; rule.vt = 'num';
@ -99,7 +132,136 @@ module.exports = function(RED) {
return; return;
} }
this.on('input', function (msg) { var pending_count = 0;
var pending_id = 0;
var pending_in = {};
var pending_out = {};
var received = {};
function add2group_in(id, msg, parts) {
if (!(id in pending_in)) {
pending_in[id] = {
count: undefined,
msgs: [],
seq_no: pending_id++
};
}
var group = pending_in[id];
group.msgs.push(msg);
pending_count++;
var max_msgs = max_kept_msgs_count(node);
if ((max_msgs > 0) && (pending_count > max_msgs)) {
clear_pending();
node.error(RED._("switch.errors.too-many"), msg);
}
if (parts.hasOwnProperty("count")) {
group.count = parts.count;
}
return group;
}
function del_group_in(id, group) {
pending_count -= group.msgs.length;
delete pending_in[id];
}
function add2pending_in(msg) {
var parts = msg.parts;
if (parts.hasOwnProperty("id") &&
parts.hasOwnProperty("index")) {
var group = add2group_in(parts.id, msg, parts);
var msgs = group.msgs;
var count = group.count;
if (count === msgs.length) {
for (var i = 0; i < msgs.length; i++) {
var msg = msgs[i];
msg.parts.count = count;
process_msg(msg, false);
}
del_group_in(parts.id, group);
}
return true;
}
return false;
}
function send_group(onwards, port_count) {
var counts = new Array(port_count).fill(0);
var ids = new Array(port_count);
for(var i = 0; i < onwards.length; i++) {
var onward = onwards[i];
for(var j = 0; j < port_count; j++) {
counts[j] += (onward[j] !== null) ? 1 : 0
}
ids[i] = RED.util.generateId();
}
var indexes = new Array(port_count).fill(0);
var ports = new Array(port_count);
for (var i = 0; i < onwards.length; i++) {
var onward = onwards[i];
for (var j = 0; j < port_count; j++) {
var msg = onward[j];
if (msg) {
var new_msg = RED.util.cloneMessage(msg);
var parts = new_msg.parts;
parts.id = ids[j];
parts.index = indexes[j];
parts.count = counts[j];
ports[j] = new_msg;
indexes[j]++;
}
else {
ports[j] = null;
}
}
node.send(ports);
}
}
function send2ports(onward, msg) {
var parts = msg.parts;
var gid = parts.id;
received[gid] = ((gid in received) ? received[gid] : 0) +1;
var send_ok = (received[gid] === parts.count);
if (!(gid in pending_out)) {
pending_out[gid] = {
onwards: []
};
}
var group = pending_out[gid];
var onwards = group.onwards;
onwards.push(onward);
pending_count++;
if (send_ok) {
send_group(onwards, onward.length, msg);
pending_count -= onward.length;
delete pending_out[gid];
delete received[gid];
}
var max_msgs = max_kept_msgs_count(node);
if ((max_msgs > 0) && (pending_count > max_msgs)) {
clear_pending();
node.error(RED._("switch.errors.too-many"), msg);
}
}
function msg_has_parts(msg) {
if (msg.hasOwnProperty("parts")) {
var parts = msg.parts;
return (parts.hasOwnProperty("id") &&
parts.hasOwnProperty("index") &&
parts.hasOwnProperty("count"));
}
return false;
}
function process_msg(msg, check_parts) {
var has_parts = msg_has_parts(msg);
if (needs_count && check_parts && has_parts &&
add2pending_in(msg)) {
return;
}
var onward = []; var onward = [];
try { try {
var prop; var prop;
@ -117,7 +279,14 @@ module.exports = function(RED) {
v1 = node.previousValue; v1 = node.previousValue;
} else if (rule.vt === 'jsonata') { } else if (rule.vt === 'jsonata') {
try { try {
v1 = RED.util.evaluateJSONataExpression(rule.v,msg); var exp = rule.v;
if (rule.t === 'jsonata_exp') {
if (has_parts) {
exp.assign("I", msg.parts.index);
exp.assign("N", msg.parts.count);
}
}
v1 = RED.util.evaluateJSONataExpression(exp,msg);
} catch(err) { } catch(err) {
node.error(RED._("switch.errors.invalid-expr",{error:err.message})); node.error(RED._("switch.errors.invalid-expr",{error:err.message}));
return; return;
@ -147,7 +316,7 @@ module.exports = function(RED) {
} }
} }
if (rule.t == "else") { test = elseflag; elseflag = true; } if (rule.t == "else") { test = elseflag; elseflag = true; }
if (operators[rule.t](test,v1,v2,rule.case)) { if (operators[rule.t](test,v1,v2,rule.case,msg.parts)) {
onward.push(msg); onward.push(msg);
elseflag = false; elseflag = false;
if (node.checkall == "false") { break; } if (node.checkall == "false") { break; }
@ -156,11 +325,33 @@ module.exports = function(RED) {
} }
} }
node.previousValue = prop; node.previousValue = prop;
this.send(onward); if (repair || !has_parts) {
node.send(onward);
}
else {
send2ports(onward, msg);
}
} catch(err) { } catch(err) {
node.warn(err); node.warn(err);
} }
}
function clear_pending() {
pending_count = 0;
pending_id = 0;
pending_in = {};
pending_out = {};
received = {};
}
this.on('input', function(msg) {
process_msg(msg, true);
});
this.on('close', function() {
clear_pending();
}); });
} }
RED.nodes.registerType("switch", SwitchNode); RED.nodes.registerType("switch", SwitchNode);
} }

View File

@ -108,7 +108,7 @@
arraySplt: {value:1}, arraySplt: {value:1},
arraySpltType: {value:"len"}, arraySpltType: {value:"len"},
stream: {value:false}, stream: {value:false},
addname: {value:""}, addname: {value:""}
}, },
inputs:1, inputs:1,
outputs:1, outputs:1,
@ -170,6 +170,8 @@
<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>
</select> </select>
</div> </div>
<div class="node-row-custom"> <div class="node-row-custom">
@ -217,6 +219,38 @@
</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="form-row">
<label for="node-input-reduceExp" data-i18n="join.reduce.exp" style="margin-left:10px;"></label>
<input type="text" id="node-input-reduceExp" data-i18n="[placeholder]join.reduce.exp-value" style="width:65%">
</div>
<div class="form-row">
<label for="node-input-reduceInit" data-i18n="join.reduce.init" style="margin-left:10px;"></label>
<input type="text" id="node-input-reduceInit" data-i18n="[placeholder]join.reduce.init" style="width:65%">
<input type="hidden" id="node-input-reduceInitType">
</div>
<div class="form-row">
<label for="node-input-reduceFixup" data-i18n="join.reduce.fixup" style="margin-left:10px;"></label>
<input type="text" id="node-input-reduceFixup" data-i18n="[placeholder]join.reduce.exp-value" style="width:65%">
</div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-reduceRight" style="display:inline-block; width:auto; vertical-align:top; margin-left:10px;">
<label for="node-input-reduceRight" style="width:70%;" data-i18n="join.reduce.right" style="margin-left:10px;"/>
</div>
</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">
@ -225,9 +259,17 @@
</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.</p> <p>Joins sequences of messages into a single message. This node provides four mode for message combination:</p>
<p>When paired with the <b>split</b> node, it will automatically join the messages <dl>
to reverse the split that was performed.</p> <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>
<dt>manual</dt>
<dd>It will 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>
<dd>When paired with the <b>split</b> node, it will reduce the message sequence into single message.</dd>
</dl>
<h3>Inputs</h3> <h3>Inputs</h3>
<dl class="message-properties"> <dl class="message-properties">
<dt class="optional">parts<span class="property-type">object</span></dt> <dt class="optional">parts<span class="property-type">object</span></dt>
@ -248,6 +290,11 @@
<dd>If set, the node will send its output message in its current state.</dd> <dd>If set, the node will send its output message in its current state.</dd>
</dl> </dl>
<h3>Details</h3> <h3>Details</h3>
<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>
<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 in a variety of ways.</p>
<ul> <ul>
@ -263,6 +310,50 @@
received.</p> received.</p>
<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>
<p>When configured to join in reduce sequence mode, following values can be specified:</p>
<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>
<dd>
initial value of reduction.
</dd>
<dt>Fixup exp</dt>
<dd>
JSONata expression applied after reduction of a message group completed. In the expression, following special variables can be used:
<ul>
<li><code>$A</code> accumulated value, </li>
<li><code>$N</code> number of messages of a group.</li>
</ul>
</dd>
<p>Order of reduction on a message group can be specified by checkbox (<b>Evaluate in reverse order (right to left)</b>).</p>
</dl>
<p><b>Example:</b> Join node outputs an average for each input message group with the following setting:
<ul>
<li><b>Reduce exp</b>: <code>$A+payload</code></li>
<li><b>Initial value</b>: <code>0</code></li>
<li><b>Fixup exp</b>: <code>$A/$N</code></li>
</ul>
</p>
<h4>Note:</h4>
<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>
</script> </script>
<script type="text/javascript"> <script type="text/javascript">
@ -280,7 +371,14 @@
joinerType: { value:"str"}, joinerType: { value:"str"},
accumulate: { value:"false" }, accumulate: { value:"false" },
timeout: {value:""}, timeout: {value:""},
count: {value:""} count: {value:""},
topics: {value:[{topic:""}]},
mergeOnChange: {value:false},
reduceRight: {value:false},
reduceExp: {value:undefined},
reduceInit: {value:undefined},
reduceInitType: {value:undefined},
reduceFixup: {value:undefined}
}, },
inputs:1, inputs:1,
outputs:1, outputs:1,
@ -292,13 +390,93 @@
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
}, },
oneditprepare: function() { oneditprepare: function() {
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');
$(".form-tips-auto").toggle(val==='auto'); $(".node-row-merge").toggle(val==='merge');
$(".node-row-reduce").toggle(val==='reduce');
$(".form-tips-auto").toggle((val==='auto') || (val==='reduce'));
if (val === "auto") { if (val === "auto") {
$("#node-input-accumulate").attr('checked', false); $("#node-input-accumulate").attr('checked', false);
} }
else if (val === "custom") {
$("#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") {
var jsonata_or_empty = {
value: "jsonata",
label: "expression",
icon: "red/images/typedInput/expr.png",
validate: function(v) {
try{
if(v !== "") {
jsonata(v);
}
return true;
}
catch(e){
return false;
}
},
expand:function() {
var that = this;
RED.editor.editExpression({
value: this.value().replace(/\t/g,"\n"),
complete: function(v) {
that.value(v.replace(/\n/g,"\t"));
}
})
}
};
$("#node-input-reduceExp").typedInput({types:[jsonata_or_empty]});
$("#node-input-reduceInit").typedInput({
default: 'num',
types:['flow','global','str','num','bool','json','bin','date','jsonata'],
typeField: $("#node-input-reduceInitType")
});
$("#node-input-reduceFixup").typedInput({types:[jsonata_or_empty]});
}
}); });
$("#node-input-build").change(function(e) { $("#node-input-build").change(function(e) {
@ -309,6 +487,7 @@
$(".node-row-trigger").toggle(val!=='auto'); $(".node-row-trigger").toggle(val!=='auto');
if (val === 'string' || val==='buffer') { if (val === 'string' || val==='buffer') {
$("#node-input-property").typedInput('types',['msg']); $("#node-input-property").typedInput('types',['msg']);
$("#node-input-joiner").typedInput("show");
} else { } else {
$("#node-input-property").typedInput('types',['msg', {value:"full",label:"complete message",hasValue:false}]); $("#node-input-property").typedInput('types',['msg', {value:"full",label:"complete message",hasValue:false}]);
} }
@ -340,6 +519,27 @@
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

@ -232,6 +232,229 @@ module.exports = function(RED) {
RED.nodes.registerType("split",SplitNode); RED.nodes.registerType("split",SplitNode);
var _max_kept_msgs_count = undefined;
function max_kept_msgs_count(node) {
if (_max_kept_msgs_count === undefined) {
var name = "joinMaxKeptMsgsCount";
if (RED.settings.hasOwnProperty(name)) {
_max_kept_msgs_count = RED.settings[name];
}
else {
_max_kept_msgs_count = 0;
}
}
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) {
exp.assign("I", index);
exp.assign("N", count);
exp.assign("A", accum);
return RED.util.evaluateJSONataExpression(exp, msg);
}
function apply_f(exp, accum, count) {
exp.assign("N", count);
exp.assign("A", accum);
return RED.util.evaluateJSONataExpression(exp, {});
}
function exp_or_undefined(exp) {
if((exp === "") ||
(exp === null)) {
return undefined;
}
return exp
}
function reduce_and_send_group(node, group) {
var is_right = node.reduce_right;
var flag = is_right ? -1 : 1;
var msgs = group.msgs;
var accum = node.reduce_init;
var reduce_exp = node.reduce_exp;
var reduce_fixup = node.reduce_fixup;
var count = group.count;
msgs.sort(function(x,y) {
var ix = x.parts.index;
var iy = y.parts.index;
if (ix < iy) return -flag;
if (ix > iy) return flag;
return 0;
});
for(var msg of msgs) {
accum = apply_r(reduce_exp, accum, msg, msg.parts.index, count);
}
if(reduce_fixup !== undefined) {
accum = apply_f(reduce_fixup, accum, count);
}
node.send({payload: accum});
}
function reduce_msg(node, msg) {
if(msg.hasOwnProperty('parts')) {
var parts = msg.parts;
var pending = node.pending;
var pending_count = node.pending_count;
var gid = msg.parts.id;
if(!pending.hasOwnProperty(gid)) {
var count = undefined;
if(parts.hasOwnProperty('count')) {
count = msg.parts.count;
}
pending[gid] = {
count: count,
msgs: []
};
}
var group = pending[gid];
var msgs = group.msgs;
if(parts.hasOwnProperty('count') &&
(group.count === undefined)) {
group.count = count;
}
msgs.push(msg);
pending_count++;
if(msgs.length === group.count) {
delete pending[gid];
try {
pending_count -= msgs.length;
reduce_and_send_group(node, group);
} catch(e) {
node.error(RED._("join.errors.invalid-expr",{error:e.message})); }
}
node.pending_count = pending_count;
var max_msgs = max_kept_msgs_count(node);
if ((max_msgs > 0) && (pending_count > max_msgs)) {
node.pending = {};
node.pending_count = 0;
node.error(RED._("join.too-many"), msg);
}
}
else {
node.send(msg);
}
}
function eval_exp(node, exp, exp_type) {
if(exp_type === "flow") {
return node.context().flow.get(exp);
}
else if(exp_type === "global") {
return node.context().global.get(exp);
}
else if(exp_type === "str") {
return exp;
}
else if(exp_type === "num") {
return Number(exp);
}
else if(exp_type === "bool") {
if (exp === 'true') {
return true;
}
else if (exp === 'false') {
return false;
}
}
else if ((exp_type === "bin") ||
(exp_type === "json")) {
return JSON.parse(exp);
}
else if(exp_type === "date") {
return Date.now();
}
else if(exp_type === "jsonata") {
var jexp = RED.util.prepareJSONataExpression(exp, node);
return RED.util.evaluateJSONataExpression(jexp, {});
}
throw new Error("unexpected initial value type");
}
function JoinNode(n) { function JoinNode(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
this.mode = n.mode||"auto"; this.mode = n.mode||"auto";
@ -246,6 +469,22 @@ module.exports = function(RED) {
this.joiner = n.joiner||""; this.joiner = n.joiner||"";
this.joinerType = n.joinerType||"str"; this.joinerType = n.joinerType||"str";
this.reduce = (this.mode === "reduce");
if (this.reduce) {
var exp_init = n.reduceInit;
var exp_init_type = n.reduceInitType;
var exp_reduce = n.reduceExp;
var exp_fixup = exp_or_undefined(n.reduceFixup);
this.reduce_right = n.reduceRight;
try {
this.reduce_init = eval_exp(this, exp_init, exp_init_type);
this.reduce_exp = RED.util.prepareJSONataExpression(exp_reduce, this);
this.reduce_fixup = (exp_fixup !== undefined) ? RED.util.prepareJSONataExpression(exp_fixup, this) : undefined;
} catch(e) {
this.error(RED._("join.errors.invalid-expr",{error:e.message}));
}
}
if (this.joinerType === "str") { if (this.joinerType === "str") {
this.joiner = this.joiner.replace(/\\n/g,"\n").replace(/\\r/g,"\r").replace(/\\t/g,"\t").replace(/\\e/g,"\e").replace(/\\f/g,"\f").replace(/\\0/g,"\0"); this.joiner = this.joiner.replace(/\\n/g,"\n").replace(/\\r/g,"\r").replace(/\\t/g,"\t").replace(/\\e/g,"\e").replace(/\\f/g,"\f").replace(/\\0/g,"\0");
} else if (this.joinerType === "bin") { } else if (this.joinerType === "bin") {
@ -259,6 +498,14 @@ module.exports = function(RED) {
this.build = n.build || "array"; this.build = n.build || "array";
this.accumulate = n.accumulate || "false"; this.accumulate = n.accumulate || "false";
this.topics = (n.topics || []).map(function(x) { return x.topic; });
this.merge_on_change = n.mergeOnChange || false;
this.topic_counts = undefined;
this.output = n.output || "stream";
this.pending = {};
this.pending_count = 0;
//this.topic = n.topic; //this.topic = n.topic;
var node = this; var node = this;
var inflight = {}; var inflight = {};
@ -321,6 +568,10 @@ 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;
@ -351,6 +602,14 @@ 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') {
reduce_msg(node, msg);
return;
}
else { else {
// Use the node configuration to identify all of the group information // Use the node configuration to identify all of the group information
partId = "_"; partId = "_";

View File

@ -19,17 +19,24 @@
<script type="text/x-red" data-template-name="sort"> <script type="text/x-red" data-template-name="sort">
<div class="form-row"> <div class="form-row">
<label><i class="fa fa-dot-circle-o"></i> <span data-i18n="sort.key-type"></span></label> <label for="node-input-target"><i class="fa fa-dot-circle-o"></i> <span data-i18n="sort.target"></span></label>
<select id="node-input-keyType" style="width:200px;"> <input type="text" id="node-input-target" style="width:70%;">
<option value="payload" data-i18n="sort.payload"></option> <input type="hidden" id="node-input-targetType">
<option value="exp" data-i18n="sort.exp"></option>
</select>
</div> </div>
<div class="node-row-sort-key"> <div class="node-row-sort-msg-key">
<div class="form-row"> <div class="form-row">
<label><i class="fa fa-filter"></i> <span data-i18n="sort.key-exp"></span></label> <label for="node-input-msgKey"><i class="fa fa-filter"></i> <span data-i18n="sort.key"></span></label>
<input type="text" id="node-input-key" style="width:70%;"> <input type="text" id="node-input-msgKey" style="width:70%;">
<input type="hidden" id="node-input-msgKeyType">
</div>
</div>
<div class="node-row-sort-seq-key">
<div class="form-row">
<label for="node-input-seqKey"><i class="fa fa-filter"></i> <span data-i18n="sort.key"></span></label>
<input type="text" id="node-input-seqKey" style="width:70%;">
<input type="hidden" id="node-input-seqKeyType">
</div> </div>
</div> </div>
@ -54,17 +61,17 @@
</script> </script>
<script type="text/x-red" data-help-name="sort"> <script type="text/x-red" data-help-name="sort">
<p>A function that sorts a sequence of messages or payload of array type.</p> <p>A function that sorts message property or a sequence of messages.</p>
<p>When paired with the <b>split</b> node, it will reorder the <p>When configured to sort message property, the node sorts array data pointed to by specified message property.</p>
messages.</p> <p>When configured to sort a sequence of messages, it will reorder the messages.</p>
<p>The sorting order can be:</p> <p>The sorting order can be:</p>
<ul> <ul>
<li><b>ascending</b>,</li> <li><b>ascending</b>,</li>
<li><b>descending</b>.</li> <li><b>descending</b>.</li>
</ul> </ul>
<p>For numbers, numerical ordering can be specified by a checkbox.</p> <p>For numbers, numerical ordering can be specified by a checkbox.</p>
<p>Sort key can be <code>payload</code> or any JSONata expression for sorting messages, element value or any JSONata expression for sorting payload of array type.</p> <p>Sort key can be element value or JSONata expression for sorting property value, or message property or JSONata expression for sorting a message sequence.<p>
<p>The sort node relies on the received messages to have <code>msg.parts</code> set for sorting messages. The split node generates this property, but can be manually created. It has the following properties:</p> <p>When sorting a message sequence, the sort node relies on the received messages to have <code>msg.parts</code> set. The split node generates this property, but can be manually created. It has the following properties:</p>
<p> <p>
<ul> <ul>
<li><code>id</code> - an identifier for the group of messages</li> <li><code>id</code> - an identifier for the group of messages</li>
@ -74,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>sortMaxKeptMsgsCount</code> property set in <b>settings.js</b>.</li> <li><code>maxKeptMsgsCount</code> property set in <b>settings.js</b>.</li>
</ul> </ul>
</p> </p>
</script> </script>
@ -86,9 +93,13 @@
defaults: { defaults: {
name: { value:"" }, name: { value:"" },
order: { value:"ascending" }, order: { value:"ascending" },
as_num : { value:false }, as_num: { value:false },
keyType : { value:"payload" }, target: { value:"payload" },
key : { value:"" } targetType: { value:"msg" },
msgKey: { value:"payload" },
msgKeyType: { value:"elem" },
seqKey: { value:"payload" },
seqKeyType: { value:"msg" }
}, },
inputs:1, inputs:1,
outputs:1, outputs:1,
@ -100,13 +111,37 @@
return this.name ? "node_label_italic" : ""; return this.name ? "node_label_italic" : "";
}, },
oneditprepare: function() { oneditprepare: function() {
$("#node-input-key").typedInput({default:'jsonata', types:['jsonata']}); var seq = {
$("#node-input-keyType").change(function(e) { value: "seq",
var val = $(this).val(); label: RED._("node-red:sort.seq"),
$(".node-row-sort-key").toggle(val === 'exp'); hasValue: false
};
var elem = {
value: "elem",
label: RED._("node-red:sort.elem"),
hasValue: false
};
$("#node-input-target").typedInput({
default:'msg',
typeField: $("#node-input-targetType"),
types:['msg', seq]
}); });
$("#node-input-keyType").change(); $("#node-input-msgKey").typedInput({
$("#node-input-order").change(); default:'elem',
typeField: $("#node-input-msgKeyType"),
types:[elem, 'jsonata']
});
$("#node-input-seqKey").typedInput({
default:'msg',
typeField: $("#node-input-seqKeyType"),
types:['msg', 'jsonata']
});
$("#node-input-target").change(function(e) {
var val = $("#node-input-target").typedInput('type');
$(".node-row-sort-msg-key").toggle(val === "msg");
$(".node-row-sort-seq-key").toggle(val === "seq");
});
$("#node-input-target").change();
} }
}); });
</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 = "sortMaxKeptMsgsCount"; var name = "maxKeptMsgsCount";
if (RED.settings.hasOwnProperty(name)) { if (RED.settings.hasOwnProperty(name)) {
_max_kept_msgs_count = RED.settings[name]; _max_kept_msgs_count = RED.settings[name];
} }
@ -60,11 +60,15 @@ module.exports = function(RED) {
var pending_id = 0; var pending_id = 0;
var order = n.order || "ascending"; var order = n.order || "ascending";
var as_num = n.as_num || false; var as_num = n.as_num || false;
var key_is_payload = (n.keyType === 'payload'); var target_prop = n.target || "payload";
var key_exp = undefined; var target_is_prop = (n.targetType === 'msg');
if (!key_is_payload) { var key_is_exp = target_is_prop ? (n.msgKeyType === "jsonata") : (n.seqKeyType === "jsonata");
var key_prop = n.seqKey || "payload";
var key_exp = target_is_prop ? n.msgKey : n.seqKey;
if (key_is_exp) {
try { try {
key_exp = RED.util.prepareJSONataExpression(n.key, this); key_exp = RED.util.prepareJSONataExpression(key_exp, this);
} }
catch (e) { catch (e) {
node.error(RED._("sort.invalid-exp")); node.error(RED._("sort.invalid-exp"));
@ -87,10 +91,12 @@ module.exports = function(RED) {
} }
function send_group(group) { function send_group(group) {
var key = key_is_payload var key = key_is_exp
? function(msg) { return msg.payload; } ? function(msg) {
: function(msg) {
return eval_jsonata(node, key_exp, msg); return eval_jsonata(node, key_exp, msg);
}
: function(msg) {
return RED.util.getMessageProperty(msg, key_prop);
}; };
var comp = gen_comp(key); var comp = gen_comp(key);
var msgs = group.msgs; var msgs = group.msgs;
@ -108,16 +114,16 @@ module.exports = function(RED) {
} }
function sort_payload(msg) { function sort_payload(msg) {
var payload = msg.payload; var data = RED.util.getMessageProperty(msg, target_prop);
if (Array.isArray(payload)) { if (Array.isArray(data)) {
var key = key_is_payload var key = key_is_exp
? function(elem) { return elem; } ? function(elem) {
: function(elem) {
return eval_jsonata(node, key_exp, elem); return eval_jsonata(node, key_exp, elem);
}; }
: function(elem) { return elem; };
var comp = gen_comp(key); var comp = gen_comp(key);
try { try {
payload.sort(comp); data.sort(comp);
} }
catch (e) { catch (e) {
return false; return false;
@ -162,7 +168,7 @@ module.exports = function(RED) {
} }
function process_msg(msg) { function process_msg(msg) {
if (!msg.hasOwnProperty("parts")) { if (target_is_prop) {
if (sort_payload(msg)) { if (sort_payload(msg)) {
node.send(msg); node.send(msg);
} }

View File

@ -0,0 +1,192 @@
<!--
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.
-->
<!DOCTYPE html>
<script type="text/x-red" data-template-name="batch">
<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>
<select type="text" id="node-input-mode" style="width: 300px;">
<option value="count" data-i18n="batch.mode.num-msgs"></option>
<option value="interval" data-i18n="batch.mode.interval"></option>
<option value="concat" data-i18n="batch.mode.concat"></option>
</select>
</div>
<div class="node-row-msg-count">
<div class="form-row node-row-count">
<label 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;">
</div>
</div>
<div class="node-row-msg-overwrap">
<div class="form-row node-row-overwrap">
<label for="node-input-count" data-i18n="batch.count.overwrap"></label>
<input type="text" id="node-input-overwrap" data-i18n="[placeholder]batch.count.count" style="width: 50px;">
</div>
</div>
<div class="node-row-msg-interval">
<div class="form-row node-row-interval">
<label 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;">
<span data-i18n="batch.interval.sec"></span>
</div>
<div class="form-row">
<input type="checkbox" id="node-input-allowEmptySequence" style="margin-left:10px; vertical-align:top; width:auto;">
<label for="node-input-allowEmptySequence" style="width:auto;" data-i18n="batch.interval.empty"></label>
</div>
</div>
<div class="node-row-msg-concat">
<div class="form-row">
<label data-i18n="batch.concat.topics-label"></label>
<div class="form-row node-input-topics-container-row">
<ol id="node-input-topics-container"></ol>
</div>
</div>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
</div>
</script>
<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>
<h3>Details</h3>
<h4>group by number of messages</h4>
<p>groups incoming messages into sequences of messages with specified counts. The output message group can be overwrapped.</p>
<h4>group by interval in seconds</h4>
<p>groups incoming messages received withn specified interval into sequences of messages. </p>
<h4>concatenate message groups</h4>
<p>creates a message sequence based on <code>topic</code> value of incoming message sequences.</p>
<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.
<ul>
<li><code>batchMaxKeptMsgsCount</code> property set in <b>settings.js</b>.</li>
</ul>
</p>
</script>
<script type="text/javascript">
RED.nodes.registerType("batch",{
category: "function",
color:"#E2D96E",
defaults: {
name: {value:""},
mode: {value:"count"},
count: {value:10},
overwrap: {value:0},
interval: {value:10},
allowEmptySequence: {value:false},
topics: {value:[{topic:""}]}
},
inputs:1,
outputs:1,
icon: "batch.png",
label: function() {
return this.name || "batch";
},
labelStyle: function() {
return this.name ? "node_label_italic" : "";
},
oneditprepare: function() {
var node = this;
var topic_str = node._("batch.concat.topic");
function resizeTopics(topic) {
var newWidth = topic.width();
topic.find('.red-ui-typedInput')
.typedInput("width",newWidth-15);
}
$("#node-input-topics-container")
.css('min-height','200px').css('min-width','430px')
.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-count").spinner({
});
$("#node-input-overwrap").spinner({
});
$("#node-input-interval").spinner({
});
$("#node-input-mode").change(function(e) {
var val = $(this).val();
$(".node-row-msg-count").toggle(val==="count");
$(".node-row-msg-overwrap").toggle(val==="count");
$(".node-row-msg-interval").toggle(val==="interval");
$(".node-row-msg-concat").toggle(val==="concat");
if (val==="concat") {
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);
}
}
});
},
oneditsave: function() {
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 type = vf.typedInput('type');
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>

View File

@ -0,0 +1,246 @@
/**
* 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) {
"use strict";
var _max_kept_msgs_count = undefined;
function max_kept_msgs_count(node) {
if (_max_kept_msgs_count === undefined) {
var name = "batchMaxKeptMsgsCount";
if (RED.settings.hasOwnProperty(name)) {
_max_kept_msgs_count = RED.settings[name];
}
else {
_max_kept_msgs_count = 0;
}
}
return _max_kept_msgs_count;
}
function send_msgs(node, msgs, clone_msg) {
var count = msgs.length;
var msg_id = msgs[0]._msgid;
for (var i = 0; i < count; i++) {
var msg = clone_msg ? RED.util.cloneMessage(msgs[i]) : msgs[i];
if (!msg.hasOwnProperty("parts")) {
msg.parts = {};
}
var parts = msg.parts;
parts.id = msg_id;
parts.index = i;
parts.count = count;
node.send(msg);
}
}
function send_interval(node, allow_empty_seq) {
let msgs = node.pending;
if (msgs.length > 0) {
send_msgs(node, msgs, false);
node.pending = [];
}
else {
if (allow_empty_seq) {
let mid = RED.util.generateId();
let msg = {
payload: null,
parts: {
id: mid,
index: 0,
count: 1
}
};
node.send(msg);
}
}
}
function is_complete(pending, topic) {
if (pending.hasOwnProperty(topic)) {
var p_topic = pending[topic];
var gids = p_topic.gids;
if (gids.length > 0) {
var gid = gids[0];
var groups = p_topic.groups;
var group = groups[gid];
return (group.count === group.msgs.length);
}
}
return false;
}
function get_msgs_of_topic(pending, topic) {
var p_topic = pending[topic];
var groups = p_topic.groups;
var gids = p_topic.gids;
var gid = gids[0];
var group = groups[gid];
return group.msgs;
}
function remove_topic(pending, topic) {
var p_topic = pending[topic];
var groups = p_topic.groups;
var gids = p_topic.gids;
var gid = gids.shift();
delete groups[gid];
}
function try_concat(node, pending) {
var topics = node.topics;
for (var topic of topics) {
if (!is_complete(pending, topic)) {
return;
}
}
var msgs = [];
for (var topic of topics) {
var t_msgs = get_msgs_of_topic(pending, topic);
msgs = msgs.concat(t_msgs);
}
for (var topic of topics) {
remove_topic(pending, topic);
}
send_msgs(node, msgs, false);
node.pending_count -= msgs.length;
}
function add_to_topic_group(pending, topic, gid, msg) {
if (!pending.hasOwnProperty(topic)) {
pending[topic] = { groups: {}, gids: [] };
}
var p_topic = pending[topic];
var groups = p_topic.groups;
var gids = p_topic.gids;
if (!groups.hasOwnProperty(gid)) {
groups[gid] = { msgs: [], count: undefined };
gids.push(gid);
}
var group = groups[gid];
group.msgs.push(msg);
if ((group.count === undefined) &&
msg.parts.hasOwnProperty('count')) {
group.count = msg.parts.count;
}
}
function concat_msg(node, msg) {
var topic = msg.topic;
if(node.topics.indexOf(topic) >= 0) {
if (!msg.hasOwnProperty("parts") ||
!msg.parts.hasOwnProperty("id") ||
!msg.parts.hasOwnProperty("index") ||
!msg.parts.hasOwnProperty("count")) {
node.error(RED._("batch.no-parts"), msg);
return;
}
var gid = msg.parts.id;
var pending = node.pending;
add_to_topic_group(pending, topic, gid, 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._("batch.too-many"), msg);
}
try_concat(node, pending);
}
}
function BatchNode(n) {
RED.nodes.createNode(this,n);
var node = this;
var mode = n.mode || "count";
node.pending_count = 0;
if (mode === "count") {
var count = Number(n.count || 1);
var overwrap = Number(n.overwrap || 0);
var is_overwrap = (overwrap > 0);
if (count <= overwrap) {
node.error(RED._("batch.count.invalid"));
return;
}
node.pending = [];
this.on("input", function(msg) {
var queue = node.pending;
queue.push(msg);
node.pending_count++;
if (queue.length === count) {
send_msgs(node, queue, is_overwrap);
node.pending =
(overwrap === 0) ? [] : queue.slice(-overwrap);
node.pending_count = 0;
}
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._("batch.too-many"), msg);
}
});
this.on("close", function() {
node.pending_count = 0;
node.pending = [];
});
}
else if (mode === "interval") {
var interval = Number(n.interval || "0") *1000;
var allow_empty_seq = n.allowEmptySequence;
node.pending = []
var timer = setInterval(function() {
send_interval(node, allow_empty_seq);
node.pending_count = 0;
}, interval);
this.on("input", function(msg) {
node.pending.push(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._("batch.too-many"), msg);
}
});
this.on("close", function() {
clearInterval(timer);
node.pending = [];
node.pending_count = 0;
});
}
else if(mode === "concat") {
node.topics = (n.topics || []).map(function(x) {
return x.topic;
});
node.pending = {};
this.on("input", function(msg) {
concat_msg(node, msg);
});
this.on("close", function() {
node.pending = {};
node.pending_count = 0;
});
}
else {
node.error(RED._("batch.unexpected"));
}
}
RED.nodes.registerType("batch", BatchNode);
}

View File

@ -47,9 +47,9 @@ 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 sort node. // The maximum number of messages kept internally in nodes.
// Zero or undefined value means not restricting number of messages. // Zero or undefined value means not restricting number of messages.
//sortMaxKeptMsgsCount: 0, //maxKeptMsgsCount: 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

@ -14,67 +14,125 @@
* limitations under the License. * limitations under the License.
**/ **/
var RED = require("../../red/red.js");
var when = require("when"); var when = require("when");
var http = require('http'); var http = require('http');
var express = require("express"); var express = require("express");
var fs = require('fs-extra'); var fs = require('fs-extra');
var path = require('path');
var app = express(); var app = express();
var RED = require("../../red/red.js");
var utilPage = require("./pageobjects/util/util_page");
var server; var server;
var homeDir = './test/resources/home'; var homeDir = './test/resources/home';
var address = '127.0.0.1'; var address = '127.0.0.1';
var listenPort = 0; // use ephemeral port var listenPort = 0; // use ephemeral port
var url; var url;
/*
* Set false when you need a flow to reproduce the failed test case.
* The flow file is under "homeDir" defined above.
*/
var isDeleteFlow = true;
function cleanup() { function getFlowFilename() {
var flowsFile = homeDir + '/flows_'+require('os').hostname()+'.json'; var orig = Error.prepareStackTrace;
var err = new Error();
Error.prepareStackTrace = function (err, stack) {
return stack;
};
// Two level higher caller is the actual caller (e.g. a caller of startServer).
var filepath = err.stack[2].getFileName();
var filename = path.basename(filepath, ".js");
Error.prepareStackTrace = orig;
var flowFile = 'flows_' + filename + '.json';
return flowFile;
}
function cleanup(flowFile) {
deleteFile(homeDir+"/"+flowFile);
deleteFile(homeDir+"/."+flowFile+".backup");
}
function deleteFile(flowFile) {
try { try {
fs.statSync(flowsFile); fs.statSync(flowFile);
fs.unlinkSync(flowsFile); if (isDeleteFlow) {
} catch (err) { fs.unlinkSync(flowFile);
} }
} catch (e) {}
}; };
module.exports = { module.exports = {
startServer: function(done) { startServer: function() {
cleanup(); try{
app.use("/",express.static("public")); utilPage.init();
server = http.createServer(app);
var settings = { // Name a flow file including caller filename so that multiple Node-RED servers can run simultaneously.
httpAdminRoot: "/", // Call this method here because retrieving the caller filename by call stack.
httpNodeRoot: "/api", var flowFilename = getFlowFilename();
userDir: homeDir, browser.windowHandleMaximize();
functionGlobalContext: { }, // enables global context browser.call(function () {
SKIP_BUILD_CHECK: true, // return when.promise(function(resolve, reject) {
logging: {console: {level:'off'}} return new Promise(function(resolve, reject) {
}; cleanup(flowFilename);
RED.init(server, settings); app.use("/",express.static("public"));
app.use(settings.httpAdminRoot,RED.httpAdmin); server = http.createServer(app);
app.use(settings.httpNodeRoot,RED.httpNode); var settings = {
server.listen(listenPort, address); httpAdminRoot: "/",
server.on('listening', function() { httpNodeRoot: "/api",
var port = server.address().port; userDir: homeDir,
url = 'http://' + address + ':' + port; flowFile: flowFilename,
}); functionGlobalContext: { }, // enables global context
RED.start().then(function() { SKIP_BUILD_CHECK: true,
done(); logging: {console: {level:'off'}}
}); };
RED.init(server, settings);
app.use(settings.httpAdminRoot,RED.httpAdmin);
app.use(settings.httpNodeRoot,RED.httpNode);
server.listen(listenPort, address);
server.on('listening', function() {
var port = server.address().port;
url = 'http://' + address + ':' + port;
});
RED.start().then(function() {
resolve();
});
});
});
browser.url(url);
browser.waitForExist('#palette_node_inject');
} catch (err) {
console.log(err);
throw err;
}
}, },
stopServer: function(done) { stopServer: function(done) {
if (server) { try {
try { // Call this method here because retrieving the caller filename by call stack.
RED.stop().then(function() { var flowFilename = getFlowFilename();
server.close(done); browser.call(function () {
cleanup(); browser.close(); // need to call this inside browser.call().
done(); return when.promise(function(resolve, reject) {
if (server) {
RED.stop().then(function() {
server.close(function() {
cleanup(flowFilename);
resolve();
});
});
} else {
cleanup(flowFilename);
resolve();
}
}); });
} catch(e) { });
cleanup(); } catch (err) {
done(); console.log(err);
} throw err;
} }
}, },

View File

@ -0,0 +1,52 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var util = require("util");
var nodePage = require("../../node_page");
function injectNode(id) {
nodePage.call(this, id);
}
util.inherits(injectNode, nodePage);
var payloadType = {
"flow": 1,
"global": 2,
"string": 3,
"num": 4,
"bool": 5,
"json": 6,
"bin": 7,
"date": 8,
};
injectNode.prototype.setPayload = function(type, value) {
// Open a payload type list.
browser.clickWithWait('//*[contains(@class, "red-ui-typedInput-container")]');
// Select a payload type.
var payloadTypeXPath = '//*[@class="red-ui-typedInput-options"]/a[' + payloadType[type] + ']';
browser.clickWithWait(payloadTypeXPath);
// Input a value.
browser.setValue('#node-input-payload', value);
}
injectNode.prototype.setTopic = function(value) {
browser.setValue('#node-input-topic', value);
}
module.exports = injectNode;

View File

@ -14,11 +14,14 @@
* limitations under the License. * limitations under the License.
**/ **/
function clickOk() { var util = require("util");
browser.click('#node-dialog-ok');
browser.pause(300); var nodePage = require("../../node_page");
function debugNode(id) {
nodePage.call(this, id);
} }
module.exports = { util.inherits(debugNode, nodePage);
clickOk: clickOk,
}; module.exports = debugNode;

View File

@ -0,0 +1,53 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var util = require("util");
var nodePage = require("../../node_page");
function changeNode(id) {
nodePage.call(this, id);
}
util.inherits(changeNode, nodePage);
function setT(rule, index) {
browser.selectByValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/select', rule);
}
changeNode.prototype.ruleSet = function(to, index) {
index = index ? index : 1;
setT("set", index);
browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[2]/div[2]/div/input', to);
}
changeNode.prototype.ruleDelete = function(index) {
index = index ? index : 1;
setT("delete", index);
}
changeNode.prototype.ruleMove = function(p, to, index) {
index = index ? index : 1;
setT("move", index);
browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p);
browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[4]/div[2]/div/input', to);
}
changeNode.prototype.addRule = function() {
browser.clickWithWait('//*[@id="dialog-form"]/div[3]/div/a');
}
module.exports = changeNode;

View File

@ -0,0 +1,38 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var util = require("util");
var nodePage = require("../../node_page");
function rangeNode(id) {
nodePage.call(this, id);
}
util.inherits(rangeNode, nodePage);
rangeNode.prototype.setAction = function(value) {
browser.selectByValue('#node-input-action', value);
}
rangeNode.prototype.setRange = function(minin, maxin, minout, maxout) {
browser.setValue('#node-input-minin', minin);
browser.setValue('#node-input-maxin', maxin);
browser.setValue('#node-input-minout', minout);
browser.setValue('#node-input-maxout', maxout);
}
module.exports = rangeNode;

View File

@ -14,29 +14,21 @@
* limitations under the License. * limitations under the License.
**/ **/
var icons = { function Node(id) {
// input this.id = '//*[@id="' + id + '"]';
"inject": "icons/node-red/inject.png",
// output
"debug": "icons/node-red/debug.png",
// function
"change": "icons/node-red/swap.png",
};
function getIdWithIcon(icon) {
//*[name()="image" and @*="icons/node-red/inject.png"]/../..
var id = browser.getAttribute('//*[name()="image" and @*="' + icon + '"]/../..', 'id');
return id;
}
function Node(type) {
this.id = '//*[@id="' + getIdWithIcon(icons[type]) + '"]';
} }
Node.prototype.edit = function() { Node.prototype.edit = function() {
browser.click(this.id); browser.clickWithWait(this.id);
browser.click(this.id); browser.clickWithWait(this.id);
browser.pause(500); // Necessary for headless mode. // Wait until an edit dialog opens.
browser.waitForVisible('#node-dialog-ok', 2000);
}
Node.prototype.clickOk = function() {
browser.clickWithWait('#node-dialog-ok');
// Wait untile an edit dialog closes.
browser.waitForVisible('#node-dialog-ok', 2000, true);
} }
Node.prototype.connect = function(targetNode) { Node.prototype.connect = function(targetNode) {
@ -46,7 +38,7 @@ Node.prototype.connect = function(targetNode) {
} }
Node.prototype.clickLeftButton = function() { Node.prototype.clickLeftButton = function() {
browser.click(this.id + '/*[@class="node_button node_left_button"]'); browser.clickWithWait(this.id + '/*[@class="node_button node_left_button"]');
} }
module.exports = Node; module.exports = Node;

View File

@ -0,0 +1,39 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var injectNode = require('./core/core/20-inject_page');
var debugNode = require('./core/core/58-debug_page');
var changeNode = require('./core/logic/15-change_page');
var rangeNode = require('./core/logic/16-range_page');
var nodeCatalog = {
// input
"inject": injectNode,
// output
"debug": debugNode,
// function
"change": changeNode,
"range": rangeNode,
}
function create(type, id) {
var node = nodeCatalog[type];
return new node(id);
}
module.exports = {
create: create,
};

View File

@ -0,0 +1,45 @@
/**
* 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.
**/
function init() {
browser.addCommand("clickWithWait", function(selector) {
browser.waitForVisible(selector);
// Wait at most 10 seconds.
for (var i = 0; i < 50; i++) {
try {
var ret = browser.click(selector);
return ret;
} catch (err) {
if (err.message.indexOf('is not clickable') !== -1) {
browser.pause(200);
} else {
throw err;
}
}
}
}, false);
browser.addCommand("getTextWithWait", function(selector) {
browser.waitForExist(selector);
browser.waitForValue(selector);
var ret = browser.getText(selector);
return ret;
}, false);
}
module.exports = {
init: init,
};

View File

@ -15,17 +15,17 @@
**/ **/
function open() { function open() {
browser.click('#red-ui-tab-debug'); browser.clickWithWait('#red-ui-tab-debug');
} }
function getMessage() { function getMessage(index) {
var debugMessagePath = '//div[@class="debug-content debug-content-list"]//span[contains(@class, "debug-message-type")]'; index = index ? index : 1;
browser.waitForExist(debugMessagePath); var debugMessagePath = '//div[@class="debug-content debug-content-list"]/div[contains(@class,"debug-message")][' + index + ']//span[contains(@class, "debug-message-type")]';
return browser.getText(debugMessagePath); return browser.getTextWithWait(debugMessagePath);
} }
function clearMessage() { function clearMessage() {
browser.click('//a[@id="debug-tab-clear"]'); browser.clickWithWait('//a[@id="debug-tab-clear"]');
} }
module.exports = { module.exports = {

View File

@ -0,0 +1,33 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var idMap = {
// input
"inject": "#palette_node_inject",
// output
"debug": "#palette_node_debug",
// function
"change": "#palette_node_change",
"range": "#palette_node_range",
};
function getId(type) {
return idMap[type];
}
module.exports = {
getId: getId,
};

View File

@ -14,26 +14,25 @@
* limitations under the License. * limitations under the License.
**/ **/
var when = require('when'); var when = require("when");
var events = require("../../../../red/runtime/events.js"); var events = require("../../../../red/runtime/events.js");
var node = require('./node_page'); var palette = require("./palette_page");
var nodeFactory = require("../nodes/nodefactory_page");
var palette = {
"inject": "#palette_node_inject",
"debug": "#palette_node_debug",
"change": "#palette_node_change",
};
function addNode(type, x, y) { function addNode(type, x, y) {
var offsetX = x ? x : 0; var offsetX = x ? x : 0;
var offsetY = y ? y : 0; var offsetY = y ? y : 0;
browser.moveToObject(palette[type]); browser.moveToObject(palette.getId(type));
browser.buttonDown(); browser.buttonDown();
browser.moveToObject("#palette-search", offsetX + 300, offsetY + 100); // adjust to the top-left corner of workspace. browser.moveToObject("#palette-search", offsetX + 300, offsetY + 100); // adjust to the top-left corner of workspace.
browser.buttonUp(); browser.buttonUp();
return new node(type); // Last node is the one that has been created right now.
var nodeElement = browser.elements('//*[@class="node nodegroup"][last()]');
var nodeId = nodeElement.getAttribute('id');
var node = nodeFactory.create(type, nodeId);
return node;
} }
function deleteAllNodes() { function deleteAllNodes() {
@ -49,10 +48,10 @@ function deploy() {
resolve(); resolve();
} }
}); });
browser.click('#btn-deploy'); browser.clickWithWait('#btn-deploy');
}); });
}); });
browser.pause(500); // Necessary for headless mode. browser.waitForText('#btn-deploy', 2000);
} }
module.exports = { module.exports = {

View File

@ -1,92 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var when = require('when');
var should = require("should");
var fs = require('fs-extra');
var helper = require("../editor_helper");
var editWindow = require('../pageobjects/workspace/editWindow_page');
var debugTab = require('../pageobjects/workspace/debugTab_page');
var workspace = require('../pageobjects/workspace/workspace_page');
var nodeWidth = 200;
describe('Node-RED main page', function() {
beforeEach(function() {
workspace.deleteAllNodes();
});
before(function() {
browser.windowHandleMaximize();
browser.call(function () {
return when.promise(function(resolve, reject) {
helper.startServer(function() {
resolve();
});
});
});
browser.url(helper.url());
browser.waitForExist('#palette_node_inject');
});
after(function() {
browser.call(function () {
return when.promise(function(resolve, reject) {
helper.stopServer(function() {
resolve();
});
});
});
});
it('should have a right title', function () {
browser.getTitle().should.equal('Node-RED');
});
it('should output a timestamp', function() {
var injectNode = workspace.addNode("inject");
var debugNode = workspace.addNode("debug", nodeWidth);
injectNode.connect(debugNode);
workspace.deploy();
debugTab.open();
debugTab.clearMessage();
injectNode.clickLeftButton();
debugTab.getMessage().should.within(1500000000000, 3000000000000);
});
it('should set a message property to a fixed value', function() {
var injectNode = workspace.addNode("inject");
var changeNode = workspace.addNode("change", nodeWidth);
var debugNode = workspace.addNode("debug", nodeWidth * 2);
changeNode.edit();
browser.setValue('.node-input-rule-property-value', 'Hello World!');
editWindow.clickOk();
injectNode.connect(changeNode);
changeNode.connect(debugNode);
workspace.deploy();
debugTab.open();
debugTab.clearMessage();
injectNode.clickLeftButton();
debugTab.getMessage().should.be.equal('"Hello World!"');
});
});

View File

@ -0,0 +1,145 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var when = require('when');
var should = require("should");
var fs = require('fs-extra');
var helper = require("../../editor_helper");
var debugTab = require('../../pageobjects/workspace/debugTab_page');
var workspace = require('../../pageobjects/workspace/workspace_page');
var nodeWidth = 200;
// https://cookbook.nodered.org/
describe('cookbook', function() {
beforeEach(function() {
workspace.deleteAllNodes();
});
before(function() {
helper.startServer();
});
after(function() {
helper.stopServer();
});
describe('messages', function() {
it('set a message property to a fixed value', function() {
var injectNode = workspace.addNode("inject");
var changeNode = workspace.addNode("change", nodeWidth);
var debugNode = workspace.addNode("debug", nodeWidth * 2);
changeNode.edit();
changeNode.ruleSet("Hello World!");
changeNode.clickOk();
injectNode.connect(changeNode);
changeNode.connect(debugNode);
workspace.deploy();
debugTab.open();
debugTab.clearMessage();
injectNode.clickLeftButton();
debugTab.getMessage().should.be.equal('"Hello World!"');
});
it('delete a message property', function() {
var injectNode = workspace.addNode("inject");
var changeNode = workspace.addNode("change", nodeWidth);
var debugNode = workspace.addNode("debug", nodeWidth * 2);
changeNode.edit();
changeNode.ruleDelete();
changeNode.clickOk();
injectNode.connect(changeNode);
changeNode.connect(debugNode);
workspace.deploy();
debugTab.open();
debugTab.clearMessage();
injectNode.clickLeftButton();
debugTab.getMessage().should.be.equal("undefined");
});
it('move a message property', function() {
var injectNode = workspace.addNode("inject");
var changeNode = workspace.addNode("change", nodeWidth);
var debugNode = workspace.addNode("debug", nodeWidth * 2);
injectNode.edit();
injectNode.setTopic("Hello");
injectNode.clickOk();
changeNode.edit();
changeNode.ruleMove("topic", "payload");
changeNode.clickOk();
injectNode.connect(changeNode);
changeNode.connect(debugNode);
workspace.deploy();
debugTab.open();
debugTab.clearMessage();
injectNode.clickLeftButton();
debugTab.getMessage().should.be.equal('"Hello"');
});
it('map a property between different numeric ranges', function() {
var injectNode1 = workspace.addNode("inject");
var injectNode2 = workspace.addNode("inject", 0, 50);
var injectNode3 = workspace.addNode("inject", 0, 100);
var rangeNode = workspace.addNode("range", nodeWidth);
var debugNode = workspace.addNode("debug", nodeWidth * 2);
injectNode1.edit();
injectNode1.setPayload("num", 0);
injectNode1.clickOk();
injectNode2.edit();
injectNode2.setPayload("num", 512);
injectNode2.clickOk();
injectNode3.edit();
injectNode3.setPayload("num", 1023);
injectNode3.clickOk();
rangeNode.edit();
rangeNode.setAction("clamp");
rangeNode.setRange(0, 1023, 0, 5);
rangeNode.clickOk();
injectNode1.connect(rangeNode);
injectNode2.connect(rangeNode);
injectNode3.connect(rangeNode);
rangeNode.connect(debugNode);
workspace.deploy();
debugTab.open();
debugTab.clearMessage();
injectNode1.clickLeftButton();
debugTab.getMessage(1).should.be.equal('0');
injectNode2.clickLeftButton();
debugTab.getMessage(2).should.be.equal('2.5024437927663734');
injectNode3.clickLeftButton();
debugTab.getMessage(3).should.be.equal('5');
});
});
});

View File

@ -0,0 +1,57 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var when = require('when');
var should = require("should");
var fs = require('fs-extra');
var helper = require("../../editor_helper");
var debugTab = require('../../pageobjects/workspace/debugTab_page');
var workspace = require('../../pageobjects/workspace/workspace_page');
var nodeWidth = 200;
describe('Workspace', function() {
beforeEach(function() {
workspace.deleteAllNodes();
});
before(function() {
helper.startServer();
});
after(function() {
helper.stopServer();
});
it('should have a right title', function () {
browser.getTitle().should.equal('Node-RED');
});
it('should output a timestamp', function() {
var injectNode = workspace.addNode("inject");
var debugNode = workspace.addNode("debug", nodeWidth);
injectNode.connect(debugNode);
workspace.deploy();
debugTab.open();
debugTab.clearMessage();
injectNode.clickLeftButton();
debugTab.getMessage().should.within(1500000000000, 3000000000000);
});
});

View File

@ -609,11 +609,70 @@ describe('trigger node', function() {
n1.emit("input", {payload:null}); // trigger n1.emit("input", {payload:null}); // trigger
n1.emit("input", {payload:null}); // blocked n1.emit("input", {payload:null}); // blocked
n1.emit("input", {payload:null}); // blocked n1.emit("input", {payload:null}); // blocked
n1.emit("input", {payload:"foo"}); // don't clear the blockage
n1.emit("input", {payload:"boo"}); // clear the blockage n1.emit("input", {payload:"boo"}); // clear the blockage
n1.emit("input", {payload:null}); // trigger n1.emit("input", {payload:null}); // trigger
}); });
}); });
it('should be able to set infinite timeout, and clear timeout by boolean true', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", reset:"true", duration:"0", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
c += 1;
msg.should.have.a.property("payload", "1");
}
catch(err) { done(err); }
});
setTimeout( function() {
if (c === 2) { done(); }
else {
done(new Error("Too many messages received"));
}
},20);
n1.emit("input", {payload:null}); // trigger
n1.emit("input", {payload:null}); // blocked
n1.emit("input", {payload:null}); // blocked
n1.emit("input", {payload:false}); // don't clear the blockage
n1.emit("input", {payload:true}); // clear the blockage
n1.emit("input", {payload:null}); // trigger
});
});
it('should be able to set infinite timeout, and clear timeout by boolean false', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", reset:"false", duration:"0", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
c += 1;
msg.should.have.a.property("payload", "1");
}
catch(err) { done(err); }
});
setTimeout( function() {
if (c === 2) { done(); }
else {
done(new Error("Too many messages received"));
}
},20);
n1.emit("input", {payload:null}); // trigger
n1.emit("input", {payload:null}); // blocked
n1.emit("input", {payload:null}); // blocked
n1.emit("input", {payload:"foo"}); // don't clear the blockage
n1.emit("input", {payload:false}); // clear the blockage
n1.emit("input", {payload:null}); // trigger
});
});
it('should be able to set a repeat, and clear loop by reset', function(done) { it('should be able to set a repeat, and clear loop by reset', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", reset:"boo", op1:"", op1type:"pay", duration:-25, wires:[["n2"]] }, var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", reset:"boo", op1:"", op1type:"pay", duration:-25, wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];

View File

@ -18,6 +18,7 @@ var should = require("should");
var switchNode = require("../../../../nodes/core/logic/10-switch.js"); var switchNode = require("../../../../nodes/core/logic/10-switch.js");
var helper = require("../../helper.js"); var helper = require("../../helper.js");
var RED = require("../../../../red/red.js");
describe('switch Node', function() { describe('switch Node', function() {
@ -28,6 +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;
}); });
it('should be loaded with some defaults', function(done) { it('should be loaded with some defaults', function(done) {
@ -119,6 +121,44 @@ describe('switch Node', function() {
}); });
} }
function customFlowSequenceSwitchTest(flow, seq_in, seq_out, done) {
helper.load(switchNode, flow, function() {
var switchNode1 = helper.getNode("switchNode1");
var helperNode1 = helper.getNode("helperNode1");
var sid = undefined;
var count = 0;
helperNode1.on("input", function(msg) {
try {
msg.should.have.property("payload", seq_out[count]);
msg.should.have.property("parts");
var parts = msg.parts;
parts.should.have.property("id");
var id = parts.id;
if (sid === undefined) {
sid = id;
}
else {
id.should.equal(sid);
}
parts.should.have.property("index", count);
parts.should.have.property("count", seq_out.length);
count++;
if (count === seq_out.length) {
done();
}
} catch (e) {
done(e);
}
});
var len = seq_in.length;
for (var i = 0; i < len; i++) {
var parts = {index:i, count:len, id:222};
var msg = {payload:seq_in[i], parts:parts};
switchNode1.receive(msg);
}
});
}
it('should check if payload equals given value', function(done) { it('should check if payload equals given value', function(done) {
genericSwitchTest("eq", "Hello", true, true, "Hello", done); genericSwitchTest("eq", "Hello", true, true, "Hello", done);
}); });
@ -498,4 +538,176 @@ describe('switch Node', function() {
{id:"helperNode1", type:"helper", wires:[]}]; {id:"helperNode1", type:"helper", wires:[]}];
customFlowSwitchTest(flow, true, -5, done); customFlowSwitchTest(flow, true, -5, done);
}); });
it('should take head of message sequence', function(done) {
var flow = [{id:"switchNode1",type:"switch",name:"switchNode",property:"payload",rules:[{"t":"head","v":3}],checkall:true,outputs:1,wires:[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
customFlowSequenceSwitchTest(flow, [0, 1, 2, 3, 4], [0, 1, 2], done);
});
it('should take tail of message sequence', function(done) {
var flow = [{id:"switchNode1",type:"switch",name:"switchNode",property:"payload",rules:[{"t":"tail","v":3}],checkall:true,outputs:1,wires:[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
customFlowSequenceSwitchTest(flow, [0, 1, 2, 3, 4], [2, 3, 4], done);
});
it('should take slice of message sequence', function(done) {
var flow = [{id:"switchNode1",type:"switch",name:"switchNode",property:"payload",rules:[{"t":"index","v":1,"v2":3}],checkall:true,outputs:1,wires:[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
customFlowSequenceSwitchTest(flow, [0, 1, 2, 3, 4], [1,2, 3], done);
});
it('should check JSONata expression is true', function(done) {
var flow = [{id:"switchNode1",type:"switch",name:"switchNode",property:"payload",
rules:[{"t":"jsonata_exp","v":"payload%2 = 1","vt":"jsonata"}],
checkall:true,outputs:1,wires:[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
customFlowSwitchTest(flow, true, 9, done);
});
it('should repair message sequence', function(done) {
var flow = [{id:"n1",type:"switch",name:"switchNode",property:"payload",
rules:[{"t":"gt","v":0},{"t":"lt","v":0},{"t":"else"}],
checkall:true,repair:true,
outputs:3,wires:[["n2"],["n3"],["n4"]]},
{id:"n2", type:"helper", wires:[]},
{id:"n3", type:"helper", wires:[]},
{id:"n4", type:"helper", wires:[]}
];
helper.load(switchNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
var data = { "0":1, "1":-2, "2":2, "3":0, "4":-1 };
var count = 0;
function check_msg(msg, vf) {
try {
msg.should.have.property("payload");
var payload = msg.payload;
msg.should.have.property("parts");
vf(payload).should.be.ok;
var parts = msg.parts;
parts.should.have.property("id", 222);
parts.should.have.property("count", 5);
parts.should.have.property("index");
var index = parts.index;
payload.should.equal(data[index]);
count++;
if (count == 5) {
done();
}
}
catch (e) {
done(e);
}
}
n2.on("input", function(msg) {
check_msg(msg, function(x) { return(x < 0); });
});
n3.on("input", function(msg) {
check_msg(msg, function(x) { return(x === 0); });
});
n4.on("input", function(msg) {
check_msg(msg, function(x) { return(x > 0); });
});
for(var i in data) {
n1.receive({payload: data[i], parts:{index:i,count:5,id:222}});
}
});
});
it('should create message sequence for each port', function(done) {
var flow = [{id:"n1",type:"switch",name:"switchNode",property:"payload",
rules:[{"t":"gt","v":0},{"t":"lt","v":0},{"t":"else"}],
checkall:true,repair:false,
outputs:3,wires:[["n2"],["n3"],["n4"]]},
{id:"n2", type:"helper", wires:[]}, // >0
{id:"n3", type:"helper", wires:[]}, // <0
{id:"n4", type:"helper", wires:[]} // ==0
];
helper.load(switchNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var n4 = helper.getNode("n4");
var data = [ 1, -2, 2, 0, -1 ];
var vals = [[1, 2], [-2, -1], [0]];
var ids = [undefined, undefined, undefined];
var counts = [0, 0, 0];
var count = 0;
function check_msg(msg, ix, vf) {
try {
msg.should.have.property("payload");
var payload = msg.payload;
msg.should.have.property("parts");
vf(payload).should.be.ok;
var parts = msg.parts;
var evals = vals[ix];
parts.should.have.property("count", evals.length);
parts.should.have.property("id");
var id = parts.id;
if (ids[ix] === undefined) {
ids[ix] = id;
}
else {
ids[ix].should.equal(id);
}
parts.should.have.property("index");
var index = parts.index;
var eindex = counts[ix];
var eval = evals[eindex];
payload.should.equal(eval);
counts[ix]++;
count++;
if (count == 5) {
done();
}
}
catch (e) {
done(e);
}
}
n2.on("input", function(msg) {
check_msg(msg, 0, function(x) { return(x > 0); });
});
n3.on("input", function(msg) {
check_msg(msg, 1, function(x) { return(x < 0); });
});
n4.on("input", function(msg) {
check_msg(msg, 2, function(x) { return(x === 0); });
});
for(var i in data) {
n1.receive({payload: data[i], parts:{index:i,count:5,id:222}});
}
});
});
it('should handle too many pending messages', function(done) {
var flow = [{id:"n1",type:"switch",name:"switchNode",property:"payload",
rules:[{"t":"tail","v":2}],
checkall:true,repair:false,
outputs:3,wires:[["n2"]]},
{id:"n2", type:"helper", wires:[]}
];
helper.load(switchNode, flow, function() {
var n1 = helper.getNode("n1");
RED.settings.switchMaxKeptMsgsCount = 2;
setTimeout(function() {
var logEvents = helper.log().args.filter(function (evt) {
return evt[0].type == "switch";
});
var evt = logEvents[0][0];
evt.should.have.property('id', "n1");
evt.should.have.property('type', "switch");
evt.should.have.property('msg', "switch.errors.too-many");
done();
}, 150);
n1.receive({payload:3, parts:{index:2, count:4, id:222}});
n1.receive({payload:2, parts:{index:1, count:4, id:222}});
n1.receive({payload:4, parts:{index:3, count:4, id:222}});
n1.receive({payload:1, parts:{index:0, count:4, id:222}});
});
});
}); });

View File

@ -18,6 +18,7 @@ var should = require("should");
var splitNode = require("../../../../nodes/core/logic/17-split.js"); var splitNode = require("../../../../nodes/core/logic/17-split.js");
var joinNode = require("../../../../nodes/core/logic/17-split.js"); var joinNode = require("../../../../nodes/core/logic/17-split.js");
var helper = require("../../helper.js"); var helper = require("../../helper.js");
var RED = require("../../../../red/red.js");
describe('SPLIT node', function() { describe('SPLIT node', function() {
@ -269,6 +270,7 @@ describe('JOIN node', function() {
afterEach(function() { afterEach(function() {
helper.unload(); helper.unload();
RED.settings.joinMaxKeptMsgsCount = 0;
}); });
it('should be loaded', function(done) { it('should be loaded', function(done) {
@ -727,6 +729,394 @@ describe('JOIN node', function() {
}); });
s1.receive({payload:[[1,2,3],"a\nb\nc",[7,8,9]]}); s1.receive({payload:[[1,2,3],"a\nb\nc",[7,8,9]]});
}); });
}) });
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) {
var flow = [{id:"n1", type:"join", mode:"reduce",
reduceRight:false,
reduceExp:"$A+payload",
reduceInit:"0",
reduceInitType:"num",
reduceFixup:undefined,
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("payload");
msg.payload.should.equal(10);
done();
}
catch(e) { done(e); }
});
n1.receive({payload:3, parts:{index:2, count:4, id:222}});
n1.receive({payload:2, parts:{index:1, count:4, id:222}});
n1.receive({payload:4, parts:{index:3, count:4, id:222}});
n1.receive({payload:1, parts:{index:0, count:4, id:222}});
});
});
it('should redece messages using $I', function(done) {
var flow = [{id:"n1", type:"join", mode:"reduce",
reduceRight:false,
reduceExp:"$A+$I",
reduceInit:"0",
reduceInitType:"num",
reduceFixup:undefined,
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("payload");
msg.payload.should.equal(6);
done();
}
catch(e) { done(e); }
});
n1.receive({payload:3, parts:{index:2, count:4, id:222}});
n1.receive({payload:2, parts:{index:1, count:4, id:222}});
n1.receive({payload:4, parts:{index:3, count:4, id:222}});
n1.receive({payload:1, parts:{index:0, count:4, id:222}});
});
});
it('should redece messages with fixup', function(done) {
var flow = [{id:"n1", type:"join", mode:"reduce",
reduceRight:false,
reduceExp:"$A+payload",
reduceInit:"0",
reduceInitType:"num",
reduceFixup:"$A/$N",
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("payload");
msg.payload.should.equal(2);
done();
}
catch(e) { done(e); }
});
n1.receive({payload:3, parts:{index:2, count:5, id:222}});
n1.receive({payload:2, parts:{index:1, count:5, id:222}});
n1.receive({payload:4, parts:{index:3, count:5, id:222}});
n1.receive({payload:1, parts:{index:0, count:5, id:222}});
n1.receive({payload:0, parts:{index:4, count:5, id:222}});
});
});
it('should redece messages (left)', function(done) {
var flow = [{id:"n1", type:"join", mode:"reduce",
reduceRight:false,
reduceExp:"'(' & $A & '+' & payload & ')'",
reduceInit:"0",
reduceInitType:"str",
reduceFixup:undefined,
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("payload");
msg.payload.should.be.an.String();
msg.payload.should.equal("((((0+1)+2)+3)+4)");
done();
}
catch(e) { done(e); }
});
n1.receive({payload:'3', parts:{index:2, count:4, id:222}});
n1.receive({payload:'2', parts:{index:1, count:4, id:222}});
n1.receive({payload:'4', parts:{index:3, count:4, id:222}});
n1.receive({payload:'1', parts:{index:0, count:4, id:222}});
});
});
it('should redece messages (right)', function(done) {
var flow = [{id:"n1", type:"join", mode:"reduce",
reduceRight:true,
reduceExp:"'(' & $A & '+' & payload & ')'",
reduceInit:"0",
reduceInitType:"str",
reduceFixup:undefined,
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("payload");
msg.payload.should.be.an.String();
msg.payload.should.equal("((((0+4)+3)+2)+1)");
done();
}
catch(e) { done(e); }
});
n1.receive({payload:'3', parts:{index:2, count:4, id:222}});
n1.receive({payload:'2', parts:{index:1, count:4, id:222}});
n1.receive({payload:'4', parts:{index:3, count:4, id:222}});
n1.receive({payload:'1', parts:{index:0, count:4, id:222}});
});
});
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) {
var flow = [{id:"n1", type:"join", mode:"reduce",
reduceRight:false,
reduceExp:"$A+payload",
reduceInit:"0",
reduceInitType:"num",
reduceFixup:undefined,
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:3, parts:{index:2, count:4, id:222}});
n1.receive({payload:2, parts:{index:1, count:4, id:222}});
n1.receive({payload:4, parts:{index:3, count:4, id:222}});
n1.receive({payload:1, parts:{index:0, count:4, id:222}});
});
});
}); });

View File

@ -27,10 +27,11 @@ describe('SORT node', function() {
afterEach(function() { afterEach(function() {
helper.unload(); helper.unload();
RED.settings.maxKeptMsgsCount = 0;
}); });
it('should be loaded', function(done) { it('should be loaded', function(done) {
var flow = [{id:"n1", type:"sort", order:"ascending", as_num:false, keyType:"payload", name: "SortNode", wires:[["n2"]]}, var flow = [{id:"n1", type:"sort", order:"ascending", as_num:false, name: "SortNode", wires:[["n2"]]},
{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");
@ -39,16 +40,58 @@ describe('SORT node', function() {
}); });
}); });
function check_sort0(flow, data_in, data_out, done) { function check_sort0(flow, target, key, key_type, data_in, data_out, done) {
var sort = flow[0];
sort.target = target;
sort.targetType = "msg";
sort.msgKey = key;
sort.msgKeyType = key_type;
helper.load(sortNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property(target);
var data = msg[target];
data.length.should.equal(data_out.length);
for(var i = 0; i < data_out.length; i++) {
data[i].should.equal(data_out[i]);
}
done();
});
var msg = {};
msg[target] = data_in;
n1.receive(msg);
});
}
function check_sort0A(flow, data_in, data_out, done) {
check_sort0(flow, "payload", "", "elem", data_in, data_out, done);
}
function check_sort0B(flow, data_in, data_out, done) {
check_sort0(flow, "data", "", "elem", data_in, data_out, done);
}
function check_sort0C(flow, exp, data_in, data_out, done) {
check_sort0(flow, "data", exp, "jsonata", data_in, data_out, done);
}
function check_sort1(flow, key, key_type, data_in, data_out, done) {
var sort = flow[0];
var prop = (key_type === "msg") ? key : "payload";
sort.targetType = "seq";
sort.seqKey = key;
sort.seqKeyType = key_type;
helper.load(sortNode, flow, function() { helper.load(sortNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
var count = 0; var count = 0;
n2.on("input", function(msg) { n2.on("input", function(msg) {
msg.should.have.property("payload"); msg.should.have.property(prop);
msg.should.have.property("parts"); msg.should.have.property("parts");
msg.parts.should.have.property("count", data_out.length); msg.parts.should.have.property("count", data_out.length);
var index = data_out.indexOf(msg.payload); var data = msg[prop];
var index = data_out.indexOf(data);
msg.parts.should.have.property("index", index); msg.parts.should.have.property("index", index);
count++; count++;
if (count === data_out.length) { if (count === data_out.length) {
@ -58,111 +101,132 @@ describe('SORT node', function() {
var len = data_in.length; var len = data_in.length;
for(var i = 0; i < len; i++) { for(var i = 0; i < len; i++) {
var parts = { id: "X", index: i, count: len }; var parts = { id: "X", index: i, count: len };
n1.receive({payload:data_in[i], parts: parts}); var msg = {parts: parts};
msg[prop] = data_in[i];
n1.receive(msg);
} }
}); });
} }
function check_sort1(flow, data_in, data_out, done) { function check_sort1A(flow, data_in, data_out, done) {
helper.load(sortNode, flow, function() { check_sort1(flow, "payload", "msg", data_in, data_out, done);
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property("payload");
msg.payload.length.should.equal(data_out.length);
for(var i = 0; i < data_out.length; i++) {
msg.payload[i].should.equal(data_out[i]);
}
done();
});
n1.receive({payload:data_in});
});
} }
function check_sort1B(flow, data_in, data_out, done) {
check_sort1(flow, "data", "msg", data_in, data_out, done);
}
function check_sort1C(flow, exp, data_in, data_out, done) {
check_sort1(flow, exp, "jsonata", data_in, data_out, done);
}
(function() { (function() {
var flow = [{id:"n1", type:"sort", order:"ascending", as_num:false, keyType:"payload", wires:[["n2"]]}, var flow = [{id:"n1", type:"sort", order:"ascending", as_num:false, wires:[["n2"]]},
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
var data_in = [ "200", "4", "30", "1000" ]; var data_in = [ "200", "4", "30", "1000" ];
var data_out = [ "1000", "200", "30", "4" ]; var data_out = [ "1000", "200", "30", "4" ];
it('should sort message group (payload, not number, ascending)', function(done) { it('should sort payload (elem, not number, ascending)', function(done) {
check_sort0(flow, data_in, data_out, done); check_sort0A(flow, data_in, data_out, done);
}); });
it('should sort payload (payload, not number, ascending)', function(done) { it('should sort msg prop (elem, not number, ascending)', function(done) {
check_sort1(flow, data_in, data_out, done); check_sort0B(flow, data_in, data_out, done);
}); });
})(); it('should sort message group/payload (not number, ascending)', function(done) {
check_sort1A(flow, data_in, data_out, done);
(function() {
var flow = [{id:"n1", type:"sort", order:"descending", as_num:false, keyType:"payload", wires:[["n2"]]},
{id:"n2", type:"helper"}];
var data_in = [ "200", "4", "30", "1000" ];
var data_out = [ "4", "30", "200", "1000" ];
it('should sort message group (payload, not number, descending)', function(done) {
check_sort0(flow, data_in, data_out, done);
}); });
it('should sort payload (payload, not number, descending)', function(done) { it('should sort message group/prop (not number, ascending)', function(done) {
check_sort1(flow, data_in, data_out, done); check_sort1B(flow, data_in, data_out, done);
});
})();
(function() {
var flow = [{id:"n1", type:"sort", order:"ascending", as_num:true, keyType:"payload", wires:[["n2"]]},
{id:"n2", type:"helper"}];
var data_in = [ "200", "4", "30", "1000" ];
var data_out = [ "4", "30", "200", "1000" ];
it('should sort message group (payload, number, ascending)', function(done) {
check_sort0(flow, data_in, data_out, done);
});
it('should sort payload (payload, number, ascending)', function(done) {
check_sort1(flow, data_in, data_out, done);
});
})();
(function() {
var flow = [{id:"n1", type:"sort", order:"descending", as_num:true, keyType:"payload", wires:[["n2"]]},
{id:"n2", type:"helper"}];
var data_in = [ "200", "4", "30", "1000" ];
var data_out = [ "1000", "200", "30", "4" ];
it('should sort message group (payload, number, descending)', function(done) {
check_sort0(flow, data_in, data_out, done);
});
it('should sort payload (payload, number, descending)', function(done) {
check_sort1(flow, data_in, data_out, done);
}); });
})(); })();
(function() { (function() {
var flow = [{id:"n1", type:"sort", order:"descending", as_num:false, wires:[["n2"]]},
{id:"n2", type:"helper"}];
var data_in = [ "200", "4", "30", "1000" ];
var data_out = [ "4", "30", "200", "1000" ];
it('should sort payload (elem, not number, descending)', function(done) {
check_sort0A(flow, data_in, data_out, done);
});
it('should sort msg prop (elem, not number, descending)', function(done) {
check_sort0B(flow, data_in, data_out, done);
});
it('should sort message group/payload (not number, descending)', function(done) {
check_sort1A(flow, data_in, data_out, done);
});
it('should sort message group/prop (not number, descending)', function(done) {
check_sort1B(flow, data_in, data_out, done);
});
})();
(function() {
var flow = [{id:"n1", type:"sort", order:"ascending", as_num:true, wires:[["n2"]]},
{id:"n2", type:"helper"}];
var data_in = [ "200", "4", "30", "1000" ];
var data_out = [ "4", "30", "200", "1000" ];
it('should sort payload (elem, number, ascending)', function(done) {
check_sort0A(flow, data_in, data_out, done);
});
it('should sort msg prop (elem, number, ascending)', function(done) {
check_sort0B(flow, data_in, data_out, done);
});
it('should sort message group/payload (number, ascending)', function(done) {
check_sort1A(flow, data_in, data_out, done);
});
it('should sort message group/prop (number, ascending)', function(done) {
check_sort1B(flow, data_in, data_out, done);
});
})();
(function() {
var flow = [{id:"n1", type:"sort", order:"descending", as_num:true, wires:[["n2"]]},
{id:"n2", type:"helper"}];
var data_in = [ "200", "4", "30", "1000" ];
var data_out = [ "1000", "200", "30", "4" ];
it('should sort payload (elem, number, descending)', function(done) {
check_sort0A(flow, data_in, data_out, done);
});
it('should sort msg prop (elem, number, descending)', function(done) {
check_sort0B(flow, data_in, data_out, done);
});
it('should sort message group/payload (number, descending)', function(done) {
check_sort1A(flow, data_in, data_out, done);
});
it('should sort message group/prop (number, descending)', function(done) {
check_sort1B(flow, data_in, data_out, done);
});
})();
(function() {
var flow = [{id:"n1", type:"sort", order:"ascending", as_num:false, wires:[["n2"]]},
{id:"n2", type:"helper"}];
var data_in = [ "C200", "A4", "B30", "D1000" ]; var data_in = [ "C200", "A4", "B30", "D1000" ];
var data_out = [ "D1000", "C200", "B30", "A4" ]; var data_out = [ "D1000", "C200", "B30", "A4" ];
it('should sort message group (exp, not number, ascending)', function(done) {
var flow = [{id:"n1", type:"sort", order:"ascending", as_num:false, keyType:"exp", key:"$substring(payload, 1)", wires:[["n2"]]},
{id:"n2", type:"helper"}];
check_sort0(flow, data_in, data_out, done);
});
it('should sort payload (exp, not number, ascending)', function(done) { it('should sort payload (exp, not number, ascending)', function(done) {
var flow = [{id:"n1", type:"sort", order:"ascending", as_num:false, keyType:"exp", key:"$substring($, 1)", wires:[["n2"]]}, check_sort0C(flow, "$substring($,1)", data_in, data_out, done);
{id:"n2", type:"helper"}]; });
check_sort1(flow, data_in, data_out, done); it('should sort message group (exp, not number, ascending)', function(done) {
check_sort1C(flow, "$substring(payload,1)", data_in, data_out, done);
}); });
})(); })();
return;
(function() { (function() {
var flow = [{id:"n1", type:"sort", order:"descending", as_num:false, wires:[["n2"]]},
{id:"n2", type:"helper"}];
var data_in = [ "C200", "A4", "B30", "D1000" ]; var data_in = [ "C200", "A4", "B30", "D1000" ];
var data_out = [ "A4", "B30", "C200", "D1000" ]; var data_out = [ "A4", "B30", "C200", "D1000" ];
it('should sort message group (exp, not number, descending)', function(done) { it('should sort message group (exp, not number, descending)', function(done) {
var flow = [{id:"n1", type:"sort", order:"descending", as_num:false, keyType:"exp", key:"$substring(payload, 1)", wires:[["n2"]]}, check_sort0C(flow, "$substring($,1)", data_in, data_out, done);
{id:"n2", type:"helper"}];
check_sort0(flow, data_in, data_out, done);
}); });
it('should sort payload (exp, not number, descending)', function(done) { it('should sort payload (exp, not number, descending)', function(done) {
var flow = [{id:"n1", type:"sort", order:"descending", as_num:false, keyType:"exp", key:"$substring($, 1)", wires:[["n2"]]}, check_sort1C(flow, "$substring(payload,1)", data_in, data_out, done);
{id:"n2", type:"helper"}];
check_sort1(flow, data_in, data_out, done);
}); });
})(); })();
it('should handle JSONata script error', function(done) { it('should handle JSONata script error', function(done) {
var flow = [{id:"n1", type:"sort", order:"ascending", as_num:false, keyType:"exp", key:"$unknown()", wires:[["n2"]]}, var flow = [{id:"n1", type:"sort", order:"ascending", as_num:false, target:"payload", targetType:"seq", seqKey:"$unknown()", seqKeyType:"jsonata", wires:[["n2"]]},
{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");
@ -183,12 +247,14 @@ describe('SORT node', function() {
}); });
}); });
return;
it('should handle too many pending messages', function(done) { it('should handle too many pending messages', function(done) {
var flow = [{id:"n1", type:"sort", order:"ascending", as_num:false, keyType:"payload", wires:[["n2"]]}, var flow = [{id:"n1", type:"sort", order:"ascending", as_num:false, target:"payload", targetType:"seq", seqKey:"payload", seqKeyType:"msg", wires:[["n2"]]},
{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.sortMaxKeptMsgsCount = 2; RED.settings.maxKeptMsgsCount = 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";
@ -208,7 +274,7 @@ describe('SORT node', function() {
}); });
it('should clear pending messages on close', function(done) { it('should clear pending messages on close', function(done) {
var flow = [{id:"n1", type:"sort", order:"ascending", as_num:false, keyType:"payload", wires:[["n2"]]}, var flow = [{id:"n1", type:"sort", order:"ascending", as_num:false, target:"payload", targetType:"seq", seqKey:"payload", seqKeyType:"msg", wires:[["n2"]]},
{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");

View File

@ -0,0 +1,338 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var batchNode = require("../../../../nodes/core/logic/19-batch.js");
var helper = require("../../helper.js");
var RED = require("../../../../red/red.js");
describe('BATCH node', function() {
this.timeout(8000);
before(function(done) {
helper.startServer(done);
});
afterEach(function() {
helper.unload();
RED.settings.batchMaxKeptMsgsCount = 0;
});
it('should be loaded with defaults', function(done) {
var flow = [{id:"n1", type:"batch", name: "BatchNode", wires:[["n2"]]},
{id:"n2", type:"helper"}];
helper.load(batchNode, flow, function() {
var n1 = helper.getNode("n1");
n1.should.have.property('name', 'BatchNode');
done();
});
});
function check_parts(msg, id, idx, count) {
msg.should.have.property("parts");
var parts = msg.parts;
parts.should.have.property("id", id);
parts.should.have.property("index", idx);
parts.should.have.property("count", count);
}
function check_data(n1, n2, results, done) {
var id = undefined;
var ix0 = 0; // seq no
var ix1 = 0; // loc. in seq
var seq = undefined;
n2.on("input", function(msg) {
try {
if (seq === undefined) {
seq = results[ix0];
}
var val = seq[ix1];
msg.should.have.property("payload", val);
if (id === undefined) {
id = msg.parts.id;
}
check_parts(msg, id, ix1, seq.length);
ix1++;
if (ix1 === seq.length) {
ix0++;
ix1 = 0;
seq = undefined;
id = undefined;
if (ix0 === results.length) {
done();
}
}
}
catch (e) {
done(e);
}
});
}
function check_count(flow, results, done) {
try {
helper.load(batchNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
check_data(n1, n2, results, done);
for(var i = 0; i < 6; i++) {
n1.receive({payload: i});
}
});
}
catch (e) {
done(e);
}
}
function delayed_send(receiver, index, count, delay) {
if (index < count) {
setTimeout(function() {
receiver.receive({payload: index});
delayed_send(receiver, index+1, count, delay);
}, delay);
}
}
function check_interval(flow, results, delay, done) {
helper.load(batchNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
check_data(n1, n2, results, done);
delayed_send(n1, 0, 4, delay);
});
}
function check_concat(flow, results, inputs, done) {
try {
helper.load(batchNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
check_data(n1, n2, results, done);
for(var data of inputs) {
var msg = {
topic: data[0],
payload: data[1],
parts: {
id: data[0],
index: data[2],
count: data[3]
}
};
n1.receive(msg);
}
});
}
catch (e) {
done(e);
}
}
describe('mode: count', function() {
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"]]},
{id:"n2", type:"helper"}];
var results = [
[0, 1],
[2, 3],
[4, 5]
];
check_count(flow, results, done);
});
it('should create seq. with count and overwrap', function(done) {
var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 3, overwrap: 2, interval: 10, allowEmptySequence: false, topics: [], wires:[["n2"]]},
{id:"n2", type:"helper"}];
var results = [
[0, 1, 2],
[1, 2, 3],
[2, 3, 4],
[3, 4, 5]
];
check_count(flow, results, 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"]]},
{id:"n2", type:"helper"}];
helper.load(batchNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
RED.settings.batchMaxKeptMsgsCount = 2;
setTimeout(function() {
var logEvents = helper.log().args.filter(function (evt) {
return evt[0].type == "batch";
});
var evt = logEvents[0][0];
evt.should.have.property('id', "n1");
evt.should.have.property('type', "batch");
evt.should.have.property('msg', "batch.too-many");
done();
}, 150);
for(var i = 0; i < 3; i++) {
n1.receive({payload: i});
}
});
});
});
describe('mode: interval', function() {
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"]]},
{id:"n2", type:"helper"}];
var results = [
[0, 1],
[2, 3]
];
check_interval(flow, results, 450, 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"]]},
{id:"n2", type:"helper"}];
var results = [
[0, 1],
[2, 3]
];
check_interval(flow, results, 225, 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"]]},
{id:"n2", type:"helper"}];
var results = [
// 1300, 2600, 3900, 5200,
[0], [1], [2], [3]
];
check_interval(flow, results, 1300, 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"]]},
{id:"n2", type:"helper"}];
var results = [
// 1300, 2600, 3900, 5200,
[null], [0], [1], [2], [null], [3]
];
check_interval(flow, results, 1300, 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"]]},
{id:"n2", type:"helper"}];
helper.load(batchNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
RED.settings.batchMaxKeptMsgsCount = 2;
setTimeout(function() {
var logEvents = helper.log().args.filter(function (evt) {
return evt[0].type == "batch";
});
var evt = logEvents[0][0];
evt.should.have.property('id', "n1");
evt.should.have.property('type', "batch");
evt.should.have.property('msg', "batch.too-many");
done();
}, 150);
for(var i = 0; i < 3; i++) {
n1.receive({payload: i});
}
});
});
});
describe('mode: concat', function() {
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"]]},
{id:"n2", type:"helper"}];
var results = [
[2, 3, 0, 1]
];
var inputs = [
["TB", 0, 0, 2],
["TB", 1, 1, 2],
["TA", 2, 0, 2],
["TA", 3, 1, 2]
];
check_concat(flow, results, inputs, 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"]]},
{id:"n2", type:"helper"}];
var results = [
[2, 3, 0, 1]
];
var inputs = [
["TA", 2, 0, 2],
["TB", 0, 0, 2],
["TA", 3, 1, 2],
["TB", 1, 1, 2]
];
check_concat(flow, results, inputs, 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"]]},
{id:"n2", type:"helper"}];
var results = [
[2, 3, 0, 1, 4]
];
var inputs = [
["TC", 4, 0, 1],
["TB", 0, 0, 2],
["TB", 1, 1, 2],
["TA", 2, 0, 2],
["TA", 3, 1, 2]
];
check_concat(flow, results, inputs, 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"]]},
{id:"n2", type:"helper"}];
helper.load(batchNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
RED.settings.batchMaxKeptMsgsCount = 2;
setTimeout(function() {
var logEvents = helper.log().args.filter(function (evt) {
return evt[0].type == "batch";
});
var evt = logEvents[0][0];
evt.should.have.property('id', "n1");
evt.should.have.property('type', "batch");
evt.should.have.property('msg', "batch.too-many");
done();
}, 150);
var C = 3;
for(var i = 0; i < C; i++) {
var parts_a = {index:i, count:C, id:"A"};
var parts_b = {index:i, count:C, id:"B"};
n1.receive({payload: i, topic: "TA", parts:parts_a});
n1.receive({payload: i, topic: "TB", parts:parts_b});
}
});
});
});
});