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

@ -889,10 +889,10 @@
} }
}, },
"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",

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

@ -49,10 +49,7 @@ module.exports = {
// The maximum number of messages kept internally in nodes. // 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,
//switchMaxKeptMsgsCount: 0,
//joinMaxKeptMsgsCount: 0,
//batchMaxKeptMsgsCount: 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

@ -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();
}); function check_sort1B(flow, data_in, data_out, done) {
n1.receive({payload:data_in}); 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);
});
it('should sort message group/prop (not number, ascending)', function(done) {
check_sort1B(flow, data_in, data_out, done);
}); });
})(); })();
(function() { (function() {
var flow = [{id:"n1", type:"sort", order:"descending", as_num:false, keyType:"payload", wires:[["n2"]]}, var flow = [{id:"n1", type:"sort", order:"descending", 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 = [ "4", "30", "200", "1000" ]; var data_out = [ "4", "30", "200", "1000" ];
it('should sort message group (payload, not number, descending)', function(done) { it('should sort payload (elem, not number, descending)', 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, descending)', function(done) { it('should sort msg prop (elem, not number, descending)', 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, 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() { (function() {
var flow = [{id:"n1", type:"sort", order:"ascending", as_num:true, keyType:"payload", wires:[["n2"]]}, var flow = [{id:"n1", type:"sort", order:"ascending", as_num:true, 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 = [ "4", "30", "200", "1000" ]; var data_out = [ "4", "30", "200", "1000" ];
it('should sort message group (payload, number, ascending)', function(done) { it('should sort payload (elem, 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, number, ascending)', function(done) { it('should sort msg prop (elem, 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 (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() { (function() {
var flow = [{id:"n1", type:"sort", order:"descending", as_num:true, keyType:"payload", wires:[["n2"]]}, var flow = [{id:"n1", type:"sort", order:"descending", as_num:true, 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, number, descending)', function(done) { it('should sort payload (elem, number, descending)', function(done) {
check_sort0(flow, data_in, data_out, done); check_sort0A(flow, data_in, data_out, done);
}); });
it('should sort payload (payload, number, descending)', function(done) { it('should sort msg prop (elem, number, descending)', function(done) {
check_sort1(flow, data_in, data_out, 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() { (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");