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

Merge branch 'dev' into outliner

This commit is contained in:
Nick O'Leary 2020-05-05 15:11:35 +01:00
commit 2af7066512
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
32 changed files with 710 additions and 247 deletions

View File

@ -2,6 +2,7 @@ sudo: false
language: node_js
matrix:
include:
- node_js: "14"
- node_js: "12"
- node_js: "10"
script:

View File

@ -1,3 +1,59 @@
#### 1.0.6: Maintenance Release
Runtime
- Update to JSONata 1.8.3
- #2536 Handle clone of null in utils
Editor
- Prevent button label wrapping in typedInput
- Handle error objects when reporting in palette manager
Nodes
- Inject: Revert to cron 1.7.2
- UDP: when reusing input socket honour the broadcast mode.
#### 1.0.5: Maintenance Release
Runtime
- #2500 Support for context stores using JSONata and evaluateNodeProperty()
- Add better handling of host-key-verify error with projects
- #2517 Handle false values in $env() properly
- #2514 Ensure complete node scope is remapped in subflows
- #2513 Flows/subflows must preinitialise their context objects
- Clear node.close timeout to avoid unnecessary work on restart
- #2532 Set flow.disabled when disabled property is false
- #2522 Ensure file context does not write 'undefined' to store
Editor
- #2489 Fix XPath in UI tests
- #2504 Fix paletteCategories order
- #2501 Add page objects for UI testing
- #2494 Check node props when deciding if pasted node can splice links
- #2521 Don't double-sanitize node name in debug sidebar
- #2519 German i18n updates
- #2523 Update nodeTabMap when replacing unknown nodes
- Update TypedInput to use flexbox and remove resizing code
- Handle nodes with no wires array
- Do not collapse whitespace in Debug string messages
Nodes
- File: Remove old legacy wording from file node info to stop confusing users.
- Join: Ensure join node handles missing buffer joiner when not in string mode
- Exec: make exec node logging consistent with itself. (only be verbose when in verbose mode)
- Trigger: reset default timeout value when switching away from wait for reset
- Join: Fix join to not crash on appending invalid types to buffer.
- MQTT out: Add warning if topic contains + or #
- #2502 WebSocket i18n update
- #2508 Add Japanese translation for join node
- TCP out: tidy up select of which rows to display
#### 1.0.4: Maintenance Release
Runtime

View File

