mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
initial support of SORT node (#1500)
* initial support of SORT node minor fix of sort node fixed error message of sort node fixed error handling of SORT node add test case for SORT node make limit of messages count computed once in SORT node * update type in message & info description
This commit is contained in:
committed by
Dave Conway-Jones
parent
f21c8154ed
commit
afce106186
@@ -846,5 +846,17 @@
|
||||
"seconds":"seconds",
|
||||
"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."
|
||||
},
|
||||
"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",
|
||||
"invalid-exp" : "invalid JSONata expression in sort node",
|
||||
"too-many" : "too many pending messages in sort node"
|
||||
}
|
||||
}
|
||||
|
112
nodes/core/logic/18-sort.html
Normal file
112
nodes/core/logic/18-sort.html
Normal file
@@ -0,0 +1,112 @@
|
||||
<!--
|
||||
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="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>
|
||||
</div>
|
||||
|
||||
<div class="node-row-sort-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%;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label><i class="fa fa-random"></i> <span data-i18n="sort.order"></span></label>
|
||||
<select id="node-input-order" style="width:200px;">
|
||||
<option value="ascending" data-i18n="sort.ascending"></option>
|
||||
<option value="descending" data-i18n="sort.descending"></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-row" id="node-as_num">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-as_num" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-as_num" style="width: 70%;" data-i18n="sort.as-number"></label>
|
||||
</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="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>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>
|
||||
<ul>
|
||||
<li><code>id</code> - an identifier for the group of messages</li>
|
||||
<li><code>index</code> - the position within the group</li>
|
||||
<li><code>count</code> - the total number of messages in the group</li>
|
||||
</ul>
|
||||
</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>
|
||||
</ul>
|
||||
</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('sort',{
|
||||
category: 'function',
|
||||
color:"#E2D96E",
|
||||
defaults: {
|
||||
name: { value:"" },
|
||||
order: { value:"ascending" },
|
||||
as_num : { value:false },
|
||||
keyType : { value:"payload" },
|
||||
key : { value:"" }
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "sort.png",
|
||||
label: function() {
|
||||
return this.name || "sort";
|
||||
},
|
||||
labelStyle: function() {
|
||||
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');
|
||||
});
|
||||
$("#node-input-keyType").change();
|
||||
$("#node-input-order").change();
|
||||
}
|
||||
});
|
||||
</script>
|
182
nodes/core/logic/18-sort.js
Normal file
182
nodes/core/logic/18-sort.js
Normal file
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* 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 = "sortMaxKeptMsgsCount";
|
||||
if (RED.settings.hasOwnProperty(name)) {
|
||||
_max_kept_msgs_count = RED.settings[name];
|
||||
}
|
||||
else {
|
||||
_max_kept_msgs_count = 0;
|
||||
}
|
||||
}
|
||||
return _max_kept_msgs_count;
|
||||
}
|
||||
|
||||
function eval_jsonata(node, code, val) {
|
||||
try {
|
||||
return RED.util.evaluateJSONataExpression(code, val);
|
||||
}
|
||||
catch (e) {
|
||||
node.error(RED._("sort.invalid-exp"));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
function get_context_val(node, name, dval) {
|
||||
var context = node.context();
|
||||
var val = context.get(name);
|
||||
if (val === undefined) {
|
||||
context.set(name, dval);
|
||||
return dval;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
function SortNode(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
var node = this;
|
||||
var pending = get_context_val(node, 'pending', {})
|
||||
var pending_count = 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) {
|
||||
try {
|
||||
key_exp = RED.util.prepareJSONataExpression(n.key, this);
|
||||
}
|
||||
catch (e) {
|
||||
node.error(RED._("sort.invalid-exp"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
var dir = (order === "ascending") ? 1 : -1;
|
||||
var conv = as_num
|
||||
? function(x) { return Number(x); }
|
||||
: function(x) { return x; };
|
||||
|
||||
function gen_comp(key) {
|
||||
return function(x, y) {
|
||||
var xp = conv(key(x));
|
||||
var yp = conv(key(y));
|
||||
if (xp === yp) return 0;
|
||||
if (xp > yp) return dir;
|
||||
return -dir;
|
||||
};
|
||||
};
|
||||
|
||||
function send_group(group) {
|
||||
var key = key_is_payload
|
||||
? function(msg) { return msg.payload; }
|
||||
: function(msg) {
|
||||
return eval_jsonata(node, key_exp, msg);
|
||||
};
|
||||
var comp = gen_comp(key);
|
||||
var msgs = group.msgs;
|
||||
try {
|
||||
msgs.sort(comp);
|
||||
}
|
||||
catch (e) {
|
||||
return; // not send when error
|
||||
}
|
||||
for (var i = 0; i < msgs.length; i++) {
|
||||
var msg = msgs[i];
|
||||
msg.parts.index = i;
|
||||
node.send(msg);
|
||||
}
|
||||
};
|
||||
|
||||
function sort_payload(msg) {
|
||||
var payload = msg.payload;
|
||||
if (Array.isArray(payload)) {
|
||||
var key = key_is_payload
|
||||
? function(elem) { return elem; }
|
||||
: function(elem) {
|
||||
return eval_jsonata(node, key_exp, elem);
|
||||
};
|
||||
var comp = gen_comp(key);
|
||||
try {
|
||||
payload.sort(comp);
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function check_parts(parts) {
|
||||
if (parts.hasOwnProperty("id") &&
|
||||
parts.hasOwnProperty("index")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function process_msg(msg) {
|
||||
if (!msg.hasOwnProperty("parts")) {
|
||||
if (sort_payload(msg)) {
|
||||
node.send(msg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var parts = msg.parts;
|
||||
if (!check_parts(parts)) {
|
||||
return;
|
||||
}
|
||||
var gid = parts.id;
|
||||
if (!pending.hasOwnProperty(gid)) {
|
||||
pending[gid] = {
|
||||
count: undefined,
|
||||
msgs: []
|
||||
};
|
||||
}
|
||||
var group = pending[gid];
|
||||
var msgs = group.msgs;
|
||||
msgs.push(msg);
|
||||
if (parts.hasOwnProperty("count")) {
|
||||
group.count = parts.count;
|
||||
}
|
||||
pending_count++;
|
||||
if (group.count === msgs.length) {
|
||||
delete pending[gid]
|
||||
send_group(group);
|
||||
pending_count -= msgs.length;
|
||||
}
|
||||
var max_msgs = max_kept_msgs_count(node);
|
||||
if ((max_msgs > 0) && (pending_count > max_msgs)) {
|
||||
pending = {};
|
||||
pending_count = 0;
|
||||
node.error(RED._("sort.too-many"));
|
||||
}
|
||||
}
|
||||
|
||||
this.on("input", function(msg) {
|
||||
process_msg(msg);
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("sort", SortNode);
|
||||
}
|
||||
|
Reference in New Issue
Block a user