mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Refactor Subflow logic into own class
This commit is contained in:
parent
da756fa568
commit
81f4e0de56
@ -35,6 +35,9 @@ function Node(n) {
|
|||||||
if (n._alias) {
|
if (n._alias) {
|
||||||
this._alias = n._alias;
|
this._alias = n._alias;
|
||||||
}
|
}
|
||||||
|
if (n._flow) {
|
||||||
|
this._flow = n._flow;
|
||||||
|
}
|
||||||
this.updateWires(n.wires);
|
this.updateWires(n.wires);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,10 +134,12 @@ Node.prototype.send = function(msg) {
|
|||||||
msg._msgid = redUtil.generateId();
|
msg._msgid = redUtil.generateId();
|
||||||
}
|
}
|
||||||
this.metric("send",msg);
|
this.metric("send",msg);
|
||||||
node = flows.get(this._wire);
|
node = this._flow.getNode(this._wire);
|
||||||
/* istanbul ignore else */
|
/* istanbul ignore else */
|
||||||
if (node) {
|
if (node) {
|
||||||
node.receive(msg);
|
node.receive(msg);
|
||||||
|
} else {
|
||||||
|
console.log("trying to send to a node not on this flow",this._wire);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@ -163,7 +168,7 @@ Node.prototype.send = function(msg) {
|
|||||||
var k = 0;
|
var k = 0;
|
||||||
// for each recipent node of that output
|
// for each recipent node of that output
|
||||||
for (var j = 0; j < wires.length; j++) {
|
for (var j = 0; j < wires.length; j++) {
|
||||||
node = flows.get(wires[j]); // node at end of wire j
|
node = this._flow.getNode(wires[j]); // node at end of wire j
|
||||||
if (node) {
|
if (node) {
|
||||||
// for each msg to send eg. [[m1, m2, ...], ...]
|
// for each msg to send eg. [[m1, m2, ...], ...]
|
||||||
for (k = 0; k < msgs.length; k++) {
|
for (k = 0; k < msgs.length; k++) {
|
||||||
@ -182,6 +187,8 @@ Node.prototype.send = function(msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
console.log("trying to send to a node not on this flow",this._wire);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -251,7 +258,7 @@ Node.prototype.error = function(logMessage,msg) {
|
|||||||
}
|
}
|
||||||
var handled = false;
|
var handled = false;
|
||||||
if (msg) {
|
if (msg) {
|
||||||
handled = flows.handleError(this,logMessage,msg);
|
handled = this._flow.handleError(this,logMessage,msg);
|
||||||
}
|
}
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
log_helper(this, Log.ERROR, logMessage);
|
log_helper(this, Log.ERROR, logMessage);
|
||||||
@ -286,6 +293,6 @@ Node.prototype.metric = function(eventname, msg, metricValue) {
|
|||||||
* status: { fill:"red|green", shape:"dot|ring", text:"blah" }
|
* status: { fill:"red|green", shape:"dot|ring", text:"blah" }
|
||||||
*/
|
*/
|
||||||
Node.prototype.status = function(status) {
|
Node.prototype.status = function(status) {
|
||||||
flows.handleStatus(this,status);
|
this._flow.handleStatus(this,status);
|
||||||
};
|
};
|
||||||
module.exports = Node;
|
module.exports = Node;
|
||||||
|
@ -16,33 +16,68 @@
|
|||||||
|
|
||||||
var when = require("when");
|
var when = require("when");
|
||||||
var clone = require("clone");
|
var clone = require("clone");
|
||||||
var typeRegistry = require("@node-red/registry");
|
var Subflow;
|
||||||
var Log;
|
var Log;
|
||||||
var redUtil = require("@node-red/util").util;
|
var redUtil = require("@node-red/util").util;
|
||||||
var flowUtil = require("./util");
|
var flowUtil = require("./util");
|
||||||
|
var events = require("../../events");
|
||||||
|
|
||||||
var nodeCloseTimeout = 15000;
|
var nodeCloseTimeout = 15000;
|
||||||
|
|
||||||
class Flow {
|
class Flow {
|
||||||
constructor(global,flow) {
|
constructor(parent,globalFlow,flow) {
|
||||||
this.global = global;
|
this.TYPE = 'flow';
|
||||||
|
this.parent = parent;
|
||||||
|
this.global = globalFlow;
|
||||||
if (typeof flow === 'undefined') {
|
if (typeof flow === 'undefined') {
|
||||||
this.flow = global;
|
this.flow = globalFlow;
|
||||||
|
this.isGlobalFlow = true;
|
||||||
} else {
|
} else {
|
||||||
this.flow = flow;
|
this.flow = flow;
|
||||||
|
this.isGlobalFlow = false;
|
||||||
}
|
}
|
||||||
|
this.id = this.flow.id || "global";
|
||||||
this.activeNodes = {};
|
this.activeNodes = {};
|
||||||
this.subflowInstanceNodes = {};
|
this.subflowInstanceNodes = {};
|
||||||
this.catchNodeMap = {};
|
this.catchNodes = [];
|
||||||
this.statusNodeMap = {};
|
this.statusNodes = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug(msg) {
|
||||||
|
Log.log({
|
||||||
|
id: this.id||"global",
|
||||||
|
level: Log.DEBUG,
|
||||||
|
type:this.TYPE,
|
||||||
|
msg:msg
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
log(msg) {
|
||||||
|
Log.log({
|
||||||
|
id: this.id||"global",
|
||||||
|
level: Log.INFO,
|
||||||
|
type:this.TYPE,
|
||||||
|
msg:msg
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
trace(msg) {
|
||||||
|
Log.log({
|
||||||
|
id: this.id||"global",
|
||||||
|
level: Log.TRACE,
|
||||||
|
type:this.TYPE,
|
||||||
|
msg:msg
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
start(diff) {
|
start(diff) {
|
||||||
|
this.trace("start "+this.TYPE);
|
||||||
var node;
|
var node;
|
||||||
var newNode;
|
var newNode;
|
||||||
var id;
|
var id;
|
||||||
this.catchNodeMap = {};
|
this.catchNodes = [];
|
||||||
this.statusNodeMap = {};
|
this.statusNodes = [];
|
||||||
|
|
||||||
var configNodes = Object.keys(this.flow.configs);
|
var configNodes = Object.keys(this.flow.configs);
|
||||||
var configNodeAttempts = {};
|
var configNodeAttempts = {};
|
||||||
@ -69,7 +104,7 @@ class Flow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (readyToCreate) {
|
if (readyToCreate) {
|
||||||
newNode = createNode(node.type,node);
|
newNode = flowUtil.createNode(this,node);
|
||||||
if (newNode) {
|
if (newNode) {
|
||||||
this.activeNodes[id] = newNode;
|
this.activeNodes[id] = newNode;
|
||||||
}
|
}
|
||||||
@ -91,7 +126,7 @@ class Flow {
|
|||||||
node = this.flow.nodes[id];
|
node = this.flow.nodes[id];
|
||||||
if (!node.subflow) {
|
if (!node.subflow) {
|
||||||
if (!this.activeNodes[id]) {
|
if (!this.activeNodes[id]) {
|
||||||
newNode = createNode(node.type,node);
|
newNode = flowUtil.createNode(this,node);
|
||||||
if (newNode) {
|
if (newNode) {
|
||||||
this.activeNodes[id] = newNode;
|
this.activeNodes[id] = newNode;
|
||||||
}
|
}
|
||||||
@ -100,13 +135,24 @@ class Flow {
|
|||||||
if (!this.subflowInstanceNodes[id]) {
|
if (!this.subflowInstanceNodes[id]) {
|
||||||
try {
|
try {
|
||||||
var subflowDefinition = this.flow.subflows[node.subflow]||this.global.subflows[node.subflow]
|
var subflowDefinition = this.flow.subflows[node.subflow]||this.global.subflows[node.subflow]
|
||||||
var nodes = createSubflow(subflowDefinition,node,this.flow.subflows,this.global.subflows,this.activeNodes);
|
// console.log("NEED TO CREATE A SUBFLOW",id,node.subflow);
|
||||||
this.subflowInstanceNodes[id] = nodes.map(function(n) { return n.id});
|
this.subflowInstanceNodes[id] = true;
|
||||||
for (var i=0;i<nodes.length;i++) {
|
var subflow = Subflow.create(
|
||||||
if (nodes[i]) {
|
this,
|
||||||
this.activeNodes[nodes[i].id] = nodes[i];
|
this.global,
|
||||||
}
|
subflowDefinition,
|
||||||
}
|
node
|
||||||
|
);
|
||||||
|
subflow.start();
|
||||||
|
this.activeNodes[id] = subflow.node;
|
||||||
|
this.subflowInstanceNodes[id] = subflow;
|
||||||
|
|
||||||
|
// this.subflowInstanceNodes[id] = nodes.map(function(n) { return n.id});
|
||||||
|
// for (var i=0;i<nodes.length;i++) {
|
||||||
|
// if (nodes[i]) {
|
||||||
|
// this.activeNodes[nodes[i].id] = nodes[i];
|
||||||
|
// }
|
||||||
|
// }
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.log(err.stack)
|
console.log(err.stack)
|
||||||
}
|
}
|
||||||
@ -115,31 +161,43 @@ class Flow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var activeCount = Object.keys(this.activeNodes).length;
|
||||||
|
if (activeCount > 0) {
|
||||||
|
this.trace("------------------|--------------|-----------------");
|
||||||
|
this.trace(" id | type | alias");
|
||||||
|
this.trace("------------------|--------------|-----------------");
|
||||||
|
}
|
||||||
|
// Build the map of catch/status nodes.
|
||||||
for (id in this.activeNodes) {
|
for (id in this.activeNodes) {
|
||||||
if (this.activeNodes.hasOwnProperty(id)) {
|
if (this.activeNodes.hasOwnProperty(id)) {
|
||||||
node = this.activeNodes[id];
|
node = this.activeNodes[id];
|
||||||
|
this.trace(" "+id.padEnd(16)+" | "+node.type.padEnd(12)+" | "+(node._alias||""));
|
||||||
if (node.type === "catch") {
|
if (node.type === "catch") {
|
||||||
this.catchNodeMap[node.z] = this.catchNodeMap[node.z] || [];
|
this.catchNodes.push(node);
|
||||||
this.catchNodeMap[node.z].push(node);
|
|
||||||
} else if (node.type === "status") {
|
} else if (node.type === "status") {
|
||||||
this.statusNodeMap[node.z] = this.statusNodeMap[node.z] || [];
|
this.statusNodes.push(node);
|
||||||
this.statusNodeMap[node.z].push(node);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (activeCount > 0) {
|
||||||
|
this.trace("------------------|--------------|-----------------");
|
||||||
|
}
|
||||||
|
// this.dump();
|
||||||
}
|
}
|
||||||
|
|
||||||
stop(stopList, removedList) {
|
stop(stopList, removedList) {
|
||||||
return new Promise((resolve,reject) => {
|
return new Promise((resolve,reject) => {
|
||||||
|
this.trace("stop "+this.TYPE);
|
||||||
var i;
|
var i;
|
||||||
if (stopList) {
|
if (stopList) {
|
||||||
for (i=0;i<stopList.length;i++) {
|
// for (i=0;i<stopList.length;i++) {
|
||||||
if (this.subflowInstanceNodes[stopList[i]]) {
|
// if (this.subflowInstanceNodes[stopList[i]]) {
|
||||||
// The first in the list is the instance node we already
|
// console.log("NEED TO STOP A SUBFLOW",stopList[i]);
|
||||||
// know about
|
// // The first in the list is the instance node we already
|
||||||
stopList = stopList.concat(this.subflowInstanceNodes[stopList[i]].slice(1))
|
// // know about
|
||||||
}
|
// // stopList = stopList.concat(this.subflowInstanceNodes[stopList[i]].slice(1))
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
} else {
|
} else {
|
||||||
stopList = Object.keys(this.activeNodes);
|
stopList = Object.keys(this.activeNodes);
|
||||||
}
|
}
|
||||||
@ -156,34 +214,20 @@ class Flow {
|
|||||||
if (node) {
|
if (node) {
|
||||||
delete this.activeNodes[stopList[i]];
|
delete this.activeNodes[stopList[i]];
|
||||||
if (this.subflowInstanceNodes[stopList[i]]) {
|
if (this.subflowInstanceNodes[stopList[i]]) {
|
||||||
|
try {
|
||||||
|
var subflow = this.subflowInstanceNodes[stopList[i]];
|
||||||
|
promises.push(this.stopNode(node,false).then(() => { subflow.stop() }));
|
||||||
|
} catch(err) {
|
||||||
|
node.error(err);
|
||||||
|
}
|
||||||
delete this.subflowInstanceNodes[stopList[i]];
|
delete this.subflowInstanceNodes[stopList[i]];
|
||||||
}
|
} else {
|
||||||
try {
|
try {
|
||||||
var removed = removedMap[stopList[i]];
|
var removed = removedMap[stopList[i]];
|
||||||
promises.push(
|
promises.push(this.stopNode(node,removed));
|
||||||
when.promise(function(resolve, reject) {
|
} catch(err) {
|
||||||
var start;
|
node.error(err);
|
||||||
var nt = node.type;
|
}
|
||||||
var nid = node.id;
|
|
||||||
var n = node;
|
|
||||||
when.promise(function(resolve) {
|
|
||||||
Log.trace("Stopping node "+nt+":"+nid+(removed?" removed":""));
|
|
||||||
start = Date.now();
|
|
||||||
resolve(n.close(removed));
|
|
||||||
}).timeout(nodeCloseTimeout).then(function(){
|
|
||||||
var delta = Date.now() - start;
|
|
||||||
Log.trace("Stopped node "+nt+":"+nid+" ("+delta+"ms)" );
|
|
||||||
resolve(delta);
|
|
||||||
},function(err) {
|
|
||||||
var delta = Date.now() - start;
|
|
||||||
n.error(Log._("nodes.flows.stopping-error",{message:err}));
|
|
||||||
Log.debug(err.stack);
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} catch(err) {
|
|
||||||
node.error(err);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,13 +237,46 @@ class Flow {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stopNode(node,removed) {
|
||||||
|
return when.promise(function(resolve, reject) {
|
||||||
|
var start;
|
||||||
|
when.promise(function(resolve) {
|
||||||
|
Log.trace("Stopping node "+node.type+":"+node.id+(removed?" removed":""));
|
||||||
|
start = Date.now();
|
||||||
|
resolve(node.close(removed));
|
||||||
|
}).timeout(nodeCloseTimeout).then(function(){
|
||||||
|
var delta = Date.now() - start;
|
||||||
|
Log.trace("Stopped node "+node.type+":"+node.id+" ("+delta+"ms)" );
|
||||||
|
resolve(delta);
|
||||||
|
},function(err) {
|
||||||
|
var delta = Date.now() - start;
|
||||||
|
node.error(Log._("nodes.flows.stopping-error",{message:err}));
|
||||||
|
Log.debug(err.stack);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
update(_global,_flow) {
|
update(_global,_flow) {
|
||||||
this.global = _global;
|
this.global = _global;
|
||||||
this.flow = _flow;
|
this.flow = _flow;
|
||||||
}
|
}
|
||||||
|
|
||||||
getNode(id) {
|
getNode(id) {
|
||||||
return this.activeNodes[id];
|
// console.log('getNode',id,!!this.activeNodes[id])
|
||||||
|
if (!id) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
// console.log((new Error().stack).toString().split("\n").slice(1,3).join("\n"))
|
||||||
|
if ((this.flow.configs && this.flow.configs[id]) || (this.flow.nodes && this.flow.nodes[id])) {
|
||||||
|
// This is a node owned by this flow, so return whatever we have got
|
||||||
|
// During a stop/restart, activeNodes could be null for this id
|
||||||
|
return this.activeNodes[id];
|
||||||
|
} else if (this.activeNodes[id]) {
|
||||||
|
// TEMP: this is a subflow internal node within this flow
|
||||||
|
return this.activeNodes[id];
|
||||||
|
}
|
||||||
|
return this.parent.getNode(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
getActiveNodes() {
|
getActiveNodes() {
|
||||||
@ -207,40 +284,40 @@ class Flow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleStatus(node,statusMessage) {
|
handleStatus(node,statusMessage) {
|
||||||
var targetStatusNodes = null;
|
events.emit("node-status",{
|
||||||
var reportingNode = node;
|
id: node.id,
|
||||||
|
status:statusMessage
|
||||||
|
});
|
||||||
|
|
||||||
var handled = false;
|
var handled = false;
|
||||||
while (reportingNode && !handled) {
|
this.statusNodes.forEach(function(targetStatusNode) {
|
||||||
targetStatusNodes = this.statusNodeMap[reportingNode.z];
|
if (targetStatusNode.scope && targetStatusNode.scope.indexOf(node.id) === -1) {
|
||||||
if (targetStatusNodes) {
|
return;
|
||||||
targetStatusNodes.forEach(function(targetStatusNode) {
|
|
||||||
if (targetStatusNode.scope && targetStatusNode.scope.indexOf(node.id) === -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var message = {
|
|
||||||
status: {
|
|
||||||
text: "",
|
|
||||||
source: {
|
|
||||||
id: node.id,
|
|
||||||
type: node.type,
|
|
||||||
name: node.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (statusMessage.hasOwnProperty("text")) {
|
|
||||||
message.status.text = statusMessage.text.toString();
|
|
||||||
}
|
|
||||||
targetStatusNode.receive(message);
|
|
||||||
handled = true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (!handled) {
|
var message = {
|
||||||
reportingNode = this.activeNodes[reportingNode.z];
|
status: {
|
||||||
|
text: "",
|
||||||
|
source: {
|
||||||
|
id: node.id,
|
||||||
|
type: node.type,
|
||||||
|
name: node.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (statusMessage.hasOwnProperty("text")) {
|
||||||
|
message.status.text = statusMessage.text.toString();
|
||||||
}
|
}
|
||||||
|
targetStatusNode.receive(message);
|
||||||
|
handled = true;
|
||||||
|
});
|
||||||
|
if (!handled) {
|
||||||
|
// // Nothing in this flow handled the status - pass it to the parent
|
||||||
|
this.parent.handleStatus(node,statusMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleError(node,logMessage,msg) {
|
handleError(node,logMessage,msg) {
|
||||||
|
// console.log("HE",logMessage);
|
||||||
var count = 1;
|
var count = 1;
|
||||||
if (msg && msg.hasOwnProperty("error") && msg.error !== null) {
|
if (msg && msg.hasOwnProperty("error") && msg.error !== null) {
|
||||||
if (msg.error.hasOwnProperty("source") && msg.error.source !== null) {
|
if (msg.error.hasOwnProperty("source") && msg.error.source !== null) {
|
||||||
@ -253,268 +330,67 @@ class Flow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var targetCatchNodes = null;
|
|
||||||
var throwingNode = node;
|
|
||||||
var handled = false;
|
var handled = false;
|
||||||
while (throwingNode && !handled) {
|
this.catchNodes.forEach(function(targetCatchNode) {
|
||||||
targetCatchNodes = this.catchNodeMap[throwingNode.z];
|
if (targetCatchNode.scope && targetCatchNode.scope.indexOf(node.id) === -1) {
|
||||||
if (targetCatchNodes) {
|
return;
|
||||||
targetCatchNodes.forEach(function(targetCatchNode) {
|
|
||||||
if (targetCatchNode.scope && targetCatchNode.scope.indexOf(throwingNode.id) === -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var errorMessage;
|
|
||||||
if (msg) {
|
|
||||||
errorMessage = redUtil.cloneMessage(msg);
|
|
||||||
} else {
|
|
||||||
errorMessage = {};
|
|
||||||
}
|
|
||||||
if (errorMessage.hasOwnProperty("error")) {
|
|
||||||
errorMessage._error = errorMessage.error;
|
|
||||||
}
|
|
||||||
errorMessage.error = {
|
|
||||||
message: logMessage.toString(),
|
|
||||||
source: {
|
|
||||||
id: node.id,
|
|
||||||
type: node.type,
|
|
||||||
name: node.name,
|
|
||||||
count: count
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (logMessage.hasOwnProperty('stack')) {
|
|
||||||
errorMessage.error.stack = logMessage.stack;
|
|
||||||
}
|
|
||||||
targetCatchNode.receive(errorMessage);
|
|
||||||
handled = true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (!handled) {
|
var errorMessage;
|
||||||
throwingNode = this.activeNodes[throwingNode.z];
|
if (msg) {
|
||||||
|
errorMessage = redUtil.cloneMessage(msg);
|
||||||
|
} else {
|
||||||
|
errorMessage = {};
|
||||||
}
|
}
|
||||||
|
if (errorMessage.hasOwnProperty("error")) {
|
||||||
|
errorMessage._error = errorMessage.error;
|
||||||
|
}
|
||||||
|
errorMessage.error = {
|
||||||
|
message: logMessage.toString(),
|
||||||
|
source: {
|
||||||
|
id: node.id,
|
||||||
|
type: node.type,
|
||||||
|
name: node.name,
|
||||||
|
count: count
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (logMessage.hasOwnProperty('stack')) {
|
||||||
|
errorMessage.error.stack = logMessage.stack;
|
||||||
|
}
|
||||||
|
targetCatchNode.receive(errorMessage);
|
||||||
|
handled = true;
|
||||||
|
});
|
||||||
|
if (!handled) {
|
||||||
|
// Nothing in this flow handled the error - pass it to the parent
|
||||||
|
handled = this.parent.handleError(node,logMessage);
|
||||||
}
|
}
|
||||||
return handled;
|
return handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dump() {
|
||||||
|
console.log("==================")
|
||||||
|
console.log(this.TYPE, this.id);
|
||||||
|
for (var id in this.activeNodes) {
|
||||||
|
if (this.activeNodes.hasOwnProperty(id)) {
|
||||||
|
var node = this.activeNodes[id];
|
||||||
|
console.log(" ",id.padEnd(16),node.type)
|
||||||
|
if (node.wires) {
|
||||||
|
console.log(" -> ",node.wires)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("==================")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new instance of a node
|
|
||||||
* @param {string} type The node type string
|
|
||||||
* @param {object} config The node configuration object
|
|
||||||
* @return {Node} The instance of the node
|
|
||||||
*/
|
|
||||||
function createNode(type,config) {
|
|
||||||
var newNode = null;
|
|
||||||
try {
|
|
||||||
var nodeTypeConstructor = typeRegistry.get(type);
|
|
||||||
if (nodeTypeConstructor) {
|
|
||||||
var conf = clone(config);
|
|
||||||
delete conf.credentials;
|
|
||||||
for (var p in conf) {
|
|
||||||
if (conf.hasOwnProperty(p)) {
|
|
||||||
flowUtil.mapEnvVarProperties(conf,p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
newNode = new nodeTypeConstructor(conf);
|
|
||||||
} catch (err) {
|
|
||||||
Log.log({
|
|
||||||
level: Log.ERROR,
|
|
||||||
id:conf.id,
|
|
||||||
type: type,
|
|
||||||
msg: err
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.error(Log._("nodes.flow.unknown-type", {type:type}));
|
|
||||||
}
|
|
||||||
} catch(err) {
|
|
||||||
Log.error(err);
|
|
||||||
}
|
|
||||||
return newNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSubflow(sf,sfn,subflows,globalSubflows,activeNodes) {
|
|
||||||
//console.log("CREATE SUBFLOW",sf.id,sfn.id);
|
|
||||||
var nodes = [];
|
|
||||||
var node_map = {};
|
|
||||||
var newNodes = [];
|
|
||||||
var node;
|
|
||||||
var wires;
|
|
||||||
var i,j,k;
|
|
||||||
|
|
||||||
var createNodeInSubflow = function(def) {
|
|
||||||
node = clone(def);
|
|
||||||
var nid = redUtil.generateId();
|
|
||||||
node_map[node.id] = node;
|
|
||||||
node._alias = node.id;
|
|
||||||
node.id = nid;
|
|
||||||
node.z = sfn.id;
|
|
||||||
newNodes.push(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone all of the subflow config node definitions and give them new IDs
|
|
||||||
for (i in sf.configs) {
|
|
||||||
if (sf.configs.hasOwnProperty(i)) {
|
|
||||||
createNodeInSubflow(sf.configs[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Clone all of the subflow node definitions and give them new IDs
|
|
||||||
for (i in sf.nodes) {
|
|
||||||
if (sf.nodes.hasOwnProperty(i)) {
|
|
||||||
createNodeInSubflow(sf.nodes[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look for any catch/status nodes and update their scope ids
|
|
||||||
// Update all subflow interior wiring to reflect new node IDs
|
|
||||||
for (i=0;i<newNodes.length;i++) {
|
|
||||||
node = newNodes[i];
|
|
||||||
if (node.wires) {
|
|
||||||
var outputs = node.wires;
|
|
||||||
for (j=0;j<outputs.length;j++) {
|
|
||||||
wires = outputs[j];
|
|
||||||
for (k=0;k<wires.length;k++) {
|
|
||||||
outputs[j][k] = node_map[outputs[j][k]].id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((node.type === 'catch' || node.type === 'status') && node.scope) {
|
|
||||||
node.scope = node.scope.map(function(id) {
|
|
||||||
return node_map[id]?node_map[id].id:""
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
for (var prop in node) {
|
|
||||||
if (node.hasOwnProperty(prop) && prop !== '_alias') {
|
|
||||||
if (node_map[node[prop]]) {
|
|
||||||
//console.log("Mapped",node.type,node.id,prop,node_map[node[prop]].id);
|
|
||||||
node[prop] = node_map[node[prop]].id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a subflow node to accept inbound messages and route appropriately
|
|
||||||
var Node = require("../Node");
|
|
||||||
var subflowInstance = {
|
|
||||||
id: sfn.id,
|
|
||||||
type: sfn.type,
|
|
||||||
z: sfn.z,
|
|
||||||
name: sfn.name,
|
|
||||||
wires: []
|
|
||||||
}
|
|
||||||
if (sf.in) {
|
|
||||||
subflowInstance.wires = sf.in.map(function(n) { return n.wires.map(function(w) { return node_map[w.id].id;})})
|
|
||||||
subflowInstance._originalWires = clone(subflowInstance.wires);
|
|
||||||
}
|
|
||||||
var subflowNode = new Node(subflowInstance);
|
|
||||||
|
|
||||||
subflowNode.on("input", function(msg) { this.send(msg);});
|
|
||||||
|
|
||||||
|
|
||||||
subflowNode._updateWires = subflowNode.updateWires;
|
|
||||||
|
|
||||||
subflowNode.updateWires = function(newWires) {
|
|
||||||
// Wire the subflow outputs
|
|
||||||
if (sf.out) {
|
|
||||||
var node,wires,i,j;
|
|
||||||
// Restore the original wiring to the internal nodes
|
|
||||||
subflowInstance.wires = clone(subflowInstance._originalWires);
|
|
||||||
for (i=0;i<sf.out.length;i++) {
|
|
||||||
wires = sf.out[i].wires;
|
|
||||||
for (j=0;j<wires.length;j++) {
|
|
||||||
if (wires[j].id != sf.id) {
|
|
||||||
node = node_map[wires[j].id];
|
|
||||||
if (node._originalWires) {
|
|
||||||
node.wires = clone(node._originalWires);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var modifiedNodes = {};
|
|
||||||
var subflowInstanceModified = false;
|
|
||||||
|
|
||||||
for (i=0;i<sf.out.length;i++) {
|
|
||||||
wires = sf.out[i].wires;
|
|
||||||
for (j=0;j<wires.length;j++) {
|
|
||||||
if (wires[j].id === sf.id) {
|
|
||||||
subflowInstance.wires[wires[j].port] = subflowInstance.wires[wires[j].port].concat(newWires[i]);
|
|
||||||
subflowInstanceModified = true;
|
|
||||||
} else {
|
|
||||||
node = node_map[wires[j].id];
|
|
||||||
node.wires[wires[j].port] = node.wires[wires[j].port].concat(newWires[i]);
|
|
||||||
modifiedNodes[node.id] = node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Object.keys(modifiedNodes).forEach(function(id) {
|
|
||||||
var node = modifiedNodes[id];
|
|
||||||
subflowNode.instanceNodes[id].updateWires(node.wires);
|
|
||||||
});
|
|
||||||
if (subflowInstanceModified) {
|
|
||||||
subflowNode._updateWires(subflowInstance.wires);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes.push(subflowNode);
|
|
||||||
|
|
||||||
// Wire the subflow outputs
|
|
||||||
if (sf.out) {
|
|
||||||
var modifiedNodes = {};
|
|
||||||
for (i=0;i<sf.out.length;i++) {
|
|
||||||
wires = sf.out[i].wires;
|
|
||||||
for (j=0;j<wires.length;j++) {
|
|
||||||
if (wires[j].id === sf.id) {
|
|
||||||
// A subflow input wired straight to a subflow output
|
|
||||||
subflowInstance.wires[wires[j].port] = subflowInstance.wires[wires[j].port].concat(sfn.wires[i])
|
|
||||||
subflowNode._updateWires(subflowInstance.wires);
|
|
||||||
} else {
|
|
||||||
node = node_map[wires[j].id];
|
|
||||||
modifiedNodes[node.id] = node;
|
|
||||||
if (!node._originalWires) {
|
|
||||||
node._originalWires = clone(node.wires);
|
|
||||||
}
|
|
||||||
node.wires[wires[j].port] = (node.wires[wires[j].port]||[]).concat(sfn.wires[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instantiate the nodes
|
|
||||||
for (i=0;i<newNodes.length;i++) {
|
|
||||||
node = newNodes[i];
|
|
||||||
var type = node.type;
|
|
||||||
|
|
||||||
var m = /^subflow:(.+)$/.exec(type);
|
|
||||||
if (!m) {
|
|
||||||
var newNode = createNode(type,node);
|
|
||||||
if (newNode) {
|
|
||||||
activeNodes[node.id] = newNode;
|
|
||||||
nodes.push(newNode);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var subflowId = m[1];
|
|
||||||
nodes = nodes.concat(createSubflow(subflows[subflowId]||globalSubflows[subflowId],node,subflows,globalSubflows,activeNodes));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
subflowNode.instanceNodes = {};
|
|
||||||
|
|
||||||
nodes.forEach(function(node) {
|
|
||||||
subflowNode.instanceNodes[node.id] = node;
|
|
||||||
});
|
|
||||||
return nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init: function(runtime) {
|
init: function(runtime) {
|
||||||
nodeCloseTimeout = runtime.settings.nodeCloseTimeout || 15000;
|
nodeCloseTimeout = runtime.settings.nodeCloseTimeout || 15000;
|
||||||
Log = runtime.log;
|
Log = runtime.log;
|
||||||
|
Subflow = require("./Subflow");
|
||||||
|
Subflow.init(runtime);
|
||||||
},
|
},
|
||||||
create: function(global,conf) {
|
create: function(parent,global,conf) {
|
||||||
return new Flow(global,conf);
|
return new Flow(parent,global,conf);
|
||||||
}
|
},
|
||||||
|
Flow: Flow
|
||||||
}
|
}
|
||||||
|
246
packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js
vendored
Normal file
246
packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js
vendored
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
const clone = require("clone");
|
||||||
|
const Flow = require('./Flow').Flow;
|
||||||
|
|
||||||
|
const redUtil = require("@node-red/util").util;
|
||||||
|
const flowUtil = require("./util");
|
||||||
|
|
||||||
|
var Log;
|
||||||
|
|
||||||
|
|
||||||
|
class Subflow extends Flow {
|
||||||
|
constructor(parent,globalFlow,subflowDef,subflowInstance) {
|
||||||
|
// console.log(subflowDef);
|
||||||
|
// console.log("CREATE SUBFLOW",subflowDef.id,subflowInstance.id);
|
||||||
|
// console.log("SubflowInstance\n"+JSON.stringify(subflowInstance," ",2));
|
||||||
|
// console.log("SubflowDef\n"+JSON.stringify(subflowDef," ",2));
|
||||||
|
var subflows = parent.flow.subflows;
|
||||||
|
var globalSubflows = parent.global.subflows;
|
||||||
|
|
||||||
|
var node_map = {};
|
||||||
|
var node;
|
||||||
|
var wires;
|
||||||
|
var i;
|
||||||
|
|
||||||
|
var subflowInternalFlowConfig = {
|
||||||
|
id: subflowInstance.id,
|
||||||
|
configs: {},
|
||||||
|
nodes: {},
|
||||||
|
subflows: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subflowDef.configs) {
|
||||||
|
// Clone all of the subflow config node definitions and give them new IDs
|
||||||
|
for (i in subflowDef.configs) {
|
||||||
|
if (subflowDef.configs.hasOwnProperty(i)) {
|
||||||
|
node = createNodeInSubflow(subflowInstance.id,subflowDef.configs[i]);
|
||||||
|
node_map[node._alias] = node;
|
||||||
|
subflowInternalFlowConfig.configs[node.id] = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (subflowDef.nodes) {
|
||||||
|
// Clone all of the subflow node definitions and give them new IDs
|
||||||
|
for (i in subflowDef.nodes) {
|
||||||
|
if (subflowDef.nodes.hasOwnProperty(i)) {
|
||||||
|
node = createNodeInSubflow(subflowInstance.id,subflowDef.nodes[i]);
|
||||||
|
node_map[node._alias] = node;
|
||||||
|
subflowInternalFlowConfig.nodes[node.id] = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subflowInternalFlowConfig.subflows = clone(subflowDef.subflows || {});
|
||||||
|
|
||||||
|
remapSubflowNodes(subflowInternalFlowConfig.configs,node_map);
|
||||||
|
remapSubflowNodes(subflowInternalFlowConfig.nodes,node_map);
|
||||||
|
|
||||||
|
// console.log("Instance config\n",JSON.stringify(subflowInternalFlowConfig,"",2));
|
||||||
|
|
||||||
|
super(parent,globalFlow,subflowInternalFlowConfig);
|
||||||
|
|
||||||
|
this.TYPE = 'subflow';
|
||||||
|
this.subflowDef = subflowDef;
|
||||||
|
this.subflowInstance = subflowInstance;
|
||||||
|
this.node_map = node_map;
|
||||||
|
}
|
||||||
|
|
||||||
|
start(diff) {
|
||||||
|
var self = this;
|
||||||
|
// Create a subflow node to accept inbound messages and route appropriately
|
||||||
|
var Node = require("../Node");
|
||||||
|
var subflowInstanceConfig = {
|
||||||
|
id: this.subflowInstance.id,
|
||||||
|
type: this.subflowInstance.type,
|
||||||
|
z: this.subflowInstance.z,
|
||||||
|
name: this.subflowInstance.name,
|
||||||
|
wires: [],
|
||||||
|
_flow: this
|
||||||
|
}
|
||||||
|
if (this.subflowDef.in) {
|
||||||
|
subflowInstanceConfig.wires = this.subflowDef.in.map(function(n) { return n.wires.map(function(w) { return self.node_map[w.id].id;})})
|
||||||
|
subflowInstanceConfig._originalWires = clone(subflowInstanceConfig.wires);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.node = new Node(subflowInstanceConfig);
|
||||||
|
this.node.on("input", function(msg) { this.send(msg);});
|
||||||
|
|
||||||
|
this.node._updateWires = this.node.updateWires;
|
||||||
|
|
||||||
|
this.node.updateWires = function(newWires) {
|
||||||
|
// Wire the subflow outputs
|
||||||
|
if (self.subflowDef.out) {
|
||||||
|
var node,wires,i,j;
|
||||||
|
// Restore the original wiring to the internal nodes
|
||||||
|
subflowInstanceConfig.wires = clone(subflowInstanceConfig._originalWires);
|
||||||
|
for (i=0;i<self.subflowDef.out.length;i++) {
|
||||||
|
wires = self.subflowDef.out[i].wires;
|
||||||
|
for (j=0;j<wires.length;j++) {
|
||||||
|
if (wires[j].id != self.subflowDef.id) {
|
||||||
|
node = self.node_map[wires[j].id];
|
||||||
|
if (node._originalWires) {
|
||||||
|
node.wires = clone(node._originalWires);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var modifiedNodes = {};
|
||||||
|
var subflowInstanceModified = false;
|
||||||
|
for (i=0;i<self.subflowDef.out.length;i++) {
|
||||||
|
wires = self.subflowDef.out[i].wires;
|
||||||
|
for (j=0;j<wires.length;j++) {
|
||||||
|
if (wires[j].id === self.subflowDef.id) {
|
||||||
|
subflowInstanceConfig.wires[wires[j].port] = subflowInstanceConfig.wires[wires[j].port].concat(newWires[i]);
|
||||||
|
subflowInstanceModified = true;
|
||||||
|
} else {
|
||||||
|
node = self.node_map[wires[j].id];
|
||||||
|
node.wires[wires[j].port] = node.wires[wires[j].port].concat(newWires[i]);
|
||||||
|
modifiedNodes[node.id] = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Object.keys(modifiedNodes).forEach(function(id) {
|
||||||
|
var node = modifiedNodes[id];
|
||||||
|
self.activeNodes[id].updateWires(node.wires);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (subflowInstanceModified) {
|
||||||
|
self.node._updateWires(subflowInstanceConfig.wires);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wire the subflow outputs
|
||||||
|
if (this.subflowDef.out) {
|
||||||
|
var modifiedNodes = {};
|
||||||
|
for (var i=0;i<this.subflowDef.out.length;i++) {
|
||||||
|
// i: the output index
|
||||||
|
// This is what this Output is wired to
|
||||||
|
wires = this.subflowDef.out[i].wires;
|
||||||
|
for (var j=0;j<wires.length;j++) {
|
||||||
|
if (wires[j].id === this.subflowDef.id) {
|
||||||
|
// A subflow input wired straight to a subflow output
|
||||||
|
subflowInstanceConfig.wires[wires[j].port] = subflowInstanceConfig.wires[wires[j].port].concat(this.subflowInstance.wires[i])
|
||||||
|
this.node._updateWires(subflowInstanceConfig.wires);
|
||||||
|
} else {
|
||||||
|
var node = self.node_map[wires[j].id];
|
||||||
|
modifiedNodes[node.id] = node;
|
||||||
|
if (!node._originalWires) {
|
||||||
|
node._originalWires = clone(node.wires);
|
||||||
|
}
|
||||||
|
node.wires[wires[j].port] = (node.wires[wires[j].port]||[]).concat(this.subflowInstance.wires[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.start(diff);
|
||||||
|
}
|
||||||
|
|
||||||
|
stop(stopList,removedList) {
|
||||||
|
return super.stop(stopList,removedList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function createNodeInSubflow(subflowInstanceId, def) {
|
||||||
|
let node = clone(def);
|
||||||
|
let nid = redUtil.generateId();
|
||||||
|
// console.log("Create Node In subflow",node.id, "--->",nid, "(",node.type,")")
|
||||||
|
// node_map[node.id] = node;
|
||||||
|
node._alias = node.id;
|
||||||
|
node.id = nid;
|
||||||
|
node.z = subflowInstanceId;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an object of {id:nodes} and a map of {old-id:node}, modifiy all
|
||||||
|
* properties in the nodes object to reference the new node ids.
|
||||||
|
* This handles:
|
||||||
|
* - node.wires,
|
||||||
|
* - node.scope of Catch and Status nodes,
|
||||||
|
* - node.XYZ for any property where XYZ is recognised as an old property
|
||||||
|
* @param {[type]} nodes [description]
|
||||||
|
* @param {[type]} nodeMap [description]
|
||||||
|
* @return {[type]} [description]
|
||||||
|
*/
|
||||||
|
function remapSubflowNodes(nodes,nodeMap) {
|
||||||
|
for (var id in nodes) {
|
||||||
|
if (nodes.hasOwnProperty(id)) {
|
||||||
|
var node = nodes[id];
|
||||||
|
if (node.wires) {
|
||||||
|
var outputs = node.wires;
|
||||||
|
for (j=0;j<outputs.length;j++) {
|
||||||
|
wires = outputs[j];
|
||||||
|
for (k=0;k<wires.length;k++) {
|
||||||
|
outputs[j][k] = nodeMap[outputs[j][k]].id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((node.type === 'catch' || node.type === 'status') && node.scope) {
|
||||||
|
node.scope = node.scope.map(function(id) {
|
||||||
|
return nodeMap[id]?nodeMap[id].id:""
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
for (var prop in node) {
|
||||||
|
if (node.hasOwnProperty(prop) && prop !== '_alias') {
|
||||||
|
if (nodeMap[node[prop]]) {
|
||||||
|
//console.log("Mapped",node.type,node.id,prop,nodeMap[node[prop]].id);
|
||||||
|
node[prop] = nodeMap[node[prop]].id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSubflow(parent,globalFlow,subflowDef,subflowInstance) {
|
||||||
|
return new Subflow(parent,globalFlow,subflowDef,subflowInstance)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: function(runtime) {
|
||||||
|
Log = runtime.log;
|
||||||
|
},
|
||||||
|
create: createSubflow
|
||||||
|
}
|
@ -42,7 +42,6 @@ var started = false;
|
|||||||
var credentialsPendingReset = false;
|
var credentialsPendingReset = false;
|
||||||
|
|
||||||
var activeNodesToFlow = {};
|
var activeNodesToFlow = {};
|
||||||
var subflowInstanceNodeMap = {};
|
|
||||||
|
|
||||||
var typeEventRegistered = false;
|
var typeEventRegistered = false;
|
||||||
|
|
||||||
@ -233,59 +232,6 @@ function getFlows() {
|
|||||||
return activeConfig;
|
return activeConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
function delegateError(node,logMessage,msg) {
|
|
||||||
var handled = false;
|
|
||||||
if (activeFlows[node.z]) {
|
|
||||||
handled = activeFlows[node.z].handleError(node,logMessage,msg);
|
|
||||||
} else if (activeNodesToFlow[node.z] && activeFlows[activeNodesToFlow[node.z]]) {
|
|
||||||
handled = activeFlows[activeNodesToFlow[node.z]].handleError(node,logMessage,msg);
|
|
||||||
} else if (activeFlowConfig.subflows[node.z] && subflowInstanceNodeMap[node.id]) {
|
|
||||||
subflowInstanceNodeMap[node.id].forEach(function(n) {
|
|
||||||
handled = handled || delegateError(getNode(n),logMessage,msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return handled;
|
|
||||||
}
|
|
||||||
function handleError(node,logMessage,msg) {
|
|
||||||
var handled = false;
|
|
||||||
if (node.z) {
|
|
||||||
handled = delegateError(node,logMessage,msg);
|
|
||||||
} else {
|
|
||||||
if (activeFlowConfig.configs[node.id]) {
|
|
||||||
activeFlowConfig.configs[node.id]._users.forEach(function(id) {
|
|
||||||
var userNode = activeFlowConfig.allNodes[id];
|
|
||||||
handled = handled || delegateError(userNode,logMessage,msg);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return handled;
|
|
||||||
}
|
|
||||||
|
|
||||||
function delegateStatus(node,statusMessage) {
|
|
||||||
if (activeFlows[node.z]) {
|
|
||||||
activeFlows[node.z].handleStatus(node,statusMessage);
|
|
||||||
} else if (activeNodesToFlow[node.z] && activeFlows[activeNodesToFlow[node.z]]) {
|
|
||||||
activeFlows[activeNodesToFlow[node.z]].handleStatus(node,statusMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function handleStatus(node,statusMessage) {
|
|
||||||
events.emit("node-status",{
|
|
||||||
id: node.id,
|
|
||||||
status:statusMessage
|
|
||||||
});
|
|
||||||
if (node.z) {
|
|
||||||
delegateStatus(node,statusMessage);
|
|
||||||
} else {
|
|
||||||
if (activeFlowConfig.configs[node.id]) {
|
|
||||||
activeFlowConfig.configs[node.id]._users.forEach(function(id) {
|
|
||||||
var userNode = activeFlowConfig.allNodes[id];
|
|
||||||
delegateStatus(userNode,statusMessage);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function start(type,diff,muteLog) {
|
function start(type,diff,muteLog) {
|
||||||
type = type||"full";
|
type = type||"full";
|
||||||
started = true;
|
started = true;
|
||||||
@ -338,7 +284,7 @@ function start(type,diff,muteLog) {
|
|||||||
// Check the 'global' flow is running
|
// Check the 'global' flow is running
|
||||||
if (!activeFlows['global']) {
|
if (!activeFlows['global']) {
|
||||||
log.debug("red/nodes/flows.start : starting flow : global");
|
log.debug("red/nodes/flows.start : starting flow : global");
|
||||||
activeFlows['global'] = Flow.create(activeFlowConfig);
|
activeFlows['global'] = Flow.create(flowAPI,activeFlowConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check each flow in the active configuration
|
// Check each flow in the active configuration
|
||||||
@ -346,7 +292,7 @@ function start(type,diff,muteLog) {
|
|||||||
if (activeFlowConfig.flows.hasOwnProperty(id)) {
|
if (activeFlowConfig.flows.hasOwnProperty(id)) {
|
||||||
if (!activeFlowConfig.flows[id].disabled && !activeFlows[id]) {
|
if (!activeFlowConfig.flows[id].disabled && !activeFlows[id]) {
|
||||||
// This flow is not disabled, nor is it currently active, so create it
|
// This flow is not disabled, nor is it currently active, so create it
|
||||||
activeFlows[id] = Flow.create(activeFlowConfig,activeFlowConfig.flows[id]);
|
activeFlows[id] = Flow.create(flowAPI,activeFlowConfig,activeFlowConfig.flows[id]);
|
||||||
log.debug("red/nodes/flows.start : starting flow : "+id);
|
log.debug("red/nodes/flows.start : starting flow : "+id);
|
||||||
} else {
|
} else {
|
||||||
log.debug("red/nodes/flows.start : not starting disabled flow : "+id);
|
log.debug("red/nodes/flows.start : not starting disabled flow : "+id);
|
||||||
@ -366,7 +312,7 @@ function start(type,diff,muteLog) {
|
|||||||
activeFlows[id].update(activeFlowConfig,activeFlowConfig.flows[id]);
|
activeFlows[id].update(activeFlowConfig,activeFlowConfig.flows[id]);
|
||||||
} else {
|
} else {
|
||||||
// This flow didn't previously exist, so create it
|
// This flow didn't previously exist, so create it
|
||||||
activeFlows[id] = Flow.create(activeFlowConfig,activeFlowConfig.flows[id]);
|
activeFlows[id] = Flow.create(flowAPI,activeFlowConfig,activeFlowConfig.flows[id]);
|
||||||
log.debug("red/nodes/flows.start : starting flow : "+id);
|
log.debug("red/nodes/flows.start : starting flow : "+id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -375,22 +321,20 @@ function start(type,diff,muteLog) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Having created or updated all flows, now start them.
|
// Having created or updated all flows, now start them.
|
||||||
for (id in activeFlows) {
|
for (id in activeFlows) {
|
||||||
if (activeFlows.hasOwnProperty(id)) {
|
if (activeFlows.hasOwnProperty(id)) {
|
||||||
activeFlows[id].start(diff);
|
try {
|
||||||
|
activeFlows[id].start(diff);
|
||||||
// Create a map of node id to flow id and also a subflowInstance lookup map
|
|
||||||
var activeNodes = activeFlows[id].getActiveNodes();
|
|
||||||
Object.keys(activeNodes).forEach(function(nid) {
|
|
||||||
activeNodesToFlow[nid] = id;
|
|
||||||
if (activeNodes[nid]._alias) {
|
|
||||||
subflowInstanceNodeMap[activeNodes[nid]._alias] = subflowInstanceNodeMap[activeNodes[nid]._alias] || [];
|
|
||||||
subflowInstanceNodeMap[activeNodes[nid]._alias].push(nid);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// Create a map of node id to flow id and also a subflowInstance lookup map
|
||||||
|
var activeNodes = activeFlows[id].getActiveNodes();
|
||||||
|
Object.keys(activeNodes).forEach(function(nid) {
|
||||||
|
activeNodesToFlow[nid] = id;
|
||||||
|
});
|
||||||
|
} catch(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
events.emit("nodes-started");
|
events.emit("nodes-started");
|
||||||
@ -465,11 +409,6 @@ function stop(type,diff,muteLog) {
|
|||||||
delete activeNodesToFlow[id];
|
delete activeNodesToFlow[id];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Ideally we'd prune just what got stopped - but mapping stopList
|
|
||||||
// id to the list of subflow instance nodes is something only Flow
|
|
||||||
// can do... so cheat by wiping the map knowing it'll be rebuilt
|
|
||||||
// in start()
|
|
||||||
subflowInstanceNodeMap = {};
|
|
||||||
if (!muteLog) {
|
if (!muteLog) {
|
||||||
if (type !== "full") {
|
if (type !== "full") {
|
||||||
log.info(log._("nodes.flows.stopped-modified-"+type));
|
log.info(log._("nodes.flows.stopped-modified-"+type));
|
||||||
@ -735,6 +674,13 @@ function removeFlow(id) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const flowAPI = {
|
||||||
|
getNode: getNode,
|
||||||
|
handleError: () => false,
|
||||||
|
handleStatus: () => false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init: init,
|
init: init,
|
||||||
|
|
||||||
@ -773,8 +719,8 @@ module.exports = {
|
|||||||
|
|
||||||
get started() { return started },
|
get started() { return started },
|
||||||
|
|
||||||
handleError: handleError,
|
// handleError: handleError,
|
||||||
handleStatus: handleStatus,
|
// handleStatus: handleStatus,
|
||||||
|
|
||||||
checkTypeInUse: checkTypeInUse,
|
checkTypeInUse: checkTypeInUse,
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
**/
|
**/
|
||||||
var clone = require("clone");
|
var clone = require("clone");
|
||||||
var redUtil = require("@node-red/util").util;
|
var redUtil = require("@node-red/util").util;
|
||||||
|
var Log = require("@node-red/util").log;
|
||||||
var subflowInstanceRE = /^subflow:(.+)$/;
|
var subflowInstanceRE = /^subflow:(.+)$/;
|
||||||
var typeRegistry = require("@node-red/registry");
|
var typeRegistry = require("@node-red/registry");
|
||||||
|
|
||||||
@ -427,9 +428,10 @@ module.exports = {
|
|||||||
// console.log(diff);
|
// console.log(diff);
|
||||||
// for (id in newConfig.allNodes) {
|
// for (id in newConfig.allNodes) {
|
||||||
// console.log(
|
// console.log(
|
||||||
// (added[id]?"+":(changed[id]?"!":" "))+(wiringChanged[id]?"w":" ")+(diff.linked.indexOf(id)!==-1?"~":" "),
|
// (added[id]?"a":(changed[id]?"c":" "))+(wiringChanged[id]?"w":" ")+(diff.linked.indexOf(id)!==-1?"l":" "),
|
||||||
// id,
|
// newConfig.allNodes[id].type.padEnd(10),
|
||||||
// newConfig.allNodes[id].type,
|
// id.padEnd(16),
|
||||||
|
// (newConfig.allNodes[id].z||"").padEnd(16),
|
||||||
// newConfig.allNodes[id].name||newConfig.allNodes[id].label||""
|
// newConfig.allNodes[id].name||newConfig.allNodes[id].label||""
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
@ -443,5 +445,44 @@ module.exports = {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
return diff;
|
return diff;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance of a node
|
||||||
|
* @param {Flow} flow The containing flow
|
||||||
|
* @param {object} config The node configuration object
|
||||||
|
* @return {Node} The instance of the node
|
||||||
|
*/
|
||||||
|
createNode: function(flow,config) {
|
||||||
|
var newNode = null;
|
||||||
|
var type = config.type;
|
||||||
|
try {
|
||||||
|
var nodeTypeConstructor = typeRegistry.get(type);
|
||||||
|
if (nodeTypeConstructor) {
|
||||||
|
var conf = clone(config);
|
||||||
|
delete conf.credentials;
|
||||||
|
for (var p in conf) {
|
||||||
|
if (conf.hasOwnProperty(p)) {
|
||||||
|
mapEnvVarProperties(conf,p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conf._flow = flow;
|
||||||
|
newNode = new nodeTypeConstructor(conf);
|
||||||
|
} catch (err) {
|
||||||
|
Log.log({
|
||||||
|
level: Log.ERROR,
|
||||||
|
id:conf.id,
|
||||||
|
type: type,
|
||||||
|
msg: err
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.error(Log._("nodes.flow.unknown-type", {type:type}));
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
Log.error(err);
|
||||||
|
}
|
||||||
|
return newNode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -167,18 +167,17 @@ describe('Node', function() {
|
|||||||
describe('#send', function() {
|
describe('#send', function() {
|
||||||
|
|
||||||
it('emits a single message', function(done) {
|
it('emits a single message', function(done) {
|
||||||
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2']]});
|
var flow = {
|
||||||
var n2 = new RedNode({id:'n2',type:'abc'});
|
getNode: (id) => { return {'n1':n1,'n2':n2}[id]},
|
||||||
var flowGet = sinon.stub(flows,"get",function(id) {
|
};
|
||||||
return {'n1':n1,'n2':n2}[id];
|
var n1 = new RedNode({_flow:flow,id:'n1',type:'abc',wires:[['n2']]});
|
||||||
});
|
var n2 = new RedNode({_flow:flow,id:'n2',type:'abc'});
|
||||||
var message = {payload:"hello world"};
|
var message = {payload:"hello world"};
|
||||||
|
|
||||||
n2.on('input',function(msg) {
|
n2.on('input',function(msg) {
|
||||||
// msg equals message, and is not a new copy
|
// msg equals message, and is not a new copy
|
||||||
should.deepEqual(msg,message);
|
should.deepEqual(msg,message);
|
||||||
should.strictEqual(msg,message);
|
should.strictEqual(msg,message);
|
||||||
flowGet.restore();
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -186,11 +185,11 @@ describe('Node', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('emits multiple messages on a single output', function(done) {
|
it('emits multiple messages on a single output', function(done) {
|
||||||
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2']]});
|
var flow = {
|
||||||
var n2 = new RedNode({id:'n2',type:'abc'});
|
getNode: (id) => { return {'n1':n1,'n2':n2}[id]},
|
||||||
var flowGet = sinon.stub(flows,"get",function(id) {
|
};
|
||||||
return {'n1':n1,'n2':n2}[id];
|
var n1 = new RedNode({_flow:flow,id:'n1',type:'abc',wires:[['n2']]});
|
||||||
});
|
var n2 = new RedNode({_flow:flow,id:'n2',type:'abc'});
|
||||||
|
|
||||||
var messages = [
|
var messages = [
|
||||||
{payload:"hello world"},
|
{payload:"hello world"},
|
||||||
@ -209,7 +208,6 @@ describe('Node', function() {
|
|||||||
// second msg sent, clone
|
// second msg sent, clone
|
||||||
msg.payload.should.equal(messages[rcvdCount].payload);
|
msg.payload.should.equal(messages[rcvdCount].payload);
|
||||||
should.notStrictEqual(msg,messages[rcvdCount]);
|
should.notStrictEqual(msg,messages[rcvdCount]);
|
||||||
flowGet.restore();
|
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -217,14 +215,14 @@ describe('Node', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('emits messages to multiple outputs', function(done) {
|
it('emits messages to multiple outputs', function(done) {
|
||||||
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2'],['n3'],['n4','n5']]});
|
var flow = {
|
||||||
var n2 = new RedNode({id:'n2',type:'abc'});
|
getNode: (id) => { return {'n1':n1,'n2':n2,'n3':n3,'n4':n4,'n5':n5}[id]},
|
||||||
var n3 = new RedNode({id:'n3',type:'abc'});
|
};
|
||||||
var n4 = new RedNode({id:'n4',type:'abc'});
|
var n1 = new RedNode({_flow:flow, id:'n1',type:'abc',wires:[['n2'],['n3'],['n4','n5']]});
|
||||||
var n5 = new RedNode({id:'n5',type:'abc'});
|
var n2 = new RedNode({_flow:flow, id:'n2',type:'abc'});
|
||||||
var flowGet = sinon.stub(flows,"get",function(id) {
|
var n3 = new RedNode({_flow:flow, id:'n3',type:'abc'});
|
||||||
return {'n1':n1,'n2':n2,'n3':n3,'n4':n4,'n5':n5}[id];
|
var n4 = new RedNode({_flow:flow, id:'n4',type:'abc'});
|
||||||
});
|
var n5 = new RedNode({_flow:flow, id:'n5',type:'abc'});
|
||||||
|
|
||||||
var messages = [
|
var messages = [
|
||||||
{payload:"hello world"},
|
{payload:"hello world"},
|
||||||
@ -241,7 +239,6 @@ describe('Node', function() {
|
|||||||
should.strictEqual(msg,messages[0]);
|
should.strictEqual(msg,messages[0]);
|
||||||
rcvdCount += 1;
|
rcvdCount += 1;
|
||||||
if (rcvdCount == 3) {
|
if (rcvdCount == 3) {
|
||||||
flowGet.restore();
|
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -257,7 +254,6 @@ describe('Node', function() {
|
|||||||
should.notStrictEqual(msg,messages[2]);
|
should.notStrictEqual(msg,messages[2]);
|
||||||
rcvdCount += 1;
|
rcvdCount += 1;
|
||||||
if (rcvdCount == 3) {
|
if (rcvdCount == 3) {
|
||||||
flowGet.restore();
|
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -269,7 +265,6 @@ describe('Node', function() {
|
|||||||
should.notStrictEqual(msg,messages[2]);
|
should.notStrictEqual(msg,messages[2]);
|
||||||
rcvdCount += 1;
|
rcvdCount += 1;
|
||||||
if (rcvdCount == 3) {
|
if (rcvdCount == 3) {
|
||||||
flowGet.restore();
|
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -278,18 +273,17 @@ describe('Node', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('emits no messages', function(done) {
|
it('emits no messages', function(done) {
|
||||||
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2']]});
|
var flow = {
|
||||||
var n2 = new RedNode({id:'n2',type:'abc'});
|
getNode: (id) => { return {'n1':n1,'n2':n2}[id]},
|
||||||
var flowGet = sinon.stub(flows,"get",function(id) {
|
};
|
||||||
return {'n1':n1,'n2':n2}[id];
|
var n1 = new RedNode({_flow:flow,id:'n1',type:'abc',wires:[['n2']]});
|
||||||
});
|
var n2 = new RedNode({_flow:flow,id:'n2',type:'abc'});
|
||||||
|
|
||||||
n2.on('input',function(msg) {
|
n2.on('input',function(msg) {
|
||||||
should.fail(null,null,"unexpected message");
|
should.fail(null,null,"unexpected message");
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
flowGet.restore();
|
|
||||||
done();
|
done();
|
||||||
}, 200);
|
}, 200);
|
||||||
|
|
||||||
@ -297,11 +291,11 @@ describe('Node', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('emits messages ignoring non-existent nodes', function(done) {
|
it('emits messages ignoring non-existent nodes', function(done) {
|
||||||
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n9'],['n2']]});
|
var flow = {
|
||||||
var n2 = new RedNode({id:'n2',type:'abc'});
|
getNode: (id) => { return {'n1':n1,'n2':n2}[id]},
|
||||||
var flowGet = sinon.stub(flows,"get",function(id) {
|
};
|
||||||
return {'n1':n1,'n2':n2}[id];
|
var n1 = new RedNode({_flow:flow,id:'n1',type:'abc',wires:[['n9'],['n2']]});
|
||||||
});
|
var n2 = new RedNode({_flow:flow,id:'n2',type:'abc'});
|
||||||
|
|
||||||
var messages = [
|
var messages = [
|
||||||
{payload:"hello world"},
|
{payload:"hello world"},
|
||||||
@ -312,7 +306,6 @@ describe('Node', function() {
|
|||||||
n2.on('input',function(msg) {
|
n2.on('input',function(msg) {
|
||||||
should.deepEqual(msg,messages[1]);
|
should.deepEqual(msg,messages[1]);
|
||||||
should.strictEqual(msg,messages[1]);
|
should.strictEqual(msg,messages[1]);
|
||||||
flowGet.restore();
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -320,12 +313,12 @@ describe('Node', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('emits messages without cloning req or res', function(done) {
|
it('emits messages without cloning req or res', function(done) {
|
||||||
var n1 = new RedNode({id:'n1',type:'abc',wires:[[['n2'],['n3']]]});
|
var flow = {
|
||||||
var n2 = new RedNode({id:'n2',type:'abc'});
|
getNode: (id) => { return {'n1':n1,'n2':n2,'n3':n3}[id]},
|
||||||
var n3 = new RedNode({id:'n3',type:'abc'});
|
};
|
||||||
var flowGet = sinon.stub(flows,"get",function(id) {
|
var n1 = new RedNode({_flow:flow,id:'n1',type:'abc',wires:[[['n2'],['n3']]]});
|
||||||
return {'n1':n1,'n2':n2,'n3':n3}[id];
|
var n2 = new RedNode({_flow:flow,id:'n2',type:'abc'});
|
||||||
});
|
var n3 = new RedNode({_flow:flow,id:'n3',type:'abc'});
|
||||||
|
|
||||||
var req = {};
|
var req = {};
|
||||||
var res = {};
|
var res = {};
|
||||||
@ -342,7 +335,6 @@ describe('Node', function() {
|
|||||||
msg.res.should.be.exactly(message.res);
|
msg.res.should.be.exactly(message.res);
|
||||||
rcvdCount += 1;
|
rcvdCount += 1;
|
||||||
if (rcvdCount == 2) {
|
if (rcvdCount == 2) {
|
||||||
flowGet.restore();
|
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -356,7 +348,6 @@ describe('Node', function() {
|
|||||||
msg.res.should.be.exactly(message.res);
|
msg.res.should.be.exactly(message.res);
|
||||||
rcvdCount += 1;
|
rcvdCount += 1;
|
||||||
if (rcvdCount == 2) {
|
if (rcvdCount == 2) {
|
||||||
flowGet.restore();
|
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -365,26 +356,25 @@ describe('Node', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("logs the uuid for all messages sent", function(done) {
|
it("logs the uuid for all messages sent", function(done) {
|
||||||
var flowGet = sinon.stub(flows,"get",function(id) {
|
|
||||||
return {'n1':sender,'n2':receiver1,'n3':receiver2}[id];
|
|
||||||
});
|
|
||||||
var logHandler = {
|
var logHandler = {
|
||||||
messagesSent: 0,
|
messagesSent: 0,
|
||||||
emit: function(event, msg) {
|
emit: function(event, msg) {
|
||||||
if (msg.event == "node.abc.send" && msg.level == Log.METRIC) {
|
if (msg.event == "node.abc.send" && msg.level == Log.METRIC) {
|
||||||
this.messagesSent++;
|
this.messagesSent++;
|
||||||
(typeof msg.msgid).should.not.be.equal("undefined");
|
(typeof msg.msgid).should.not.be.equal("undefined");
|
||||||
flowGet.restore();
|
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Log.addHandler(logHandler);
|
Log.addHandler(logHandler);
|
||||||
|
var flow = {
|
||||||
|
getNode: (id) => { return {'n1':sender,'n2':receiver1,'n3':receiver2}[id]},
|
||||||
|
};
|
||||||
|
|
||||||
var sender = new RedNode({id:'n1',type:'abc', wires:[['n2', 'n3']]});
|
var sender = new RedNode({_flow:flow,id:'n1',type:'abc', wires:[['n2', 'n3']]});
|
||||||
var receiver1 = new RedNode({id:'n2',type:'abc'});
|
var receiver1 = new RedNode({_flow:flow,id:'n2',type:'abc'});
|
||||||
var receiver2 = new RedNode({id:'n3',type:'abc'});
|
var receiver2 = new RedNode({_flow:flow,id:'n3',type:'abc'});
|
||||||
sender.send({"some": "message"});
|
sender.send({"some": "message"});
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@ -434,50 +424,35 @@ describe('Node', function() {
|
|||||||
|
|
||||||
describe('#error', function() {
|
describe('#error', function() {
|
||||||
it('handles a null error message', function(done) {
|
it('handles a null error message', function(done) {
|
||||||
var n = new RedNode({id:'123',type:'abc',z:'789'});
|
var flow = {
|
||||||
var loginfo = {};
|
handleError: sinon.stub()
|
||||||
sinon.stub(Log, 'log', function(msg) {
|
}
|
||||||
loginfo = msg;
|
var n = new RedNode({_flow:flow, id:'123',type:'abc',z:'789'});
|
||||||
});
|
|
||||||
sinon.stub(flows,"handleError", function(node,message,msg) {
|
|
||||||
});
|
|
||||||
|
|
||||||
var message = {a:1};
|
var message = {a:1};
|
||||||
|
|
||||||
n.error(null,message);
|
n.error(null,message);
|
||||||
should.deepEqual({level:Log.ERROR, id:n.id, type:n.type, msg:"",z:'789'}, loginfo);
|
|
||||||
|
|
||||||
flows.handleError.called.should.be.true();
|
flow.handleError.called.should.be.true();
|
||||||
flows.handleError.args[0][0].should.eql(n);
|
flow.handleError.args[0][0].should.eql(n);
|
||||||
flows.handleError.args[0][1].should.eql("");
|
flow.handleError.args[0][1].should.eql("");
|
||||||
flows.handleError.args[0][2].should.eql(message);
|
flow.handleError.args[0][2].should.eql(message);
|
||||||
|
|
||||||
Log.log.restore();
|
|
||||||
flows.handleError.restore();
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('produces an error message', function(done) {
|
it('produces an error message', function(done) {
|
||||||
var n = new RedNode({id:'123',type:'abc',z:'789'});
|
var flow = {
|
||||||
var loginfo = {};
|
handleError: sinon.stub()
|
||||||
sinon.stub(Log, 'log', function(msg) {
|
}
|
||||||
loginfo = msg;
|
var n = new RedNode({_flow:flow, id:'123',type:'abc',z:'789'});
|
||||||
});
|
|
||||||
sinon.stub(flows,"handleError", function(node,message,msg) {
|
|
||||||
});
|
|
||||||
|
|
||||||
var message = {a:2};
|
var message = {a:2};
|
||||||
|
|
||||||
n.error("This is an error",message);
|
n.error("This is an error",message);
|
||||||
should.deepEqual({level:Log.ERROR, id:n.id, type:n.type, msg:"This is an error",z:'789'}, loginfo);
|
|
||||||
|
|
||||||
flows.handleError.called.should.be.true();
|
flow.handleError.called.should.be.true();
|
||||||
flows.handleError.args[0][0].should.eql(n);
|
flow.handleError.args[0][0].should.eql(n);
|
||||||
flows.handleError.args[0][1].should.eql("This is an error");
|
flow.handleError.args[0][1].should.eql("This is an error");
|
||||||
flows.handleError.args[0][2].should.eql(message);
|
flow.handleError.args[0][2].should.eql(message);
|
||||||
|
|
||||||
Log.log.restore();
|
|
||||||
flows.handleError.restore();
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -528,8 +503,10 @@ describe('Node', function() {
|
|||||||
|
|
||||||
describe('#status', function() {
|
describe('#status', function() {
|
||||||
it('publishes status', function(done) {
|
it('publishes status', function(done) {
|
||||||
sinon.stub(flows,"handleStatus", function(node,message,msg) {});
|
var flow = {
|
||||||
var n = new RedNode({id:'123',type:'abc'});
|
handleStatus: sinon.stub()
|
||||||
|
}
|
||||||
|
var n = new RedNode({_flow:flow,id:'123',type:'abc'});
|
||||||
var status = {fill:"green",shape:"dot",text:"connected"};
|
var status = {fill:"green",shape:"dot",text:"connected"};
|
||||||
var topic;
|
var topic;
|
||||||
var message;
|
var message;
|
||||||
@ -537,10 +514,9 @@ describe('Node', function() {
|
|||||||
|
|
||||||
n.status(status);
|
n.status(status);
|
||||||
|
|
||||||
flows.handleStatus.called.should.be.true();
|
flow.handleStatus.called.should.be.true();
|
||||||
flows.handleStatus.args[0][0].should.eql(n);
|
flow.handleStatus.args[0][0].should.eql(n);
|
||||||
flows.handleStatus.args[0][1].should.eql(status);
|
flow.handleStatus.args[0][1].should.eql(status);
|
||||||
flows.handleStatus.restore();
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -31,7 +31,6 @@ var typeRegistry = NR_TEST_UTILS.require("@node-red/registry");
|
|||||||
|
|
||||||
describe('Flow', function() {
|
describe('Flow', function() {
|
||||||
var getType;
|
var getType;
|
||||||
var getNode;
|
|
||||||
|
|
||||||
var stoppedNodes = {};
|
var stoppedNodes = {};
|
||||||
var currentNodes = {};
|
var currentNodes = {};
|
||||||
@ -44,11 +43,11 @@ describe('Flow', function() {
|
|||||||
rewiredNodes = {};
|
rewiredNodes = {};
|
||||||
createCount = 0;
|
createCount = 0;
|
||||||
Flow.init({settings:{},log:{
|
Flow.init({settings:{},log:{
|
||||||
log: sinon.stub(),
|
log: sinon.stub(), // function() { console.log("l",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//
|
||||||
debug: sinon.stub(),
|
debug: sinon.stub(), // function() { console.log("d",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//sinon.stub(),
|
||||||
trace: sinon.stub(),
|
trace: sinon.stub(), // function() { console.log("t",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//sinon.stub(),
|
||||||
warn: sinon.stub(),
|
warn: sinon.stub(), // function() { console.log("w",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//sinon.stub(),
|
||||||
info: sinon.stub(),
|
info: sinon.stub(), // function() { console.log("i",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//sinon.stub(),
|
||||||
metric: sinon.stub(),
|
metric: sinon.stub(),
|
||||||
_: function() { return "abc"}
|
_: function() { return "abc"}
|
||||||
}});
|
}});
|
||||||
@ -64,6 +63,7 @@ describe('Flow', function() {
|
|||||||
this.stopped = false;
|
this.stopped = false;
|
||||||
currentNodes[node.id] = node;
|
currentNodes[node.id] = node;
|
||||||
this.on('input',function(msg) {
|
this.on('input',function(msg) {
|
||||||
|
// console.log(this.id,msg.payload);
|
||||||
node.handled++;
|
node.handled++;
|
||||||
node.send(msg);
|
node.send(msg);
|
||||||
});
|
});
|
||||||
@ -81,6 +81,34 @@ describe('Flow', function() {
|
|||||||
}
|
}
|
||||||
util.inherits(TestNode,Node);
|
util.inherits(TestNode,Node);
|
||||||
|
|
||||||
|
var TestErrorNode = function(n) {
|
||||||
|
Node.call(this,n);
|
||||||
|
this._index = createCount++;
|
||||||
|
this.scope = n.scope;
|
||||||
|
this.name = n.name;
|
||||||
|
var node = this;
|
||||||
|
this.foo = n.foo;
|
||||||
|
this.handled = 0;
|
||||||
|
this.stopped = false;
|
||||||
|
currentNodes[node.id] = node;
|
||||||
|
this.on('input',function(msg) {
|
||||||
|
node.handled++;
|
||||||
|
node.error("test error",msg);
|
||||||
|
});
|
||||||
|
this.on('close',function() {
|
||||||
|
node.stopped = true;
|
||||||
|
stoppedNodes[node.id] = node;
|
||||||
|
delete currentNodes[node.id];
|
||||||
|
});
|
||||||
|
this.__updateWires = this.updateWires;
|
||||||
|
this.updateWires = function(newWires) {
|
||||||
|
rewiredNodes[node.id] = node;
|
||||||
|
node.newWires = newWires;
|
||||||
|
node.__updateWires(newWires);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
util.inherits(TestErrorNode,Node);
|
||||||
|
|
||||||
var TestAsyncNode = function(n) {
|
var TestAsyncNode = function(n) {
|
||||||
Node.call(this,n);
|
Node.call(this,n);
|
||||||
var node = this;
|
var node = this;
|
||||||
@ -111,26 +139,21 @@ describe('Flow', function() {
|
|||||||
getType = sinon.stub(typeRegistry,"get",function(type) {
|
getType = sinon.stub(typeRegistry,"get",function(type) {
|
||||||
if (type=="test") {
|
if (type=="test") {
|
||||||
return TestNode;
|
return TestNode;
|
||||||
|
} else if (type=="testError"){
|
||||||
|
return TestErrorNode;
|
||||||
} else {
|
} else {
|
||||||
return TestAsyncNode;
|
return TestAsyncNode;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
getNode = sinon.stub(flows,"get",function(id) {
|
|
||||||
return currentNodes[id];
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
after(function() {
|
after(function() {
|
||||||
getType.restore();
|
getType.restore();
|
||||||
getNode.restore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
describe('#constructor',function() {
|
describe('#constructor',function() {
|
||||||
it('called with an empty flow',function() {
|
it('called with an empty flow',function() {
|
||||||
var config = flowUtils.parseConfig([]);
|
var config = flowUtils.parseConfig([]);
|
||||||
var flow = Flow.create(config);
|
var flow = Flow.create({},config);
|
||||||
|
|
||||||
var nodeCount = 0;
|
var nodeCount = 0;
|
||||||
Object.keys(flow.getActiveNodes()).length.should.equal(0);
|
Object.keys(flow.getActiveNodes()).length.should.equal(0);
|
||||||
@ -145,7 +168,7 @@ describe('Flow', function() {
|
|||||||
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
||||||
{id:"4",z:"t1",type:"test",foo:"a"}
|
{id:"4",z:"t1",type:"test",foo:"a"}
|
||||||
]);
|
]);
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
var flow = Flow.create({},config,config.flows["t1"]);
|
||||||
flow.start();
|
flow.start();
|
||||||
|
|
||||||
Object.keys(flow.getActiveNodes()).should.have.length(4);
|
Object.keys(flow.getActiveNodes()).should.have.length(4);
|
||||||
@ -188,7 +211,6 @@ describe('Flow', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it("instantiates config nodes in the right order",function(done) {
|
it("instantiates config nodes in the right order",function(done) {
|
||||||
var config = flowUtils.parseConfig([
|
var config = flowUtils.parseConfig([
|
||||||
{id:"t1",type:"tab"},
|
{id:"t1",type:"tab"},
|
||||||
@ -198,7 +220,7 @@ describe('Flow', function() {
|
|||||||
{id:"4",z:"t1",type:"test",foo:"5"}, // This node depends on #5
|
{id:"4",z:"t1",type:"test",foo:"5"}, // This node depends on #5
|
||||||
{id:"5",z:"t1",type:"test"}
|
{id:"5",z:"t1",type:"test"}
|
||||||
]);
|
]);
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
var flow = Flow.create({},config,config.flows["t1"]);
|
||||||
flow.start();
|
flow.start();
|
||||||
|
|
||||||
Object.keys(flow.getActiveNodes()).should.have.length(5);
|
Object.keys(flow.getActiveNodes()).should.have.length(5);
|
||||||
@ -238,130 +260,12 @@ describe('Flow', function() {
|
|||||||
{id:"node1",z:"t1",type:"test",foo:"node2"}, // This node depends on #5
|
{id:"node1",z:"t1",type:"test",foo:"node2"}, // This node depends on #5
|
||||||
{id:"node2",z:"t1",type:"test",foo:"node1"}
|
{id:"node2",z:"t1",type:"test",foo:"node1"}
|
||||||
]);
|
]);
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
var flow = Flow.create({},config,config.flows["t1"]);
|
||||||
/*jshint immed: false */
|
/*jshint immed: false */
|
||||||
(function(){
|
(function(){
|
||||||
flow.start();
|
flow.start();
|
||||||
}).should.throw("Circular config node dependency detected: node1");
|
}).should.throw("Circular config node dependency detected: node1");
|
||||||
});
|
});
|
||||||
it("instantiates a subflow and stops it",function(done) {
|
|
||||||
var config = flowUtils.parseConfig([
|
|
||||||
{id:"t1",type:"tab"},
|
|
||||||
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]},
|
|
||||||
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3","4"]},
|
|
||||||
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
|
||||||
{id:"4",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
|
||||||
{id:"sf1",type:"subflow","name":"Subflow 2","info":"",
|
|
||||||
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-2","port":0}]},{"wires":[{"id":"sf1","port":0}]}]},
|
|
||||||
{id:"sf1-1",type:"test","z":"sf1",x:166,y:99,"wires":[["sf1-2"]]},
|
|
||||||
{id:"sf1-2",type:"test","z":"sf1",foo:"sf1-cn",x:166,y:99,"wires":[[]]},
|
|
||||||
{id:"sf1-cn",type:"test","z":"sf1"}
|
|
||||||
]);
|
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
|
||||||
|
|
||||||
getNode.restore();
|
|
||||||
getNode = sinon.stub(flows,"get",function(id) {
|
|
||||||
return flow.getNode(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
flow.start();
|
|
||||||
|
|
||||||
var activeNodes = flow.getActiveNodes();
|
|
||||||
Object.keys(activeNodes).should.have.length(7);
|
|
||||||
var sfInstanceId = Object.keys(activeNodes)[5];
|
|
||||||
var sfInstanceId2 = Object.keys(activeNodes)[6];
|
|
||||||
var sfConfigId = Object.keys(activeNodes)[4];
|
|
||||||
|
|
||||||
flow.getNode('1').should.have.a.property('id','1');
|
|
||||||
flow.getNode('2').should.have.a.property('id','2');
|
|
||||||
flow.getNode('3').should.have.a.property('id','3');
|
|
||||||
flow.getNode('4').should.have.a.property('id','4');
|
|
||||||
flow.getNode(sfInstanceId).should.have.a.property('id',sfInstanceId);
|
|
||||||
flow.getNode(sfInstanceId2).should.have.a.property('id',sfInstanceId2);
|
|
||||||
flow.getNode(sfConfigId).should.have.a.property('id',sfConfigId);
|
|
||||||
|
|
||||||
flow.getNode(sfInstanceId2).should.have.a.property('foo',sfConfigId);
|
|
||||||
|
|
||||||
currentNodes.should.have.a.property("1");
|
|
||||||
currentNodes.should.not.have.a.property("2");
|
|
||||||
currentNodes.should.have.a.property("3");
|
|
||||||
currentNodes.should.have.a.property("4");
|
|
||||||
currentNodes.should.have.a.property(sfInstanceId);
|
|
||||||
currentNodes.should.have.a.property(sfInstanceId2);
|
|
||||||
currentNodes.should.have.a.property(sfConfigId);
|
|
||||||
|
|
||||||
currentNodes["1"].should.have.a.property("handled",0);
|
|
||||||
currentNodes["3"].should.have.a.property("handled",0);
|
|
||||||
currentNodes["4"].should.have.a.property("handled",0);
|
|
||||||
currentNodes[sfInstanceId].should.have.a.property("handled",0);
|
|
||||||
currentNodes[sfInstanceId2].should.have.a.property("handled",0);
|
|
||||||
|
|
||||||
currentNodes["1"].receive({payload:"test"});
|
|
||||||
|
|
||||||
currentNodes["1"].should.have.a.property("handled",1);
|
|
||||||
currentNodes[sfInstanceId].should.have.a.property("handled",1);
|
|
||||||
currentNodes[sfInstanceId2].should.have.a.property("handled",1);
|
|
||||||
currentNodes["3"].should.have.a.property("handled",1);
|
|
||||||
currentNodes["4"].should.have.a.property("handled",1);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
flow.stop().then(function() {
|
|
||||||
currentNodes.should.not.have.a.property("1");
|
|
||||||
currentNodes.should.not.have.a.property("3");
|
|
||||||
currentNodes.should.not.have.a.property("4");
|
|
||||||
currentNodes.should.not.have.a.property(sfInstanceId);
|
|
||||||
currentNodes.should.not.have.a.property(sfInstanceId2);
|
|
||||||
currentNodes.should.not.have.a.property(sfConfigId);
|
|
||||||
stoppedNodes.should.have.a.property("1");
|
|
||||||
stoppedNodes.should.have.a.property("3");
|
|
||||||
stoppedNodes.should.have.a.property("4");
|
|
||||||
stoppedNodes.should.have.a.property(sfInstanceId);
|
|
||||||
stoppedNodes.should.have.a.property(sfInstanceId2);
|
|
||||||
stoppedNodes.should.have.a.property(sfConfigId);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("instantiates a subflow inside a subflow and stops it",function(done) {
|
|
||||||
var config = flowUtils.parseConfig([
|
|
||||||
{id:"t1",type:"tab"},
|
|
||||||
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]},
|
|
||||||
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3","4"]},
|
|
||||||
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
|
||||||
{id:"4",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
|
||||||
{id:"sf1",type:"subflow","name":"Subflow 1","info":"",
|
|
||||||
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-2","port":0}]}]},
|
|
||||||
{id:"sf2",type:"subflow","name":"Subflow 2","info":"",
|
|
||||||
"in":[{wires:[]}],"out":[{"wires":[{"id":"sf2","port":0}]}]},
|
|
||||||
{id:"sf1-1",type:"test","z":"sf1",x:166,y:99,"wires":[["sf1-2"]]},
|
|
||||||
{id:"sf1-2",type:"subflow:sf2","z":"sf1",x:166,y:99,"wires":[[]]}
|
|
||||||
|
|
||||||
]);
|
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
|
||||||
|
|
||||||
getNode.restore();
|
|
||||||
getNode = sinon.stub(flows,"get",function(id) {
|
|
||||||
return flow.getNode(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
flow.start();
|
|
||||||
|
|
||||||
currentNodes["1"].should.have.a.property("handled",0);
|
|
||||||
currentNodes["3"].should.have.a.property("handled",0);
|
|
||||||
|
|
||||||
currentNodes["1"].receive({payload:"test"});
|
|
||||||
|
|
||||||
currentNodes["1"].should.have.a.property("handled",1);
|
|
||||||
currentNodes["3"].should.have.a.property("handled",1);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
flow.stop().then(function() {
|
|
||||||
Object.keys(currentNodes).should.have.length(0);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("rewires nodes specified by diff",function(done) {
|
it("rewires nodes specified by diff",function(done) {
|
||||||
var config = flowUtils.parseConfig([
|
var config = flowUtils.parseConfig([
|
||||||
@ -371,7 +275,7 @@ describe('Flow', function() {
|
|||||||
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}
|
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
var flow = Flow.create({},config,config.flows["t1"]);
|
||||||
createCount.should.equal(0);
|
createCount.should.equal(0);
|
||||||
flow.start();
|
flow.start();
|
||||||
//TODO: use update to pass in new wiring and verify the change
|
//TODO: use update to pass in new wiring and verify the change
|
||||||
@ -382,72 +286,6 @@ describe('Flow', function() {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rewires a subflow node on update/start",function(done){
|
|
||||||
|
|
||||||
var rawConfig = [
|
|
||||||
{id:"t1",type:"tab"},
|
|
||||||
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]},
|
|
||||||
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
|
|
||||||
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
|
||||||
{id:"4",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
|
||||||
{id:"sf1",type:"subflow","name":"Subflow 2","info":"",
|
|
||||||
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-2","port":0}]}]},
|
|
||||||
{id:"sf1-1",type:"test1","z":"sf1",x:166,y:99,"wires":[["sf1-2"]]},
|
|
||||||
{id:"sf1-2",type:"test2","z":"sf1",x:166,y:99,"wires":[[]]}
|
|
||||||
];
|
|
||||||
|
|
||||||
var config = flowUtils.parseConfig(clone(rawConfig));
|
|
||||||
|
|
||||||
rawConfig[2].wires = [["4"]];
|
|
||||||
|
|
||||||
var newConfig = flowUtils.parseConfig(rawConfig);
|
|
||||||
var diff = flowUtils.diffConfigs(config,newConfig);
|
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
|
||||||
|
|
||||||
getNode.restore();
|
|
||||||
getNode = sinon.stub(flows,"get",function(id) {
|
|
||||||
return flow.getNode(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
flow.start();
|
|
||||||
|
|
||||||
var activeNodes = flow.getActiveNodes();
|
|
||||||
Object.keys(activeNodes).should.have.length(6);
|
|
||||||
var sfInstanceId = Object.keys(activeNodes)[4];
|
|
||||||
var sfInstanceId2 = Object.keys(activeNodes)[5];
|
|
||||||
|
|
||||||
currentNodes["1"].should.have.a.property("handled",0);
|
|
||||||
currentNodes["3"].should.have.a.property("handled",0);
|
|
||||||
currentNodes["4"].should.have.a.property("handled",0);
|
|
||||||
|
|
||||||
currentNodes["1"].receive({payload:"test"});
|
|
||||||
|
|
||||||
currentNodes["1"].should.have.a.property("handled",1);
|
|
||||||
currentNodes[sfInstanceId].should.have.a.property("handled",1);
|
|
||||||
currentNodes[sfInstanceId2].should.have.a.property("handled",1);
|
|
||||||
currentNodes["3"].should.have.a.property("handled",1);
|
|
||||||
currentNodes["4"].should.have.a.property("handled",0);
|
|
||||||
|
|
||||||
flow.update(newConfig,newConfig.flows["t1"]);
|
|
||||||
flow.start(diff)
|
|
||||||
|
|
||||||
currentNodes["1"].receive({payload:"test2"});
|
|
||||||
|
|
||||||
currentNodes["1"].should.have.a.property("handled",2);
|
|
||||||
currentNodes[sfInstanceId].should.have.a.property("handled",2);
|
|
||||||
currentNodes[sfInstanceId2].should.have.a.property("handled",2);
|
|
||||||
currentNodes["3"].should.have.a.property("handled",1);
|
|
||||||
currentNodes["4"].should.have.a.property("handled",1);
|
|
||||||
|
|
||||||
|
|
||||||
flow.stop().then(function() {
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it("instantiates a node with environment variable property values",function(done) {
|
it("instantiates a node with environment variable property values",function(done) {
|
||||||
after(function() {
|
after(function() {
|
||||||
delete process.env.NODE_RED_TEST_VALUE;
|
delete process.env.NODE_RED_TEST_VALUE;
|
||||||
@ -462,7 +300,7 @@ describe('Flow', function() {
|
|||||||
{id:"5",x:10,y:10,z:"t1",type:"test",foo:"$(NODE_RED_TEST_VALUE_NONE)",wires:[]},
|
{id:"5",x:10,y:10,z:"t1",type:"test",foo:"$(NODE_RED_TEST_VALUE_NONE)",wires:[]},
|
||||||
{id:"6",x:10,y:10,z:"t1",type:"test",foo:["$(NODE_RED_TEST_VALUE)"],wires:[]}
|
{id:"6",x:10,y:10,z:"t1",type:"test",foo:["$(NODE_RED_TEST_VALUE)"],wires:[]}
|
||||||
]);
|
]);
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
var flow = Flow.create({},config,config.flows["t1"]);
|
||||||
flow.start();
|
flow.start();
|
||||||
|
|
||||||
var activeNodes = flow.getActiveNodes();
|
var activeNodes = flow.getActiveNodes();
|
||||||
@ -492,7 +330,7 @@ describe('Flow', function() {
|
|||||||
{id:"2",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["3"]},
|
{id:"2",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["3"]},
|
||||||
{id:"3",x:10,y:10,z:"t1",type:"asyncTest",foo:"a",wires:[]}
|
{id:"3",x:10,y:10,z:"t1",type:"asyncTest",foo:"a",wires:[]}
|
||||||
]);
|
]);
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
var flow = Flow.create({},config,config.flows["t1"]);
|
||||||
flow.start();
|
flow.start();
|
||||||
|
|
||||||
|
|
||||||
@ -518,7 +356,7 @@ describe('Flow', function() {
|
|||||||
{id:"2",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["3"]},
|
{id:"2",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["3"]},
|
||||||
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}
|
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}
|
||||||
]);
|
]);
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
var flow = Flow.create({},config,config.flows["t1"]);
|
||||||
flow.start();
|
flow.start();
|
||||||
|
|
||||||
currentNodes.should.have.a.property("1");
|
currentNodes.should.have.a.property("1");
|
||||||
@ -536,35 +374,6 @@ describe('Flow', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("stops subflow instance nodes",function(done) {
|
|
||||||
var config = flowUtils.parseConfig([
|
|
||||||
{id:"t1",type:"tab"},
|
|
||||||
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]},
|
|
||||||
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
|
|
||||||
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
|
||||||
{id:"sf1",type:"subflow","name":"Subflow 2","info":"",
|
|
||||||
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]},
|
|
||||||
{id:"sf1-1",type:"test","z":"sf1",x:166,y:99,"wires":[[]]}
|
|
||||||
]);
|
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
|
||||||
|
|
||||||
getNode.restore();
|
|
||||||
getNode = sinon.stub(flows,"get",function(id) {
|
|
||||||
return flow.getNode(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
flow.start();
|
|
||||||
|
|
||||||
var activeNodes = flow.getActiveNodes();
|
|
||||||
Object.keys(activeNodes).should.have.length(4);
|
|
||||||
var sfInstanceId = Object.keys(activeNodes)[3];
|
|
||||||
flow.stop(["2"]).then(function() {
|
|
||||||
currentNodes.should.not.have.a.property(sfInstanceId);
|
|
||||||
stoppedNodes.should.have.a.property(sfInstanceId);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Times out a node that fails to close", function(done) {
|
it("Times out a node that fails to close", function(done) {
|
||||||
Flow.init({settings:{nodeCloseTimeout:50},log:{
|
Flow.init({settings:{nodeCloseTimeout:50},log:{
|
||||||
log: sinon.stub(),
|
log: sinon.stub(),
|
||||||
@ -581,7 +390,7 @@ describe('Flow', function() {
|
|||||||
{id:"2",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["3"]},
|
{id:"2",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["3"]},
|
||||||
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}
|
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]}
|
||||||
]);
|
]);
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
var flow = Flow.create({},config,config.flows["t1"]);
|
||||||
flow.start();
|
flow.start();
|
||||||
|
|
||||||
currentNodes.should.have.a.property("1");
|
currentNodes.should.have.a.property("1");
|
||||||
@ -615,12 +424,7 @@ describe('Flow', function() {
|
|||||||
{id:"sn",x:10,y:10,z:"t1",type:"status",foo:"a",wires:[]},
|
{id:"sn",x:10,y:10,z:"t1",type:"status",foo:"a",wires:[]},
|
||||||
{id:"sn2",x:10,y:10,z:"t1",type:"status",foo:"a",wires:[]}
|
{id:"sn2",x:10,y:10,z:"t1",type:"status",foo:"a",wires:[]}
|
||||||
]);
|
]);
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
var flow = Flow.create({},config,config.flows["t1"]);
|
||||||
|
|
||||||
getNode.restore();
|
|
||||||
getNode = sinon.stub(flows,"get",function(id) {
|
|
||||||
return flow.getNode(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
flow.start();
|
flow.start();
|
||||||
|
|
||||||
@ -664,12 +468,7 @@ describe('Flow', function() {
|
|||||||
{id:"sn",x:10,y:10,z:"t1",type:"status",scope:["2"],foo:"a",wires:[]},
|
{id:"sn",x:10,y:10,z:"t1",type:"status",scope:["2"],foo:"a",wires:[]},
|
||||||
{id:"sn2",x:10,y:10,z:"t1",type:"status",scope:["1"],foo:"a",wires:[]}
|
{id:"sn2",x:10,y:10,z:"t1",type:"status",scope:["1"],foo:"a",wires:[]}
|
||||||
]);
|
]);
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
var flow = Flow.create({},config,config.flows["t1"]);
|
||||||
|
|
||||||
getNode.restore();
|
|
||||||
getNode = sinon.stub(flows,"get",function(id) {
|
|
||||||
return flow.getNode(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
flow.start();
|
flow.start();
|
||||||
|
|
||||||
@ -696,147 +495,6 @@ describe('Flow', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passes a status event to the adjacent status node in subflow",function(done) {
|
|
||||||
var config = flowUtils.parseConfig([
|
|
||||||
{id:"t1",type:"tab"},
|
|
||||||
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
|
|
||||||
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
|
|
||||||
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
|
||||||
{id:"sf1",type:"subflow","name":"Subflow 2","info":"",
|
|
||||||
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]},
|
|
||||||
{id:"sf1-1",type:"test2","z":"sf1",x:166,y:99,"wires":[[]]},
|
|
||||||
{id:"sf1-sn",x:10,y:10,z:"sf1",type:"status",foo:"a",wires:[]}
|
|
||||||
]);
|
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
|
||||||
|
|
||||||
getNode.restore();
|
|
||||||
getNode = sinon.stub(flows,"get",function(id) {
|
|
||||||
return flow.getNode(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
flow.start();
|
|
||||||
|
|
||||||
var activeNodes = flow.getActiveNodes();
|
|
||||||
var sfInstanceId = Object.keys(activeNodes)[3];
|
|
||||||
var statusInstanceId = Object.keys(activeNodes)[4];
|
|
||||||
|
|
||||||
flow.handleStatus(activeNodes[sfInstanceId],{text:"my-status"});
|
|
||||||
|
|
||||||
currentNodes[statusInstanceId].should.have.a.property("handled",1);
|
|
||||||
var statusMessage = currentNodes[statusInstanceId].messages[0];
|
|
||||||
|
|
||||||
statusMessage.should.have.a.property("status");
|
|
||||||
statusMessage.status.should.have.a.property("text","my-status");
|
|
||||||
statusMessage.status.should.have.a.property("source");
|
|
||||||
statusMessage.status.source.should.have.a.property("id",sfInstanceId);
|
|
||||||
statusMessage.status.source.should.have.a.property("type","test2");
|
|
||||||
statusMessage.status.source.should.have.a.property("name",undefined);
|
|
||||||
flow.stop().then(function() {
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("passes a status event to the multiple adjacent status nodes in subflow",function(done) {
|
|
||||||
var config = flowUtils.parseConfig([
|
|
||||||
{id:"t1",type:"tab"},
|
|
||||||
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
|
|
||||||
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
|
|
||||||
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
|
||||||
{id:"4",x:10,y:10,z:"t1",type:"status",foo:"a",wires:[]},
|
|
||||||
{id:"sf1",type:"subflow","name":"Subflow 2","info":"",
|
|
||||||
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]},
|
|
||||||
{id:"sf1-1",type:"test2","z":"sf1",x:166,y:99,"wires":[[]]},
|
|
||||||
{id:"sf1-sn",x:10,y:10,z:"sf1",type:"status",foo:"a",wires:[]},
|
|
||||||
{id:"sf1-sn2",x:10,y:10,z:"sf1",type:"status",scope:["none"],wires:[]},
|
|
||||||
{id:"sf1-sn3",x:10,y:10,z:"sf1",type:"status",scope:["sf1-1"],wires:[]}
|
|
||||||
]);
|
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
|
||||||
|
|
||||||
getNode.restore();
|
|
||||||
getNode = sinon.stub(flows,"get",function(id) {
|
|
||||||
return flow.getNode(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
flow.start();
|
|
||||||
|
|
||||||
var activeNodes = flow.getActiveNodes();
|
|
||||||
|
|
||||||
var sfInstanceId = Object.keys(activeNodes)[4];
|
|
||||||
var statusInstanceId = Object.keys(activeNodes)[5];
|
|
||||||
var statusInstanceId2 = Object.keys(activeNodes)[6];
|
|
||||||
var statusInstanceId3 = Object.keys(activeNodes)[7];
|
|
||||||
|
|
||||||
flow.handleStatus(activeNodes[sfInstanceId],{text:"my-status"});
|
|
||||||
|
|
||||||
currentNodes[statusInstanceId].should.have.a.property("handled",1);
|
|
||||||
var statusMessage = currentNodes[statusInstanceId].messages[0];
|
|
||||||
|
|
||||||
statusMessage.should.have.a.property("status");
|
|
||||||
statusMessage.status.should.have.a.property("text","my-status");
|
|
||||||
statusMessage.status.should.have.a.property("source");
|
|
||||||
statusMessage.status.source.should.have.a.property("id",sfInstanceId);
|
|
||||||
statusMessage.status.source.should.have.a.property("type","test2");
|
|
||||||
statusMessage.status.source.should.have.a.property("name",undefined);
|
|
||||||
|
|
||||||
activeNodes["4"].should.have.a.property("handled",0);
|
|
||||||
|
|
||||||
currentNodes[statusInstanceId2].should.have.a.property("handled",0);
|
|
||||||
|
|
||||||
currentNodes[statusInstanceId3].should.have.a.property("handled",1);
|
|
||||||
statusMessage = currentNodes[statusInstanceId3].messages[0];
|
|
||||||
|
|
||||||
statusMessage.should.have.a.property("status");
|
|
||||||
statusMessage.status.should.have.a.property("text","my-status");
|
|
||||||
statusMessage.status.should.have.a.property("source");
|
|
||||||
statusMessage.status.source.should.have.a.property("id",sfInstanceId);
|
|
||||||
statusMessage.status.source.should.have.a.property("type","test2");
|
|
||||||
statusMessage.status.source.should.have.a.property("name",undefined);
|
|
||||||
|
|
||||||
flow.stop().then(function() {
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("passes a status event to the subflow's parent tab status node",function(done) {
|
|
||||||
var config = flowUtils.parseConfig([
|
|
||||||
{id:"t1",type:"tab"},
|
|
||||||
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
|
|
||||||
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
|
|
||||||
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
|
||||||
{id:"sf1",type:"subflow","name":"Subflow 2","info":"",
|
|
||||||
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]},
|
|
||||||
{id:"sf1-1",type:"test2","z":"sf1",x:166,y:99,"wires":[[]]},
|
|
||||||
{id:"sn",x:10,y:10,z:"t1",type:"status",foo:"a",wires:[]}
|
|
||||||
]);
|
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
|
||||||
|
|
||||||
getNode.restore();
|
|
||||||
getNode = sinon.stub(flows,"get",function(id) {
|
|
||||||
return flow.getNode(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
flow.start();
|
|
||||||
|
|
||||||
var activeNodes = flow.getActiveNodes();
|
|
||||||
var sfInstanceId = Object.keys(activeNodes)[3];
|
|
||||||
|
|
||||||
flow.handleStatus(activeNodes[sfInstanceId],{text:"my-status"});
|
|
||||||
|
|
||||||
currentNodes["sn"].should.have.a.property("handled",1);
|
|
||||||
var statusMessage = currentNodes["sn"].messages[0];
|
|
||||||
|
|
||||||
statusMessage.should.have.a.property("status");
|
|
||||||
statusMessage.status.should.have.a.property("text","my-status");
|
|
||||||
statusMessage.status.should.have.a.property("source");
|
|
||||||
statusMessage.status.source.should.have.a.property("id",sfInstanceId);
|
|
||||||
statusMessage.status.source.should.have.a.property("type","test2");
|
|
||||||
statusMessage.status.source.should.have.a.property("name",undefined);
|
|
||||||
|
|
||||||
flow.stop().then(function() {
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -850,12 +508,7 @@ describe('Flow', function() {
|
|||||||
{id:"sn",x:10,y:10,z:"t1",type:"catch",foo:"a",wires:[]},
|
{id:"sn",x:10,y:10,z:"t1",type:"catch",foo:"a",wires:[]},
|
||||||
{id:"sn2",x:10,y:10,z:"t1",type:"catch",foo:"a",wires:[]}
|
{id:"sn2",x:10,y:10,z:"t1",type:"catch",foo:"a",wires:[]}
|
||||||
]);
|
]);
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
var flow = Flow.create({},config,config.flows["t1"]);
|
||||||
|
|
||||||
getNode.restore();
|
|
||||||
getNode = sinon.stub(flows,"get",function(id) {
|
|
||||||
return flow.getNode(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
flow.start();
|
flow.start();
|
||||||
|
|
||||||
@ -899,12 +552,7 @@ describe('Flow', function() {
|
|||||||
{id:"sn",x:10,y:10,z:"t1",type:"catch",scope:["2"],foo:"a",wires:[]},
|
{id:"sn",x:10,y:10,z:"t1",type:"catch",scope:["2"],foo:"a",wires:[]},
|
||||||
{id:"sn2",x:10,y:10,z:"t1",type:"catch",scope:["1"],foo:"a",wires:[]}
|
{id:"sn2",x:10,y:10,z:"t1",type:"catch",scope:["1"],foo:"a",wires:[]}
|
||||||
]);
|
]);
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
var flow = Flow.create({},config,config.flows["t1"]);
|
||||||
|
|
||||||
getNode.restore();
|
|
||||||
getNode = sinon.stub(flows,"get",function(id) {
|
|
||||||
return flow.getNode(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
flow.start();
|
flow.start();
|
||||||
|
|
||||||
@ -930,143 +578,7 @@ describe('Flow', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passes an error event to the adjacent catch node in subflow",function(done) {
|
|
||||||
var config = flowUtils.parseConfig([
|
|
||||||
{id:"t1",type:"tab"},
|
|
||||||
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
|
|
||||||
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
|
|
||||||
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
|
||||||
{id:"sf1",type:"subflow","name":"Subflow 2","info":"",
|
|
||||||
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]},
|
|
||||||
{id:"sf1-1",type:"test2","z":"sf1",x:166,y:99,"wires":[[]]},
|
|
||||||
{id:"sf1-sn",x:10,y:10,z:"sf1",type:"catch",foo:"a",wires:[]}
|
|
||||||
]);
|
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
|
||||||
|
|
||||||
getNode.restore();
|
|
||||||
getNode = sinon.stub(flows,"get",function(id) {
|
|
||||||
return flow.getNode(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
flow.start();
|
|
||||||
|
|
||||||
var activeNodes = flow.getActiveNodes();
|
|
||||||
var sfInstanceId = Object.keys(activeNodes)[3];
|
|
||||||
var catchInstanceId = Object.keys(activeNodes)[4];
|
|
||||||
|
|
||||||
flow.handleError(activeNodes[sfInstanceId],"my-error",{a:"foo"});
|
|
||||||
|
|
||||||
currentNodes[catchInstanceId].should.have.a.property("handled",1);
|
|
||||||
var statusMessage = currentNodes[catchInstanceId].messages[0];
|
|
||||||
|
|
||||||
statusMessage.should.have.a.property("error");
|
|
||||||
statusMessage.error.should.have.a.property("message","my-error");
|
|
||||||
statusMessage.error.should.have.a.property("source");
|
|
||||||
statusMessage.error.source.should.have.a.property("id",sfInstanceId);
|
|
||||||
statusMessage.error.source.should.have.a.property("type","test2");
|
|
||||||
statusMessage.error.source.should.have.a.property("name",undefined);
|
|
||||||
|
|
||||||
flow.stop().then(function() {
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("passes an error event to the multiple adjacent catch nodes in subflow",function(done) {
|
|
||||||
var config = flowUtils.parseConfig([
|
|
||||||
{id:"t1",type:"tab"},
|
|
||||||
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
|
|
||||||
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
|
|
||||||
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
|
||||||
{id:"sf1",type:"subflow","name":"Subflow 2","info":"",
|
|
||||||
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]},
|
|
||||||
{id:"sf1-1",type:"test2","z":"sf1",x:166,y:99,"wires":[[]]},
|
|
||||||
{id:"sf1-sn",x:10,y:10,z:"sf1",type:"catch",foo:"a",wires:[]},
|
|
||||||
{id:"sf1-sn2",x:10,y:10,z:"sf1",type:"catch",scope:["none"],wires:[]},
|
|
||||||
{id:"sf1-sn3",x:10,y:10,z:"sf1",type:"catch",scope:["sf1-1"],wires:[]}
|
|
||||||
]);
|
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
|
||||||
|
|
||||||
getNode.restore();
|
|
||||||
getNode = sinon.stub(flows,"get",function(id) {
|
|
||||||
return flow.getNode(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
flow.start();
|
|
||||||
|
|
||||||
var activeNodes = flow.getActiveNodes();
|
|
||||||
var sfInstanceId = Object.keys(activeNodes)[3];
|
|
||||||
var catchInstanceId = Object.keys(activeNodes)[4];
|
|
||||||
var catchInstanceId2 = Object.keys(activeNodes)[5];
|
|
||||||
var catchInstanceId3 = Object.keys(activeNodes)[6];
|
|
||||||
|
|
||||||
flow.handleError(activeNodes[sfInstanceId],"my-error",{a:"foo"});
|
|
||||||
|
|
||||||
currentNodes[catchInstanceId].should.have.a.property("handled",1);
|
|
||||||
var statusMessage = currentNodes[catchInstanceId].messages[0];
|
|
||||||
|
|
||||||
statusMessage.should.have.a.property("error");
|
|
||||||
statusMessage.error.should.have.a.property("message","my-error");
|
|
||||||
statusMessage.error.should.have.a.property("source");
|
|
||||||
statusMessage.error.source.should.have.a.property("id",sfInstanceId);
|
|
||||||
statusMessage.error.source.should.have.a.property("type","test2");
|
|
||||||
statusMessage.error.source.should.have.a.property("name",undefined);
|
|
||||||
|
|
||||||
currentNodes[catchInstanceId2].should.have.a.property("handled",0);
|
|
||||||
|
|
||||||
currentNodes[catchInstanceId3].should.have.a.property("handled",1);
|
|
||||||
statusMessage = currentNodes[catchInstanceId3].messages[0];
|
|
||||||
|
|
||||||
statusMessage.should.have.a.property("error");
|
|
||||||
statusMessage.error.should.have.a.property("message","my-error");
|
|
||||||
statusMessage.error.should.have.a.property("source");
|
|
||||||
statusMessage.error.source.should.have.a.property("id",sfInstanceId);
|
|
||||||
statusMessage.error.source.should.have.a.property("type","test2");
|
|
||||||
statusMessage.error.source.should.have.a.property("name",undefined);
|
|
||||||
|
|
||||||
flow.stop().then(function() {
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("passes an error event to the subflow's parent tab catch node",function(done) {
|
|
||||||
var config = flowUtils.parseConfig([
|
|
||||||
{id:"t1",type:"tab"},
|
|
||||||
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
|
|
||||||
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
|
|
||||||
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
|
||||||
{id:"sf1",type:"subflow","name":"Subflow 2","info":"",
|
|
||||||
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]},
|
|
||||||
{id:"sf1-1",type:"test2","z":"sf1",x:166,y:99,"wires":[[]]},
|
|
||||||
{id:"sn",x:10,y:10,z:"t1",type:"catch",foo:"a",wires:[]}
|
|
||||||
]);
|
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
|
||||||
|
|
||||||
getNode.restore();
|
|
||||||
getNode = sinon.stub(flows,"get",function(id) {
|
|
||||||
return flow.getNode(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
flow.start();
|
|
||||||
|
|
||||||
var activeNodes = flow.getActiveNodes();
|
|
||||||
var sfInstanceId = Object.keys(activeNodes)[3];
|
|
||||||
|
|
||||||
flow.handleError(activeNodes[sfInstanceId],"my-error",{a:"foo"});
|
|
||||||
|
|
||||||
currentNodes["sn"].should.have.a.property("handled",1);
|
|
||||||
var statusMessage = currentNodes["sn"].messages[0];
|
|
||||||
|
|
||||||
statusMessage.should.have.a.property("error");
|
|
||||||
statusMessage.error.should.have.a.property("message","my-error");
|
|
||||||
statusMessage.error.should.have.a.property("source");
|
|
||||||
statusMessage.error.source.should.have.a.property("id",sfInstanceId);
|
|
||||||
statusMessage.error.source.should.have.a.property("type","test2");
|
|
||||||
statusMessage.error.source.should.have.a.property("name",undefined);
|
|
||||||
|
|
||||||
flow.stop().then(function() {
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("moves any existing error object sideways",function(done){
|
it("moves any existing error object sideways",function(done){
|
||||||
var config = flowUtils.parseConfig([
|
var config = flowUtils.parseConfig([
|
||||||
{id:"t1",type:"tab"},
|
{id:"t1",type:"tab"},
|
||||||
@ -1075,12 +587,7 @@ describe('Flow', function() {
|
|||||||
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
||||||
{id:"sn",x:10,y:10,z:"t1",type:"catch",foo:"a",wires:[]}
|
{id:"sn",x:10,y:10,z:"t1",type:"catch",foo:"a",wires:[]}
|
||||||
]);
|
]);
|
||||||
var flow = Flow.create(config,config.flows["t1"]);
|
var flow = Flow.create({},config,config.flows["t1"]);
|
||||||
|
|
||||||
getNode.restore();
|
|
||||||
getNode = sinon.stub(flows,"get",function(id) {
|
|
||||||
return flow.getNode(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
flow.start();
|
flow.start();
|
||||||
|
|
||||||
|
460
test/unit/@node-red/runtime/lib/nodes/flows/Subflow_spec.js
Normal file
460
test/unit/@node-red/runtime/lib/nodes/flows/Subflow_spec.js
Normal file
@ -0,0 +1,460 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
|
||||||
|
var should = require("should");
|
||||||
|
var sinon = require('sinon');
|
||||||
|
var clone = require('clone');
|
||||||
|
var util = require("util");
|
||||||
|
|
||||||
|
var NR_TEST_UTILS = require("nr-test-utils");
|
||||||
|
|
||||||
|
var Subflow = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/flows/Subflow");
|
||||||
|
var Flow = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/flows/Flow");
|
||||||
|
|
||||||
|
var flowUtils = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/flows/util");
|
||||||
|
var flows = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/flows");
|
||||||
|
var Node = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/Node");
|
||||||
|
var typeRegistry = NR_TEST_UTILS.require("@node-red/registry");
|
||||||
|
|
||||||
|
describe('Subflow', function() {
|
||||||
|
var getType;
|
||||||
|
|
||||||
|
var stoppedNodes = {};
|
||||||
|
var currentNodes = {};
|
||||||
|
var rewiredNodes = {};
|
||||||
|
var createCount = 0;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
currentNodes = {};
|
||||||
|
stoppedNodes = {};
|
||||||
|
rewiredNodes = {};
|
||||||
|
createCount = 0;
|
||||||
|
var runtime = {
|
||||||
|
settings:{},
|
||||||
|
log:{
|
||||||
|
log: sinon.stub(), // function() { console.log("l",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//
|
||||||
|
debug: sinon.stub(), // function() { console.log("d",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//sinon.stub(),
|
||||||
|
trace: sinon.stub(), // function() { console.log("t",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//sinon.stub(),
|
||||||
|
warn: sinon.stub(), // function() { console.log("w",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//sinon.stub(),
|
||||||
|
info: sinon.stub(), // function() { console.log("i",[...arguments].map(a => JSON.stringify(a)).join(" ")) },//sinon.stub(),
|
||||||
|
metric: sinon.stub(),
|
||||||
|
_: function() { return "abc"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Flow.init(runtime);
|
||||||
|
Subflow.init(runtime);
|
||||||
|
});
|
||||||
|
|
||||||
|
var TestNode = function(n) {
|
||||||
|
Node.call(this,n);
|
||||||
|
this._index = createCount++;
|
||||||
|
this.scope = n.scope;
|
||||||
|
var node = this;
|
||||||
|
this.foo = n.foo;
|
||||||
|
this.handled = 0;
|
||||||
|
this.stopped = false;
|
||||||
|
currentNodes[node.id] = node;
|
||||||
|
this.on('input',function(msg) {
|
||||||
|
// console.log(this.id,msg.payload);
|
||||||
|
node.handled++;
|
||||||
|
node.send(msg);
|
||||||
|
});
|
||||||
|
this.on('close',function() {
|
||||||
|
node.stopped = true;
|
||||||
|
stoppedNodes[node.id] = node;
|
||||||
|
delete currentNodes[node.id];
|
||||||
|
});
|
||||||
|
this.__updateWires = this.updateWires;
|
||||||
|
this.updateWires = function(newWires) {
|
||||||
|
rewiredNodes[node.id] = node;
|
||||||
|
node.newWires = newWires;
|
||||||
|
node.__updateWires(newWires);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
util.inherits(TestNode,Node);
|
||||||
|
|
||||||
|
var TestErrorNode = function(n) {
|
||||||
|
Node.call(this,n);
|
||||||
|
this._index = createCount++;
|
||||||
|
this.scope = n.scope;
|
||||||
|
this.name = n.name;
|
||||||
|
var node = this;
|
||||||
|
this.foo = n.foo;
|
||||||
|
this.handled = 0;
|
||||||
|
this.stopped = false;
|
||||||
|
currentNodes[node.id] = node;
|
||||||
|
this.on('input',function(msg) {
|
||||||
|
node.handled++;
|
||||||
|
node.error("test error",msg);
|
||||||
|
});
|
||||||
|
this.on('close',function() {
|
||||||
|
node.stopped = true;
|
||||||
|
stoppedNodes[node.id] = node;
|
||||||
|
delete currentNodes[node.id];
|
||||||
|
});
|
||||||
|
this.__updateWires = this.updateWires;
|
||||||
|
this.updateWires = function(newWires) {
|
||||||
|
rewiredNodes[node.id] = node;
|
||||||
|
node.newWires = newWires;
|
||||||
|
node.__updateWires(newWires);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
util.inherits(TestErrorNode,Node);
|
||||||
|
|
||||||
|
|
||||||
|
var TestStatusNode = function(n) {
|
||||||
|
Node.call(this,n);
|
||||||
|
this._index = createCount++;
|
||||||
|
this.scope = n.scope;
|
||||||
|
this.name = n.name;
|
||||||
|
var node = this;
|
||||||
|
this.foo = n.foo;
|
||||||
|
this.handled = 0;
|
||||||
|
this.stopped = false;
|
||||||
|
currentNodes[node.id] = node;
|
||||||
|
this.on('input',function(msg) {
|
||||||
|
node.handled++;
|
||||||
|
node.status({text:"test status"});
|
||||||
|
});
|
||||||
|
this.on('close',function() {
|
||||||
|
node.stopped = true;
|
||||||
|
stoppedNodes[node.id] = node;
|
||||||
|
delete currentNodes[node.id];
|
||||||
|
});
|
||||||
|
this.__updateWires = this.updateWires;
|
||||||
|
this.updateWires = function(newWires) {
|
||||||
|
rewiredNodes[node.id] = node;
|
||||||
|
node.newWires = newWires;
|
||||||
|
node.__updateWires(newWires);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
util.inherits(TestStatusNode,Node);
|
||||||
|
|
||||||
|
var TestAsyncNode = function(n) {
|
||||||
|
Node.call(this,n);
|
||||||
|
var node = this;
|
||||||
|
this.scope = n.scope;
|
||||||
|
this.foo = n.foo;
|
||||||
|
this.handled = 0;
|
||||||
|
this.messages = [];
|
||||||
|
this.stopped = false;
|
||||||
|
this.closeDelay = n.closeDelay || 50;
|
||||||
|
currentNodes[node.id] = node;
|
||||||
|
this.on('input',function(msg) {
|
||||||
|
node.handled++;
|
||||||
|
node.messages.push(msg);
|
||||||
|
node.send(msg);
|
||||||
|
});
|
||||||
|
this.on('close',function(done) {
|
||||||
|
setTimeout(function() {
|
||||||
|
node.stopped = true;
|
||||||
|
stoppedNodes[node.id] = node;
|
||||||
|
delete currentNodes[node.id];
|
||||||
|
done();
|
||||||
|
},node.closeDelay);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
util.inherits(TestAsyncNode,Node);
|
||||||
|
|
||||||
|
before(function() {
|
||||||
|
getType = sinon.stub(typeRegistry,"get",function(type) {
|
||||||
|
if (type=="test") {
|
||||||
|
return TestNode;
|
||||||
|
} else if (type=="testError"){
|
||||||
|
return TestErrorNode;
|
||||||
|
} else if (type=="testStatus"){
|
||||||
|
return TestStatusNode;
|
||||||
|
} else {
|
||||||
|
return TestAsyncNode;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
after(function() {
|
||||||
|
getType.restore();
|
||||||
|
});
|
||||||
|
describe('#start',function() {
|
||||||
|
it("instantiates a subflow and stops it",function(done) {
|
||||||
|
var config = flowUtils.parseConfig([
|
||||||
|
{id:"t1",type:"tab"},
|
||||||
|
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]},
|
||||||
|
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3","4"]},
|
||||||
|
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
||||||
|
{id:"4",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow","name":"Subflow 2","info":"",
|
||||||
|
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-2","port":0}]},{"wires":[{"id":"sf1","port":0}]}]},
|
||||||
|
{id:"sf1-1",type:"test","z":"sf1",x:166,y:99,"wires":[["sf1-2"]]},
|
||||||
|
{id:"sf1-2",type:"test","z":"sf1",foo:"sf1-cn",x:166,y:99,"wires":[[]]},
|
||||||
|
{id:"sf1-cn",type:"test","z":"sf1"}
|
||||||
|
]);
|
||||||
|
var flow = Flow.create({handleError: (a,b,c) => { console.log(a,b,c); }},config,config.flows["t1"]);
|
||||||
|
|
||||||
|
flow.start();
|
||||||
|
|
||||||
|
var activeNodes = flow.getActiveNodes();
|
||||||
|
Object.keys(activeNodes).should.have.length(4);
|
||||||
|
// var sfInstanceId = Object.keys(activeNodes)[5];
|
||||||
|
// var sfInstanceId2 = Object.keys(activeNodes)[6];
|
||||||
|
var sfConfigId = Object.keys(activeNodes)[4];
|
||||||
|
|
||||||
|
flow.getNode('1').should.have.a.property('id','1');
|
||||||
|
flow.getNode('2').should.have.a.property('id','2');
|
||||||
|
flow.getNode('3').should.have.a.property('id','3');
|
||||||
|
flow.getNode('4').should.have.a.property('id','4');
|
||||||
|
// flow.getNode(sfInstanceId).should.have.a.property('id',sfInstanceId);
|
||||||
|
// flow.getNode(sfInstanceId2).should.have.a.property('id',sfInstanceId2);
|
||||||
|
// flow.getNode(sfConfigId).should.have.a.property('id',sfConfigId);
|
||||||
|
|
||||||
|
// flow.getNode(sfInstanceId2).should.have.a.property('foo',sfConfigId);
|
||||||
|
|
||||||
|
Object.keys(currentNodes).should.have.length(6);
|
||||||
|
|
||||||
|
currentNodes.should.have.a.property("1");
|
||||||
|
currentNodes.should.not.have.a.property("2");
|
||||||
|
currentNodes.should.have.a.property("3");
|
||||||
|
currentNodes.should.have.a.property("4");
|
||||||
|
// currentNodes.should.have.a.property(sfInstanceId);
|
||||||
|
// currentNodes.should.have.a.property(sfInstanceId2);
|
||||||
|
// currentNodes.should.have.a.property(sfConfigId);
|
||||||
|
|
||||||
|
currentNodes["1"].should.have.a.property("handled",0);
|
||||||
|
currentNodes["3"].should.have.a.property("handled",0);
|
||||||
|
currentNodes["4"].should.have.a.property("handled",0);
|
||||||
|
// currentNodes[sfInstanceId].should.have.a.property("handled",0);
|
||||||
|
// currentNodes[sfInstanceId2].should.have.a.property("handled",0);
|
||||||
|
|
||||||
|
currentNodes["1"].receive({payload:"test"});
|
||||||
|
|
||||||
|
currentNodes["1"].should.have.a.property("handled",1);
|
||||||
|
// currentNodes[sfInstanceId].should.have.a.property("handled",1);
|
||||||
|
// currentNodes[sfInstanceId2].should.have.a.property("handled",1);
|
||||||
|
currentNodes["3"].should.have.a.property("handled",1);
|
||||||
|
currentNodes["4"].should.have.a.property("handled",1);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
flow.stop().then(function() {
|
||||||
|
Object.keys(currentNodes).should.have.length(0);
|
||||||
|
Object.keys(stoppedNodes).should.have.length(6);
|
||||||
|
|
||||||
|
// currentNodes.should.not.have.a.property("1");
|
||||||
|
// currentNodes.should.not.have.a.property("3");
|
||||||
|
// currentNodes.should.not.have.a.property("4");
|
||||||
|
// // currentNodes.should.not.have.a.property(sfInstanceId);
|
||||||
|
// // currentNodes.should.not.have.a.property(sfInstanceId2);
|
||||||
|
// // currentNodes.should.not.have.a.property(sfConfigId);
|
||||||
|
// stoppedNodes.should.have.a.property("1");
|
||||||
|
// stoppedNodes.should.have.a.property("3");
|
||||||
|
// stoppedNodes.should.have.a.property("4");
|
||||||
|
// // stoppedNodes.should.have.a.property(sfInstanceId);
|
||||||
|
// // stoppedNodes.should.have.a.property(sfInstanceId2);
|
||||||
|
// // stoppedNodes.should.have.a.property(sfConfigId);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("instantiates a subflow inside a subflow and stops it",function(done) {
|
||||||
|
var config = flowUtils.parseConfig([
|
||||||
|
{id:"t1",type:"tab"},
|
||||||
|
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]},
|
||||||
|
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3","4"]},
|
||||||
|
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
||||||
|
{id:"4",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow","name":"Subflow 1","info":"",
|
||||||
|
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-2","port":0}]}]},
|
||||||
|
{id:"sf2",type:"subflow","name":"Subflow 2","info":"",
|
||||||
|
"in":[{wires:[]}],"out":[{"wires":[{"id":"sf2","port":0}]}]},
|
||||||
|
{id:"sf1-1",type:"test","z":"sf1",x:166,y:99,"wires":[["sf1-2"]]},
|
||||||
|
{id:"sf1-2",type:"subflow:sf2","z":"sf1",x:166,y:99,"wires":[[]]}
|
||||||
|
|
||||||
|
]);
|
||||||
|
var flow = Flow.create({},config,config.flows["t1"]);
|
||||||
|
|
||||||
|
flow.start();
|
||||||
|
|
||||||
|
currentNodes["1"].should.have.a.property("handled",0);
|
||||||
|
currentNodes["3"].should.have.a.property("handled",0);
|
||||||
|
|
||||||
|
currentNodes["1"].receive({payload:"test"});
|
||||||
|
|
||||||
|
currentNodes["1"].should.have.a.property("handled",1);
|
||||||
|
|
||||||
|
|
||||||
|
currentNodes["3"].should.have.a.property("handled",1);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
flow.stop().then(function() {
|
||||||
|
Object.keys(currentNodes).should.have.length(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("rewires a subflow node on update/start",function(done){
|
||||||
|
|
||||||
|
var rawConfig = [
|
||||||
|
{id:"t1",type:"tab"},
|
||||||
|
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]},
|
||||||
|
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
|
||||||
|
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
||||||
|
{id:"4",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow","name":"Subflow 2","info":"",
|
||||||
|
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-2","port":0}]}]},
|
||||||
|
{id:"sf1-1",type:"test1","z":"sf1",x:166,y:99,"wires":[["sf1-2"]]},
|
||||||
|
{id:"sf1-2",type:"test2","z":"sf1",x:166,y:99,"wires":[[]]}
|
||||||
|
];
|
||||||
|
|
||||||
|
var config = flowUtils.parseConfig(clone(rawConfig));
|
||||||
|
|
||||||
|
rawConfig[2].wires = [["4"]];
|
||||||
|
|
||||||
|
var newConfig = flowUtils.parseConfig(rawConfig);
|
||||||
|
var diff = flowUtils.diffConfigs(config,newConfig);
|
||||||
|
var flow = Flow.create({},config,config.flows["t1"]);
|
||||||
|
|
||||||
|
flow.start();
|
||||||
|
|
||||||
|
var activeNodes = flow.getActiveNodes();
|
||||||
|
Object.keys(activeNodes).should.have.length(4);
|
||||||
|
// var sfInstanceId = Object.keys(activeNodes)[4];
|
||||||
|
// var sfInstanceId2 = Object.keys(activeNodes)[5];
|
||||||
|
|
||||||
|
currentNodes["1"].should.have.a.property("handled",0);
|
||||||
|
currentNodes["3"].should.have.a.property("handled",0);
|
||||||
|
currentNodes["4"].should.have.a.property("handled",0);
|
||||||
|
|
||||||
|
currentNodes["1"].receive({payload:"test"});
|
||||||
|
|
||||||
|
currentNodes["1"].should.have.a.property("handled",1);
|
||||||
|
// currentNodes[sfInstanceId].should.have.a.property("handled",1);
|
||||||
|
// currentNodes[sfInstanceId2].should.have.a.property("handled",1);
|
||||||
|
currentNodes["3"].should.have.a.property("handled",1);
|
||||||
|
currentNodes["4"].should.have.a.property("handled",0);
|
||||||
|
|
||||||
|
flow.update(newConfig,newConfig.flows["t1"]);
|
||||||
|
flow.start(diff)
|
||||||
|
|
||||||
|
currentNodes["1"].receive({payload:"test2"});
|
||||||
|
|
||||||
|
currentNodes["1"].should.have.a.property("handled",2);
|
||||||
|
// currentNodes[sfInstanceId].should.have.a.property("handled",2);
|
||||||
|
// currentNodes[sfInstanceId2].should.have.a.property("handled",2);
|
||||||
|
currentNodes["3"].should.have.a.property("handled",1);
|
||||||
|
currentNodes["4"].should.have.a.property("handled",1);
|
||||||
|
|
||||||
|
|
||||||
|
flow.stop().then(function() {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#stop', function() {
|
||||||
|
it("stops subflow instance nodes",function(done) {
|
||||||
|
var config = flowUtils.parseConfig([
|
||||||
|
{id:"t1",type:"tab"},
|
||||||
|
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]},
|
||||||
|
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
|
||||||
|
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow","name":"Subflow 2","info":"",
|
||||||
|
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]},
|
||||||
|
{id:"sf1-1",type:"test","z":"sf1",x:166,y:99,"wires":[[]]}
|
||||||
|
]);
|
||||||
|
var flow = Flow.create({},config,config.flows["t1"]);
|
||||||
|
|
||||||
|
flow.start();
|
||||||
|
|
||||||
|
var activeNodes = flow.getActiveNodes();
|
||||||
|
Object.keys(activeNodes).should.have.length(3);
|
||||||
|
Object.keys(stoppedNodes).should.have.length(0);
|
||||||
|
flow.stop(["2"]).then(function() {
|
||||||
|
Object.keys(currentNodes).should.have.length(2);
|
||||||
|
Object.keys(stoppedNodes).should.have.length(1);
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("#handleStatus",function() {
|
||||||
|
it("passes a status event to the subflow's parent tab status node",function(done) {
|
||||||
|
var config = flowUtils.parseConfig([
|
||||||
|
{id:"t1",type:"tab"},
|
||||||
|
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
|
||||||
|
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
|
||||||
|
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow","name":"Subflow 2","info":"",
|
||||||
|
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]},
|
||||||
|
{id:"sf1-1",type:"testStatus",name:"test-status-node","z":"sf1",x:166,y:99,"wires":[[]]},
|
||||||
|
{id:"sn",x:10,y:10,z:"t1",type:"status",foo:"a",wires:[]}
|
||||||
|
]);
|
||||||
|
var flow = Flow.create({},config,config.flows["t1"]);
|
||||||
|
|
||||||
|
flow.start();
|
||||||
|
|
||||||
|
var activeNodes = flow.getActiveNodes();
|
||||||
|
|
||||||
|
activeNodes["1"].receive({payload:"test"});
|
||||||
|
|
||||||
|
currentNodes["sn"].should.have.a.property("handled",1);
|
||||||
|
var statusMessage = currentNodes["sn"].messages[0];
|
||||||
|
|
||||||
|
statusMessage.should.have.a.property("status");
|
||||||
|
statusMessage.status.should.have.a.property("text","test status");
|
||||||
|
statusMessage.status.should.have.a.property("source");
|
||||||
|
statusMessage.status.source.should.have.a.property("type","testStatus");
|
||||||
|
statusMessage.status.source.should.have.a.property("name","test-status-node");
|
||||||
|
|
||||||
|
flow.stop().then(function() {
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#handleError",function() {
|
||||||
|
it("passes an error event to the subflow's parent tab catch node",function(done) {
|
||||||
|
var config = flowUtils.parseConfig([
|
||||||
|
{id:"t1",type:"tab"},
|
||||||
|
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
|
||||||
|
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
|
||||||
|
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"a",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow","name":"Subflow 2","info":"",
|
||||||
|
"in":[{"wires":[{"id":"sf1-1"}]}],"out":[{"wires":[{"id":"sf1-1","port":0}]}]},
|
||||||
|
{id:"sf1-1",name:"test-error-node",type:"testError","z":"sf1",x:166,y:99,"wires":[[]]},
|
||||||
|
{id:"sn",x:10,y:10,z:"t1",type:"catch",foo:"a",wires:[]}
|
||||||
|
]);
|
||||||
|
var flow = Flow.create({},config,config.flows["t1"]);
|
||||||
|
|
||||||
|
flow.start();
|
||||||
|
|
||||||
|
var activeNodes = flow.getActiveNodes();
|
||||||
|
|
||||||
|
activeNodes["1"].receive({payload:"test"});
|
||||||
|
|
||||||
|
currentNodes["sn"].should.have.a.property("handled",1);
|
||||||
|
var statusMessage = currentNodes["sn"].messages[0];
|
||||||
|
|
||||||
|
statusMessage.should.have.a.property("error");
|
||||||
|
statusMessage.error.should.have.a.property("message","test error");
|
||||||
|
statusMessage.error.should.have.a.property("source");
|
||||||
|
statusMessage.error.source.should.have.a.property("type","testError");
|
||||||
|
statusMessage.error.source.should.have.a.property("name","test-error-node");
|
||||||
|
|
||||||
|
flow.stop().then(function() {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -70,7 +70,7 @@ describe('flows/index', function() {
|
|||||||
credentialsLoad = sinon.stub(credentials,"load",function() {
|
credentialsLoad = sinon.stub(credentials,"load",function() {
|
||||||
return when.resolve();
|
return when.resolve();
|
||||||
});
|
});
|
||||||
flowCreate = sinon.stub(Flow,"create",function(global, flow) {
|
flowCreate = sinon.stub(Flow,"create",function(parent, global, flow) {
|
||||||
var id;
|
var id;
|
||||||
if (typeof flow === 'undefined') {
|
if (typeof flow === 'undefined') {
|
||||||
flow = global;
|
flow = global;
|
||||||
@ -354,113 +354,113 @@ describe('flows/index', function() {
|
|||||||
describe('#stopFlows', function() {
|
describe('#stopFlows', function() {
|
||||||
|
|
||||||
});
|
});
|
||||||
describe('#handleError', function() {
|
// describe('#handleError', function() {
|
||||||
it('passes error to correct flow', function(done) {
|
// it('passes error to correct flow', function(done) {
|
||||||
var originalConfig = [
|
// var originalConfig = [
|
||||||
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
// {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
||||||
{id:"t1",type:"tab"}
|
// {id:"t1",type:"tab"}
|
||||||
];
|
// ];
|
||||||
storage.getFlows = function() {
|
// storage.getFlows = function() {
|
||||||
return when.resolve({flows:originalConfig});
|
// return when.resolve({flows:originalConfig});
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
events.once('nodes-started',function() {
|
// events.once('nodes-started',function() {
|
||||||
flows.handleError(originalConfig[0],"message",{});
|
// flows.handleError(originalConfig[0],"message",{});
|
||||||
flowCreate.flows['t1'].handleError.called.should.be.true();
|
// flowCreate.flows['t1'].handleError.called.should.be.true();
|
||||||
done();
|
// done();
|
||||||
});
|
// });
|
||||||
|
//
|
||||||
flows.init({log:mockLog, settings:{},storage:storage});
|
// flows.init({log:mockLog, settings:{},storage:storage});
|
||||||
flows.load().then(function() {
|
// flows.load().then(function() {
|
||||||
flows.startFlows();
|
// flows.startFlows();
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
it('passes error to flows that use the originating global config', function(done) {
|
// it('passes error to flows that use the originating global config', function(done) {
|
||||||
var originalConfig = [
|
// var originalConfig = [
|
||||||
{id:"configNode",type:"test"},
|
// {id:"configNode",type:"test"},
|
||||||
{id:"t1",type:"tab"},
|
// {id:"t1",type:"tab"},
|
||||||
{id:"t1-1",x:10,y:10,z:"t1",type:"test",config:"configNode",wires:[]},
|
// {id:"t1-1",x:10,y:10,z:"t1",type:"test",config:"configNode",wires:[]},
|
||||||
{id:"t2",type:"tab"},
|
// {id:"t2",type:"tab"},
|
||||||
{id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]},
|
// {id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]},
|
||||||
{id:"t3",type:"tab"},
|
// {id:"t3",type:"tab"},
|
||||||
{id:"t3-1",x:10,y:10,z:"t3",type:"test",config:"configNode",wires:[]}
|
// {id:"t3-1",x:10,y:10,z:"t3",type:"test",config:"configNode",wires:[]}
|
||||||
];
|
// ];
|
||||||
storage.getFlows = function() {
|
// storage.getFlows = function() {
|
||||||
return when.resolve({flows:originalConfig});
|
// return when.resolve({flows:originalConfig});
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
events.once('nodes-started',function() {
|
// events.once('nodes-started',function() {
|
||||||
flows.handleError(originalConfig[0],"message",{});
|
// flows.handleError(originalConfig[0],"message",{});
|
||||||
try {
|
// try {
|
||||||
flowCreate.flows['t1'].handleError.called.should.be.true();
|
// flowCreate.flows['t1'].handleError.called.should.be.true();
|
||||||
flowCreate.flows['t2'].handleError.called.should.be.false();
|
// flowCreate.flows['t2'].handleError.called.should.be.false();
|
||||||
flowCreate.flows['t3'].handleError.called.should.be.true();
|
// flowCreate.flows['t3'].handleError.called.should.be.true();
|
||||||
done();
|
// done();
|
||||||
} catch(err) {
|
// } catch(err) {
|
||||||
done(err);
|
// done(err);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
//
|
||||||
flows.init({log:mockLog, settings:{},storage:storage});
|
// flows.init({log:mockLog, settings:{},storage:storage});
|
||||||
flows.load().then(function() {
|
// flows.load().then(function() {
|
||||||
flows.startFlows();
|
// flows.startFlows();
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
describe('#handleStatus', function() {
|
// describe('#handleStatus', function() {
|
||||||
it('passes status to correct flow', function(done) {
|
// it('passes status to correct flow', function(done) {
|
||||||
var originalConfig = [
|
// var originalConfig = [
|
||||||
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
// {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
||||||
{id:"t1",type:"tab"}
|
// {id:"t1",type:"tab"}
|
||||||
];
|
// ];
|
||||||
storage.getFlows = function() {
|
// storage.getFlows = function() {
|
||||||
return when.resolve({flows:originalConfig});
|
// return when.resolve({flows:originalConfig});
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
events.once('nodes-started',function() {
|
// events.once('nodes-started',function() {
|
||||||
flows.handleStatus(originalConfig[0],"message");
|
// flows.handleStatus(originalConfig[0],"message");
|
||||||
flowCreate.flows['t1'].handleStatus.called.should.be.true();
|
// flowCreate.flows['t1'].handleStatus.called.should.be.true();
|
||||||
done();
|
// done();
|
||||||
});
|
// });
|
||||||
|
//
|
||||||
flows.init({log:mockLog, settings:{},storage:storage});
|
// flows.init({log:mockLog, settings:{},storage:storage});
|
||||||
flows.load().then(function() {
|
// flows.load().then(function() {
|
||||||
flows.startFlows();
|
// flows.startFlows();
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
//
|
||||||
it('passes status to flows that use the originating global config', function(done) {
|
// it('passes status to flows that use the originating global config', function(done) {
|
||||||
var originalConfig = [
|
// var originalConfig = [
|
||||||
{id:"configNode",type:"test"},
|
// {id:"configNode",type:"test"},
|
||||||
{id:"t1",type:"tab"},
|
// {id:"t1",type:"tab"},
|
||||||
{id:"t1-1",x:10,y:10,z:"t1",type:"test",config:"configNode",wires:[]},
|
// {id:"t1-1",x:10,y:10,z:"t1",type:"test",config:"configNode",wires:[]},
|
||||||
{id:"t2",type:"tab"},
|
// {id:"t2",type:"tab"},
|
||||||
{id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]},
|
// {id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]},
|
||||||
{id:"t3",type:"tab"},
|
// {id:"t3",type:"tab"},
|
||||||
{id:"t3-1",x:10,y:10,z:"t3",type:"test",config:"configNode",wires:[]}
|
// {id:"t3-1",x:10,y:10,z:"t3",type:"test",config:"configNode",wires:[]}
|
||||||
];
|
// ];
|
||||||
storage.getFlows = function() {
|
// storage.getFlows = function() {
|
||||||
return when.resolve({flows:originalConfig});
|
// return when.resolve({flows:originalConfig});
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
events.once('nodes-started',function() {
|
// events.once('nodes-started',function() {
|
||||||
flows.handleStatus(originalConfig[0],"message");
|
// flows.handleStatus(originalConfig[0],"message");
|
||||||
try {
|
// try {
|
||||||
flowCreate.flows['t1'].handleStatus.called.should.be.true();
|
// flowCreate.flows['t1'].handleStatus.called.should.be.true();
|
||||||
flowCreate.flows['t2'].handleStatus.called.should.be.false();
|
// flowCreate.flows['t2'].handleStatus.called.should.be.false();
|
||||||
flowCreate.flows['t3'].handleStatus.called.should.be.true();
|
// flowCreate.flows['t3'].handleStatus.called.should.be.true();
|
||||||
done();
|
// done();
|
||||||
} catch(err) {
|
// } catch(err) {
|
||||||
done(err);
|
// done(err);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
//
|
||||||
flows.init({log:mockLog, settings:{},storage:storage});
|
// flows.init({log:mockLog, settings:{},storage:storage});
|
||||||
flows.load().then(function() {
|
// flows.load().then(function() {
|
||||||
flows.startFlows();
|
// flows.startFlows();
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
describe('#checkTypeInUse', function() {
|
describe('#checkTypeInUse', function() {
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user