@ -32,9 +32,9 @@
"clone": "2.1.2",
"content-type": "1.0.4",
"cookie": "0.4.0",
"cookie-parser": "1.4.4",
"cookie-parser": "1.4.5",
"cors": "2.8.5",
"cron": "1.8.2",
"cron": "1.7.2",
"denque": "1.4.1",
"express": "4.17.1",
"express-session": "1.17.0",
@ -47,18 +47,18 @@
"is-utf8": "0.2.1",
"js-yaml": "3.13.1",
"json-stringify-safe": "5.0.1",
"jsonata": "1.8.1",
"jsonata": "1.8.3",
"lodash.clonedeep": "^4.5.0",
"media-typer": "1.1.0",
"memorystore": "1.6.2",
"mime": "2.4.4",
"mqtt": "2.18.8",
"multer": "1.4.2",
"mustache": "4.0.0",
"mustache": "4.0.1",
"node-red-node-rbe": "^0.2.6",
"node-red-node-sentiment": "^0.1.6",
"node-red-node-tail": "^0.1.0",
"nopt": "4.0.1",
"nopt": "4.0.3",
"oauth2orize": "1.11.0",
"on-headers": "1.0.2",
"passport": "0.4.1",
@ -67,16 +67,16 @@
"raw-body": "2.4.1",
"request": "2.88.0",
"semver": "6.3.0",
"uglify-js": "3.8.0",
"uglify-js": "3.8.1",
"when": "3.7.8",
"ws": "6.2.1",
"xml2js": "0.4.23"
},
"optionalDependencies": {
"bcrypt": "3.0.6"
"bcrypt": "3.0.8"
},
"devDependencies": {
"marked": "0.8.0",
"marked": "0.8.2",
"dompurify": "2.0.8",
"grunt": "~1.0.4",
"grunt-chmod": "~1.1.1",

View File

@ -26,7 +26,7 @@
"express": "4.17.1",
"memorystore": "1.6.2",
"mime": "2.4.4",
"mustache": "4.0.0",
"mustache": "4.0.1",
"oauth2orize": "1.11.0",
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",

View File

@ -1227,7 +1227,7 @@ RED.nodes = (function() {
defaults: {},
label: "unknown: "+n.type,
labelStyle: "red-ui-flow-node-label-italic",
outputs: n.outputs||n.wires.length,
outputs: n.outputs|| (n.wires && n.wires.length) || 0,
set: registry.getNodeSet("node-red/unknown")
}
} else {

View File

@ -737,11 +737,13 @@
this.optionExpandButton.shown = false;
}
if (this.optionSelectTrigger) {
this.optionSelectTrigger.show();
this.optionSelectTrigger.css({"display":"inline-flex"});
if (!opt.hasValue) {
this.optionSelectTrigger.css({"flex-grow":1})
this.elementDiv.hide();
this.valueLabelContainer.hide();
} else {
this.optionSelectTrigger.css({"flex-grow":0})
this.elementDiv.show();
this.valueLabelContainer.hide();
}

View File

@ -223,7 +223,11 @@ RED.palette.editor = (function() {
var setElements = nodeEntry.sets[setName];
if (set.err) {
errorCount++;
$("<li>").text(set.err).appendTo(nodeEntry.errorList);
var errMessage = set.err;
if (set.err.message) {
errMessage = set.err.message;
}
$("<li>").text(errMessage).appendTo(nodeEntry.errorList);
}
if (set.enabled) {
activeTypeCount += set.types.length;

View File

@ -685,6 +685,8 @@ RED.projects = (function() {
}
}
},projectData).then(function() {
RED.menu.setDisabled('menu-item-projects-open',false);
RED.menu.setDisabled('menu-item-projects-settings',false);
RED.events.emit("project:change", {name:name});
}).always(function() {
setTimeout(function() {

View File

@ -217,6 +217,10 @@
.red-ui-debug-msg-type-number { color: $debug-message-text-color-msg-type-number; };
.red-ui-debug-msg-type-number-toggle { cursor: pointer;}
.red-ui-debug-msg-type-string {
white-space: pre-wrap;
}
.red-ui-debug-msg-row {
display: block;
padding: 4px 2px 2px;

View File

@ -757,7 +757,7 @@ button.red-ui-toggleButton.toggle {
.red-ui-typedInput-value-label,.red-ui-typedInput-option-label {
select,.placeholder-input {
margin: 3px;
height: 26px;
height: 24px;
width: calc(100% - 10px);
padding-left: 3px;
}

View File

@ -110,9 +110,9 @@ button.red-ui-typedInput-option-trigger
background: $form-button-background;
height: 32px;
line-height: 30px;
min-width: 23px;
vertical-align: middle;
color: $form-text-color;
white-space: nowrap;
i.red-ui-typedInput-icon {
margin-left: 1px;
margin-right: 2px;
@ -174,25 +174,21 @@ button.red-ui-typedInput-option-trigger {
padding: 0 0 0 0;
position:relative;
flex-grow: 1;
line-height: 32px;
display: inline-flex;
.red-ui-typedInput-option-label {
background:$form-button-background;
color: $form-text-color;
position:absolute;
left:0;
right:23px;
top: 0;
padding: 0 5px 0 8px;
i.red-ui-typedInput-icon {
margin-right: 4px;
}
flex-grow: 1;
padding: 0 0 0 8px;
display:inline-block;
}
.red-ui-typedInput-option-caret {
top: 0;
position: absolute;
right: 0;
bottom: 0;
width: 17px;
padding-left: 5px;
flex-grow: 0;
display:inline-block;
width: 23px;
text-align: center;
height: 100%;
&:before {
content:'';
display: inline-block;

View File

@ -16,14 +16,12 @@
<script type="text/html" data-template-name="inject">
<div class="form-row">
<label for="node-input-payload"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label>
<input type="text" id="node-input-payload" style="width:70%">
<input type="hidden" id="node-input-payloadType">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-row">
<label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
<input type="text" id="node-input-topic">
<div class="form-row node-input-property-container-row">
<ol id="node-input-property-container"></ol>
</div>
<div class="form-row" id="node-once">
@ -114,12 +112,7 @@
</div>
</div>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-tips" data-i18n="[html]inject.tip"></div>
</script>
<style>
.inject-time-row {
@ -155,41 +148,114 @@
</style>
<script type="text/javascript">
(function() {
function resizeDialog(size) {
size = size || { height: $(".red-ui-tray-content form").height() }
var rows = $("#dialog-form>div:not(.node-input-property-container-row):visible");
var height = size.height;
for (var i=0; i<rows.length; i++) {
height -= $(rows[i]).outerHeight(true);
}
var editorRow = $("#dialog-form>div.node-input-property-container-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
height += 16;
$("#node-input-property-container").editableList('height',height);
}
RED.nodes.registerType('inject',{
category: 'common',
color:"#a6bbcf",
defaults: {
name: {value:""},
topic: {value:""},
payload: {value:"", validate: RED.validators.typedInput("payloadType")},
payloadType: {value:"date"},
props:{value:[{p:"payload",v:"",vt:"date"},{p:"topic",v:"",vt:"str"}]},
repeat: {value:"", validate:function(v) { return ((v === "") || (RED.validators.number(v) && (v >= 0) && (v <= 2147483))) }},
crontab: {value:""},
once: {value:false},
onceDelay: {value:0.1}
onceDelay: {value:0.1},
/* Legacy */
topic: {value:""},
payload: {value:"", validate: RED.validators.typedInput("payloadType")},
payloadType: {value:"date"},
/* */
},
icon: "inject.svg",
inputs:0,
outputs:1,
outputLabels: function(index) {
var lab = this.payloadType;
if (lab === "json") {
try {
lab = typeof JSON.parse(this.payload);
if (lab === "object") {
if (Array.isArray(JSON.parse(this.payload))) { lab = "Array"; }
}
} catch(e) {
return this._("inject.label.invalid"); }
var lab = '';
// if only payload and topic - display payload type
// if only one property - show it's type
// if more than one property (other than payload and topic) - show "x properties" where x is the number of properties.
// this.props will not be an array for legacy inject nodes until they are re-deployed
//
var props = this.props;
if (!Array.isArray(props)) {
props = [
{ p:"payload", v: this.payload, vt: this.payloadType },
{ p:"topic", v: this.topic, vt: "str" }
]
}
var name = "inject.label."+lab;
var label = this._(name);
if (name !== label) {
return label;
if (props) {
for (var i=0,l=props.length; i<l; i++) {
if (i > 0) lab += "\n";
if (i === 5) {
lab += " + "+(props.length-4);
break;
}
lab += props[i].p+": ";
var propType = props[i].vt;
if (propType === "json") {
try {
var parsedProp = JSON.parse(props[i].v);
propType = typeof parsedProp;
if (propType === "object" && Array.isArray(parsedProp)) {
propType = "Array";
}
} catch(e) {
propType = "invalid";
}
}
lab += this._("inject.label."+propType);
}
}
return lab;
},
label: function() {
if (Array.isArray(this.props)) {
// find the payload & topic
var payloadProperty;
var topicProperty;
var payload;
var payloadType;
var topic;
for (var i=0,l=this.props.length; i<l; i++) {
if (this.props[i].p === 'payload') {
payloadProperty = this.props[i];
} else if (this.props[i].p === 'topic' && this.props[i].vt === 'str') {
topicProperty = this.props[i];
}
}
// If no payload/topic are found, use the first property instead
if (this.props[0]){
payloadProperty = payloadProperty === undefined ? this.props[0] : payloadProperty;
topicProperty = topicProperty === undefined ? {v: payloadProperty.p} : topicProperty; // if no topic, use the property name of the payload
}
payload = payloadProperty === undefined ? "" : payloadProperty.v;
payloadType = payloadProperty === undefined ? "str" : payloadProperty.vt;
topic = topicProperty === undefined ? "" : topicProperty.v;
} else {
/* Legacy */
payload = this.payload;
payloadType = this.payloadType;
topic = this.topic;
}
var suffix = "";
// if fire once then add small indication
if (this.once) {
@ -201,27 +267,27 @@
}
if (this.name) {
return this.name+suffix;
} else if (this.payloadType === "string" ||
this.payloadType === "str" ||
this.payloadType === "num" ||
this.payloadType === "bool" ||
this.payloadType === "json") {
if ((this.topic !== "") && ((this.topic.length + this.payload.length) <= 32)) {
return this.topic + ":" + this.payload+suffix;
} else if (this.payload.length > 0 && this.payload.length < 24) {
return this.payload+suffix;
} else if (payloadType === "string" ||
payloadType === "str" ||
payloadType === "num" ||
payloadType === "bool" ||
payloadType === "json") {
if ((topic !== "") && ((topic.length + payload.length) <= 32)) {
return topic + ":" + payload+suffix;
} else if (payload.length > 0 && payload.length < 24) {
return payload+suffix;
} else {
return this._("inject.inject")+suffix;
}
} else if (this.payloadType === 'date') {
if ((this.topic !== "") && (this.topic.length <= 16)) {
return this.topic + ":" + this._("inject.timestamp")+suffix;
} else if (payloadType === 'date' || payloadType === 'bin' || payloadType === 'env') {
if ((topic !== "") && (topic.length <= 16)) {
return topic + ":" + this._(`inject.label.${payloadType}`)+suffix;
} else {
return this._("inject.timestamp")+suffix;
return this._(`inject.label.${payloadType}`)+suffix;
}
} else if (this.payloadType === 'flow' || this.payloadType === 'global') {
var key = RED.utils.parseContextKey(this.payload);
return this.payloadType+"."+key.key+suffix;
} else if (payloadType === 'flow' || payloadType === 'global') {
var key = RED.utils.parseContextKey(payload);
return payloadType+"."+key.key+suffix;
} else {
return this._("inject.inject")+suffix;
}
@ -239,13 +305,6 @@
} else if (this.payloadType === 'string' || this.payloadType === 'none') {
this.payloadType = "str";
}
$("#node-input-payloadType").val(this.payloadType);
$("#node-input-payload").typedInput({
default: 'str',
typeField: $("#node-input-payloadType"),
types:['flow','global','str','num','bool','json','bin','date','env']
});
$("#inject-time-type-select").on("change", function() {
$("#node-input-crontab").val('');
@ -259,6 +318,11 @@
$("#node-once").hide();
$("#node-input-once").prop('checked', false);
}
// Scroll down
var scrollDiv = $("#dialog-form").parent();
scrollDiv.scrollTop(scrollDiv.prop('scrollHeight'));
resizeDialog();
});
$("#node-input-once").on("change", function() {
@ -378,13 +442,74 @@
$("#inject-time-type-select").val(repeattype);
$("#inject-time-row-"+repeattype).show();
$("#node-input-payload").typedInput('type',this.payloadType);
/* */
$('#node-input-property-container').css('min-height','120px').css('min-width','450px').editableList({
addItem: function(container,i,opt) {
var prop = opt;
if (!prop.hasOwnProperty('p')) {
prop = {p:"",v:"",vt:"str"};
}
container.css({
overflow: 'hidden',
whiteSpace: 'nowrap'
});
var row = $('<div/>').appendTo(container);
var propertyName = $('<input/>',{class:"node-input-prop-property-name",type:"text"})
.css("width","30%")
.appendTo(row)
.typedInput({types:['msg']});
$('<div/>',{style: 'display:inline-block; padding:0px 6px;'})
.text('=')
.appendTo(row);
var propertyValue = $('<input/>',{class:"node-input-prop-property-value",type:"text"})
.css("width","calc(70% - 30px)")
.appendTo(row)
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','date','jsonata','env']});
propertyName.typedInput('value',prop.p);
propertyValue.typedInput('value',prop.v);
propertyValue.typedInput('type',prop.vt);
},
removable: true,
sortable: true
});
if (!this.props) {
var payload = {
p:'payload',
v: this.payload ? this.payload : '',
vt:this.payloadType ? this.payloadType : 'date'
};
var topic = {
p:'topic',
v: this.topic ? this.topic : '',
vt:'string'
}
this.props = [payload,topic];
}
for (var i=0; i<this.props.length; i++) {
var prop = this.props[i];
$("#node-input-property-container").editableList('addItem',prop);
}
$("#inject-time-type-select").trigger("change");
$("#inject-time-interval-time-start").trigger("change");
},
oneditsave: function() {
/* Cleanup Legacy */
delete this.payload;
delete this.payloadType
delete this.topic
/* */
var repeat = "";
var crontab = "";
var type = $("#inject-time-type-select").val();
@ -474,6 +599,22 @@
$("#node-input-repeat").val(repeat);
$("#node-input-crontab").val(crontab);
/* Gather the injected properties of the msg object */
var props = $("#node-input-property-container").editableList('items');
var node = this;
node.props= [];
props.each(function(i) {
var prop = $(this);
var p = {
p:prop.find(".node-input-prop-property-name").typedInput('value')
};
if (p.p) {
p.v = prop.find(".node-input-prop-property-value").typedInput('value');
p.vt = prop.find(".node-input-prop-property-value").typedInput('type');
node.props.push(p);
}
});
},
button: {
enabled: function() {
@ -483,12 +624,7 @@
if (this.changed) {
return RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.undeployedChanges")}),"warning");
}
var payload = this.payload;
if ((this.payloadType === 'flow') ||
(this.payloadType === 'global')) {
var key = RED.utils.parseContextKey(payload);
payload = this.payloadType+"."+key.key;
}
var label = this._def.label.call(this);
if (label.length > 30) {
label = label.substring(0,50)+"...";
@ -514,7 +650,8 @@
}
});
}
}
},
oneditresize: resizeDialog
});
})();
</script>

View File

@ -20,9 +20,23 @@ module.exports = function(RED) {
function InjectNode(n) {
RED.nodes.createNode(this,n);
this.topic = n.topic;
this.payload = n.payload;
this.payloadType = n.payloadType;
/* Handle legacy */
if(!Array.isArray(n.props)){
n.props = [];
n.props.push({
p:'payload',
v:n.payload,
vt:n.payloadType
});
n.props.push({
p:'topic',
v:n.topic,
vt:'str'
});
}
this.props = n.props;
this.repeat = n.repeat;
this.crontab = n.crontab;
this.once = n.once;
@ -62,34 +76,27 @@ module.exports = function(RED) {
node.repeaterSetup();
}
this.on("input",function(msg) {
msg.topic = this.topic;
if (this.payloadType !== 'flow' && this.payloadType !== 'global') {
try {
if ( (this.payloadType == null && this.payload === "") || this.payloadType === "date") {
msg.payload = Date.now();
} else if (this.payloadType == null) {
msg.payload = this.payload;
} else if (this.payloadType === 'none') {
msg.payload = "";
} else {
msg.payload = RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg);
}
this.send(msg);
msg = null;
} catch(err) {
this.error(err,msg);
}
} else {
RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg, function(err,res) {
if (err) {
node.error(err,msg);
} else {
msg.payload = res;
node.send(msg);
}
this.on("input", function(msg) {
var errors = [];
});
this.props.forEach(p => {
var property = p.p;
var value = p.v ? p.v : '';
var valueType = p.vt ? p.vt : 'str';
if (!property) return;
try {
RED.util.setMessageProperty(msg,property,RED.util.evaluateNodeProperty(value, valueType, this, msg),true);
} catch (err) {
errors.push(err);
}
});
if (errors.length) {
node.error(errors.join('; '), msg);
} else {
node.send(msg);
}
});
}

View File

@ -1,5 +1,10 @@
<script type="text/html" data-template-name="change">
<style>
ol#node-input-rule-container .red-ui-typedInput-container {
flex:1;
}
</style>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
@ -76,11 +81,6 @@
var replace = this._("change.action.replace");
var regex = this._("change.label.regex");
function resizeRule(rule) {
var newWidth = rule.width();
rule.find('.red-ui-typedInput').typedInput("width",newWidth-130);
}
$('#node-input-rule-container').css('min-height','150px').css('min-width','450px').editableList({
addItem: function(container,i,opt) {
var rule = opt;
@ -106,10 +106,11 @@
overflow: 'hidden',
whiteSpace: 'nowrap'
});
var row1 = $('<div/>').appendTo(container);
var row2 = $('<div/>',{style:"margin-top:8px;"}).appendTo(container);
var row3 = $('<div/>',{style:"margin-top:8px;"}).appendTo(container);
var row4 = $('<div/>',{style:"margin-top:8px;"}).appendTo(container);
let fragment = document.createDocumentFragment();
var row1 = $('<div/>',{style:"display:flex;"}).appendTo(fragment);
var row2 = $('<div/>',{style:"display:flex;margin-top:8px;"}).appendTo(fragment);
var row3 = $('<div/>',{style:"margin-top:8px;"}).appendTo(fragment);
var row4 = $('<div/>',{style:"display:flex;margin-top:8px;"}).appendTo(fragment);
var selectField = $('<select/>',{class:"node-input-rule-type",style:"width:110px; margin-right:10px;"}).appendTo(row1);
var selectOptions = [{v:"set",l:set},{v:"change",l:change},{v:"delete",l:del},{v:"move",l:move}];
@ -124,41 +125,82 @@
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
.text(to)
.appendTo(row2);
var propertyValue = $('<input/>',{class:"node-input-rule-property-value",type:"text"})
function createPropertyValue() {
return $('<input/>',{class:"node-input-rule-property-value",type:"text"})
.appendTo(row2)
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','date','jsonata','env']});
}
var row3_1 = $('<div/>').appendTo(row3);
var row3_1 = $('<div/>', {style:"display:flex;"}).appendTo(row3);
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
.text(search)
.appendTo(row3_1);
var fromValue = $('<input/>',{class:"node-input-rule-property-search-value",type:"text"})
function createFromValue() {
return $('<input/>',{class:"node-input-rule-property-search-value",type:"text"})
.appendTo(row3_1)
.typedInput({default:'str',types:['msg','flow','global','str','re','num','bool','env']});
}
var row3_2 = $('<div/>',{style:"margin-top:8px;"}).appendTo(row3);
var row3_2 = $('<div/>',{style:"display:flex;margin-top:8px;"}).appendTo(row3);
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
.text(replace)
.appendTo(row3_2);
var toValue = $('<input/>',{class:"node-input-rule-property-replace-value",type:"text"})
function createToValue() {
return $('<input/>',{class:"node-input-rule-property-replace-value",type:"text"})
.appendTo(row3_2)
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','env']});
}
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
.text(to)
.appendTo(row4);
var moveValue = $('<input/>',{class:"node-input-rule-property-move-value",type:"text"})
function createMoveValue() {
return $('<input/>',{class:"node-input-rule-property-move-value",type:"text"})
.appendTo(row4)
.typedInput({default:'msg',types:['msg','flow','global']});
}
let propertyValue = null;
let fromValue = null;
let toValue = null;
let moveValue = null;
selectField.on("change", function() {
var width = $("#node-input-rule-container").width();
var type = $(this).val();
if (propertyValue) {
propertyValue.typedInput('hide');
}
if (fromValue) {
fromValue.typedInput('hide');
}
if (toValue) {
toValue.typedInput('hide');
}
if (moveValue) {
moveValue.typedInput('hide');
}
if (type == "set") {
if(!propertyValue) {
propertyValue = createPropertyValue();
}
propertyValue.typedInput('show');
row2.show();
row3.hide();
row4.hide();
} else if (type == "change") {
if(!fromValue) {
fromValue = createFromValue();
}
fromValue.typedInput('show');
if(!toValue) {
toValue = createToValue();
}
toValue.typedInput('show');
row2.hide();
row3.show();
row4.hide();
@ -167,30 +209,48 @@
row3.hide();
row4.hide();
} else if (type == "move") {
if(!moveValue) {
moveValue = createMoveValue();
}
moveValue.typedInput('show');
row2.hide();
row3.hide();
row4.show();
}
resizeRule(container);
});
selectField.val(rule.t);
propertyName.typedInput('value',rule.p);
propertyName.typedInput('type',rule.pt);
propertyValue.typedInput('value',rule.to);
propertyValue.typedInput('type',rule.tot);
moveValue.typedInput('value',rule.to);
moveValue.typedInput('type',rule.tot);
fromValue.typedInput('value',rule.from);
fromValue.typedInput('type',rule.fromt);
toValue.typedInput('value',rule.to);
toValue.typedInput('type',rule.tot);
if (rule.t == "set") {
if(!propertyValue) {
propertyValue = createPropertyValue();
}
propertyValue.typedInput('value',rule.to);
propertyValue.typedInput('type',rule.tot);
}
if (rule.t == "move") {
if(!moveValue) {
moveValue = createMoveValue();
}
moveValue.typedInput('value',rule.to);
moveValue.typedInput('type',rule.tot);
}
if (rule.t == "change") {
if(!fromValue) {
fromValue = createFromValue();
}
fromValue.typedInput('value',rule.from);
fromValue.typedInput('type',rule.fromt);
if (!toValue) {
toValue = createToValue();
}
toValue.typedInput('value',rule.to);
toValue.typedInput('type',rule.tot);
}
selectField.change();
var newWidth = $("#node-input-rule-container").width();
resizeRule(container);
container[0].appendChild(fragment);
},
resizeItem: resizeRule,
removable: true,
sortable: true
});

View File

@ -47,6 +47,10 @@
<input type="hidden" id="node-input-op2type">
<input style="width:70%" type="text" id="node-input-op2" placeholder="0">
</div>
<div class="form-row">
<label></label>
<input type="checkbox" id="node-input-second" style="margin-left: 0px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;" for="node-input-second" data-i18n="trigger.second"></label>
</div>
<div class="form-row">
<label data-i18n="trigger.label.reset" style="width:auto"></label>
<div style="display:inline-block; width:70%;vertical-align:top">
@ -58,10 +62,13 @@
<br/>
<div class="form-row">
<label data-i18n="trigger.for" for="node-input-bytopic"></label>
<select id="node-input-bytopic">
<select id="node-input-bytopic" style="width:120px;">
<option value="all" data-i18n="trigger.alltopics"></option>
<option value="topic" data-i18n="trigger.bytopics"></option>
</select>
<span class="form-row" id="node-stream-topic">
<input type="text" id="node-input-topic" style="width:46%;"/>
</span>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
@ -74,6 +81,7 @@
category: 'function',
color:"#E6E0F8",
defaults: {
name: {value:""},
op1: {value:"1", validate: RED.validators.typedInput("op1type")},
op2: {value:"0", validate: RED.validators.typedInput("op2type")},
op1type: {value:"val"},
@ -82,8 +90,9 @@
extend: {value:"false"},
units: {value:"ms"},
reset: {value:""},
bytopic: {value: "all"},
name: {value:""}
bytopic: {value:"all"},
topic: {value:"topic",required:true},
outputs: {value:1}
},
inputs:1,
outputs:1,
@ -103,6 +112,28 @@
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var that = this;
if (this.topic === undefined) { $("#node-input-topic").val("topic"); }
$("#node-input-topic").typedInput({default:'msg',types:['msg']});
$("#node-input-bytopic").on("change", function() {
if ($("#node-input-bytopic").val() === "all") {
$("#node-stream-topic").hide();
} else {
$("#node-stream-topic").show();
}
});
if (this.outputs == 2) { $("#node-input-second").prop('checked', true) }
else { $("#node-input-second").prop('checked', false) }
$("#node-input-second").change(function() {
if ($("#node-input-second").is(":checked")) {
that.outputs = 2;
}
else {
that.outputs = 1;
}
});
$("#node-then-type").on("change", function() {
if ($(this).val() == "block") {
$(".node-type-wait").hide();
@ -177,7 +208,7 @@
}
if ($("#node-then-type").val() == "loop") {
$("#node-input-duration").val($("#node-input-duration").val() * -1);
}
}
}
});
</script>

View File

@ -24,6 +24,8 @@ module.exports = function(RED) {
this.op2 = n.op2 || "0";
this.op1type = n.op1type || "str";
this.op2type = n.op2type || "str";
this.second = (n.outputs == 2) ? true : false;
this.topic = n.topic || "topic";
if (this.op1type === 'val') {
if (this.op1 === 'true' || this.op1 === 'false') {
@ -112,7 +114,7 @@ module.exports = function(RED) {
});
var processMessage = function(msg) {
var topic = msg.topic || "_none";
var topic = RED.util.getMessageProperty(msg,node.topic) || "_none";
var promise;
if (node.bytopic === "all") { topic = "_none"; }
node.topics[topic] = node.topics[topic] || {};
@ -189,12 +191,14 @@ module.exports = function(RED) {
}
promise.then(() => {
if (node.op2type === "payl") {
node.send(npay[topic]);
if (node.second === true) { node.send([null,npay[topic]]); }
else { node.send(npay[topic]); }
delete npay[topic];
}
else {
msg2.payload = node.topics[topic].m2;
node.send(msg2);
if (node.second === true) { node.send([null,msg2]); }
else { node.send(msg2); }
}
delete node.topics[topic];
node.status({});
@ -246,7 +250,8 @@ module.exports = function(RED) {
}
delete node.topics[topic];
node.status({});
node.send(msg2);
if (node.second === true) { node.send([null,msg2]); }
else { node.send(msg2); }
}).catch(err => {
node.error(err);
});

View File

@ -170,15 +170,14 @@
$("#node-input-port-row").hide();
$("#node-input-host-row").hide();
$("#node-input-end-row").hide();
} else if (sockettype == "client"){
$("#node-input-port-row").show();
$("#node-input-host-row").show();
$("#node-input-end-row").show();
} else {
$("#node-input-port-row").show();
$("#node-input-end-row").show();
}
if (sockettype == "client") {
$("#node-input-host-row").show();
} else {
$("#node-input-host-row").hide();
$("#node-input-end-row").show();
}
};
updateOptions();

View File

@ -180,6 +180,10 @@ module.exports = function(RED) {
node.tout = setTimeout(function() {
if (udpInputPortsInUse[p]) {
sock = udpInputPortsInUse[p];
if (node.multicast != "false") {
sock.setBroadcast(true);
sock.setMulticastLoopback(false);
}
node.log(RED._("udp.status.re-use",{outport:node.outport,host:node.addr,port:node.port}));
if (node.iface) { node.status({text:n.iface+" : "+node.iface}); }
}

View File

@ -28,7 +28,7 @@
</div>
<div class="form-row" style="padding-left:20px;">
<label><i class="fa fa-sign-in"></i> <span data-i18n="csv.label.input"></span></label>
<span data-i18n="csv.label.skip-s"></span>&nbsp;<input type="text" id="node-input-skip" style="width:30px; height:25px;"/>&nbsp;<span data-i18n="csv.label.skip-e"></span><br/>
<span data-i18n="csv.label.skip-s"></span>&nbsp;<input type="text" id="node-input-skip" style="width:40px; height:25px;"/>&nbsp;<span data-i18n="csv.label.skip-e"></span><br/>
<label>&nbsp;</label>
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-hdrin"><label style="width:auto; margin-top:7px;" for="node-input-hdrin"><span data-i18n="csv.label.firstrow"></span></label><br/>
<label>&nbsp;</label>
@ -49,8 +49,13 @@
<label style="width:100%;"><span data-i18n="csv.label.o2c"></span></label>
</div>
<div class="form-row" style="padding-left:20px;">
<label><i class="fa fa-sign-in"></i> <span data-i18n="csv.label.output"></span></label>
<input style="width:20px; vertical-align:top; margin-right:5px;" type="checkbox" id="node-input-hdrout"><label style="width:auto;" for="node-input-hdrout"><span data-i18n="csv.label.includerow"></span></span>
<label><i class="fa fa-sign-out"></i> <span data-i18n="csv.label.output"></span></label>
<!-- <input style="width:20px; vertical-align:top; margin-right:5px;" type="checkbox" id="node-input-hdrout"><label style="width:auto;" for="node-input-hdrout"><span data-i18n="csv.label.includerow"></span></span> -->
<select style="width:60%" id="node-input-hdrout">
<option value="none" data-i18n="csv.hdrout.none"></option>
<option value="all" data-i18n="csv.hdrout.all"></option>
<option value="once" data-i18n="csv.hdrout.once"></option>
</select>
</div>
<div class="form-row" style="padding-left:20px;">
<label></label>
@ -73,7 +78,7 @@
sep: {value:',',required:true,validate:RED.validators.regex(/^.{1,2}$/)},
//quo: {value:'"',required:true},
hdrin: {value:""},
hdrout: {value:""},
hdrout: {value:"none"},
multi: {value:"one",required:true},
ret: {value:'\\n'},
temp: {value:""},
@ -92,6 +97,8 @@
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
if (this.hdrout === false) { this.hdrout = "none"; $("#node-input-hdrout").val("none"); }
if (this.hdrout === true) { this.hdrout = "all"; $("#node-input-hdrout").val("all");}
if (this.strings === undefined) { this.strings = true; $("#node-input-strings").prop('checked', true); }
if (this.skip === undefined) { this.skip = 0; $("#node-input-skip").val("0");}
$("#node-input-skip").spinner({ min:0 });

View File

@ -26,7 +26,7 @@ module.exports = function(RED) {
this.lineend = "\n";
this.multi = n.multi || "one";
this.hdrin = n.hdrin || false;
this.hdrout = n.hdrout || false;
this.hdrout = n.hdrout || "none";
this.goodtmpl = true;
this.skip = parseInt(n.skip || 0);
this.store = [];
@ -34,6 +34,8 @@ module.exports = function(RED) {
this.include_empty_strings = n.include_empty_strings || false;
this.include_null_values = n.include_null_values || false;
if (this.parsestrings === undefined) { this.parsestrings = true; }
if (this.hdrout === false) { this.hdrout = "none"; }
if (this.hdrout === true) { this.hdrout = "all"; }
var tmpwarn = true;
var node = this;
@ -51,14 +53,22 @@ module.exports = function(RED) {
return col;
}
node.template = clean(node.template);
node.hdrSent = false;
this.on("input", function(msg) {
if (msg.hasOwnProperty("reset")) {
node.hdrSent = false;
}
if (msg.hasOwnProperty("payload")) {
if (typeof msg.payload == "object") { // convert object to CSV string
try {
var ou = "";
if (node.hdrout) {
if (node.hdrout !== "none" && node.hdrSent === false) {
if ((node.template.length === 1) && (node.template[0] === '') && (msg.hasOwnProperty("columns"))) {
node.template = clean((msg.columns || "").split(","));
}
ou += node.template.join(node.sep) + node.ret;
if (node.hdrout === "once") { node.hdrSent = true; }
}
if (!Array.isArray(msg.payload)) { msg.payload = [ msg.payload ]; }
for (var s = 0; s < msg.payload.length; s++) {
@ -77,13 +87,15 @@ module.exports = function(RED) {
ou += msg.payload[s].join(node.sep) + node.ret;
}
else {
if ((node.template.length === 1) && (node.template[0] === '') && (msg.hasOwnProperty("columns"))) {
node.template = clean((msg.columns || "").split(","));
}
if ((node.template.length === 1) && (node.template[0] === '')) {
/* istanbul ignore else */
if (tmpwarn === true) { // just warn about missing template once
node.warn(RED._("csv.errors.obj_csv"));
tmpwarn = false;
}
ou = "";
for (var p in msg.payload[0]) {
/* istanbul ignore else */
if (msg.payload[0].hasOwnProperty(p)) {
@ -127,6 +139,7 @@ module.exports = function(RED) {
}
}
msg.payload = ou;
msg.columns = node.template.join(',');
if (msg.payload !== '') { node.send(msg); }
}
catch(e) { node.error(e,msg); }
@ -213,7 +226,7 @@ module.exports = function(RED) {
}
}
// Finished so finalize and send anything left
//console.log(j,k,o,k[j]);
if (f === false) { node.warn(RED._("csv.errors.bad_csv")); }
if (!node.goodtmpl) { node.template[j] = "col"+(j+1); }
if ( node.template[j] && (node.template[j] !== "") ) {
@ -227,6 +240,7 @@ module.exports = function(RED) {
a.push(o); // add to the array
}
var has_parts = msg.hasOwnProperty("parts");
if (node.multi !== "one") {
msg.payload = a;
if (has_parts) {
@ -235,12 +249,14 @@ module.exports = function(RED) {
}
if (msg.parts.index + 1 === msg.parts.count) {
msg.payload = node.store;
msg.columns = node.template.filter(val => val).join(',');
delete msg.parts;
node.send(msg);
node.store = [];
}
}
else {
msg.columns = node.template.filter(val => val).join(',');
node.send(msg); // finally send the array
}
}
@ -248,6 +264,7 @@ module.exports = function(RED) {
var len = a.length;
for (var i = 0; i < len; i++) {
var newMessage = RED.util.cloneMessage(msg);
newMessage.columns = node.template.filter(val => val).join(',');
newMessage.payload = a[i];
if (!has_parts) {
newMessage.parts = {
@ -273,7 +290,11 @@ module.exports = function(RED) {
}
else { node.warn(RED._("csv.errors.csv_js")); }
}
else { node.send(msg); } // If no payload - just pass it on.
else {
if (!msg.hasOwnProperty("reset")) {
node.send(msg); // If no payload and not reset - just pass it on.
}
}
});
}
RED.nodes.registerType("csv",CSVNode);

View File

@ -40,6 +40,6 @@
progress will be cleared and no message triggered.</p>
<p>The node can be configured to resend a message at a regular interval until it
is reset by a received message.</p>
<p>Optionally, the node can be configured to treat messages with <code>msg.topic</code> as if they
are separate streams.</p>
<p>Optionally, the node can be configured to treat messages as if they are separate streams,
using a msg property to identify each stream. Default <code>msg.topic</code>.</p>
</script>

View File

@ -37,6 +37,7 @@
"stopped": "stopped",
"failed": "Inject failed: __error__",
"label": {
"properties": "Properties",
"repeat": "Repeat",
"flow": "flow context",
"global": "global context",
@ -51,7 +52,7 @@
"string": "string",
"boolean": "boolean",
"number": "number",
"Array": "Array",
"Array": "array",
"invalid": "Invalid JSON Object"
},
"timestamp": "timestamp",
@ -79,7 +80,6 @@
"on": "on",
"onstart": "Inject once after",
"onceDelay": "seconds, then",
"tip": "<b>Note:</b> \"interval between times\" and \"at a specific time\" will use cron.<br/>\"interval\" should be 596 hours or less.<br/>See info box for details.",
"success": "Successfully injected: __label__",
"errors": {
"failed": "inject failed, see log for details",
@ -302,7 +302,7 @@
"wait-for": "wait for",
"wait-loop": "resend it every",
"for": "Handling",
"bytopics": "each msg.topic independently",
"bytopics": "each",
"alltopics": "all messages",
"duration": {
"ms": "Milliseconds",
@ -311,6 +311,7 @@
"h": "Hours"
},
"extend": " extend delay if new message arrives",
"second": " send second message to separate output",
"label": {
"trigger": "trigger",
"trigger-block": "trigger & block",
@ -723,9 +724,15 @@
"mac": "Mac (\\r)",
"windows": "Windows (\\r\\n)"
},
"hdrout": {
"none": "never send column headers",
"all": "always send column headers",
"once": "send headers once, until msg.reset"
},
"errors": {
"csv_js": "This node only handles CSV strings or js objects.",
"obj_csv": "No columns template specified for object -> CSV."
"obj_csv": "No columns template specified for object -> CSV.",
"bad_csv": "Malformed CSV file - output probably corrupt."
}
},
"html": {

View File

@ -38,11 +38,13 @@
<p>The column template can contain an ordered list of column names. When converting CSV to an object, the column names
will be used as the property names. Alternatively, the column names can be taken from the first row of the CSV.</p>
<p>When converting to CSV, the column template is used to identify which properties to extract from the object and in what order.</p>
<p>If the template is blank then the node can use a simple comma separated list of properties supplied in <code>msg.columns</code> to
determine what to extract. If that is not present then all the object properties are ouput in the order in which they are found.</p>
<p>If the input is an array then the columns template is only used to optionally generate a row of column titles.</p>
<p>If 'parse numerical values' option is checked, string numerical values will be returned as numbers, ie. middle value '1,"1.5",2'.</p>
<p>If 'include empty strings' option is checked, empty strings will be returned in result, ie. middle value '"1","",3'.</p>
<p>If 'include null values' option is checked, null values will be returned in result, ie. middle value '"1",,3'.</p>
<p>The node can accept a multi-part input as long as the <code>parts</code> property is set correctly.</p>
<p>The node can accept a multi-part input as long as the <code>parts</code> property is set correctly, for example from a file-in node or split node.</p>
<p>If outputting multiple messages they will have their <code>parts</code> property set and form a complete message sequence.</p>
<p><b>Note:</b> the column template must be comma separated - even if a different separator is chosen for the data.</p>
</script>

View File

@ -302,7 +302,7 @@
"wait-for": "指定した時間待機",
"wait-loop": "指定した時間間隔毎に送信を繰り返す",
"for": "処理対象",
"bytopics": "msg.topic毎",
"bytopics": "毎",
"alltopics": "全メッセージ",
"duration": {
"ms": "ミリ秒",
@ -719,6 +719,11 @@
"mac": "Mac (\\r)",
"windows": "Windows (\\r\\n)"
},
"hdrout": {
"none": "カラムヘッダを送信しない",
"all": "カラムヘッダを常に送信する",
"once": "ヘッダを一度だけ送信する(msg.resetの受け付けると再送)"
},
"errors": {
"csv_js": "本ードが処理できる形式は、CSV文字列またはJSONのみです",
"obj_csv": "オブジェクトをCSVへ変換する際の列名が設定されていません"

View File

@ -19,10 +19,10 @@
"body-parser": "1.19.0",
"cheerio": "0.22.0",
"content-type": "1.0.4",
"cookie-parser": "1.4.4",
"cookie-parser": "1.4.5",
"cookie": "0.4.0",
"cors": "2.8.5",
"cron": "1.8.2",
"cron": "1.7.2",
"denque": "1.4.1",
"fs-extra": "8.1.0",
"fs.notify": "0.0.4",
@ -33,7 +33,7 @@
"media-typer": "1.1.0",
"mqtt": "2.18.8",
"multer": "1.4.2",
"mustache": "4.0.0",
"mustache": "4.0.1",
"on-headers": "1.0.2",
"raw-body": "2.4.1",
"request": "2.88.0",

View File

@ -18,7 +18,7 @@
"dependencies": {
"@node-red/util": "1.1.0",
"semver": "6.3.0",
"uglify-js": "3.8.0",
"uglify-js": "3.8.1",
"when": "3.7.8"
}
}

View File

@ -203,10 +203,10 @@ LocalFileSystem.prototype.open = function(){
var newContext = self.cache._export();
scopes.forEach(function(scope) {
var storagePath = getStoragePath(self.storageBaseDir,scope);
var context = newContext[scope];
var context = newContext[scope] || {};
var stringifiedContext = stringify(context);
if (stringifiedContext.circular && !self.knownCircularRefs[scope]) {
log.warn(log._("error-circular",{scope:scope}));
log.warn(log._("context.localfilesystem.error-circular",{scope:scope}));
self.knownCircularRefs[scope] = true;
} else {
delete self.knownCircularRefs[scope];
@ -324,7 +324,7 @@ LocalFileSystem.prototype.set = function(scope, key, value, callback) {
}
var stringifiedContext = stringify(obj);
if (stringifiedContext.circular && !self.knownCircularRefs[scope]) {
log.warn(log._("error-circular",{scope:scope}));
log.warn(log._("context.localfilesystem.error-circular",{scope:scope}));
self.knownCircularRefs[scope] = true;
} else {
delete self.knownCircularRefs[scope];

View File

@ -553,7 +553,7 @@ function getFlow(id) {
if (flow.label) {
result.label = flow.label;
}
if (flow.disabled) {
if (flow.hasOwnProperty('disabled')) {
result.disabled = flow.disabled;
}
if (flow.hasOwnProperty('info')) {

View File

@ -18,7 +18,7 @@
"clone": "2.1.2",
"i18next": "15.1.2",
"json-stringify-safe": "5.0.1",
"jsonata": "1.8.1",
"jsonata": "1.8.3",
"lodash.clonedeep": "^4.5.0",
"when": "3.7.8"
}

View File

@ -41,7 +41,7 @@
"fs-extra": "8.1.0",
"node-red-node-rbe": "^0.2.6",
"node-red-node-tail": "^0.1.0",
"nopt": "4.0.1",
"nopt": "4.0.3",
"semver": "6.3.0"
},
"optionalDependencies": {

View File

@ -102,20 +102,20 @@ describe('trigger node', function() {
function basicTest(type, val, rval) {
it('should output 1st value when triggered ('+type+')', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1:val, op1type:type, op2:"", op2type:"null", duration:"20", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
process.env[val] = rval;
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
if (rval) {
msg.should.have.property("payload");
should.deepEqual(msg.payload, rval);
}
else {
msg.should.have.property("payload", val);
}
if (rval) {
msg.should.have.property("payload");
should.deepEqual(msg.payload, rval);
}
else {
msg.should.have.property("payload", val);
}
delete process.env[val];
done();
}
@ -127,7 +127,7 @@ describe('trigger node', function() {
it('should output 2st value when triggered ('+type+')', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1:"foo", op1type:"str", op2:val, op2type:type, duration:"20", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
process.env[val] = rval;
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
@ -136,17 +136,17 @@ describe('trigger node', function() {
n2.on("input", function(msg) {
try {
if (c === 0) {
msg.should.have.property("payload", "foo");
msg.should.have.property("payload", "foo");
c++;
}
else {
if (rval) {
msg.should.have.property("payload");
should.deepEqual(msg.payload, rval);
}
else {
msg.should.have.property("payload", val);
}
if (rval) {
msg.should.have.property("payload");
should.deepEqual(msg.payload, rval);
}
else {
msg.should.have.property("payload", val);
}
delete process.env[val];
done();
}
@ -378,6 +378,51 @@ describe('trigger node', function() {
});
});
it('should handle multiple other properties individually if asked to do so', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", bytopic:"topic", topic:"foo", op1:"1", op2:"0", op1type:"num", op2type:"num", duration:"30", 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;
if (c === 1) {
msg.should.have.a.property("payload", 1);
msg.should.have.a.property("foo", "A");
}
else if (c === 2) {
msg.should.have.a.property("payload", 1);
msg.should.have.a.property("foo", "B");
}
else if (c === 3) {
msg.should.have.a.property("payload", 1);
msg.should.have.a.property("foo", "C");
}
else if (c === 4) {
msg.should.have.a.property("payload", 0);
msg.should.have.a.property("foo", "A");
}
else if (c === 5) {
msg.should.have.a.property("payload", 0);
msg.should.have.a.property("foo", "B");
}
else if (c === 6) {
msg.should.have.a.property("payload", 0);
msg.should.have.a.property("foo", "C");
done();
}
} catch(err) {
done(err);
}
});
n1.emit("input", {payload:1,foo:"A"});
n1.emit("input", {payload:2,foo:"B"});
n1.emit("input", {payload:3,foo:"C"});
});
});
it('should be able to return things from flow and global context variables', function(done) {
var spy = sinon.stub(RED.util, 'evaluateNodeProperty',
function(arg1, arg2, arg3, arg4, arg5) { if (arg5) { arg5(null, arg1) } else { return arg1; } }
@ -408,8 +453,8 @@ describe('trigger node', function() {
it('should be able to return things from persistable flow and global context variables', function (done) {
var flow = [{"id": "n1", "type": "trigger", "name": "triggerNode", "op1": "#:(memory1)::foo", "op1type": "flow",
"op2": "#:(memory1)::bar", "op2type": "global", "duration": "20", "wires": [["n2"]], "z": "flow" },
{"id": "n2", "type": "helper"}];
"op2": "#:(memory1)::bar", "op2type": "global", "duration": "20", "wires": [["n2"]], "z": "flow" },
{"id": "n2", "type": "helper"}];
helper.load(triggerNode, flow, function () {
initContext(function () {
var n1 = helper.getNode("n1");
@ -442,11 +487,11 @@ describe('trigger node', function() {
it('should be able to return things from multiple persistable global context variables', function (done) {
var flow = [{"id": "n1", "z": "flow", "type": "trigger",
"duration": "20", "wires": [["n2"]],
"op1": "#:(memory1)::val", "op1type": "global",
"op2": "#:(memory2)::val", "op2type": "global"
},
{"id": "n2", "type": "helper"}];
"duration": "20", "wires": [["n2"]],
"op1": "#:(memory1)::val", "op1type": "global",
"op2": "#:(memory2)::val", "op2type": "global"
},
{"id": "n2", "type": "helper"}];
helper.load(triggerNode, flow, function () {
initContext(function () {
var n1 = helper.getNode("n1");
@ -481,11 +526,11 @@ describe('trigger node', function() {
it('should be able to return things from multiple persistable flow context variables', function (done) {
var flow = [{"id": "n1", "z": "flow", "type": "trigger",
"duration": "20", "wires": [["n2"]],
"op1": "#:(memory1)::val", "op1type": "flow",
"op2": "#:(memory2)::val", "op2type": "flow"
},
{"id": "n2", "type": "helper"}];
"duration": "20", "wires": [["n2"]],
"op1": "#:(memory1)::val", "op1type": "flow",
"op2": "#:(memory2)::val", "op2type": "flow"
},
{"id": "n2", "type": "helper"}];
helper.load(triggerNode, flow, function () {
initContext(function () {
var n1 = helper.getNode("n1");
@ -520,11 +565,11 @@ describe('trigger node', function() {
it('should be able to return things from multiple persistable flow & global context variables', function (done) {
var flow = [{"id": "n1", "z": "flow", "type": "trigger",
"duration": "20", "wires": [["n2"]],
"op1": "#:(memory1)::val", "op1type": "flow",
"op2": "#:(memory2)::val", "op2type": "global"
},
{"id": "n2", "type": "helper"}];
"duration": "20", "wires": [["n2"]],
"op1": "#:(memory1)::val", "op1type": "flow",
"op2": "#:(memory2)::val", "op2type": "global"
},
{"id": "n2", "type": "helper"}];
helper.load(triggerNode, flow, function () {
initContext(function () {
var n1 = helper.getNode("n1");
@ -818,6 +863,40 @@ describe('trigger node', function() {
});
});
it('should be able to send 2nd message to a 2nd output', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"val", op1:"hello", op2:"world", duration:"50", outputs:2, wires:[["n2"],["n3"]] },
{id:"n2", type:"helper"}, {id:"n3", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var c = 0;
n2.on("input", function(msg) {
try {
if (c === 0) {
msg.should.have.a.property("payload", "hello");
msg.should.have.a.property("topic", "test");
c+=1;
}
else { done(err); }
}
catch(err) { done(err); }
});
n3.on("input", function(msg) {
try {
if (c === 1) {
msg.should.have.a.property("payload", "world");
msg.should.have.a.property("topic", "test");
done();
}
else { done(err); }
}
catch(err) { done(err); }
});
n1.emit("input", {payload:"go",topic:"test"});
});
});
it('should handle string null as null', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"pay", op1:"null", op2:"null", duration:"40", wires:[["n2"]] },
{id:"n2", type:"helper"} ];

View File

@ -1,3 +1,4 @@
/* eslint-disable no-undef */
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
@ -70,12 +71,13 @@ describe('CSV node', function() {
it('should convert a simple csv string to a javascript object', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('payload', { a: 1, b: 2, c: 3, d: 4 });
msg.should.have.property('columns', "a,b,c,d");
check_parts(msg, 0, 1);
done();
});
@ -86,7 +88,7 @@ describe('CSV node', function() {
it('should remove quotes and whitespace from template', function(done) {
var flow = [ { id:"n1", type:"csv", temp:'"a", "b" , " c "," d " ', wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@ -102,12 +104,13 @@ describe('CSV node', function() {
it('should create column names if no template provided', function(done) {
var flow = [ { id:"n1", type:"csv", temp:'', wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('payload', { col1: 1, col2: 2, col3: 3, col4: 4 });
msg.should.have.property('columns', "col1,col2,col3,col4");
check_parts(msg, 0, 1);
done();
});
@ -118,12 +121,13 @@ describe('CSV node', function() {
it('should allow dropping of fields from the template', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,,,d", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('payload', { a: 1, d: 4 });
msg.should.have.property('columns', 'a,d');
check_parts(msg, 0, 1);
done();
});
@ -134,7 +138,7 @@ describe('CSV node', function() {
it('should leave numbers starting with 0, e and + as strings (except 0.)', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@ -150,7 +154,7 @@ describe('CSV node', function() {
it('should not parse numbers when told not to do so', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", strings:false, wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@ -166,7 +170,7 @@ describe('CSV node', function() {
it('should leave handle strings with scientific notation as numbers', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@ -183,7 +187,7 @@ describe('CSV node', function() {
it('should allow quotes in the input (but drop blank strings)', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g,h", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@ -327,12 +331,13 @@ describe('CSV node', function() {
it('should be able to output multiple lines as one array', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", multi:"yes", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('payload', [ { a: 1, b: 2, c: 3, d: 4 },{ a: 5, b: -6, c: '07', d: '+8' },{ a: 9, b: 0, c: 'a', d: 'b' },{ a: 'c', b: 'd', c: 'e', d: 'f' } ]);
msg.should.have.property('columns','a,b,c,d');
msg.should.not.have.property('parts');
done();
});
@ -343,7 +348,7 @@ describe('CSV node', function() {
it('should handle numbers in strings but not IP addresses', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@ -359,7 +364,7 @@ describe('CSV node', function() {
it('should preserve parts property', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@ -403,7 +408,7 @@ describe('CSV node', function() {
it('should skip several lines from start if requested', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", skip: 2, wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@ -417,9 +422,9 @@ describe('CSV node', function() {
});
});
it('should skip several lines from start then use next line as a tempate', function(done) {
it('should skip several lines from start then use next line as a template', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", hdrin:true, skip: 2, wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@ -435,7 +440,7 @@ describe('CSV node', function() {
it('should skip several lines from start and correct parts', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", skip: 2, wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@ -467,11 +472,13 @@ describe('CSV node', function() {
n2.on("input", function(msg) {
if (c === 0) {
msg.should.have.property('payload', { w: 1, x: 2, y: 3, z: 4 });
msg.should.have.property('columns', 'w,x,y,z');
check_parts(msg, 0, 2);
c += 1;
}
else {
msg.should.have.property('payload', { w: 5, x: 6, y: 7, z: 8 });
msg.should.have.property('columns', 'w,x,y,z');
check_parts(msg, 1, 2);
done();
}
@ -495,7 +502,7 @@ describe('CSV node', function() {
it('should convert a simple object back to a csv', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,,e", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@ -513,7 +520,7 @@ describe('CSV node', function() {
it('should convert a simple object back to a csv with no template', function(done) {
var flow = [ { id:"n1", type:"csv", temp:" ", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@ -531,7 +538,7 @@ describe('CSV node', function() {
it('should handle a template with spaces in the property names', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b o,c p,,e", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@ -549,7 +556,7 @@ describe('CSV node', function() {
it('should convert an array of objects to a multi-line csv', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@ -567,7 +574,7 @@ describe('CSV node', function() {
it('should convert a simple array back to a csv', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@ -585,7 +592,7 @@ describe('CSV node', function() {
it('should convert an array of arrays back to a multi-line csv', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@ -603,7 +610,7 @@ describe('CSV node', function() {
it('should be able to include column names as first row', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", hdrout:true, ret:"\r\n", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@ -619,9 +626,36 @@ describe('CSV node', function() {
});
});
it('should be able to pass in column names', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"", hdrout:"once", ret:"\r\n", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var count = 0;
n2.on("input", function(msg) {
count += 1;
try {
if (count === 1) {
msg.should.have.property('payload', 'a,,b,a\r\n4,,3,4\r\n');
}
if (count === 3) {
msg.should.have.property('payload', '4,,3,4\r\n');
done()
}
}
catch(e) { done(e); }
});
var testJson = [{ d: 1, b: 3, c: 2, a: 4 }];
n1.emit("input", {payload:testJson, columns:"a,,b,a"});
n1.emit("input", {payload:testJson});
n1.emit("input", {payload:testJson});
});
});
it('should handle quotes and sub-properties', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@ -641,7 +675,7 @@ describe('CSV node', function() {
it('should just pass through if no payload provided', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
@ -661,7 +695,7 @@ describe('CSV node', function() {
it('should warn if provided a number or boolean', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");