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

update UI for SORT node (#1567)

* update UI of SORT node

* fix maxKeptMsgsCount of SORT node
This commit is contained in:
Hiroyasu Nishiyama 2018-01-22 09:23:22 +09:00 committed by Nick O'Leary
parent 00dcb304c7
commit 6b466d217a
5 changed files with 258 additions and 154 deletions

View File

@ -871,55 +871,55 @@
"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.",
"too-many" : "too many pending messages in join node",
"merge": {
"topics-label":"Merged Topics",
"topics":"topics",
"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"
"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" : {
"key-type" : "Key type",
"payload" : "payload or element",
"exp" : "expression",
"key-exp" : "Key exp.",
"order" : "Order",
"ascending" : "ascending",
"descending" : "descending",
"as-number" : "as number",
"target" : "Sort",
"seq" : "message sequence",
"key" : "Key",
"elem" : "element value",
"order" : "Order",
"ascending" : "ascending",
"descending" : "descending",
"as-number" : "as number",
"invalid-exp" : "invalid JSONata expression in sort node",
"too-many" : "too many pending messages in sort node",
"clear" : "clear pending message in sort node"
},
"batch" : {
"mode": {
"label" : "Mode",
"num-msgs" : "number of messages",
"interval" : "interval in seconds",
"mode": {
"label" : "Mode",
"num-msgs" : "number of messages",
"interval" : "interval in seconds",
"concat" : "concatenate sequences"
},
"count": {
"label" : "Number of msgs",
"overwrap" : "Overwrap",
"count" : "count",
},
"count": {
"label" : "Number of msgs",
"overwrap" : "Overwrap",
"count" : "count",
"invalid" : "Invalid count and overwrap"
},
"interval": {
"label" : "Interval (sec)",
"seconds" : "seconds",
"sec" : "sec",
},
"interval": {
"label" : "Interval (sec)",
"seconds" : "seconds",
"sec" : "sec",
"empty" : "send empty message when no message arrives"
},
},
"concat": {
"topics-label": "Topics",
"topic" : "topic"

View File

@ -19,17 +19,24 @@
<script type="text/x-red" data-template-name="sort">
<div class="form-row">
<label><i class="fa fa-dot-circle-o"></i> <span data-i18n="sort.key-type"></span></label>
<select id="node-input-keyType" style="width:200px;">
<option value="payload" data-i18n="sort.payload"></option>
<option value="exp" data-i18n="sort.exp"></option>
</select>
<label for="node-input-target"><i class="fa fa-dot-circle-o"></i> <span data-i18n="sort.target"></span></label>
<input type="text" id="node-input-target" style="width:70%;">
<input type="hidden" id="node-input-targetType">
</div>
<div class="node-row-sort-key">
<div class="node-row-sort-msg-key">
<div class="form-row">
<label><i class="fa fa-filter"></i> <span data-i18n="sort.key-exp"></span></label>
<input type="text" id="node-input-key" style="width:70%;">
<label for="node-input-msgKey"><i class="fa fa-filter"></i> <span data-i18n="sort.key"></span></label>
<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>
@ -54,17 +61,17 @@
</script>
<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>When paired with the <b>split</b> node, it will reorder the
messages.</p>
<p>A function that sorts message property or a sequence of messages.</p>
<p>When configured to sort message property, the node sorts array data pointed to by specified message property.</p>
<p>When configured to sort a sequence of messages, it will reorder the messages.</p>
<p>The sorting order can be:</p>
<ul>
<li><b>ascending</b>,</li>
<li><b>descending</b>.</li>
</ul>
<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>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>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>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>
<ul>
<li><code>id</code> - an identifier for the group of messages</li>
@ -74,7 +81,7 @@
</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>sortMaxKeptMsgsCount</code> property set in <b>settings.js</b>.</li>
<li><code>maxKeptMsgsCount</code> property set in <b>settings.js</b>.</li>
</ul>
</p>
</script>
@ -86,9 +93,13 @@
defaults: {
name: { value:"" },
order: { value:"ascending" },
as_num : { value:false },
keyType : { value:"payload" },
key : { value:"" }
as_num: { value:false },
target: { value:"payload" },
targetType: { value:"msg" },
msgKey: { value:"payload" },
msgKeyType: { value:"elem" },
seqKey: { value:"payload" },
seqKeyType: { value:"msg" }
},
inputs:1,
outputs:1,
@ -100,13 +111,37 @@
return this.name ? "node_label_italic" : "";
},
oneditprepare: function() {
$("#node-input-key").typedInput({default:'jsonata', types:['jsonata']});
$("#node-input-keyType").change(function(e) {
var val = $(this).val();
$(".node-row-sort-key").toggle(val === 'exp');
var seq = {
value: "seq",
label: RED._("node-red:sort.seq"),
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-order").change();
$("#node-input-msgKey").typedInput({
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>

View File

@ -21,7 +21,7 @@ module.exports = function(RED) {
function max_kept_msgs_count(node) {
if (_max_kept_msgs_count === undefined) {
var name = "sortMaxKeptMsgsCount";
var name = "maxKeptMsgsCount";
if (RED.settings.hasOwnProperty(name)) {
_max_kept_msgs_count = RED.settings[name];
}
@ -60,11 +60,15 @@ module.exports = function(RED) {
var pending_id = 0;
var order = n.order || "ascending";
var as_num = n.as_num || false;
var key_is_payload = (n.keyType === 'payload');
var key_exp = undefined;
if (!key_is_payload) {
var target_prop = n.target || "payload";
var target_is_prop = (n.targetType === 'msg');
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 {
key_exp = RED.util.prepareJSONataExpression(n.key, this);
key_exp = RED.util.prepareJSONataExpression(key_exp, this);
}
catch (e) {
node.error(RED._("sort.invalid-exp"));
@ -87,10 +91,12 @@ module.exports = function(RED) {
}
function send_group(group) {
var key = key_is_payload
? function(msg) { return msg.payload; }
: function(msg) {
var key = key_is_exp
? function(msg) {
return eval_jsonata(node, key_exp, msg);
}
: function(msg) {
return RED.util.getMessageProperty(msg, key_prop);
};
var comp = gen_comp(key);
var msgs = group.msgs;
@ -108,16 +114,16 @@ module.exports = function(RED) {
}
function sort_payload(msg) {
var payload = msg.payload;
if (Array.isArray(payload)) {
var key = key_is_payload
? function(elem) { return elem; }
: function(elem) {
var data = RED.util.getMessageProperty(msg, target_prop);
if (Array.isArray(data)) {
var key = key_is_exp
? function(elem) {
return eval_jsonata(node, key_exp, elem);
};
}
: function(elem) { return elem; };
var comp = gen_comp(key);
try {
payload.sort(comp);
data.sort(comp);
}
catch (e) {
return false;
@ -162,7 +168,7 @@ module.exports = function(RED) {
}
function process_msg(msg) {
if (!msg.hasOwnProperty("parts")) {
if (target_is_prop) {
if (sort_payload(msg)) {
node.send(msg);
}

View File

@ -49,10 +49,7 @@ module.exports = {
// The maximum number of messages kept internally in nodes.
// Zero or undefined value means not restricting number of messages.
//sortMaxKeptMsgsCount: 0,
//switchMaxKeptMsgsCount: 0,
//joinMaxKeptMsgsCount: 0,
//batchMaxKeptMsgsCount: 0,
//maxKeptMsgsCount: 0,
// To disable the option for using local files for storing keys and certificates in the TLS configuration
// node, set this to true

View File

@ -27,10 +27,11 @@ describe('SORT node', function() {
afterEach(function() {
helper.unload();
RED.settings.maxKeptMsgsCount = 0;
});
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"}];
helper.load(sortNode, flow, function() {
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() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var count = 0;
n2.on("input", function(msg) {
msg.should.have.property("payload");
msg.should.have.property(prop);
msg.should.have.property("parts");
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);
count++;
if (count === data_out.length) {
@ -58,111 +101,132 @@ describe('SORT node', function() {
var len = data_in.length;
for(var i = 0; i < len; i++) {
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) {
helper.load(sortNode, flow, function() {
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_sort1A(flow, data_in, data_out, done) {
check_sort1(flow, "payload", "msg", data_in, data_out, done);
}
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() {
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"}];
var data_in = [ "200", "4", "30", "1000" ];
var data_out = [ "1000", "200", "30", "4" ];
it('should sort message group (payload, not number, ascending)', function(done) {
check_sort0(flow, data_in, data_out, done);
it('should sort payload (elem, not number, ascending)', function(done) {
check_sort0A(flow, data_in, data_out, done);
});
it('should sort payload (payload, not number, ascending)', function(done) {
check_sort1(flow, data_in, data_out, done);
it('should sort msg prop (elem, not number, ascending)', function(done) {
check_sort0B(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 message group/payload (not number, ascending)', function(done) {
check_sort1A(flow, data_in, data_out, done);
});
it('should sort payload (payload, not number, descending)', function(done) {
check_sort1(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);
it('should sort message group/prop (not number, ascending)', function(done) {
check_sort1B(flow, data_in, data_out, done);
});
})();
(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_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) {
var flow = [{id:"n1", type:"sort", order:"ascending", as_num:false, keyType:"exp", key:"$substring($, 1)", wires:[["n2"]]},
{id:"n2", type:"helper"}];
check_sort1(flow, data_in, data_out, done);
check_sort0C(flow, "$substring($,1)", 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() {
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_out = [ "A4", "B30", "C200", "D1000" ];
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"]]},
{id:"n2", type:"helper"}];
check_sort0(flow, data_in, data_out, done);
check_sort0C(flow, "$substring($,1)", data_in, data_out, 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"]]},
{id:"n2", type:"helper"}];
check_sort1(flow, data_in, data_out, done);
check_sort1C(flow, "$substring(payload,1)", data_in, data_out, 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"}];
helper.load(sortNode, flow, function() {
var n1 = helper.getNode("n1");
@ -183,12 +247,14 @@ describe('SORT node', function() {
});
});
return;
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"}];
helper.load(sortNode, flow, function() {
var n1 = helper.getNode("n1");
RED.settings.sortMaxKeptMsgsCount = 2;
RED.settings.maxKeptMsgsCount = 2;
setTimeout(function() {
var logEvents = helper.log().args.filter(function (evt) {
return evt[0].type == "sort";
@ -208,7 +274,7 @@ describe('SORT node', function() {
});
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"}];
helper.load(sortNode, flow, function() {
var n1 = helper.getNode("n1");