mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Merge branch 'flowengine'
This commit is contained in:
commit
71ecb89abc
@ -1,845 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2015 IBM Corp.
|
|
||||||
*
|
|
||||||
* 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 when = require("when");
|
|
||||||
var clone = require("clone");
|
|
||||||
|
|
||||||
var typeRegistry = require("./registry");
|
|
||||||
var credentials = require("./credentials");
|
|
||||||
var redUtil = require("../util");
|
|
||||||
var events = require("../events");
|
|
||||||
var Log = require("../log");
|
|
||||||
|
|
||||||
function getID() {
|
|
||||||
return (1+Math.random()*4294967295).toString(16);
|
|
||||||
}
|
|
||||||
|
|
||||||
var EnvVarPropertyRE = /^\$\((\S+)\)$/;
|
|
||||||
|
|
||||||
function mapEnvVarProperties(obj,prop) {
|
|
||||||
if (Buffer.isBuffer(obj[prop])) {
|
|
||||||
return;
|
|
||||||
} else if (Array.isArray(obj[prop])) {
|
|
||||||
for (var i=0;i<obj[prop].length;i++) {
|
|
||||||
mapEnvVarProperties(obj[prop],i);
|
|
||||||
}
|
|
||||||
} else if (typeof obj[prop] === 'string') {
|
|
||||||
var m;
|
|
||||||
if ( (m = EnvVarPropertyRE.exec(obj[prop])) !== null) {
|
|
||||||
if (process.env.hasOwnProperty(m[1])) {
|
|
||||||
obj[prop] = process.env[m[1]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (var p in obj[prop]) {
|
|
||||||
if (obj[prop].hasOwnProperty) {
|
|
||||||
mapEnvVarProperties(obj[prop],p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createNode(type,config) {
|
|
||||||
var nn = null;
|
|
||||||
var m;
|
|
||||||
var nt = typeRegistry.get(type);
|
|
||||||
if (nt) {
|
|
||||||
for (var p in config) {
|
|
||||||
if (config.hasOwnProperty(p)) {
|
|
||||||
mapEnvVarProperties(config,p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
nn = new nt(clone(config));
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
Log.log({
|
|
||||||
level: Log.ERROR,
|
|
||||||
id:config.id,
|
|
||||||
type: type,
|
|
||||||
msg: err
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.error(Log._("nodes.flow.unknown-type", {type:type}));
|
|
||||||
}
|
|
||||||
return nn;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function createSubflow(sf,sfn,subflows) {
|
|
||||||
//console.log("CREATE SUBFLOW",sf.config.id,sfn.id);
|
|
||||||
var nodes = [];
|
|
||||||
var node_map = {};
|
|
||||||
var newNodes = [];
|
|
||||||
var node;
|
|
||||||
var wires;
|
|
||||||
var i,j,k;
|
|
||||||
|
|
||||||
// Clone all of the subflow node definitions and give them new IDs
|
|
||||||
for (i=0;i<sf.nodes.length;i++) {
|
|
||||||
node = clone(sf.nodes[i].config);
|
|
||||||
var nid = getID();
|
|
||||||
node_map[node.id] = node;
|
|
||||||
node._alias = node.id;
|
|
||||||
node.id = nid;
|
|
||||||
node.z = sfn.id;
|
|
||||||
newNodes.push(node);
|
|
||||||
}
|
|
||||||
// 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];
|
|
||||||
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:""
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.config.in) {
|
|
||||||
subflowInstance.wires = sf.config.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.config.out) {
|
|
||||||
var node,wires,i,j;
|
|
||||||
// Restore the original wiring to the internal nodes
|
|
||||||
|
|
||||||
subflowInstance.wires = clone(subflowInstance._originalWires);
|
|
||||||
|
|
||||||
for (i=0;i<sf.config.out.length;i++) {
|
|
||||||
wires = sf.config.out[i].wires;
|
|
||||||
for (j=0;j<wires.length;j++) {
|
|
||||||
if (wires[j].id != sf.config.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.config.out.length;i++) {
|
|
||||||
wires = sf.config.out[i].wires;
|
|
||||||
for (j=0;j<wires.length;j++) {
|
|
||||||
if (wires[j].id === sf.config.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.config.out) {
|
|
||||||
var modifiedNodes = {};
|
|
||||||
for (i=0;i<sf.config.out.length;i++) {
|
|
||||||
wires = sf.config.out[i].wires;
|
|
||||||
for (j=0;j<wires.length;j++) {
|
|
||||||
if (wires[j].id === sf.config.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) {
|
|
||||||
nodes.push(createNode(type,node));
|
|
||||||
} else {
|
|
||||||
var subflowId = m[1];
|
|
||||||
nodes = nodes.concat(createSubflow(subflows[subflowId],node,subflows));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
subflowNode.instanceNodes = {};
|
|
||||||
|
|
||||||
nodes.forEach(function(node) {
|
|
||||||
subflowNode.instanceNodes[node.id] = node;
|
|
||||||
});
|
|
||||||
|
|
||||||
return nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function diffNodeConfigs(oldNode,newNode) {
|
|
||||||
if (oldNode == null) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
for (var p in newNode) {
|
|
||||||
if (newNode.hasOwnProperty(p) && p != "x" && p != "y" && p != "wires") {
|
|
||||||
if (!redUtil.compareObjects(oldNode[p],newNode[p])) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var subflowInstanceRE = /^subflow:(.+)$/;
|
|
||||||
|
|
||||||
function Flow(config) {
|
|
||||||
|
|
||||||
this.activeNodes = {};
|
|
||||||
this.subflowInstanceNodes = {};
|
|
||||||
this.catchNodeMap = {};
|
|
||||||
this.statusNodeMap = {};
|
|
||||||
this.started = false;
|
|
||||||
|
|
||||||
this.parseConfig(config);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow.prototype.parseConfig = function(config) {
|
|
||||||
var i;
|
|
||||||
var nodeConfig;
|
|
||||||
var nodeType;
|
|
||||||
|
|
||||||
this.config = config;
|
|
||||||
|
|
||||||
this.allNodes = {};
|
|
||||||
|
|
||||||
this.nodes = {};
|
|
||||||
this.subflows = {};
|
|
||||||
|
|
||||||
this.configNodes = {};
|
|
||||||
|
|
||||||
var unknownTypes = {};
|
|
||||||
|
|
||||||
for (i=0;i<this.config.length;i++) {
|
|
||||||
nodeConfig = this.config[i];
|
|
||||||
nodeType = nodeConfig.type;
|
|
||||||
this.allNodes[nodeConfig.id] = nodeConfig;
|
|
||||||
if (nodeType == "subflow") {
|
|
||||||
this.subflows[nodeConfig.id] = {
|
|
||||||
type: "subflow",
|
|
||||||
config: nodeConfig,
|
|
||||||
nodes: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
//console.log("Known subflows:",Object.keys(this.subflows));
|
|
||||||
for (i=0;i<this.config.length;i++) {
|
|
||||||
nodeConfig = this.config[i];
|
|
||||||
|
|
||||||
|
|
||||||
nodeType = nodeConfig.type;
|
|
||||||
|
|
||||||
if (nodeConfig.credentials) {
|
|
||||||
delete nodeConfig.credentials;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nodeType != "tab" && nodeType != "subflow") {
|
|
||||||
var m = subflowInstanceRE.exec(nodeType);
|
|
||||||
if ((m && !this.subflows[m[1]]) || (!m && !typeRegistry.get(nodeType))) {
|
|
||||||
// This is an unknown subflow or an unknown type
|
|
||||||
unknownTypes[nodeType] = true;
|
|
||||||
} else {
|
|
||||||
var nodeInfo = {
|
|
||||||
type: nodeType,
|
|
||||||
config:nodeConfig
|
|
||||||
}
|
|
||||||
if (m) {
|
|
||||||
nodeInfo.subflow = m[1];
|
|
||||||
}
|
|
||||||
if (this.subflows[nodeConfig.z]) {
|
|
||||||
this.subflows[nodeConfig.z].nodes.push(nodeInfo);
|
|
||||||
} else {
|
|
||||||
this.nodes[nodeConfig.id] = nodeInfo;
|
|
||||||
}
|
|
||||||
if (nodeConfig.type != "catch" && nodeConfig.type != "status") {
|
|
||||||
for (var prop in nodeConfig) {
|
|
||||||
if (nodeConfig.hasOwnProperty(prop) &&
|
|
||||||
prop != "type" &&
|
|
||||||
prop != "id" &&
|
|
||||||
prop != "z" &&
|
|
||||||
prop != "wires" &&
|
|
||||||
this.allNodes[nodeConfig[prop]]) {
|
|
||||||
this.configNodes[nodeConfig[prop]] = this.allNodes[nodeConfig[prop]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//console.log("NODES");
|
|
||||||
//for (i in this.nodes) {
|
|
||||||
// if (this.nodes.hasOwnProperty(i)) {
|
|
||||||
// console.log(" ",i,this.nodes[i].type,this.nodes[i].config.name||"");
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//console.log("SUBFLOWS");
|
|
||||||
//for (i in this.subflows) {
|
|
||||||
// if (this.subflows.hasOwnProperty(i)) {
|
|
||||||
// console.log(" ",i,this.subflows[i].type,this.subflows[i].config.name||"");
|
|
||||||
// for (var j=0;j<this.subflows[i].nodes.length;j++) {
|
|
||||||
// console.log(" ",this.subflows[i].nodes[j].config.id,this.subflows[i].nodes[j].type,this.subflows[i].nodes[j].config.name||"");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
this.missingTypes = Object.keys(unknownTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow.prototype.start = function(configDiff) {
|
|
||||||
if (configDiff) {
|
|
||||||
for (var j=0;j<configDiff.rewire.length;j++) {
|
|
||||||
var rewireNode = this.activeNodes[configDiff.rewire[j]];
|
|
||||||
if (rewireNode) {
|
|
||||||
rewireNode.updateWires(this.allNodes[rewireNode.id].wires);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.started = true;
|
|
||||||
if (this.missingTypes.length > 0) {
|
|
||||||
throw new Error(Log._("nodes.flow.missing-types"));
|
|
||||||
}
|
|
||||||
events.emit("nodes-starting");
|
|
||||||
|
|
||||||
var id;
|
|
||||||
var node;
|
|
||||||
|
|
||||||
for (id in this.configNodes) {
|
|
||||||
if (this.configNodes.hasOwnProperty(id)) {
|
|
||||||
node = this.configNodes[id];
|
|
||||||
if (!this.activeNodes[id]) {
|
|
||||||
this.activeNodes[id] = createNode(node.type,node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (id in this.nodes) {
|
|
||||||
if (this.nodes.hasOwnProperty(id)) {
|
|
||||||
node = this.nodes[id];
|
|
||||||
if (!node.subflow) {
|
|
||||||
if (!this.activeNodes[id]) {
|
|
||||||
this.activeNodes[id] = createNode(node.type,node.config);
|
|
||||||
//console.log(id,"created");
|
|
||||||
} else {
|
|
||||||
//console.log(id,"already running");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!this.subflowInstanceNodes[id]) {
|
|
||||||
var nodes = createSubflow(this.subflows[node.subflow],node.config,this.subflows);
|
|
||||||
this.subflowInstanceNodes[id] = nodes.map(function(n) { return n.id});
|
|
||||||
for (var i=0;i<nodes.length;i++) {
|
|
||||||
this.activeNodes[nodes[i].id] = nodes[i];
|
|
||||||
}
|
|
||||||
//console.log(id,"(sf) created");
|
|
||||||
} else {
|
|
||||||
//console.log(id,"(sf) already running");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
this.catchNodeMap = {};
|
|
||||||
this.statusNodeMap = {};
|
|
||||||
|
|
||||||
for (id in this.activeNodes) {
|
|
||||||
if (this.activeNodes.hasOwnProperty(id)) {
|
|
||||||
node = this.activeNodes[id];
|
|
||||||
if (node.type === "catch") {
|
|
||||||
this.catchNodeMap[node.z] = this.catchNodeMap[node.z] || [];
|
|
||||||
this.catchNodeMap[node.z].push(node);
|
|
||||||
} else if (node.type === "status") {
|
|
||||||
this.statusNodeMap[node.z] = this.statusNodeMap[node.z] || [];
|
|
||||||
this.statusNodeMap[node.z].push(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
credentials.clean(this.config);
|
|
||||||
events.emit("nodes-started");
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow.prototype.stop = function(configDiff) {
|
|
||||||
var nodeList;
|
|
||||||
|
|
||||||
if (configDiff) {
|
|
||||||
nodeList = configDiff.stop;
|
|
||||||
} else {
|
|
||||||
nodeList = Object.keys(this.activeNodes);
|
|
||||||
}
|
|
||||||
var flow = this;
|
|
||||||
return when.promise(function(resolve) {
|
|
||||||
events.emit("nodes-stopping");
|
|
||||||
var promises = [];
|
|
||||||
for (var i=0;i<nodeList.length;i++) {
|
|
||||||
var node = flow.activeNodes[nodeList[i]];
|
|
||||||
if (node) {
|
|
||||||
try {
|
|
||||||
var p = node.close();
|
|
||||||
if (p) {
|
|
||||||
promises.push(p);
|
|
||||||
}
|
|
||||||
} catch(err) {
|
|
||||||
node.error(err);
|
|
||||||
}
|
|
||||||
delete flow.subflowInstanceNodes[nodeList[i]];
|
|
||||||
delete flow.activeNodes[nodeList[i]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
when.settle(promises).then(function() {
|
|
||||||
events.emit("nodes-stopped");
|
|
||||||
flow.started = false;
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow.prototype.getMissingTypes = function() {
|
|
||||||
return this.missingTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow.prototype.typeRegistered = function(type) {
|
|
||||||
if (this.missingTypes.length > 0) {
|
|
||||||
var i = this.missingTypes.indexOf(type);
|
|
||||||
if (i != -1) {
|
|
||||||
this.missingTypes.splice(i,1);
|
|
||||||
if (this.missingTypes.length === 0 && this.started) {
|
|
||||||
this.start();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow.prototype.getNode = function(id) {
|
|
||||||
return this.activeNodes[id];
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow.prototype.getFlow = function() {
|
|
||||||
//console.log(this.config);
|
|
||||||
return this.config;
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow.prototype.eachNode = function(callback) {
|
|
||||||
for (var id in this.activeNodes) {
|
|
||||||
if (this.activeNodes.hasOwnProperty(id)) {
|
|
||||||
callback(this.activeNodes[id]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow.prototype.diffConfig = function(config,type) {
|
|
||||||
|
|
||||||
var activeNodesToStop = [];
|
|
||||||
var nodesToRewire = [];
|
|
||||||
|
|
||||||
if (type && type!="full") {
|
|
||||||
var diff = diffFlow(this,config);
|
|
||||||
//var diff = {
|
|
||||||
// deleted:[]
|
|
||||||
// changed:[]
|
|
||||||
// linked:[]
|
|
||||||
// wiringChanged: []
|
|
||||||
//}
|
|
||||||
|
|
||||||
var nodesToStop = [];
|
|
||||||
nodesToRewire = diff.wiringChanged;
|
|
||||||
|
|
||||||
if (type == "nodes") {
|
|
||||||
nodesToStop = diff.deleted.concat(diff.changed);
|
|
||||||
} else if (type == "flows") {
|
|
||||||
nodesToStop = diff.deleted.concat(diff.changed).concat(diff.linked);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i=0;i<nodesToStop.length;i++) {
|
|
||||||
var id = nodesToStop[i];
|
|
||||||
if (this.subflowInstanceNodes[id]) {
|
|
||||||
activeNodesToStop = activeNodesToStop.concat(this.subflowInstanceNodes[id]);
|
|
||||||
} else if (this.activeNodes[id]) {
|
|
||||||
activeNodesToStop.push(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
activeNodesToStop = Object.keys(this.activeNodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: type,
|
|
||||||
stop: activeNodesToStop,
|
|
||||||
rewire: nodesToRewire,
|
|
||||||
config: config
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function diffFlow(flow,config) {
|
|
||||||
|
|
||||||
//if (!flow.started) {
|
|
||||||
// throw new Error("Cannot diff an unstarted flow");
|
|
||||||
//}
|
|
||||||
var flowNodes = {};
|
|
||||||
var changedNodes = {};
|
|
||||||
var deletedNodes = {};
|
|
||||||
var deletedSubflows = {};
|
|
||||||
var deletedTabs = {};
|
|
||||||
var linkChangedNodes = {};
|
|
||||||
|
|
||||||
var activeLinks = {};
|
|
||||||
var newLinks = {};
|
|
||||||
|
|
||||||
var changedSubflowStack = [];
|
|
||||||
var changedSubflows = {};
|
|
||||||
|
|
||||||
var buildNodeLinks = function(nodeLinks,n,nodes) {
|
|
||||||
nodeLinks[n.id] = nodeLinks[n.id] || [];
|
|
||||||
if (n.wires) {
|
|
||||||
for (var j=0;j<n.wires.length;j++) {
|
|
||||||
var wires = n.wires[j];
|
|
||||||
for (var k=0;k<wires.length;k++) {
|
|
||||||
nodeLinks[n.id].push(wires[k]);
|
|
||||||
var nn = nodes[wires[k]];
|
|
||||||
if (nn) {
|
|
||||||
nodeLinks[nn.id] = nodeLinks[nn.id] || [];
|
|
||||||
nodeLinks[nn.id].push(n.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
config.forEach(function(node) {
|
|
||||||
flowNodes[node.id] = node;
|
|
||||||
});
|
|
||||||
|
|
||||||
config.forEach(function(node) {
|
|
||||||
var changed = false;
|
|
||||||
if (node.credentials) {
|
|
||||||
changed = true;
|
|
||||||
delete node.credentials;
|
|
||||||
} else {
|
|
||||||
changed = diffNodeConfigs(flow.allNodes[node.id],node);
|
|
||||||
if (!changed) {
|
|
||||||
if (flowNodes[node.z] && flowNodes[node.z].type == "subflow") {
|
|
||||||
var originalNode = flow.allNodes[node.id];
|
|
||||||
if (originalNode && !redUtil.compareObjects(originalNode.wires,node.wires)) {
|
|
||||||
// This is a node in a subflow whose wiring has changed. Mark subflow as changed
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (changed) {
|
|
||||||
changedNodes[node.id] = node;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
flow.config.forEach(function(node) {
|
|
||||||
if (!flowNodes[node.id]) {
|
|
||||||
if (node.type === "tab") {
|
|
||||||
deletedTabs[node.id] = node;
|
|
||||||
} else if (node.type === "subflow") {
|
|
||||||
deletedSubflows[node.id] = node;
|
|
||||||
} else {
|
|
||||||
deletedNodes[node.id] = node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buildNodeLinks(activeLinks,node,flow.allNodes);
|
|
||||||
});
|
|
||||||
|
|
||||||
flow.config.forEach(function(node) {
|
|
||||||
for (var prop in node) {
|
|
||||||
if (node.hasOwnProperty(prop) && prop != "z" && prop != "id" && prop != "wires") {
|
|
||||||
// This node has a property that references a changed node
|
|
||||||
// Assume it is a config node change and mark this node as
|
|
||||||
// changed.
|
|
||||||
if (changedNodes[node[prop]]) {
|
|
||||||
changedNodes[node.id] = node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
var checkSubflowMembership = function(nodes,id) {
|
|
||||||
var node = nodes[id];
|
|
||||||
if (node) {
|
|
||||||
if (node.type == "subflow") {
|
|
||||||
changedSubflows[id] = node;
|
|
||||||
changedSubflowStack.push(id);
|
|
||||||
} else if (nodes[node.z] && nodes[node.z].type == "subflow") {
|
|
||||||
if (!changedSubflows[node.z]) {
|
|
||||||
changedSubflows[node.z] = nodes[node.z];
|
|
||||||
changedSubflowStack.push(node.z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.keys(changedNodes).forEach(function(n) { checkSubflowMembership(flowNodes,n)});
|
|
||||||
Object.keys(deletedNodes).forEach(function(n) { checkSubflowMembership(flow.allNodes,n)});
|
|
||||||
|
|
||||||
while (changedSubflowStack.length > 0) {
|
|
||||||
var subflowId = changedSubflowStack.pop();
|
|
||||||
|
|
||||||
config.forEach(function(node) {
|
|
||||||
if (node.type == "subflow:"+subflowId) {
|
|
||||||
if (!changedNodes[node.id]) {
|
|
||||||
changedNodes[node.id] = node;
|
|
||||||
checkSubflowMembership(flowNodes,node.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
config.forEach(function(node) {
|
|
||||||
buildNodeLinks(newLinks,node,flowNodes);
|
|
||||||
});
|
|
||||||
|
|
||||||
var markLinkedNodes = function(linkChanged,otherChangedNodes,linkMap,allNodes) {
|
|
||||||
var stack = Object.keys(changedNodes).concat(Object.keys(otherChangedNodes));
|
|
||||||
var visited = {};
|
|
||||||
|
|
||||||
while(stack.length > 0) {
|
|
||||||
var id = stack.pop();
|
|
||||||
var linkedNodes = linkMap[id];
|
|
||||||
if (linkedNodes) {
|
|
||||||
for (var i=0;i<linkedNodes.length;i++) {
|
|
||||||
var linkedNodeId = linkedNodes[i];
|
|
||||||
if (changedNodes[linkedNodeId] || deletedNodes[linkedNodeId] || otherChangedNodes[linkedNodeId] || linkChanged[linkedNodeId]) {
|
|
||||||
// Do nothing - this linked node is already marked as changed, so will get done
|
|
||||||
} else {
|
|
||||||
linkChanged[linkedNodeId] = true;
|
|
||||||
stack.push(linkedNodeId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
markLinkedNodes(linkChangedNodes,{},newLinks,flowNodes);
|
|
||||||
markLinkedNodes(linkChangedNodes,{},activeLinks,flow.allNodes);
|
|
||||||
|
|
||||||
var modifiedLinkNodes = {};
|
|
||||||
|
|
||||||
config.forEach(function(node) {
|
|
||||||
if (!changedNodes[node.id]) {
|
|
||||||
// only concerned about unchanged nodes whose wiring may have changed
|
|
||||||
var newNodeLinks = newLinks[node.id];
|
|
||||||
var oldNodeLinks = activeLinks[node.id];
|
|
||||||
|
|
||||||
var newLinkMap = {};
|
|
||||||
newNodeLinks.forEach(function(l) { newLinkMap[l] = (newLinkMap[l]||0)+1;});
|
|
||||||
|
|
||||||
var oldLinkMap = {};
|
|
||||||
oldNodeLinks.forEach(function(l) { oldLinkMap[l] = (oldLinkMap[l]||0)+1;});
|
|
||||||
|
|
||||||
newNodeLinks.forEach(function(link) {
|
|
||||||
if (newLinkMap[link] != oldLinkMap[link]) {
|
|
||||||
modifiedLinkNodes[node.id] = node;
|
|
||||||
linkChangedNodes[node.id] = node;
|
|
||||||
if (!changedNodes[link] && !deletedNodes[link]) {
|
|
||||||
modifiedLinkNodes[link] = flowNodes[link];
|
|
||||||
linkChangedNodes[link] = flowNodes[link];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
oldNodeLinks.forEach(function(link) {
|
|
||||||
if (newLinkMap[link] != oldLinkMap[link]) {
|
|
||||||
modifiedLinkNodes[node.id] = node;
|
|
||||||
linkChangedNodes[node.id] = node;
|
|
||||||
if (!changedNodes[link] && !deletedNodes[link]) {
|
|
||||||
modifiedLinkNodes[link] = flowNodes[link];
|
|
||||||
linkChangedNodes[link] = flowNodes[link];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
markLinkedNodes(linkChangedNodes,modifiedLinkNodes,newLinks,flowNodes);
|
|
||||||
|
|
||||||
// config.forEach(function(n) {
|
|
||||||
// console.log((changedNodes[n.id]!=null)?"[C]":"[ ]",(linkChangedNodes[n.id]!=null)?"[L]":"[ ]","[ ]",n.id,n.type,n.name);
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// Object.keys(deletedNodes).forEach(function(id) {
|
|
||||||
// var n = flow.allNodes[id];
|
|
||||||
// console.log("[ ] [ ] [D]",n.id,n.type);
|
|
||||||
// });
|
|
||||||
var diff = {
|
|
||||||
deleted: Object.keys(deletedNodes).filter(function(id) { return deletedNodes[id].type != "subflow" && (!deletedNodes[id].z || deletedTabs[deletedNodes[id].z] || !(deletedSubflows[deletedNodes[id].z] || flowNodes[deletedNodes[id].z].type == "subflow"))}),
|
|
||||||
changed: Object.keys(changedNodes).filter(function(id) { return changedNodes[id].type != "subflow" && (!changedNodes[id].z || flowNodes[changedNodes[id].z].type != "subflow")}),
|
|
||||||
linked: Object.keys(linkChangedNodes).filter(function(id) { return linkChangedNodes[id].type != "subflow" && (!linkChangedNodes[id].z || flowNodes[linkChangedNodes[id].z].type != "subflow")}),
|
|
||||||
wiringChanged: []
|
|
||||||
}
|
|
||||||
|
|
||||||
config.forEach(function(n) {
|
|
||||||
if (!flowNodes[n.z] || flowNodes[n.z].type != "subflow") {
|
|
||||||
var originalNode = flow.allNodes[n.id];
|
|
||||||
if (originalNode && !redUtil.compareObjects(originalNode.wires,n.wires)) {
|
|
||||||
diff.wiringChanged.push(n.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return diff;
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow.prototype.handleStatus = function(node,statusMessage) {
|
|
||||||
var targetStatusNodes = null;
|
|
||||||
var reportingNode = node;
|
|
||||||
var handled = false;
|
|
||||||
while(reportingNode && !handled) {
|
|
||||||
targetStatusNodes = this.statusNodeMap[reportingNode.z];
|
|
||||||
if (targetStatusNodes) {
|
|
||||||
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.text) {
|
|
||||||
message.status.text = statusMessage.text;
|
|
||||||
}
|
|
||||||
targetStatusNode.receive(message);
|
|
||||||
handled = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!handled) {
|
|
||||||
reportingNode = this.activeNodes[reportingNode.z];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow.prototype.handleError = function(node,logMessage,msg) {
|
|
||||||
var count = 1;
|
|
||||||
if (msg && msg.hasOwnProperty("error")) {
|
|
||||||
if (msg.error.hasOwnProperty("source")) {
|
|
||||||
if (msg.error.source.id === node.id) {
|
|
||||||
count = msg.error.source.count+1;
|
|
||||||
if (count === 10) {
|
|
||||||
node.warn(Log._("nodes.flow.error-loop"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var targetCatchNodes = null;
|
|
||||||
var throwingNode = node;
|
|
||||||
var handled = false;
|
|
||||||
while (throwingNode && !handled) {
|
|
||||||
targetCatchNodes = this.catchNodeMap[throwingNode.z];
|
|
||||||
if (targetCatchNodes) {
|
|
||||||
targetCatchNodes.forEach(function(targetCatchNode) {
|
|
||||||
if (targetCatchNode.scope && targetCatchNode.scope.indexOf(node.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,
|
|
||||||
count: count
|
|
||||||
}
|
|
||||||
};
|
|
||||||
targetCatchNode.receive(errorMessage);
|
|
||||||
handled = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!handled) {
|
|
||||||
throwingNode = this.activeNodes[throwingNode.z];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = Flow;
|
|
@ -33,12 +33,16 @@ function Node(n) {
|
|||||||
if (n.name) {
|
if (n.name) {
|
||||||
this.name = n.name;
|
this.name = n.name;
|
||||||
}
|
}
|
||||||
|
if (n._alias) {
|
||||||
|
this._alias = n._alias;
|
||||||
|
}
|
||||||
this.updateWires(n.wires);
|
this.updateWires(n.wires);
|
||||||
}
|
}
|
||||||
|
|
||||||
util.inherits(Node, EventEmitter);
|
util.inherits(Node, EventEmitter);
|
||||||
|
|
||||||
Node.prototype.updateWires = function(wires) {
|
Node.prototype.updateWires = function(wires) {
|
||||||
|
//console.log("UPDATE",this.id);
|
||||||
this.wires = wires || [];
|
this.wires = wires || [];
|
||||||
delete this._wire;
|
delete this._wire;
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ module.exports = {
|
|||||||
storage = _storage;
|
storage = _storage;
|
||||||
redApp = _app;
|
redApp = _app;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the credentials from storage.
|
* Loads the credentials from storage.
|
||||||
*/
|
*/
|
||||||
@ -79,7 +79,7 @@ module.exports = {
|
|||||||
log.warn(log._("nodes.credentials.error",{message: err}));
|
log.warn(log._("nodes.credentials.error",{message: err}));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a set of credentials for the given node id.
|
* Adds a set of credentials for the given node id.
|
||||||
* @param id the node id for the credentials
|
* @param id the node id for the credentials
|
||||||
@ -118,7 +118,7 @@ module.exports = {
|
|||||||
clean: function (config) {
|
clean: function (config) {
|
||||||
var existingIds = {};
|
var existingIds = {};
|
||||||
config.forEach(function(n) {
|
config.forEach(function(n) {
|
||||||
existingIds[n.id] = true;
|
existingIds[n.id] = true;
|
||||||
});
|
});
|
||||||
var deletedCredentials = false;
|
var deletedCredentials = false;
|
||||||
for (var c in credentialCache) {
|
for (var c in credentialCache) {
|
||||||
@ -135,7 +135,7 @@ module.exports = {
|
|||||||
return when.resolve();
|
return when.resolve();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a node credential definition.
|
* Registers a node credential definition.
|
||||||
* @param type the node type
|
* @param type the node type
|
||||||
@ -146,14 +146,14 @@ module.exports = {
|
|||||||
credentialsDef[dashedType] = definition;
|
credentialsDef[dashedType] = definition;
|
||||||
registerEndpoint(dashedType);
|
registerEndpoint(dashedType);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts and stores any credential updates in the provided node.
|
* Extracts and stores any credential updates in the provided node.
|
||||||
* The provided node may have a .credentials property that contains
|
* The provided node may have a .credentials property that contains
|
||||||
* new credentials for the node.
|
* new credentials for the node.
|
||||||
* This function loops through the credentials in the definition for
|
* This function loops through the credentials in the definition for
|
||||||
* the node-type and applies any of the updates provided in the node.
|
* the node-type and applies any of the updates provided in the node.
|
||||||
*
|
*
|
||||||
* This function does not save the credentials to disk as it is expected
|
* This function does not save the credentials to disk as it is expected
|
||||||
* to be called multiple times when a new flow is deployed.
|
* to be called multiple times when a new flow is deployed.
|
||||||
*
|
*
|
||||||
@ -171,7 +171,7 @@ module.exports = {
|
|||||||
log.warn(log._("nodes.credentials.not-registered",{type:nodeType}));
|
log.warn(log._("nodes.credentials.not-registered",{type:nodeType}));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var cred in definition) {
|
for (var cred in definition) {
|
||||||
if (definition.hasOwnProperty(cred)) {
|
if (definition.hasOwnProperty(cred)) {
|
||||||
if (newCreds[cred] === undefined) {
|
if (newCreds[cred] === undefined) {
|
||||||
@ -191,7 +191,7 @@ module.exports = {
|
|||||||
delete node.credentials;
|
delete node.credentials;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the credentials to storage
|
* Saves the credentials to storage
|
||||||
* @return a promise for the saving of credentials to storage
|
* @return a promise for the saving of credentials to storage
|
||||||
@ -199,7 +199,7 @@ module.exports = {
|
|||||||
save: function () {
|
save: function () {
|
||||||
return storage.saveCredentials(credentialCache);
|
return storage.saveCredentials(credentialCache);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the credential definition for the given node type
|
* Gets the credential definition for the given node type
|
||||||
* @param type the node type
|
* @param type the node type
|
||||||
|
@ -1,199 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2014, 2015 IBM Corp.
|
|
||||||
*
|
|
||||||
* 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 clone = require("clone");
|
|
||||||
var when = require("when");
|
|
||||||
|
|
||||||
var typeRegistry = require("./registry");
|
|
||||||
var credentials = require("./credentials");
|
|
||||||
var Flow = require("./Flow");
|
|
||||||
var log = require("../log");
|
|
||||||
|
|
||||||
var events = require("../events");
|
|
||||||
var redUtil = require("../util");
|
|
||||||
var storage = null;
|
|
||||||
var settings = null;
|
|
||||||
var deprecated = require("./deprecated");
|
|
||||||
|
|
||||||
var activeFlow = null;
|
|
||||||
|
|
||||||
var nodes = {};
|
|
||||||
var subflows = {};
|
|
||||||
var activeConfig = [];
|
|
||||||
var activeConfigNodes = {};
|
|
||||||
|
|
||||||
events.on('type-registered',function(type) {
|
|
||||||
if (activeFlow && activeFlow.typeRegistered(type)) {
|
|
||||||
log.info(log._("nodes.flows.registered-missing", {type:type}));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var flowNodes = module.exports = {
|
|
||||||
init: function(_settings, _storage) {
|
|
||||||
settings = _settings;
|
|
||||||
storage = _storage;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the current activeConfig from storage and start it running
|
|
||||||
* @return a promise for the loading of the config
|
|
||||||
*/
|
|
||||||
load: function() {
|
|
||||||
return storage.getFlows().then(function(flows) {
|
|
||||||
return credentials.load().then(function() {
|
|
||||||
activeFlow = new Flow(flows);
|
|
||||||
flowNodes.startFlows();
|
|
||||||
});
|
|
||||||
}).otherwise(function(err) {
|
|
||||||
log.warn(log._("nodes.flows.error",{message:err.toString()}));
|
|
||||||
console.log(err.stack);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a node
|
|
||||||
* @param i the node id
|
|
||||||
* @return the node
|
|
||||||
*/
|
|
||||||
get: function(i) {
|
|
||||||
return activeFlow.getNode(i);
|
|
||||||
},
|
|
||||||
|
|
||||||
eachNode: function(cb) {
|
|
||||||
activeFlow.eachNode(cb);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the active configuration
|
|
||||||
*/
|
|
||||||
getFlows: function() {
|
|
||||||
return activeFlow.getFlow();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the current active config.
|
|
||||||
* @param config the configuration to enable
|
|
||||||
* @param type the type of deployment to do: full (default), nodes, flows
|
|
||||||
* @return a promise for the starting of the new flow
|
|
||||||
*/
|
|
||||||
setFlows: function (config,type) {
|
|
||||||
|
|
||||||
type = type||"full";
|
|
||||||
|
|
||||||
var credentialsChanged = false;
|
|
||||||
|
|
||||||
var credentialSavePromise = null;
|
|
||||||
|
|
||||||
|
|
||||||
// Clone config and extract credentials prior to saving
|
|
||||||
// Original config needs to retain credentials so that flow.applyConfig
|
|
||||||
// knows which nodes have had changes.
|
|
||||||
var cleanConfig = clone(config);
|
|
||||||
cleanConfig.forEach(function(node) {
|
|
||||||
if (node.credentials) {
|
|
||||||
credentials.extract(node);
|
|
||||||
credentialsChanged = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (credentialsChanged) {
|
|
||||||
credentialSavePromise = credentials.save();
|
|
||||||
} else {
|
|
||||||
credentialSavePromise = when.resolve();
|
|
||||||
}
|
|
||||||
if (type=="full") {
|
|
||||||
return credentialSavePromise
|
|
||||||
.then(function() { return storage.saveFlows(cleanConfig);})
|
|
||||||
.then(function() { return flowNodes.stopFlows(); })
|
|
||||||
.then(function() { activeFlow = new Flow(config); flowNodes.startFlows();});
|
|
||||||
} else {
|
|
||||||
return credentialSavePromise
|
|
||||||
.then(function() { return storage.saveFlows(cleanConfig);})
|
|
||||||
.then(function() {
|
|
||||||
var configDiff = activeFlow.diffConfig(config,type);
|
|
||||||
return flowNodes.stopFlows(configDiff).then(function() {
|
|
||||||
activeFlow.parseConfig(config);
|
|
||||||
flowNodes.startFlows(configDiff);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
startFlows: function(configDiff) {
|
|
||||||
if (configDiff) {
|
|
||||||
log.info(log._("nodes.flows.starting-modified-"+configDiff.type));
|
|
||||||
} else {
|
|
||||||
log.info(log._("nodes.flows.starting-flows"));
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
activeFlow.start(configDiff);
|
|
||||||
if (configDiff) {
|
|
||||||
log.info(log._("nodes.flows.started-modified-"+configDiff.type));
|
|
||||||
} else {
|
|
||||||
log.info(log._("nodes.flows.started-flows"));
|
|
||||||
}
|
|
||||||
} catch(err) {
|
|
||||||
var missingTypes = activeFlow.getMissingTypes();
|
|
||||||
if (missingTypes.length > 0) {
|
|
||||||
log.info(log._("nodes.flows.missing-types"));
|
|
||||||
var knownUnknowns = 0;
|
|
||||||
for (var i=0;i<missingTypes.length;i++) {
|
|
||||||
var type = missingTypes[i];
|
|
||||||
var info = deprecated.get(type);
|
|
||||||
if (info) {
|
|
||||||
log.info(log._("nodes.flows.missing-type-provided",{type:missingTypes[i],module:info.module}));
|
|
||||||
knownUnknowns += 1;
|
|
||||||
} else {
|
|
||||||
log.info(" - "+missingTypes[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (knownUnknowns > 0) {
|
|
||||||
log.info(log._("nodes.flows.missing-type-install-1"));
|
|
||||||
log.info(" npm install <module name>");
|
|
||||||
log.info(log._("nodes.flows.missing-type-install-2"));
|
|
||||||
log.info(" "+settings.userDir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stopFlows: function(configDiff) {
|
|
||||||
if (configDiff) {
|
|
||||||
log.info(log._("nodes.flows.stopping-modified-"+configDiff.type));
|
|
||||||
} else {
|
|
||||||
log.info(log._("nodes.flows.stopping-flows"));
|
|
||||||
}
|
|
||||||
if (activeFlow) {
|
|
||||||
return activeFlow.stop(configDiff).then(function() {
|
|
||||||
if (configDiff) {
|
|
||||||
log.info(log._("nodes.flows.stopped-modified-"+configDiff.type));
|
|
||||||
} else {
|
|
||||||
log.info(log._("nodes.flows.stopped-flows"));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
log.info(log._("nodes.flows.stopped"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleError: function(node,logMessage,msg) {
|
|
||||||
activeFlow.handleError(node,logMessage,msg);
|
|
||||||
},
|
|
||||||
handleStatus: function(node,statusMessage) {
|
|
||||||
activeFlow.handleStatus(node,statusMessage);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var activeFlow = null;
|
|
466
red/nodes/flows/Flow.js
Normal file
466
red/nodes/flows/Flow.js
Normal file
@ -0,0 +1,466 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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 when = require("when");
|
||||||
|
var clone = require("clone");
|
||||||
|
var typeRegistry = require("../registry");
|
||||||
|
var Log = require("../../log");
|
||||||
|
var redUtil = require("../../util");
|
||||||
|
var flowUtil = require("./util");
|
||||||
|
|
||||||
|
function Flow(global,flow) {
|
||||||
|
if (typeof flow === 'undefined') {
|
||||||
|
flow = global;
|
||||||
|
}
|
||||||
|
var activeNodes = {};
|
||||||
|
var subflowInstanceNodes = {};
|
||||||
|
var catchNodeMap = {};
|
||||||
|
var statusNodeMap = {};
|
||||||
|
|
||||||
|
this.start = function(diff) {
|
||||||
|
var node;
|
||||||
|
var id;
|
||||||
|
catchNodeMap = {};
|
||||||
|
statusNodeMap = {};
|
||||||
|
for (id in flow.configs) {
|
||||||
|
if (flow.configs.hasOwnProperty(id)) {
|
||||||
|
node = flow.configs[id];
|
||||||
|
if (!activeNodes[id]) {
|
||||||
|
activeNodes[id] = createNode(node.type,node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (diff) {
|
||||||
|
for (var j=0;j<diff.rewired.length;j++) {
|
||||||
|
var rewireNode = activeNodes[diff.rewired[j]];
|
||||||
|
if (rewireNode) {
|
||||||
|
rewireNode.updateWires(flow.nodes[rewireNode.id].wires);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (id in flow.nodes) {
|
||||||
|
if (flow.nodes.hasOwnProperty(id)) {
|
||||||
|
node = flow.nodes[id];
|
||||||
|
if (!node.subflow) {
|
||||||
|
if (!activeNodes[id]) {
|
||||||
|
activeNodes[id] = createNode(node.type,node);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!subflowInstanceNodes[id]) {
|
||||||
|
try {
|
||||||
|
var nodes = createSubflow(flow.subflows[node.subflow]||global.subflows[node.subflow],node,flow.subflows,global.subflows,activeNodes);
|
||||||
|
subflowInstanceNodes[id] = nodes.map(function(n) { return n.id});
|
||||||
|
for (var i=0;i<nodes.length;i++) {
|
||||||
|
activeNodes[nodes[i].id] = nodes[i];
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
console.log(err.stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (id in activeNodes) {
|
||||||
|
if (activeNodes.hasOwnProperty(id)) {
|
||||||
|
node = activeNodes[id];
|
||||||
|
if (node.type === "catch") {
|
||||||
|
catchNodeMap[node.z] = catchNodeMap[node.z] || [];
|
||||||
|
catchNodeMap[node.z].push(node);
|
||||||
|
} else if (node.type === "status") {
|
||||||
|
statusNodeMap[node.z] = statusNodeMap[node.z] || [];
|
||||||
|
statusNodeMap[node.z].push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stop = function(stopList) {
|
||||||
|
return when.promise(function(resolve) {
|
||||||
|
var i;
|
||||||
|
if (stopList) {
|
||||||
|
for (i=0;i<stopList.length;i++) {
|
||||||
|
if (subflowInstanceNodes[stopList[i]]) {
|
||||||
|
// The first in the list is the instance node we already
|
||||||
|
// know about
|
||||||
|
stopList = stopList.concat(subflowInstanceNodes[stopList[i]].slice(1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stopList = Object.keys(activeNodes);
|
||||||
|
}
|
||||||
|
var promises = [];
|
||||||
|
for (i=0;i<stopList.length;i++) {
|
||||||
|
var node = activeNodes[stopList[i]];
|
||||||
|
if (node) {
|
||||||
|
delete activeNodes[stopList[i]];
|
||||||
|
if (subflowInstanceNodes[stopList[i]]) {
|
||||||
|
delete subflowInstanceNodes[stopList[i]];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var p = node.close();
|
||||||
|
if (p) {
|
||||||
|
promises.push(p);
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
node.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
when.settle(promises).then(function() {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.update = function(_global,_flow) {
|
||||||
|
global = _global;
|
||||||
|
flow = _flow;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getNode = function(id) {
|
||||||
|
return activeNodes[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getActiveNodes = function() {
|
||||||
|
return activeNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handleStatus = function(node,statusMessage) {
|
||||||
|
var targetStatusNodes = null;
|
||||||
|
var reportingNode = node;
|
||||||
|
var handled = false;
|
||||||
|
while(reportingNode && !handled) {
|
||||||
|
targetStatusNodes = statusNodeMap[reportingNode.z];
|
||||||
|
if (targetStatusNodes) {
|
||||||
|
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.text) {
|
||||||
|
message.status.text = statusMessage.text;
|
||||||
|
}
|
||||||
|
targetStatusNode.receive(message);
|
||||||
|
handled = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!handled) {
|
||||||
|
reportingNode = activeNodes[reportingNode.z];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handleError = function(node,logMessage,msg) {
|
||||||
|
var count = 1;
|
||||||
|
if (msg && msg.hasOwnProperty("error")) {
|
||||||
|
if (msg.error.hasOwnProperty("source")) {
|
||||||
|
if (msg.error.source.id === node.id) {
|
||||||
|
count = msg.error.source.count+1;
|
||||||
|
if (count === 10) {
|
||||||
|
node.warn(Log._("nodes.flow.error-loop"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var targetCatchNodes = null;
|
||||||
|
var throwingNode = node;
|
||||||
|
var handled = false;
|
||||||
|
while (throwingNode && !handled) {
|
||||||
|
targetCatchNodes = catchNodeMap[throwingNode.z];
|
||||||
|
if (targetCatchNodes) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
targetCatchNode.receive(errorMessage);
|
||||||
|
handled = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!handled) {
|
||||||
|
throwingNode = activeNodes[throwingNode.z];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getID() {
|
||||||
|
return (1+Math.random()*4294967295).toString(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
var EnvVarPropertyRE = /^\$\((\S+)\)$/;
|
||||||
|
|
||||||
|
function mapEnvVarProperties(obj,prop) {
|
||||||
|
if (Buffer.isBuffer(obj[prop])) {
|
||||||
|
return;
|
||||||
|
} else if (Array.isArray(obj[prop])) {
|
||||||
|
for (var i=0;i<obj[prop].length;i++) {
|
||||||
|
mapEnvVarProperties(obj[prop],i);
|
||||||
|
}
|
||||||
|
} else if (typeof obj[prop] === 'string') {
|
||||||
|
var m;
|
||||||
|
if ( (m = EnvVarPropertyRE.exec(obj[prop])) !== null) {
|
||||||
|
if (process.env.hasOwnProperty(m[1])) {
|
||||||
|
obj[prop] = process.env[m[1]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (var p in obj[prop]) {
|
||||||
|
if (obj[prop].hasOwnProperty) {
|
||||||
|
mapEnvVarProperties(obj[prop],p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNode(type,config) {
|
||||||
|
// console.log("CREATE",type,config.id);
|
||||||
|
var nn = null;
|
||||||
|
var nt = typeRegistry.get(type);
|
||||||
|
if (nt) {
|
||||||
|
var conf = clone(config);
|
||||||
|
delete conf.credentials;
|
||||||
|
for (var p in conf) {
|
||||||
|
if (conf.hasOwnProperty(p)) {
|
||||||
|
mapEnvVarProperties(conf,p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
nn = new nt(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}));
|
||||||
|
}
|
||||||
|
return nn;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = getID();
|
||||||
|
node_map[node.id] = node;
|
||||||
|
node._alias = node.id;
|
||||||
|
node.id = nid;
|
||||||
|
node.z = sfn.id;
|
||||||
|
newNodes.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone all of the subflow 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);
|
||||||
|
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 = {
|
||||||
|
create: function(global,conf) {
|
||||||
|
return new Flow(global,conf);
|
||||||
|
}
|
||||||
|
}
|
366
red/nodes/flows/index.js
Normal file
366
red/nodes/flows/index.js
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2014, 2015 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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 clone = require("clone");
|
||||||
|
var when = require("when");
|
||||||
|
|
||||||
|
var Flow = require('./Flow');
|
||||||
|
|
||||||
|
var typeRegistry = require("../registry");
|
||||||
|
var credentials = require("../credentials");
|
||||||
|
|
||||||
|
var flowUtil = require("./util");
|
||||||
|
var log = require("../../log");
|
||||||
|
var events = require("../../events");
|
||||||
|
var redUtil = require("../../util");
|
||||||
|
var deprecated = require("../registry/deprecated");
|
||||||
|
|
||||||
|
var storage = null;
|
||||||
|
var settings = null;
|
||||||
|
|
||||||
|
var activeConfig = null;
|
||||||
|
var activeFlowConfig = null;
|
||||||
|
|
||||||
|
var activeFlows = {};
|
||||||
|
var started = false;
|
||||||
|
|
||||||
|
var activeNodesToFlow = {};
|
||||||
|
var subflowInstanceNodeMap = {};
|
||||||
|
|
||||||
|
var typeEventRegistered = false;
|
||||||
|
|
||||||
|
function init(_settings, _storage) {
|
||||||
|
if (started) {
|
||||||
|
throw new Error("Cannot init without a stop");
|
||||||
|
}
|
||||||
|
settings = _settings;
|
||||||
|
storage = _storage;
|
||||||
|
started = false;
|
||||||
|
if (!typeEventRegistered) {
|
||||||
|
events.on('type-registered',function(type) {
|
||||||
|
if (activeFlowConfig && activeFlowConfig.missingTypes.length > 0) {
|
||||||
|
var i = activeFlowConfig.missingTypes.indexOf(type);
|
||||||
|
if (i != -1) {
|
||||||
|
log.info(log._("nodes.flows.registered-missing", {type:type}));
|
||||||
|
activeFlowConfig.missingTypes.splice(i,1);
|
||||||
|
if (activeFlowConfig.missingTypes.length === 0 && started) {
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
typeEventRegistered = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function load() {
|
||||||
|
return storage.getFlows().then(function(flows) {
|
||||||
|
return credentials.load().then(function() {
|
||||||
|
return setConfig(flows,"load");
|
||||||
|
});
|
||||||
|
}).otherwise(function(err) {
|
||||||
|
log.warn(log._("nodes.flows.error",{message:err.toString()}));
|
||||||
|
console.log(err.stack);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setConfig(_config,type) {
|
||||||
|
var config = clone(_config);
|
||||||
|
type = type||"full";
|
||||||
|
|
||||||
|
var credentialsChanged = false;
|
||||||
|
var credentialSavePromise = null;
|
||||||
|
var configSavePromise = null;
|
||||||
|
|
||||||
|
var diff;
|
||||||
|
var newFlowConfig = flowUtil.parseConfig(clone(config));
|
||||||
|
if (type !== 'full' && type !== 'load') {
|
||||||
|
diff = flowUtil.diffConfigs(activeFlowConfig,newFlowConfig);
|
||||||
|
}
|
||||||
|
config.forEach(function(node) {
|
||||||
|
if (node.credentials) {
|
||||||
|
credentials.extract(node);
|
||||||
|
credentialsChanged = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (credentialsChanged) {
|
||||||
|
credentialSavePromise = credentials.save();
|
||||||
|
} else {
|
||||||
|
credentialSavePromise = when.resolve();
|
||||||
|
}
|
||||||
|
if (type === 'load') {
|
||||||
|
configSavePromise = credentialSavePromise;
|
||||||
|
type = 'full';
|
||||||
|
} else {
|
||||||
|
configSavePromise = credentialSavePromise.then(function() {
|
||||||
|
return storage.saveFlows(config);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return configSavePromise
|
||||||
|
.then(function() {
|
||||||
|
activeConfig = config;
|
||||||
|
activeFlowConfig = newFlowConfig;
|
||||||
|
return credentials.clean(activeConfig).then(function() {
|
||||||
|
if (started) {
|
||||||
|
return stop(type,diff).then(function() {
|
||||||
|
start(type,diff);
|
||||||
|
}).otherwise(function(err) {
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNode(id) {
|
||||||
|
var node;
|
||||||
|
if (activeNodesToFlow[id]) {
|
||||||
|
return activeFlows[activeNodesToFlow[id]].getNode(id);
|
||||||
|
}
|
||||||
|
for (var flowId in activeFlows) {
|
||||||
|
if (activeFlows.hasOwnProperty(flowId)) {
|
||||||
|
node = activeFlows[flowId].getNode(id);
|
||||||
|
if (node) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function eachNode(cb) {
|
||||||
|
for (var id in activeFlowConfig.allNodes) {
|
||||||
|
if (activeFlowConfig.allNodes.hasOwnProperty(id)) {
|
||||||
|
cb(activeFlowConfig.allNodes[id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConfig() {
|
||||||
|
return activeConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
function delegateError(node,logMessage,msg) {
|
||||||
|
if (activeFlows[node.z]) {
|
||||||
|
activeFlows[node.z].handleError(node,logMessage,msg);
|
||||||
|
} else if (activeNodesToFlow[node.z]) {
|
||||||
|
activeFlows[activeNodesToFlow[node.z]].handleError(node,logMessage,msg);
|
||||||
|
} else if (activeFlowConfig.subflows[node.z]) {
|
||||||
|
subflowInstanceNodeMap[node.id].forEach(function(n) {
|
||||||
|
delegateError(getNode(n),logMessage,msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleError(node,logMessage,msg) {
|
||||||
|
if (node.z) {
|
||||||
|
delegateError(node,logMessage,msg);
|
||||||
|
} else {
|
||||||
|
if (activeFlowConfig.configs[node.id]) {
|
||||||
|
activeFlowConfig.configs[node.id]._users.forEach(function(id) {
|
||||||
|
var userNode = activeFlowConfig.allNodes[id];
|
||||||
|
delegateError(userNode,logMessage,msg);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function delegateStatus(node,statusMessage) {
|
||||||
|
if (activeFlows[node.z]) {
|
||||||
|
activeFlows[node.z].handleStatus(node,statusMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleStatus(node,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) {
|
||||||
|
type = type||"full";
|
||||||
|
started = true;
|
||||||
|
var i;
|
||||||
|
if (activeFlowConfig.missingTypes.length > 0) {
|
||||||
|
log.info(log._("nodes.flows.missing-types"));
|
||||||
|
var knownUnknowns = 0;
|
||||||
|
for (i=0;i<activeFlowConfig.missingTypes.length;i++) {
|
||||||
|
var nodeType = activeFlowConfig.missingTypes[i];
|
||||||
|
var info = deprecated.get(nodeType);
|
||||||
|
if (info) {
|
||||||
|
log.info(log._("nodes.flows.missing-type-provided",{type:activeFlowConfig.missingTypes[i],module:info.module}));
|
||||||
|
knownUnknowns += 1;
|
||||||
|
} else {
|
||||||
|
log.info(" - "+activeFlowConfig.missingTypes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (knownUnknowns > 0) {
|
||||||
|
log.info(log._("nodes.flows.missing-type-install-1"));
|
||||||
|
log.info(" npm install <module name>");
|
||||||
|
log.info(log._("nodes.flows.missing-type-install-2"));
|
||||||
|
log.info(" "+settings.userDir);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (diff) {
|
||||||
|
log.info(log._("nodes.flows.starting-modified-"+type));
|
||||||
|
} else {
|
||||||
|
log.info(log._("nodes.flows.starting-flows"));
|
||||||
|
}
|
||||||
|
var id;
|
||||||
|
if (!diff) {
|
||||||
|
activeFlows['_GLOBAL_'] = Flow.create(activeFlowConfig);
|
||||||
|
for (id in activeFlowConfig.flows) {
|
||||||
|
if (activeFlowConfig.flows.hasOwnProperty(id)) {
|
||||||
|
activeFlows[id] = Flow.create(activeFlowConfig,activeFlowConfig.flows[id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
activeFlows['_GLOBAL_'].update(activeFlowConfig,activeFlowConfig);
|
||||||
|
for (id in activeFlowConfig.flows) {
|
||||||
|
if (activeFlowConfig.flows.hasOwnProperty(id)) {
|
||||||
|
if (activeFlows[id]) {
|
||||||
|
activeFlows[id].update(activeFlowConfig,activeFlowConfig.flows[id]);
|
||||||
|
} else {
|
||||||
|
activeFlows[id] = Flow.create(activeFlowConfig,activeFlowConfig.flows[id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (id in activeFlows) {
|
||||||
|
if (activeFlows.hasOwnProperty(id)) {
|
||||||
|
activeFlows[id].start(diff);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
events.emit("nodes-started");
|
||||||
|
if (diff) {
|
||||||
|
log.info(log._("nodes.flows.started-modified-"+type));
|
||||||
|
} else {
|
||||||
|
log.info(log._("nodes.flows.started-flows"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop(type,diff) {
|
||||||
|
type = type||"full";
|
||||||
|
if (diff) {
|
||||||
|
log.info(log._("nodes.flows.stopping-modified-"+type));
|
||||||
|
} else {
|
||||||
|
log.info(log._("nodes.flows.stopping-flows"));
|
||||||
|
}
|
||||||
|
started = false;
|
||||||
|
var promises = [];
|
||||||
|
var stopList;
|
||||||
|
if (type === 'nodes') {
|
||||||
|
stopList = diff.changed.concat(diff.removed);
|
||||||
|
} else if (type === 'flows') {
|
||||||
|
stopList = diff.changed.concat(diff.removed).concat(diff.linked);
|
||||||
|
}
|
||||||
|
for (var id in activeFlows) {
|
||||||
|
if (activeFlows.hasOwnProperty(id)) {
|
||||||
|
promises = promises.concat(activeFlows[id].stop(stopList));
|
||||||
|
if (!diff || diff.removed[id]) {
|
||||||
|
delete activeFlows[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return when.promise(function(resolve,reject) {
|
||||||
|
when.settle(promises).then(function() {
|
||||||
|
for (id in activeNodesToFlow) {
|
||||||
|
if (activeNodesToFlow.hasOwnProperty(id)) {
|
||||||
|
if (!activeFlows[activeNodesToFlow[id]]) {
|
||||||
|
delete activeNodesToFlow[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (stopList) {
|
||||||
|
stopList.forEach(function(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 (diff) {
|
||||||
|
log.info(log._("nodes.flows.stopped-modified-"+type));
|
||||||
|
} else {
|
||||||
|
log.info(log._("nodes.flows.stopped-flows"));
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: init,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the current flow configuration from storage
|
||||||
|
* @return a promise for the loading of the config
|
||||||
|
*/
|
||||||
|
load: load,
|
||||||
|
|
||||||
|
get:getNode,
|
||||||
|
eachNode: eachNode,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current flow configuration
|
||||||
|
*/
|
||||||
|
getFlows: getConfig,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current active config.
|
||||||
|
* @param config the configuration to enable
|
||||||
|
* @param type the type of deployment to do: full (default), nodes, flows, load
|
||||||
|
* @return a promise for the saving/starting of the new flow
|
||||||
|
*/
|
||||||
|
setFlows: setConfig,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the current flow configuration
|
||||||
|
*/
|
||||||
|
startFlows: start,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the current flow configuration
|
||||||
|
* @return a promise for the stopping of the flow
|
||||||
|
*/
|
||||||
|
stopFlows: stop,
|
||||||
|
|
||||||
|
|
||||||
|
handleError: handleError,
|
||||||
|
handleStatus: handleStatus
|
||||||
|
};
|
327
red/nodes/flows/util.js
Normal file
327
red/nodes/flows/util.js
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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 clone = require("clone");
|
||||||
|
var redUtil = require("../../util");
|
||||||
|
var subflowInstanceRE = /^subflow:(.+)$/;
|
||||||
|
var typeRegistry = require("../registry");
|
||||||
|
|
||||||
|
function diffNodes(oldNode,newNode) {
|
||||||
|
if (oldNode == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
var oldKeys = Object.keys(oldNode).filter(function(p) { return p != "x" && p != "y" && p != "wires" });
|
||||||
|
var newKeys = Object.keys(newNode).filter(function(p) { return p != "x" && p != "y" && p != "wires" });
|
||||||
|
if (oldKeys.length != newKeys.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (var i=0;i<newKeys.length;i++) {
|
||||||
|
var p = newKeys[i];
|
||||||
|
if (!redUtil.compareObjects(oldNode[p],newNode[p])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
diffNodes: diffNodes,
|
||||||
|
|
||||||
|
parseConfig: function(config) {
|
||||||
|
var flow = {};
|
||||||
|
flow.allNodes = {};
|
||||||
|
flow.subflows = {};
|
||||||
|
flow.configs = {};
|
||||||
|
flow.flows = {};
|
||||||
|
flow.missingTypes = [];
|
||||||
|
|
||||||
|
config.forEach(function(n) {
|
||||||
|
flow.allNodes[n.id] = clone(n);
|
||||||
|
if (n.type === 'tab') {
|
||||||
|
flow.flows[n.id] = n;
|
||||||
|
flow.flows[n.id].subflows = {};
|
||||||
|
flow.flows[n.id].configs = {};
|
||||||
|
flow.flows[n.id].nodes = {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
config.forEach(function(n) {
|
||||||
|
if (n.type === 'subflow') {
|
||||||
|
flow.subflows[n.id] = n;
|
||||||
|
flow.subflows[n.id].configs = {};
|
||||||
|
flow.subflows[n.id].nodes = {};
|
||||||
|
flow.subflows[n.id].instances = [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
config.forEach(function(n) {
|
||||||
|
if (n.type !== 'subflow' && n.type !== 'tab') {
|
||||||
|
var subflowDetails = subflowInstanceRE.exec(n.type);
|
||||||
|
|
||||||
|
if ( (subflowDetails && !flow.subflows[subflowDetails[1]]) || (!subflowDetails && !typeRegistry.get(n.type)) ) {
|
||||||
|
if (flow.missingTypes.indexOf(n.type) === -1) {
|
||||||
|
flow.missingTypes.push(n.type);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var container = null;
|
||||||
|
if (flow.flows[n.z]) {
|
||||||
|
container = flow.flows[n.z];
|
||||||
|
} else if (flow.subflows[n.z]) {
|
||||||
|
container = flow.subflows[n.z];
|
||||||
|
}
|
||||||
|
if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) {
|
||||||
|
if (subflowDetails) {
|
||||||
|
var subflowType = subflowDetails[1]
|
||||||
|
n.subflow = subflowType;
|
||||||
|
flow.subflows[subflowType].instances.push(n)
|
||||||
|
}
|
||||||
|
if (container) {
|
||||||
|
container.nodes[n.id] = n;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (container) {
|
||||||
|
container.configs[n.id] = n;
|
||||||
|
} else {
|
||||||
|
flow.configs[n.id] = n;
|
||||||
|
flow.configs[n.id]._users = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
config.forEach(function(n) {
|
||||||
|
if (n.type !== 'subflow' && n.type !== 'tab') {
|
||||||
|
for (var prop in n) {
|
||||||
|
if (n.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== '_users' && flow.configs[n[prop]]) {
|
||||||
|
// This property references a global config node
|
||||||
|
flow.configs[n[prop]]._users.push(n.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return flow;
|
||||||
|
},
|
||||||
|
|
||||||
|
diffConfigs: function(oldConfig, newConfig) {
|
||||||
|
var id;
|
||||||
|
var node;
|
||||||
|
var nn;
|
||||||
|
var wires;
|
||||||
|
var j,k;
|
||||||
|
|
||||||
|
var changedSubflows = {};
|
||||||
|
|
||||||
|
var added = {};
|
||||||
|
var removed = {};
|
||||||
|
var changed = {};
|
||||||
|
var wiringChanged = {};
|
||||||
|
|
||||||
|
var linkMap = {};
|
||||||
|
|
||||||
|
for (id in oldConfig.allNodes) {
|
||||||
|
if (oldConfig.allNodes.hasOwnProperty(id)) {
|
||||||
|
node = oldConfig.allNodes[id];
|
||||||
|
// build the map of what this node was previously wired to
|
||||||
|
if (node.wires) {
|
||||||
|
linkMap[node.id] = linkMap[node.id] || [];
|
||||||
|
for (j=0;j<node.wires.length;j++) {
|
||||||
|
wires = node.wires[j];
|
||||||
|
for (k=0;k<wires.length;k++) {
|
||||||
|
linkMap[node.id].push(wires[k]);
|
||||||
|
nn = oldConfig.allNodes[wires[k]];
|
||||||
|
if (nn) {
|
||||||
|
linkMap[nn.id] = linkMap[nn.id] || [];
|
||||||
|
linkMap[nn.id].push(node.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This node has been removed
|
||||||
|
if (!newConfig.allNodes.hasOwnProperty(id)) {
|
||||||
|
removed[id] = node;
|
||||||
|
// Mark the container as changed
|
||||||
|
if (newConfig.allNodes[removed[id].z]) {
|
||||||
|
changed[removed[id].z] = newConfig.allNodes[removed[id].z];
|
||||||
|
if (changed[removed[id].z].type === "subflow") {
|
||||||
|
changedSubflows[removed[id].z] = changed[removed[id].z];
|
||||||
|
delete removed[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This node has a material configuration change
|
||||||
|
if (diffNodes(node,newConfig.allNodes[id]) || newConfig.allNodes[id].credentials) {
|
||||||
|
changed[id] = newConfig.allNodes[id];
|
||||||
|
if (changed[id].type === "subflow") {
|
||||||
|
changedSubflows[id] = changed[id];
|
||||||
|
}
|
||||||
|
// Mark the container as changed
|
||||||
|
if (newConfig.allNodes[changed[id].z]) {
|
||||||
|
changed[changed[id].z] = newConfig.allNodes[changed[id].z];
|
||||||
|
if (changed[changed[id].z].type === "subflow") {
|
||||||
|
changedSubflows[changed[id].z] = changed[changed[id].z];
|
||||||
|
delete changed[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This node's wiring has changed
|
||||||
|
if (!redUtil.compareObjects(node.wires,newConfig.allNodes[id].wires)) {
|
||||||
|
wiringChanged[id] = newConfig.allNodes[id];
|
||||||
|
// Mark the container as changed
|
||||||
|
if (newConfig.allNodes[wiringChanged[id].z]) {
|
||||||
|
changed[wiringChanged[id].z] = newConfig.allNodes[wiringChanged[id].z];
|
||||||
|
if (changed[wiringChanged[id].z].type === "subflow") {
|
||||||
|
changedSubflows[wiringChanged[id].z] = changed[wiringChanged[id].z];
|
||||||
|
delete wiringChanged[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Look for added nodes
|
||||||
|
for (id in newConfig.allNodes) {
|
||||||
|
if (newConfig.allNodes.hasOwnProperty(id)) {
|
||||||
|
node = newConfig.allNodes[id];
|
||||||
|
// build the map of what this node is now wired to
|
||||||
|
if (node.wires) {
|
||||||
|
linkMap[node.id] = linkMap[node.id] || [];
|
||||||
|
for (j=0;j<node.wires.length;j++) {
|
||||||
|
wires = node.wires[j];
|
||||||
|
for (k=0;k<wires.length;k++) {
|
||||||
|
if (linkMap[node.id].indexOf(wires[k]) === -1) {
|
||||||
|
linkMap[node.id].push(wires[k]);
|
||||||
|
}
|
||||||
|
nn = newConfig.allNodes[wires[k]];
|
||||||
|
if (nn) {
|
||||||
|
linkMap[nn.id] = linkMap[nn.id] || [];
|
||||||
|
if (linkMap[nn.id].indexOf(node.id) === -1) {
|
||||||
|
linkMap[nn.id].push(node.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This node has been added
|
||||||
|
if (!oldConfig.allNodes.hasOwnProperty(id)) {
|
||||||
|
added[id] = node;
|
||||||
|
// Mark the container as changed
|
||||||
|
if (newConfig.allNodes[added[id].z]) {
|
||||||
|
changed[added[id].z] = newConfig.allNodes[added[id].z];
|
||||||
|
if (changed[added[id].z].type === "subflow") {
|
||||||
|
changedSubflows[added[id].z] = changed[added[id].z];
|
||||||
|
delete added[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (id in newConfig.allNodes) {
|
||||||
|
if (newConfig.allNodes.hasOwnProperty(id)) {
|
||||||
|
node = newConfig.allNodes[id];
|
||||||
|
for (var prop in node) {
|
||||||
|
if (node.hasOwnProperty(prop) && prop != "z" && prop != "id" && prop != "wires") {
|
||||||
|
// This node has a property that references a changed/removed node
|
||||||
|
// Assume it is a config node change and mark this node as
|
||||||
|
// changed.
|
||||||
|
if (changed[node[prop]] || removed[node[prop]]) {
|
||||||
|
if (!changed[node.id]) {
|
||||||
|
changed[node.id] = node;
|
||||||
|
if (newConfig.allNodes[node.z]) {
|
||||||
|
changed[node.z] = newConfig.allNodes[node.z];
|
||||||
|
if (changed[node.z].type === "subflow") {
|
||||||
|
changedSubflows[node.z] = changed[node.z];
|
||||||
|
delete changed[node.id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Recursively mark all instances of changed subflows as changed
|
||||||
|
var changedSubflowStack = Object.keys(changedSubflows);
|
||||||
|
while(changedSubflowStack.length > 0) {
|
||||||
|
var subflowId = changedSubflowStack.pop();
|
||||||
|
for (id in newConfig.allNodes) {
|
||||||
|
if (newConfig.allNodes.hasOwnProperty(id)) {
|
||||||
|
node = newConfig.allNodes[id];
|
||||||
|
if (node.type === 'subflow:'+subflowId) {
|
||||||
|
if (!changed[node.id]) {
|
||||||
|
changed[node.id] = node;
|
||||||
|
if (!changed[changed[node.id].z] && newConfig.allNodes[changed[node.id].z]) {
|
||||||
|
changed[changed[node.id].z] = newConfig.allNodes[changed[node.id].z];
|
||||||
|
if (newConfig.allNodes[changed[node.id].z].type === "subflow") {
|
||||||
|
// This subflow instance is inside a subflow. Add the
|
||||||
|
// containing subflow to the stack to mark
|
||||||
|
changedSubflowStack.push(changed[node.id].z);
|
||||||
|
delete changed[node.id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var diff = {
|
||||||
|
added:Object.keys(added),
|
||||||
|
changed:Object.keys(changed),
|
||||||
|
removed:Object.keys(removed),
|
||||||
|
rewired:Object.keys(wiringChanged),
|
||||||
|
linked:[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverse the links of all modified nodes to mark the connected nodes
|
||||||
|
var modifiedNodes = diff.added.concat(diff.changed).concat(diff.removed).concat(diff.rewired);
|
||||||
|
var visited = {};
|
||||||
|
while(modifiedNodes.length > 0) {
|
||||||
|
node = modifiedNodes.pop();
|
||||||
|
if (!visited[node]) {
|
||||||
|
visited[node] = true;
|
||||||
|
if (linkMap[node]) {
|
||||||
|
if (!changed[node] && !added[node] && !removed[node] && !wiringChanged[node]) {
|
||||||
|
diff.linked.push(node);
|
||||||
|
}
|
||||||
|
modifiedNodes = modifiedNodes.concat(linkMap[node]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// for (id in newConfig.allNodes) {
|
||||||
|
// console.log(
|
||||||
|
// (added[id]?"+":(changed[id]?"!":" "))+(wiringChanged[id]?"w":" ")+(diff.linked.indexOf(id)!==-1?"~":" "),
|
||||||
|
// id,
|
||||||
|
// newConfig.allNodes[id].type,
|
||||||
|
// newConfig.allNodes[id].name||newConfig.allNodes[id].label||""
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// for (id in removed) {
|
||||||
|
// console.log(
|
||||||
|
// "- "+(diff.linked.indexOf(id)!==-1?"~":" "),
|
||||||
|
// id,
|
||||||
|
// oldConfig.allNodes[id].type,
|
||||||
|
// oldConfig.allNodes[id].name||oldConfig.allNodes[id].label||""
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
}
|
@ -140,6 +140,7 @@ module.exports = {
|
|||||||
|
|
||||||
// Flow handling
|
// Flow handling
|
||||||
loadFlows: flows.load,
|
loadFlows: flows.load,
|
||||||
|
startFlows: flows.startFlows,
|
||||||
stopFlows: flows.stopFlows,
|
stopFlows: flows.stopFlows,
|
||||||
setFlows: flows.setFlows,
|
setFlows: flows.setFlows,
|
||||||
getFlows: flows.getFlows,
|
getFlows: flows.getFlows,
|
||||||
@ -149,4 +150,3 @@ module.exports = {
|
|||||||
getCredentials: credentials.get,
|
getCredentials: credentials.get,
|
||||||
deleteCredentials: credentials.delete
|
deleteCredentials: credentials.delete
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ function start() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.info(log._("runtime.paths.settings",{path:settings.settingsFile}));
|
log.info(log._("runtime.paths.settings",{path:settings.settingsFile}));
|
||||||
redNodes.loadFlows();
|
redNodes.loadFlows().then(redNodes.startFlows);
|
||||||
comms.start();
|
comms.start();
|
||||||
}).otherwise(function(err) {
|
}).otherwise(function(err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
@ -110,6 +110,7 @@ module.exports = {
|
|||||||
testNode(red);
|
testNode(red);
|
||||||
}
|
}
|
||||||
flows.load().then(function() {
|
flows.load().then(function() {
|
||||||
|
flows.startFlows();
|
||||||
should.deepEqual(testFlows, flows.getFlows());
|
should.deepEqual(testFlows, flows.getFlows());
|
||||||
cb();
|
cb();
|
||||||
});
|
});
|
||||||
|
File diff suppressed because it is too large
Load Diff
1000
test/red/nodes/flows/Flow_spec.js
Normal file
1000
test/red/nodes/flows/Flow_spec.js
Normal file
File diff suppressed because it is too large
Load Diff
454
test/red/nodes/flows/index_spec.js
Normal file
454
test/red/nodes/flows/index_spec.js
Normal file
@ -0,0 +1,454 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2014, 2015 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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 when = require("when");
|
||||||
|
var clone = require("clone");
|
||||||
|
var flows = require("../../../../red/nodes/flows");
|
||||||
|
var RedNode = require("../../../../red/nodes/Node");
|
||||||
|
var RED = require("../../../../red/nodes");
|
||||||
|
var events = require("../../../../red/events");
|
||||||
|
var credentials = require("../../../../red/nodes/credentials");
|
||||||
|
var typeRegistry = require("../../../../red/nodes/registry");
|
||||||
|
var Flow = require("../../../../red/nodes/flows/Flow");
|
||||||
|
|
||||||
|
describe('flows/index', function() {
|
||||||
|
|
||||||
|
var storage;
|
||||||
|
var eventsOn;
|
||||||
|
var credentialsExtract;
|
||||||
|
var credentialsSave;
|
||||||
|
var credentialsClean;
|
||||||
|
var credentialsLoad;
|
||||||
|
|
||||||
|
var flowCreate;
|
||||||
|
var getType;
|
||||||
|
|
||||||
|
before(function() {
|
||||||
|
getType = sinon.stub(typeRegistry,"get",function(type) {
|
||||||
|
return type.indexOf('missing') === -1;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
after(function() {
|
||||||
|
getType.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
eventsOn = sinon.spy(events,"on");
|
||||||
|
credentialsExtract = sinon.stub(credentials,"extract",function(conf) {
|
||||||
|
delete conf.credentials;
|
||||||
|
});
|
||||||
|
credentialsSave = sinon.stub(credentials,"save",function() {
|
||||||
|
return when.resolve();
|
||||||
|
});
|
||||||
|
credentialsClean = sinon.stub(credentials,"clean",function(conf) {
|
||||||
|
return when.resolve();
|
||||||
|
});
|
||||||
|
credentialsLoad = sinon.stub(credentials,"load",function() {
|
||||||
|
return when.resolve();
|
||||||
|
});
|
||||||
|
flowCreate = sinon.stub(Flow,"create",function(global, flow) {
|
||||||
|
var id;
|
||||||
|
if (typeof flow === 'undefined') {
|
||||||
|
flow = global;
|
||||||
|
id = '_GLOBAL_';
|
||||||
|
} else {
|
||||||
|
id = flow.id;
|
||||||
|
}
|
||||||
|
flowCreate.flows[id] = {
|
||||||
|
flow: flow,
|
||||||
|
global: global,
|
||||||
|
start: sinon.spy(),
|
||||||
|
update: sinon.spy(),
|
||||||
|
stop: sinon.spy(),
|
||||||
|
getActiveNodes: function() {
|
||||||
|
return flow.nodes||{};
|
||||||
|
},
|
||||||
|
handleError: sinon.spy(),
|
||||||
|
handleStatus: sinon.spy()
|
||||||
|
|
||||||
|
}
|
||||||
|
return flowCreate.flows[id];
|
||||||
|
});
|
||||||
|
flowCreate.flows = {};
|
||||||
|
|
||||||
|
storage = {
|
||||||
|
saveFlows: function(conf) {
|
||||||
|
storage.conf = conf;
|
||||||
|
return when.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function(done) {
|
||||||
|
eventsOn.restore();
|
||||||
|
credentialsExtract.restore();
|
||||||
|
credentialsSave.restore();
|
||||||
|
credentialsClean.restore();
|
||||||
|
credentialsLoad.restore();
|
||||||
|
flowCreate.restore();
|
||||||
|
|
||||||
|
flows.stopFlows().then(done);
|
||||||
|
|
||||||
|
});
|
||||||
|
// describe('#init',function() {
|
||||||
|
// it('registers the type-registered handler', function() {
|
||||||
|
// flows.init({},{});
|
||||||
|
// eventsOn.calledOnce.should.be.true;
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
describe('#setFlows', function() {
|
||||||
|
it('sets the full flow', function(done) {
|
||||||
|
var originalConfig = [
|
||||||
|
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
||||||
|
{id:"t1",type:"tab"}
|
||||||
|
];
|
||||||
|
flows.init({},storage);
|
||||||
|
flows.setFlows(originalConfig).then(function() {
|
||||||
|
credentialsExtract.called.should.be.false;
|
||||||
|
credentialsClean.called.should.be.true;
|
||||||
|
storage.hasOwnProperty('conf').should.be.true;
|
||||||
|
flows.getFlows().should.eql(originalConfig);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
it('sets the full flow for type load', function(done) {
|
||||||
|
var originalConfig = [
|
||||||
|
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
||||||
|
{id:"t1",type:"tab"}
|
||||||
|
];
|
||||||
|
flows.init({},storage);
|
||||||
|
flows.setFlows(originalConfig,"load").then(function() {
|
||||||
|
credentialsExtract.called.should.be.false;
|
||||||
|
credentialsClean.called.should.be.true;
|
||||||
|
// 'load' type does not trigger a save
|
||||||
|
storage.hasOwnProperty('conf').should.be.false;
|
||||||
|
flows.getFlows().should.eql(originalConfig);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('extracts credentials from the full flow', function(done) {
|
||||||
|
var originalConfig = [
|
||||||
|
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[],credentials:{}},
|
||||||
|
{id:"t1",type:"tab"}
|
||||||
|
];
|
||||||
|
flows.init({},storage);
|
||||||
|
flows.setFlows(originalConfig).then(function() {
|
||||||
|
credentialsExtract.called.should.be.true;
|
||||||
|
credentialsClean.called.should.be.true;
|
||||||
|
storage.hasOwnProperty('conf').should.be.true;
|
||||||
|
var cleanedFlows = flows.getFlows();
|
||||||
|
storage.conf.should.eql(cleanedFlows);
|
||||||
|
cleanedFlows.should.not.eql(originalConfig);
|
||||||
|
cleanedFlows[0].credentials = {};
|
||||||
|
cleanedFlows.should.eql(originalConfig);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates existing flows with partial deployment - nodes', function(done) {
|
||||||
|
var originalConfig = [
|
||||||
|
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
||||||
|
{id:"t1",type:"tab"}
|
||||||
|
];
|
||||||
|
var newConfig = clone(originalConfig);
|
||||||
|
newConfig.push({id:"t1-2",x:10,y:10,z:"t1",type:"test",wires:[]});
|
||||||
|
newConfig.push({id:"t2",type:"tab"});
|
||||||
|
newConfig.push({id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]});
|
||||||
|
storage.getFlows = function() {
|
||||||
|
return when.resolve(originalConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
events.once('nodes-started',function() {
|
||||||
|
flows.setFlows(newConfig,"nodes").then(function() {
|
||||||
|
flows.getFlows().should.eql(newConfig);
|
||||||
|
flowCreate.flows['t1'].update.called.should.be.true;
|
||||||
|
flowCreate.flows['t2'].start.called.should.be.true;
|
||||||
|
flowCreate.flows['_GLOBAL_'].update.called.should.be.true;
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
flows.init({},storage);
|
||||||
|
flows.load().then(function() {
|
||||||
|
flows.startFlows();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates existing flows with partial deployment - flows', function(done) {
|
||||||
|
var originalConfig = [
|
||||||
|
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
||||||
|
{id:"t1",type:"tab"}
|
||||||
|
];
|
||||||
|
var newConfig = clone(originalConfig);
|
||||||
|
newConfig.push({id:"t1-2",x:10,y:10,z:"t1",type:"test",wires:[]});
|
||||||
|
newConfig.push({id:"t2",type:"tab"});
|
||||||
|
newConfig.push({id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]});
|
||||||
|
storage.getFlows = function() {
|
||||||
|
return when.resolve(originalConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
events.once('nodes-started',function() {
|
||||||
|
flows.setFlows(newConfig,"nodes").then(function() {
|
||||||
|
flows.getFlows().should.eql(newConfig);
|
||||||
|
flowCreate.flows['t1'].update.called.should.be.true;
|
||||||
|
flowCreate.flows['t2'].start.called.should.be.true;
|
||||||
|
flowCreate.flows['_GLOBAL_'].update.called.should.be.true;
|
||||||
|
flows.stopFlows().then(done);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
flows.init({},storage);
|
||||||
|
flows.load().then(function() {
|
||||||
|
flows.startFlows();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#load', function() {
|
||||||
|
it('loads the flow config', function(done) {
|
||||||
|
var originalConfig = [
|
||||||
|
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
||||||
|
{id:"t1",type:"tab"}
|
||||||
|
];
|
||||||
|
storage.getFlows = function() {
|
||||||
|
return when.resolve(originalConfig);
|
||||||
|
}
|
||||||
|
flows.init({},storage);
|
||||||
|
flows.load().then(function() {
|
||||||
|
credentialsExtract.called.should.be.false;
|
||||||
|
credentialsLoad.called.should.be.true;
|
||||||
|
credentialsClean.called.should.be.true;
|
||||||
|
// 'load' type does not trigger a save
|
||||||
|
storage.hasOwnProperty('conf').should.be.false;
|
||||||
|
flows.getFlows().should.eql(originalConfig);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#startFlows', function() {
|
||||||
|
it('starts the loaded config', function(done) {
|
||||||
|
var originalConfig = [
|
||||||
|
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
||||||
|
{id:"t1",type:"tab"}
|
||||||
|
];
|
||||||
|
storage.getFlows = function() {
|
||||||
|
return when.resolve(originalConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
events.once('nodes-started',function() {
|
||||||
|
Object.keys(flowCreate.flows).should.eql(['_GLOBAL_','t1']);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
flows.init({},storage);
|
||||||
|
flows.load().then(function() {
|
||||||
|
flows.startFlows();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('does not start if nodes missing', function(done) {
|
||||||
|
var originalConfig = [
|
||||||
|
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
||||||
|
{id:"t1-2",x:10,y:10,z:"t1",type:"missing",wires:[]},
|
||||||
|
{id:"t1",type:"tab"}
|
||||||
|
];
|
||||||
|
storage.getFlows = function() {
|
||||||
|
return when.resolve(originalConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
flows.init({},storage);
|
||||||
|
flows.load().then(function() {
|
||||||
|
flows.startFlows();
|
||||||
|
flowCreate.called.should.be.false;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('starts when missing nodes registered', function(done) {
|
||||||
|
var originalConfig = [
|
||||||
|
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
||||||
|
{id:"t1-2",x:10,y:10,z:"t1",type:"missing",wires:[]},
|
||||||
|
{id:"t1-3",x:10,y:10,z:"t1",type:"missing2",wires:[]},
|
||||||
|
{id:"t1",type:"tab"}
|
||||||
|
];
|
||||||
|
storage.getFlows = function() {
|
||||||
|
return when.resolve(originalConfig);
|
||||||
|
}
|
||||||
|
flows.init({},storage);
|
||||||
|
flows.load().then(function() {
|
||||||
|
flows.startFlows();
|
||||||
|
flowCreate.called.should.be.false;
|
||||||
|
|
||||||
|
events.emit("type-registered","missing");
|
||||||
|
setTimeout(function() {
|
||||||
|
flowCreate.called.should.be.false;
|
||||||
|
events.emit("type-registered","missing2");
|
||||||
|
setTimeout(function() {
|
||||||
|
flowCreate.called.should.be.true;
|
||||||
|
done();
|
||||||
|
},10);
|
||||||
|
},10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#get',function() {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#eachNode', function() {
|
||||||
|
it('iterates the flow nodes', function(done) {
|
||||||
|
var originalConfig = [
|
||||||
|
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
||||||
|
{id:"t1",type:"tab"}
|
||||||
|
];
|
||||||
|
storage.getFlows = function() {
|
||||||
|
return when.resolve(originalConfig);
|
||||||
|
}
|
||||||
|
flows.init({},storage);
|
||||||
|
flows.load().then(function() {
|
||||||
|
var c = 0;
|
||||||
|
flows.eachNode(function(node) {
|
||||||
|
c++
|
||||||
|
})
|
||||||
|
c.should.equal(2);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#stopFlows', function() {
|
||||||
|
|
||||||
|
});
|
||||||
|
describe('#handleError', function() {
|
||||||
|
it('passes error to correct flow', function(done) {
|
||||||
|
var originalConfig = [
|
||||||
|
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
||||||
|
{id:"t1",type:"tab"}
|
||||||
|
];
|
||||||
|
storage.getFlows = function() {
|
||||||
|
return when.resolve(originalConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
events.once('nodes-started',function() {
|
||||||
|
flows.handleError(originalConfig[0],"message",{});
|
||||||
|
flowCreate.flows['t1'].handleError.called.should.be.true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
flows.init({},storage);
|
||||||
|
flows.load().then(function() {
|
||||||
|
flows.startFlows();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('passes error to flows that use the originating global config', function(done) {
|
||||||
|
var originalConfig = [
|
||||||
|
{id:"configNode",type:"test"},
|
||||||
|
{id:"t1",type:"tab"},
|
||||||
|
{id:"t1-1",x:10,y:10,z:"t1",type:"test",config:"configNode",wires:[]},
|
||||||
|
{id:"t2",type:"tab"},
|
||||||
|
{id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]},
|
||||||
|
{id:"t3",type:"tab"},
|
||||||
|
{id:"t3-1",x:10,y:10,z:"t3",type:"test",config:"configNode",wires:[]}
|
||||||
|
];
|
||||||
|
storage.getFlows = function() {
|
||||||
|
return when.resolve(originalConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
events.once('nodes-started',function() {
|
||||||
|
flows.handleError(originalConfig[0],"message",{});
|
||||||
|
try {
|
||||||
|
flowCreate.flows['t1'].handleError.called.should.be.true;
|
||||||
|
flowCreate.flows['t2'].handleError.called.should.be.false;
|
||||||
|
flowCreate.flows['t3'].handleError.called.should.be.true;
|
||||||
|
done();
|
||||||
|
} catch(err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
flows.init({},storage);
|
||||||
|
flows.load().then(function() {
|
||||||
|
flows.startFlows();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#handleStatus', function() {
|
||||||
|
it('passes status to correct flow', function(done) {
|
||||||
|
var originalConfig = [
|
||||||
|
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
||||||
|
{id:"t1",type:"tab"}
|
||||||
|
];
|
||||||
|
storage.getFlows = function() {
|
||||||
|
return when.resolve(originalConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
events.once('nodes-started',function() {
|
||||||
|
flows.handleStatus(originalConfig[0],"message");
|
||||||
|
flowCreate.flows['t1'].handleStatus.called.should.be.true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
flows.init({},storage);
|
||||||
|
flows.load().then(function() {
|
||||||
|
flows.startFlows();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes status to flows that use the originating global config', function(done) {
|
||||||
|
var originalConfig = [
|
||||||
|
{id:"configNode",type:"test"},
|
||||||
|
{id:"t1",type:"tab"},
|
||||||
|
{id:"t1-1",x:10,y:10,z:"t1",type:"test",config:"configNode",wires:[]},
|
||||||
|
{id:"t2",type:"tab"},
|
||||||
|
{id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]},
|
||||||
|
{id:"t3",type:"tab"},
|
||||||
|
{id:"t3-1",x:10,y:10,z:"t3",type:"test",config:"configNode",wires:[]}
|
||||||
|
];
|
||||||
|
storage.getFlows = function() {
|
||||||
|
return when.resolve(originalConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
events.once('nodes-started',function() {
|
||||||
|
flows.handleStatus(originalConfig[0],"message");
|
||||||
|
try {
|
||||||
|
flowCreate.flows['t1'].handleStatus.called.should.be.true;
|
||||||
|
flowCreate.flows['t2'].handleStatus.called.should.be.false;
|
||||||
|
flowCreate.flows['t3'].handleStatus.called.should.be.true;
|
||||||
|
done();
|
||||||
|
} catch(err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
flows.init({},storage);
|
||||||
|
flows.load().then(function() {
|
||||||
|
flows.startFlows();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
604
test/red/nodes/flows/util_spec.js
Normal file
604
test/red/nodes/flows/util_spec.js
Normal file
@ -0,0 +1,604 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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 when = require("when");
|
||||||
|
var clone = require("clone");
|
||||||
|
var flowUtil = require("../../../../red/nodes/flows/util");
|
||||||
|
var typeRegistry = require("../../../../red/nodes/registry");
|
||||||
|
var redUtil = require("../../../../red/util");
|
||||||
|
|
||||||
|
describe('flows/util', function() {
|
||||||
|
var getType;
|
||||||
|
|
||||||
|
before(function() {
|
||||||
|
getType = sinon.stub(typeRegistry,"get",function(type) {
|
||||||
|
return type!=='missing';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
after(function() {
|
||||||
|
getType.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
describe('#diffNodes',function() {
|
||||||
|
it('handles a null old node', function() {
|
||||||
|
flowUtil.diffNodes(null,{}).should.be.true;
|
||||||
|
});
|
||||||
|
it('ignores x/y changes', function() {
|
||||||
|
flowUtil.diffNodes({x:10,y:10},{x:20,y:10}).should.be.false;
|
||||||
|
flowUtil.diffNodes({x:10,y:10},{x:10,y:20}).should.be.false;
|
||||||
|
});
|
||||||
|
it('ignores wiring changes', function() {
|
||||||
|
flowUtil.diffNodes({wires:[]},{wires:[1,2,3]}).should.be.false;
|
||||||
|
});
|
||||||
|
it('spots existing property change - string', function() {
|
||||||
|
flowUtil.diffNodes({a:"foo"},{a:"bar"}).should.be.true;
|
||||||
|
});
|
||||||
|
it('spots existing property change - number', function() {
|
||||||
|
flowUtil.diffNodes({a:0},{a:1}).should.be.true;
|
||||||
|
});
|
||||||
|
it('spots existing property change - boolean', function() {
|
||||||
|
flowUtil.diffNodes({a:true},{a:false}).should.be.true;
|
||||||
|
});
|
||||||
|
it('spots existing property change - truthy', function() {
|
||||||
|
flowUtil.diffNodes({a:true},{a:1}).should.be.true;
|
||||||
|
});
|
||||||
|
it('spots existing property change - falsey', function() {
|
||||||
|
flowUtil.diffNodes({a:false},{a:0}).should.be.true;
|
||||||
|
});
|
||||||
|
it('spots existing property change - array', function() {
|
||||||
|
flowUtil.diffNodes({a:[0,1,2]},{a:[0,2,3]}).should.be.true;
|
||||||
|
});
|
||||||
|
it('spots existing property change - object', function() {
|
||||||
|
flowUtil.diffNodes({a:{a:[0,1,2]}},{a:{a:[0,2,3]}}).should.be.true;
|
||||||
|
flowUtil.diffNodes({a:{a:[0,1,2]}},{a:{b:[0,1,2]}}).should.be.true;
|
||||||
|
});
|
||||||
|
it('spots added property', function() {
|
||||||
|
flowUtil.diffNodes({a:"foo"},{a:"foo",b:"bar"}).should.be.true;
|
||||||
|
});
|
||||||
|
it('spots removed property', function() {
|
||||||
|
flowUtil.diffNodes({a:"foo",b:"bar"},{a:"foo"}).should.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#parseConfig',function() {
|
||||||
|
|
||||||
|
it('parses a single-tab flow', function() {
|
||||||
|
var originalConfig = [
|
||||||
|
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
||||||
|
{id:"t1",type:"tab"}
|
||||||
|
];
|
||||||
|
var parsedConfig = flowUtil.parseConfig(originalConfig);
|
||||||
|
var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"t1":{"id":"t1","type":"tab"}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}}},"missingTypes":[]};
|
||||||
|
|
||||||
|
redUtil.compareObjects(parsedConfig,expectedConfig).should.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses a single-tab flow with global config node', function() {
|
||||||
|
var originalConfig = [
|
||||||
|
{id:"t1-1",x:10,y:10,z:"t1",type:"test",foo:"cn", wires:[]},
|
||||||
|
{id:"cn",type:"test"},
|
||||||
|
{id:"t1",type:"tab"}
|
||||||
|
];
|
||||||
|
var parsedConfig = flowUtil.parseConfig(originalConfig);
|
||||||
|
var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]},"cn":{"id":"cn","type":"test"},"t1":{"id":"t1","type":"tab"}},"subflows":{},"configs":{"cn":{"id":"cn","type":"test","_users":["t1-1"]}},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]}}}},"missingTypes":[]};
|
||||||
|
redUtil.compareObjects(parsedConfig,expectedConfig).should.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses a multi-tab flow', function() {
|
||||||
|
var originalConfig = [
|
||||||
|
{id:"t1",type:"tab"},
|
||||||
|
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
||||||
|
{id:"t2",type:"tab"},
|
||||||
|
{id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]}
|
||||||
|
];
|
||||||
|
var parsedConfig = flowUtil.parseConfig(originalConfig);
|
||||||
|
var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"t2":{"id":"t2","type":"tab"},"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}},"t2":{"id":"t2","type":"tab","subflows":{},"configs":{},"nodes":{"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}}}},"missingTypes":[]};
|
||||||
|
|
||||||
|
redUtil.compareObjects(parsedConfig,expectedConfig).should.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses a subflow flow', function() {
|
||||||
|
var originalConfig = [
|
||||||
|
{id:"t1",type:"tab"},
|
||||||
|
{id:"t1-1",x:10,y:10,z:"t1",type:"subflow:sf1",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow"},
|
||||||
|
{id:"sf1-1",x:10,y:10,z:"sf1",type:"test",wires:[]}
|
||||||
|
];
|
||||||
|
var parsedConfig = flowUtil.parseConfig(originalConfig);
|
||||||
|
var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[]},"sf1":{"id":"sf1","type":"subflow"},"sf1-1":{"id":"sf1-1","x":10,"y":10,"z":"sf1","type":"test","wires":[]}},"subflows":{"sf1":{"id":"sf1","type":"subflow","configs":{},"nodes":{"sf1-1":{"id":"sf1-1","x":10,"y":10,"z":"sf1","type":"test","wires":[]}},"instances":[{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}]}},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}}}},"missingTypes":[]};
|
||||||
|
|
||||||
|
redUtil.compareObjects(parsedConfig,expectedConfig).should.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses a flow with a missing type', function() {
|
||||||
|
var originalConfig = [
|
||||||
|
{id:"t1",type:"tab"},
|
||||||
|
{id:"t1-1",x:10,y:10,z:"t1",type:"sf1",wires:[]},
|
||||||
|
{id:"t1-2",x:10,y:10,z:"t1",type:"missing",wires:[]},
|
||||||
|
];
|
||||||
|
var parsedConfig = flowUtil.parseConfig(originalConfig);
|
||||||
|
parsedConfig.missingTypes.should.eql(['missing']);
|
||||||
|
var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},"t1-2":{"id":"t1-2","x":10,"y":10,"z":"t1","type":"missing","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]}}}},"missingTypes":["missing"]};
|
||||||
|
|
||||||
|
redUtil.compareObjects(parsedConfig,expectedConfig).should.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#diffConfigs', function() {
|
||||||
|
|
||||||
|
it('handles an identical configuration', function() {
|
||||||
|
var config = [{id:"123",type:"test",foo:"a",wires:[]}];
|
||||||
|
|
||||||
|
var originalConfig = flowUtil.parseConfig(clone(config));
|
||||||
|
var changedConfig = flowUtil.parseConfig(clone(config));
|
||||||
|
|
||||||
|
originalConfig.missingTypes.should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
|
||||||
|
|
||||||
|
diffResult.added.should.have.length(0);
|
||||||
|
diffResult.changed.should.have.length(0);
|
||||||
|
diffResult.removed.should.have.length(0);
|
||||||
|
diffResult.rewired.should.have.length(0);
|
||||||
|
diffResult.linked.should.have.length(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('identifies nodes with changed properties, including downstream linked', function() {
|
||||||
|
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[[1]]},{id:"3",type:"test",foo:"a",wires:[]}];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[0].foo = "b";
|
||||||
|
|
||||||
|
var originalConfig = flowUtil.parseConfig(config);
|
||||||
|
var changedConfig = flowUtil.parseConfig(newConfig);
|
||||||
|
|
||||||
|
originalConfig.missingTypes.should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
|
||||||
|
diffResult.added.should.have.length(0);
|
||||||
|
diffResult.changed.should.eql(["1"]);
|
||||||
|
diffResult.removed.should.have.length(0);
|
||||||
|
diffResult.rewired.should.have.length(0);
|
||||||
|
diffResult.linked.should.eql(["2"]);
|
||||||
|
|
||||||
|
});
|
||||||
|
it('identifies nodes with changed properties, including upstream linked', function() {
|
||||||
|
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[["1"]]},{id:"3",type:"test",foo:"a",wires:[]}];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[1].bar = "c";
|
||||||
|
|
||||||
|
var originalConfig = flowUtil.parseConfig(config);
|
||||||
|
var changedConfig = flowUtil.parseConfig(newConfig);
|
||||||
|
|
||||||
|
originalConfig.missingTypes.should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
|
||||||
|
diffResult.added.should.have.length(0);
|
||||||
|
diffResult.changed.should.eql(["2"]);
|
||||||
|
diffResult.removed.should.have.length(0);
|
||||||
|
diffResult.rewired.should.have.length(0);
|
||||||
|
diffResult.linked.should.eql(["1"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('identifies nodes with changed credentials, including downstream linked', function() {
|
||||||
|
var config = [{id:"1",type:"test",wires:[]},{id:"2",type:"test",bar:"b",wires:[["1"]]},{id:"3",type:"test",foo:"a",wires:[]}];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[0].credentials = {};
|
||||||
|
|
||||||
|
var originalConfig = flowUtil.parseConfig(config);
|
||||||
|
var changedConfig = flowUtil.parseConfig(newConfig);
|
||||||
|
|
||||||
|
originalConfig.missingTypes.should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
|
||||||
|
diffResult.added.should.have.length(0);
|
||||||
|
diffResult.changed.should.eql(["1"]);
|
||||||
|
diffResult.removed.should.have.length(0);
|
||||||
|
diffResult.rewired.should.have.length(0);
|
||||||
|
diffResult.linked.should.eql(["2"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('identifies nodes with changed wiring', function() {
|
||||||
|
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[["1"]]},{id:"3",type:"test",foo:"a",wires:[]}];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[1].wires[0][0] = "3";
|
||||||
|
|
||||||
|
var originalConfig = flowUtil.parseConfig(config);
|
||||||
|
var changedConfig = flowUtil.parseConfig(newConfig);
|
||||||
|
|
||||||
|
originalConfig.missingTypes.should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
|
||||||
|
diffResult.added.should.have.length(0);
|
||||||
|
diffResult.changed.should.have.length(0);
|
||||||
|
diffResult.removed.should.have.length(0);
|
||||||
|
diffResult.rewired.should.eql(["2"]);
|
||||||
|
diffResult.linked.sort().should.eql(["1","3"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('identifies nodes with changed wiring - second connection added', function() {
|
||||||
|
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"2",type:"test",bar:"b",wires:[["1"]]},{id:"3",type:"test",foo:"a",wires:[]}];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[1].wires[0].push("1");
|
||||||
|
|
||||||
|
var originalConfig = flowUtil.parseConfig(config);
|
||||||
|
var changedConfig = flowUtil.parseConfig(newConfig);
|
||||||
|
|
||||||
|
originalConfig.missingTypes.should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
|
||||||
|
diffResult.added.should.have.length(0);
|
||||||
|
diffResult.changed.should.have.length(0);
|
||||||
|
diffResult.removed.should.have.length(0);
|
||||||
|
diffResult.rewired.should.eql(["2"]);
|
||||||
|
diffResult.linked.sort().should.eql(["1"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('identifies nodes with changed wiring - node connected', function() {
|
||||||
|
var config = [{id:"1",type:"test",foo:"a",wires:[["2"]]},{id:"2",type:"test",bar:"b",wires:[[]]},{id:"3",type:"test",foo:"a",wires:[]}];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[1].wires.push("3");
|
||||||
|
|
||||||
|
var originalConfig = flowUtil.parseConfig(config);
|
||||||
|
var changedConfig = flowUtil.parseConfig(newConfig);
|
||||||
|
|
||||||
|
originalConfig.missingTypes.should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
|
||||||
|
diffResult.added.should.have.length(0);
|
||||||
|
diffResult.changed.should.have.length(0);
|
||||||
|
diffResult.removed.should.have.length(0);
|
||||||
|
diffResult.rewired.should.eql(["2"]);
|
||||||
|
diffResult.linked.sort().should.eql(["1","3"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('identifies new nodes', function() {
|
||||||
|
var config = [{id:"1",type:"test",foo:"a",wires:[]},{id:"3",type:"test",foo:"a",wires:[]}];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig.push({id:"2",type:"test",bar:"b",wires:[["1"]]});
|
||||||
|
|
||||||
|
var originalConfig = flowUtil.parseConfig(config);
|
||||||
|
var changedConfig = flowUtil.parseConfig(newConfig);
|
||||||
|
|
||||||
|
originalConfig.missingTypes.should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
|
||||||
|
diffResult.added.should.eql(["2"]);
|
||||||
|
diffResult.changed.should.have.length(0);
|
||||||
|
diffResult.removed.should.have.length(0);
|
||||||
|
diffResult.rewired.should.have.length(0);
|
||||||
|
diffResult.linked.sort().should.eql(["1"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('identifies deleted nodes', function() {
|
||||||
|
var config = [{id:"1",type:"test",foo:"a",wires:[["2"]]},{id:"2",type:"test",bar:"b",wires:[["3"]]},{id:"3",type:"test",foo:"a",wires:[]}];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig.splice(1,1);
|
||||||
|
newConfig[0].wires = [];
|
||||||
|
|
||||||
|
var originalConfig = flowUtil.parseConfig(config);
|
||||||
|
var changedConfig = flowUtil.parseConfig(newConfig);
|
||||||
|
|
||||||
|
originalConfig.missingTypes.should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
|
||||||
|
diffResult.added.should.have.length(0);
|
||||||
|
diffResult.changed.should.have.length(0);
|
||||||
|
diffResult.removed.should.eql(["2"]);
|
||||||
|
diffResult.rewired.should.eql(["1"]);
|
||||||
|
diffResult.linked.sort().should.eql(["3"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('identifies config nodes changes', function() {
|
||||||
|
var config = [
|
||||||
|
{id:"1",type:"test",foo:"configNode",wires:[["2"]]},
|
||||||
|
{id:"2",type:"test",bar:"b",wires:[["3"]]},
|
||||||
|
{id:"3",type:"test",foo:"a",wires:[]},
|
||||||
|
{id:"configNode",type:"testConfig"}
|
||||||
|
];
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[3].foo = "bar";
|
||||||
|
|
||||||
|
var originalConfig = flowUtil.parseConfig(config);
|
||||||
|
var changedConfig = flowUtil.parseConfig(newConfig);
|
||||||
|
|
||||||
|
originalConfig.missingTypes.should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
|
||||||
|
diffResult.added.should.have.length(0);
|
||||||
|
diffResult.changed.sort().should.eql(["1","configNode"]);
|
||||||
|
diffResult.removed.should.have.length(0);
|
||||||
|
diffResult.rewired.should.have.length(0);
|
||||||
|
diffResult.linked.sort().should.eql(["2","3"]);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks a parent subflow as changed for an internal property change', function() {
|
||||||
|
var config = [
|
||||||
|
{id:"1",type:"test",wires:[["2"]]},
|
||||||
|
{id:"2",type:"subflow:sf1",wires:[["3"]]},
|
||||||
|
{id:"3",type:"test",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow"},
|
||||||
|
{id:"sf1-1",z:"sf1",type:"test",foo:"a",wires:[]},
|
||||||
|
{id:"4",type:"subflow:sf1",wires:[]}
|
||||||
|
];
|
||||||
|
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[4].foo = "b";
|
||||||
|
|
||||||
|
var originalConfig = flowUtil.parseConfig(config);
|
||||||
|
var changedConfig = flowUtil.parseConfig(newConfig);
|
||||||
|
|
||||||
|
originalConfig.missingTypes.should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
|
||||||
|
diffResult.added.should.have.length(0);
|
||||||
|
diffResult.changed.sort().should.eql(['2', '4', 'sf1']);
|
||||||
|
diffResult.removed.should.have.length(0);
|
||||||
|
diffResult.rewired.should.have.length(0);
|
||||||
|
diffResult.linked.sort().should.eql(["1","3"]);
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks a parent subflow as changed for an internal wiring change', function() {
|
||||||
|
var config = [
|
||||||
|
{id:"1",type:"test",wires:[["2"]]},
|
||||||
|
{id:"2",type:"subflow:sf1",wires:[["3"]]},
|
||||||
|
{id:"3",type:"test",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow"},
|
||||||
|
{id:"sf1-1",z:"sf1",type:"test",wires:[]},
|
||||||
|
{id:"sf1-2",z:"sf1",type:"test",wires:[]}
|
||||||
|
];
|
||||||
|
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[4].wires = [["sf1-2"]];
|
||||||
|
|
||||||
|
var originalConfig = flowUtil.parseConfig(config);
|
||||||
|
var changedConfig = flowUtil.parseConfig(newConfig);
|
||||||
|
|
||||||
|
originalConfig.missingTypes.should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
|
||||||
|
diffResult.added.should.have.length(0);
|
||||||
|
diffResult.changed.sort().should.eql(['2', 'sf1']);
|
||||||
|
diffResult.removed.should.have.length(0);
|
||||||
|
diffResult.rewired.should.have.length(0);
|
||||||
|
diffResult.linked.sort().should.eql(["1","3"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks a parent subflow as changed for an internal node add', function() {
|
||||||
|
var config = [
|
||||||
|
{id:"1",type:"test",wires:[["2"]]},
|
||||||
|
{id:"2",type:"subflow:sf1",wires:[["3"]]},
|
||||||
|
{id:"3",type:"test",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow"},
|
||||||
|
{id:"sf1-1",z:"sf1",type:"test",wires:[]},
|
||||||
|
{id:"sf1-2",z:"sf1",type:"test",wires:[]}
|
||||||
|
];
|
||||||
|
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig.push({id:"sf1-3",z:"sf1",type:"test",wires:[]});
|
||||||
|
|
||||||
|
var originalConfig = flowUtil.parseConfig(config);
|
||||||
|
var changedConfig = flowUtil.parseConfig(newConfig);
|
||||||
|
|
||||||
|
originalConfig.missingTypes.should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
|
||||||
|
diffResult.added.should.have.length(0);
|
||||||
|
diffResult.changed.sort().should.eql(['2', 'sf1']);
|
||||||
|
diffResult.removed.should.have.length(0);
|
||||||
|
diffResult.rewired.should.have.length(0);
|
||||||
|
diffResult.linked.sort().should.eql(["1","3"]);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks a parent subflow as changed for an internal node delete', function() {
|
||||||
|
var config = [
|
||||||
|
{id:"1",type:"test",wires:[["2"]]},
|
||||||
|
{id:"2",type:"subflow:sf1",wires:[["3"]]},
|
||||||
|
{id:"3",type:"test",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow"},
|
||||||
|
{id:"sf1-1",z:"sf1",type:"test",wires:[]},
|
||||||
|
{id:"sf1-2",z:"sf1",type:"test",wires:[]}
|
||||||
|
];
|
||||||
|
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig.splice(5,1);
|
||||||
|
|
||||||
|
var originalConfig = flowUtil.parseConfig(config);
|
||||||
|
var changedConfig = flowUtil.parseConfig(newConfig);
|
||||||
|
|
||||||
|
originalConfig.missingTypes.should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
|
||||||
|
diffResult.added.should.have.length(0);
|
||||||
|
diffResult.changed.sort().should.eql(['2', 'sf1']);
|
||||||
|
diffResult.removed.should.have.length(0);
|
||||||
|
diffResult.rewired.should.have.length(0);
|
||||||
|
diffResult.linked.sort().should.eql(["1","3"]);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks a parent subflow as changed for an internal subflow wiring change - input removed', function() {
|
||||||
|
var config = [
|
||||||
|
{id:"1",type:"test",wires:[["2"]]},
|
||||||
|
{id:"2",type:"subflow:sf1",wires:[["3"]]},
|
||||||
|
{id:"3",type:"test",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow","in": [{"wires": [{"id": "sf1-1"}]}],"out": [{"wires": [{"id": "sf1-2","port": 0}]}]},
|
||||||
|
{id:"sf1-1",z:"sf1",type:"test",wires:[]},
|
||||||
|
{id:"sf1-2",z:"sf1",type:"test",wires:[]}
|
||||||
|
];
|
||||||
|
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[3].in[0].wires = [];
|
||||||
|
|
||||||
|
var originalConfig = flowUtil.parseConfig(config);
|
||||||
|
var changedConfig = flowUtil.parseConfig(newConfig);
|
||||||
|
|
||||||
|
originalConfig.missingTypes.should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
|
||||||
|
diffResult.added.should.have.length(0);
|
||||||
|
diffResult.changed.sort().should.eql(['2', 'sf1']);
|
||||||
|
diffResult.removed.should.have.length(0);
|
||||||
|
diffResult.rewired.should.have.length(0);
|
||||||
|
diffResult.linked.sort().should.eql(["1","3"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks a parent subflow as changed for an internal subflow wiring change - input added', function() {
|
||||||
|
var config = [
|
||||||
|
{id:"1",type:"test",wires:[["2"]]},
|
||||||
|
{id:"2",type:"subflow:sf1",wires:[["3"]]},
|
||||||
|
{id:"3",type:"test",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow","in": [{"wires": [{"id": "sf1-1"}]}],"out": [{"wires": [{"id": "sf1-2","port": 0}]}]},
|
||||||
|
{id:"sf1-1",z:"sf1",type:"test",wires:[]},
|
||||||
|
{id:"sf1-2",z:"sf1",type:"test",wires:[]}
|
||||||
|
];
|
||||||
|
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[3].in[0].wires.push({"id":"sf1-2"});
|
||||||
|
|
||||||
|
var originalConfig = flowUtil.parseConfig(config);
|
||||||
|
var changedConfig = flowUtil.parseConfig(newConfig);
|
||||||
|
|
||||||
|
originalConfig.missingTypes.should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
|
||||||
|
diffResult.added.should.have.length(0);
|
||||||
|
diffResult.changed.sort().should.eql(['2', 'sf1']);
|
||||||
|
diffResult.removed.should.have.length(0);
|
||||||
|
diffResult.rewired.should.have.length(0);
|
||||||
|
diffResult.linked.sort().should.eql(["1","3"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks a parent subflow as changed for an internal subflow wiring change - output added', function() {
|
||||||
|
var config = [
|
||||||
|
{id:"1",type:"test",wires:[["2"]]},
|
||||||
|
{id:"2",type:"subflow:sf1",wires:[["3"]]},
|
||||||
|
{id:"3",type:"test",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow","in": [{"wires": [{"id": "sf1-1"}]}],"out": [{"wires": [{"id": "sf1-2","port": 0}]}]},
|
||||||
|
{id:"sf1-1",z:"sf1",type:"test",wires:[]},
|
||||||
|
{id:"sf1-2",z:"sf1",type:"test",wires:[]}
|
||||||
|
];
|
||||||
|
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[3].out[0].wires.push({"id":"sf1-2","port":0});
|
||||||
|
|
||||||
|
var originalConfig = flowUtil.parseConfig(config);
|
||||||
|
var changedConfig = flowUtil.parseConfig(newConfig);
|
||||||
|
|
||||||
|
originalConfig.missingTypes.should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
|
||||||
|
diffResult.added.should.have.length(0);
|
||||||
|
diffResult.changed.sort().should.eql(['2', 'sf1']);
|
||||||
|
diffResult.removed.should.have.length(0);
|
||||||
|
diffResult.rewired.should.have.length(0);
|
||||||
|
diffResult.linked.sort().should.eql(["1","3"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks a parent subflow as changed for an internal subflow wiring change - output removed', function() {
|
||||||
|
var config = [
|
||||||
|
{id:"1",type:"test",wires:[["2"]]},
|
||||||
|
{id:"2",type:"subflow:sf1",wires:[["3"]]},
|
||||||
|
{id:"3",type:"test",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow","in": [{"wires": [{"id": "sf1-1"}]}],"out": [{"wires": [{"id": "sf1-2","port": 0}]}]},
|
||||||
|
{id:"sf1-1",z:"sf1",type:"test",wires:[]},
|
||||||
|
{id:"sf1-2",z:"sf1",type:"test",wires:[]}
|
||||||
|
];
|
||||||
|
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[3].out[0].wires = [];
|
||||||
|
|
||||||
|
var originalConfig = flowUtil.parseConfig(config);
|
||||||
|
var changedConfig = flowUtil.parseConfig(newConfig);
|
||||||
|
|
||||||
|
originalConfig.missingTypes.should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
|
||||||
|
diffResult.added.should.have.length(0);
|
||||||
|
diffResult.changed.sort().should.eql(['2', 'sf1']);
|
||||||
|
diffResult.removed.should.have.length(0);
|
||||||
|
diffResult.rewired.should.have.length(0);
|
||||||
|
diffResult.linked.sort().should.eql(["1","3"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks a parent subflow as changed for a global config node change', function() {
|
||||||
|
var config = [
|
||||||
|
{id:"1",type:"test",wires:[["2"]]},
|
||||||
|
{id:"2",type:"subflow:sf1",wires:[["3"]]},
|
||||||
|
{id:"3",type:"test",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow"},
|
||||||
|
{id:"sf1-1",z:"sf1",prop:"configNode",type:"test",wires:[]},
|
||||||
|
{id:"sf1-2",z:"sf1",type:"test",wires:[]},
|
||||||
|
{id:"configNode",a:"foo",type:"test",wires:[]}
|
||||||
|
];
|
||||||
|
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[6].a = "bar";
|
||||||
|
|
||||||
|
var originalConfig = flowUtil.parseConfig(config);
|
||||||
|
var changedConfig = flowUtil.parseConfig(newConfig);
|
||||||
|
|
||||||
|
originalConfig.missingTypes.should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
|
||||||
|
diffResult.added.should.have.length(0);
|
||||||
|
diffResult.changed.sort().should.eql(['2', "configNode", 'sf1']);
|
||||||
|
diffResult.removed.should.have.length(0);
|
||||||
|
diffResult.rewired.should.have.length(0);
|
||||||
|
diffResult.linked.sort().should.eql(["1","3"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks a parent subflow as changed for an internal subflow instance change', function() {
|
||||||
|
var config = [
|
||||||
|
{id:"1",type:"test",wires:[["2"]]},
|
||||||
|
{id:"2",type:"subflow:sf1",wires:[["3"]]},
|
||||||
|
{id:"3",type:"test",wires:[]},
|
||||||
|
{id:"sf1",type:"subflow"},
|
||||||
|
{id:"sf2",type:"subflow"},
|
||||||
|
{id:"sf1-1",z:"sf1",type:"test",wires:[]},
|
||||||
|
{id:"sf1-2",z:"sf1",type:"subflow:sf2",wires:[]},
|
||||||
|
{id:"sf2-1",z:"sf2",type:"test",wires:[]},
|
||||||
|
{id:"sf2-2",z:"sf2",type:"test",wires:[]},
|
||||||
|
];
|
||||||
|
|
||||||
|
var newConfig = clone(config);
|
||||||
|
newConfig[8].a = "bar";
|
||||||
|
|
||||||
|
var originalConfig = flowUtil.parseConfig(config);
|
||||||
|
var changedConfig = flowUtil.parseConfig(newConfig);
|
||||||
|
|
||||||
|
originalConfig.missingTypes.should.have.length(0);
|
||||||
|
|
||||||
|
var diffResult = flowUtil.diffConfigs(originalConfig,changedConfig);
|
||||||
|
diffResult.added.should.have.length(0);
|
||||||
|
diffResult.changed.sort().should.eql(['2', 'sf1', 'sf2']);
|
||||||
|
diffResult.removed.should.have.length(0);
|
||||||
|
diffResult.rewired.should.have.length(0);
|
||||||
|
diffResult.linked.sort().should.eql(["1","3"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,230 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2014 IBM Corp.
|
|
||||||
*
|
|
||||||
* 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 when = require("when");
|
|
||||||
var clone = require("clone");
|
|
||||||
var flows = require("../../../red/nodes/flows");
|
|
||||||
var RedNode = require("../../../red/nodes/Node");
|
|
||||||
var RED = require("../../../red/nodes");
|
|
||||||
var events = require("../../../red/events");
|
|
||||||
var credentials = require("../../../red/nodes/credentials");
|
|
||||||
var typeRegistry = require("../../../red/nodes/registry");
|
|
||||||
var Flow = require("../../../red/nodes/Flow");
|
|
||||||
|
|
||||||
|
|
||||||
var settings = {
|
|
||||||
available: function() { return false; }
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadFlows(testFlows, cb) {
|
|
||||||
var storage = {
|
|
||||||
getFlows: function() {
|
|
||||||
return when.resolve(testFlows);
|
|
||||||
},
|
|
||||||
getCredentials: function() {
|
|
||||||
return when.resolve({});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
RED.init(settings, storage);
|
|
||||||
flows.load().then(function() {
|
|
||||||
should.deepEqual(testFlows, flows.getFlows());
|
|
||||||
cb();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('flows', function() {
|
|
||||||
|
|
||||||
afterEach(function(done) {
|
|
||||||
flows.stopFlows().then(function() {
|
|
||||||
loadFlows([],done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('#load',function() {
|
|
||||||
|
|
||||||
it('should load nothing when storage is empty',function(done) {
|
|
||||||
loadFlows([], done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should load and start an empty tab flow',function(done) {
|
|
||||||
loadFlows([{"type":"tab","id":"tab1","label":"Sheet 1"}], function() {});
|
|
||||||
events.once('nodes-started', function() { done(); });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should load and start a registered node type', function(done) {
|
|
||||||
RED.registerType('debug', function() {});
|
|
||||||
var typeRegistryGet = sinon.stub(typeRegistry,"get",function(nt) {
|
|
||||||
return RedNode;
|
|
||||||
});
|
|
||||||
loadFlows([{"id":"n1","type":"debug"}], function() { });
|
|
||||||
events.once('nodes-started', function() {
|
|
||||||
typeRegistryGet.restore();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should load and start when node type is registered', function(done) {
|
|
||||||
var typeRegistryGet = sinon.stub(typeRegistry,"get");
|
|
||||||
typeRegistryGet.onCall(0).returns(null);
|
|
||||||
typeRegistryGet.returns(RedNode);
|
|
||||||
loadFlows([{"id":"n2","type":"inject"}], function() {
|
|
||||||
events.emit('type-registered','inject');
|
|
||||||
});
|
|
||||||
events.once('nodes-started', function() {
|
|
||||||
typeRegistryGet.restore();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not instantiate nodes of an unused subflow', function(done) {
|
|
||||||
RED.registerType('abc', function() {});
|
|
||||||
var typeRegistryGet = sinon.stub(typeRegistry,"get",function(nt) {
|
|
||||||
return RedNode;
|
|
||||||
});
|
|
||||||
loadFlows([{"id":"n1","type":"subflow",inputs:[],outputs:[],wires:[]},
|
|
||||||
{"id":"n2","type":"abc","z":"n1",wires:[]}
|
|
||||||
],function() { });
|
|
||||||
events.once('nodes-started', function() {
|
|
||||||
(flows.get("n2") == null).should.be.true;
|
|
||||||
var ncount = 0
|
|
||||||
flows.eachNode(function(n) {
|
|
||||||
ncount++;
|
|
||||||
});
|
|
||||||
ncount.should.equal(0);
|
|
||||||
typeRegistryGet.restore();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('should instantiate nodes of an used subflow with new IDs', function(done) {
|
|
||||||
RED.registerType('abc', function() {});
|
|
||||||
var typeRegistryGet = sinon.stub(typeRegistry,"get",function(nt) {
|
|
||||||
return RedNode;
|
|
||||||
});
|
|
||||||
loadFlows([{"id":"n1","type":"subflow",inputs:[],outputs:[]},
|
|
||||||
{"id":"n2","type":"abc","z":"n1","name":"def",wires:[]},
|
|
||||||
{"id":"n3","type":"subflow:n1"}
|
|
||||||
], function() { });
|
|
||||||
events.once('nodes-started', function() {
|
|
||||||
// n2 should not get instantiated with that id
|
|
||||||
(flows.get("n2") == null).should.be.true;
|
|
||||||
var ncount = 0
|
|
||||||
var nodes = [];
|
|
||||||
flows.eachNode(function(n) {
|
|
||||||
nodes.push(n);
|
|
||||||
});
|
|
||||||
nodes.should.have.lengthOf(2);
|
|
||||||
|
|
||||||
// Assume the nodes are instantiated in this order - not
|
|
||||||
// a requirement, but makes the test easier to write.
|
|
||||||
nodes[0].should.have.property("id","n3");
|
|
||||||
nodes[0].should.have.property("type","subflow:n1");
|
|
||||||
nodes[1].should.not.have.property("id","n2");
|
|
||||||
nodes[1].should.have.property("name","def");
|
|
||||||
|
|
||||||
// TODO: verify instance wiring is correct
|
|
||||||
typeRegistryGet.restore();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('#setFlows',function() {
|
|
||||||
var credentialsExtact;
|
|
||||||
var credentialsSave;
|
|
||||||
var stopFlows;
|
|
||||||
var startFlows;
|
|
||||||
var credentialsExtractNode;
|
|
||||||
beforeEach(function() {
|
|
||||||
credentialsExtact = sinon.stub(credentials,"extract",function(node) {credentialsExtractNode = clone(node);delete node.credentials;});
|
|
||||||
credentialsSave = sinon.stub(credentials,"save",function() { return when.resolve();});
|
|
||||||
stopFlows = sinon.stub(flows,"stopFlows",function() {return when.resolve();});
|
|
||||||
startFlows = sinon.stub(flows,"startFlows",function() {});
|
|
||||||
});
|
|
||||||
afterEach(function() {
|
|
||||||
credentialsExtact.restore();
|
|
||||||
credentialsSave.restore();
|
|
||||||
startFlows.restore();
|
|
||||||
stopFlows.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should extract credentials from nodes', function(done) {
|
|
||||||
var testFlow = [{"type":"testNode","credentials":{"a":1}},{"type":"testNode2"}];
|
|
||||||
var resultFlow = clone(testFlow);
|
|
||||||
var storage = { saveFlows: sinon.spy() };
|
|
||||||
flows.init({},storage);
|
|
||||||
flows.setFlows(testFlow,"full").then(function() {
|
|
||||||
try {
|
|
||||||
credentialsExtact.calledOnce.should.be.true;
|
|
||||||
// credential property stripped
|
|
||||||
testFlow.should.not.have.property("credentials");
|
|
||||||
credentialsExtractNode.should.eql(resultFlow[0]);
|
|
||||||
credentialsExtractNode.should.not.equal(resultFlow[0]);
|
|
||||||
|
|
||||||
credentialsSave.calledOnce.should.be.true;
|
|
||||||
|
|
||||||
storage.saveFlows.calledOnce.should.be.true;
|
|
||||||
storage.saveFlows.args[0][0].should.eql(testFlow);
|
|
||||||
|
|
||||||
stopFlows.calledOnce.should.be.true;
|
|
||||||
startFlows.calledOnce.should.be.true;
|
|
||||||
|
|
||||||
done();
|
|
||||||
} catch(err) {
|
|
||||||
done(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should apply diff on partial deployment', function(done) {
|
|
||||||
var testFlow = [{"type":"testNode"},{"type":"testNode2"}];
|
|
||||||
var testFlow2 = [{"type":"testNode3"},{"type":"testNode4"}];
|
|
||||||
var storage = { saveFlows: sinon.spy() };
|
|
||||||
flows.init({},storage);
|
|
||||||
|
|
||||||
flows.setFlows(testFlow,"full").then(function() {
|
|
||||||
flows.setFlows(testFlow2,"nodes").then(function() {
|
|
||||||
try {
|
|
||||||
credentialsExtact.called.should.be.false;
|
|
||||||
|
|
||||||
storage.saveFlows.calledTwice.should.be.true;
|
|
||||||
storage.saveFlows.args[1][0].should.eql(testFlow2);
|
|
||||||
|
|
||||||
stopFlows.calledTwice.should.be.true;
|
|
||||||
startFlows.calledTwice.should.be.true;
|
|
||||||
|
|
||||||
var configDiff = {
|
|
||||||
type: 'nodes',
|
|
||||||
stop: [],
|
|
||||||
rewire: [],
|
|
||||||
config: testFlow2
|
|
||||||
}
|
|
||||||
stopFlows.args[1][0].should.eql(configDiff);
|
|
||||||
startFlows.args[1][0].should.eql(configDiff);
|
|
||||||
|
|
||||||
done();
|
|
||||||
} catch(err) {
|
|
||||||
done(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
@ -21,8 +21,15 @@ var when = require("when");
|
|||||||
var sinon = require('sinon');
|
var sinon = require('sinon');
|
||||||
|
|
||||||
var index = require("../../../red/nodes/index");
|
var index = require("../../../red/nodes/index");
|
||||||
|
var flows = require("../../../red/nodes/flows");
|
||||||
|
|
||||||
describe("red/nodes/index", function() {
|
describe("red/nodes/index", function() {
|
||||||
|
before(function() {
|
||||||
|
sinon.stub(flows,"startFlows");
|
||||||
|
});
|
||||||
|
after(function() {
|
||||||
|
flows.startFlows.restore();
|
||||||
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
index.clearRegistry();
|
index.clearRegistry();
|
||||||
|
@ -13,10 +13,10 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
var should = require("should");
|
var should = require("should");
|
||||||
|
|
||||||
var deprecated = require("../../../red/nodes/deprecated.js");
|
var deprecated = require("../../../../red/nodes/registry/deprecated.js");
|
||||||
|
|
||||||
describe('deprecated', function() {
|
describe('deprecated', function() {
|
||||||
it('should return info on a node',function() {
|
it('should return info on a node',function() {
|
@ -30,11 +30,11 @@ var log = require("../../red/log");
|
|||||||
describe("red/server", function() {
|
describe("red/server", function() {
|
||||||
var commsMessages = [];
|
var commsMessages = [];
|
||||||
var commsPublish;
|
var commsPublish;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
commsMessages = [];
|
commsMessages = [];
|
||||||
});
|
});
|
||||||
|
|
||||||
before(function() {
|
before(function() {
|
||||||
commsPublish = sinon.stub(comms,"publish", function(topic,msg,retained) {
|
commsPublish = sinon.stub(comms,"publish", function(topic,msg,retained) {
|
||||||
commsMessages.push({topic:topic,msg:msg,retained:retained});
|
commsMessages.push({topic:topic,msg:msg,retained:retained});
|
||||||
@ -43,22 +43,22 @@ describe("red/server", function() {
|
|||||||
after(function() {
|
after(function() {
|
||||||
commsPublish.restore();
|
commsPublish.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("initialises components", function() {
|
it("initialises components", function() {
|
||||||
var commsInit = sinon.stub(comms,"init",function() {});
|
var commsInit = sinon.stub(comms,"init",function() {});
|
||||||
var dummyServer = {};
|
var dummyServer = {};
|
||||||
server.init(dummyServer,{testSettings: true, httpAdminRoot:"/", load:function() { return when.resolve();}});
|
server.init(dummyServer,{testSettings: true, httpAdminRoot:"/", load:function() { return when.resolve();}});
|
||||||
|
|
||||||
commsInit.called.should.be.true;
|
commsInit.called.should.be.true;
|
||||||
|
|
||||||
should.exist(server.app);
|
should.exist(server.app);
|
||||||
should.exist(server.nodeApp);
|
should.exist(server.nodeApp);
|
||||||
|
|
||||||
server.server.should.equal(dummyServer);
|
server.server.should.equal(dummyServer);
|
||||||
|
|
||||||
commsInit.restore();
|
commsInit.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("start",function() {
|
describe("start",function() {
|
||||||
var commsInit;
|
var commsInit;
|
||||||
var storageInit;
|
var storageInit;
|
||||||
@ -73,8 +73,9 @@ describe("red/server", function() {
|
|||||||
var redNodesCleanModuleList;
|
var redNodesCleanModuleList;
|
||||||
var redNodesGetNodeList;
|
var redNodesGetNodeList;
|
||||||
var redNodesLoadFlows;
|
var redNodesLoadFlows;
|
||||||
|
var redNodesStartFlows;
|
||||||
var commsStart;
|
var commsStart;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
commsInit = sinon.stub(comms,"init",function() {});
|
commsInit = sinon.stub(comms,"init",function() {});
|
||||||
storageInit = sinon.stub(storage,"init",function(settings) {return when.resolve();});
|
storageInit = sinon.stub(storage,"init",function(settings) {return when.resolve();});
|
||||||
@ -86,7 +87,8 @@ describe("red/server", function() {
|
|||||||
redNodesInit = sinon.stub(redNodes,"init", function() {});
|
redNodesInit = sinon.stub(redNodes,"init", function() {});
|
||||||
redNodesLoad = sinon.stub(redNodes,"load", function() {return when.resolve()});
|
redNodesLoad = sinon.stub(redNodes,"load", function() {return when.resolve()});
|
||||||
redNodesCleanModuleList = sinon.stub(redNodes,"cleanModuleList",function(){});
|
redNodesCleanModuleList = sinon.stub(redNodes,"cleanModuleList",function(){});
|
||||||
redNodesLoadFlows = sinon.stub(redNodes,"loadFlows",function() {});
|
redNodesLoadFlows = sinon.stub(redNodes,"loadFlows",function() {return when.resolve()});
|
||||||
|
redNodesStartFlows = sinon.stub(redNodes,"startFlows",function() {});
|
||||||
commsStart = sinon.stub(comms,"start",function(){});
|
commsStart = sinon.stub(comms,"start",function(){});
|
||||||
});
|
});
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
@ -99,9 +101,10 @@ describe("red/server", function() {
|
|||||||
logLog.restore();
|
logLog.restore();
|
||||||
redNodesInit.restore();
|
redNodesInit.restore();
|
||||||
redNodesLoad.restore();
|
redNodesLoad.restore();
|
||||||
redNodesGetNodeList.restore();
|
redNodesGetNodeList.restore();
|
||||||
redNodesCleanModuleList.restore();
|
redNodesCleanModuleList.restore();
|
||||||
redNodesLoadFlows.restore();
|
redNodesLoadFlows.restore();
|
||||||
|
redNodesStartFlows.restore();
|
||||||
commsStart.restore();
|
commsStart.restore();
|
||||||
});
|
});
|
||||||
it("reports errored/missing modules",function(done) {
|
it("reports errored/missing modules",function(done) {
|
||||||
@ -120,7 +123,7 @@ describe("red/server", function() {
|
|||||||
redNodesLoad.calledOnce.should.be.true;
|
redNodesLoad.calledOnce.should.be.true;
|
||||||
commsStart.calledOnce.should.be.true;
|
commsStart.calledOnce.should.be.true;
|
||||||
redNodesLoadFlows.calledOnce.should.be.true;
|
redNodesLoadFlows.calledOnce.should.be.true;
|
||||||
|
|
||||||
logWarn.calledWithMatch("Failed to register 1 node type");
|
logWarn.calledWithMatch("Failed to register 1 node type");
|
||||||
logWarn.calledWithMatch("Missing node modules");
|
logWarn.calledWithMatch("Missing node modules");
|
||||||
logWarn.calledWithMatch(" - module: typeA, typeB");
|
logWarn.calledWithMatch(" - module: typeA, typeB");
|
||||||
@ -168,7 +171,7 @@ describe("red/server", function() {
|
|||||||
});
|
});
|
||||||
server.init({},{testSettings: true, verbose:true, httpAdminRoot:"/", load:function() { return when.resolve();}});
|
server.init({},{testSettings: true, verbose:true, httpAdminRoot:"/", load:function() { return when.resolve();}});
|
||||||
server.start().then(function() {
|
server.start().then(function() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
apiInit.calledOnce.should.be.true;
|
apiInit.calledOnce.should.be.true;
|
||||||
logWarn.neverCalledWithMatch("Failed to register 1 node type");
|
logWarn.neverCalledWithMatch("Failed to register 1 node type");
|
||||||
@ -179,7 +182,7 @@ describe("red/server", function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("reports runtime metrics",function(done) {
|
it("reports runtime metrics",function(done) {
|
||||||
var commsStop = sinon.stub(comms,"stop",function() {} );
|
var commsStop = sinon.stub(comms,"stop",function() {} );
|
||||||
var stopFlows = sinon.stub(redNodes,"stopFlows",function() {} );
|
var stopFlows = sinon.stub(redNodes,"stopFlows",function() {} );
|
||||||
@ -208,8 +211,8 @@ describe("red/server", function() {
|
|||||||
}
|
}
|
||||||
},500);
|
},500);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't init api if httpAdminRoot set to false",function(done) {
|
it("doesn't init api if httpAdminRoot set to false",function(done) {
|
||||||
redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function() {return []});
|
redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function() {return []});
|
||||||
server.init({},{testSettings: true, httpAdminRoot:false, load:function() { return when.resolve();}});
|
server.init({},{testSettings: true, httpAdminRoot:false, load:function() { return when.resolve();}});
|
||||||
@ -225,20 +228,20 @@ describe("red/server", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("stops components", function() {
|
it("stops components", function() {
|
||||||
var commsStop = sinon.stub(comms,"stop",function() {} );
|
var commsStop = sinon.stub(comms,"stop",function() {} );
|
||||||
var stopFlows = sinon.stub(redNodes,"stopFlows",function() {} );
|
var stopFlows = sinon.stub(redNodes,"stopFlows",function() {} );
|
||||||
|
|
||||||
server.stop();
|
server.stop();
|
||||||
|
|
||||||
commsStop.called.should.be.true;
|
commsStop.called.should.be.true;
|
||||||
stopFlows.called.should.be.true;
|
stopFlows.called.should.be.true;
|
||||||
|
|
||||||
commsStop.restore();
|
commsStop.restore();
|
||||||
stopFlows.restore();
|
stopFlows.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("reports added modules", function() {
|
it("reports added modules", function() {
|
||||||
var nodes = {nodes:[
|
var nodes = {nodes:[
|
||||||
{types:["a"]},
|
{types:["a"]},
|
||||||
@ -246,13 +249,13 @@ describe("red/server", function() {
|
|||||||
{types:["c"],err:"error"}
|
{types:["c"],err:"error"}
|
||||||
]};
|
]};
|
||||||
var result = server.reportAddedModules(nodes);
|
var result = server.reportAddedModules(nodes);
|
||||||
|
|
||||||
result.should.equal(nodes);
|
result.should.equal(nodes);
|
||||||
commsMessages.should.have.length(1);
|
commsMessages.should.have.length(1);
|
||||||
commsMessages[0].topic.should.equal("node/added");
|
commsMessages[0].topic.should.equal("node/added");
|
||||||
commsMessages[0].msg.should.eql(nodes.nodes);
|
commsMessages[0].msg.should.eql(nodes.nodes);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("reports removed modules", function() {
|
it("reports removed modules", function() {
|
||||||
var nodes = [
|
var nodes = [
|
||||||
{types:["a"]},
|
{types:["a"]},
|
||||||
@ -260,13 +263,13 @@ describe("red/server", function() {
|
|||||||
{types:["c"],err:"error"}
|
{types:["c"],err:"error"}
|
||||||
];
|
];
|
||||||
var result = server.reportRemovedModules(nodes);
|
var result = server.reportRemovedModules(nodes);
|
||||||
|
|
||||||
result.should.equal(nodes);
|
result.should.equal(nodes);
|
||||||
commsMessages.should.have.length(1);
|
commsMessages.should.have.length(1);
|
||||||
commsMessages[0].topic.should.equal("node/removed");
|
commsMessages[0].topic.should.equal("node/removed");
|
||||||
commsMessages[0].msg.should.eql(nodes);
|
commsMessages[0].msg.should.eql(nodes);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("installs module", function() {
|
describe("installs module", function() {
|
||||||
it("rejects invalid module names", function(done) {
|
it("rejects invalid module names", function(done) {
|
||||||
var promises = [];
|
var promises = [];
|
||||||
@ -278,12 +281,12 @@ describe("red/server", function() {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects when npm returns a 404", function(done) {
|
it("rejects when npm returns a 404", function(done) {
|
||||||
var exec = sinon.stub(child_process,"exec",function(cmd,opt,cb) {
|
var exec = sinon.stub(child_process,"exec",function(cmd,opt,cb) {
|
||||||
cb(new Error(),""," 404 this_wont_exist");
|
cb(new Error(),""," 404 this_wont_exist");
|
||||||
});
|
});
|
||||||
|
|
||||||
server.installModule("this_wont_exist").otherwise(function(err) {
|
server.installModule("this_wont_exist").otherwise(function(err) {
|
||||||
err.code.should.be.eql(404);
|
err.code.should.be.eql(404);
|
||||||
done();
|
done();
|
||||||
@ -295,7 +298,7 @@ describe("red/server", function() {
|
|||||||
var exec = sinon.stub(child_process,"exec",function(cmd,opt,cb) {
|
var exec = sinon.stub(child_process,"exec",function(cmd,opt,cb) {
|
||||||
cb(new Error("test_error"),"","");
|
cb(new Error("test_error"),"","");
|
||||||
});
|
});
|
||||||
|
|
||||||
server.installModule("this_wont_exist").then(function() {
|
server.installModule("this_wont_exist").then(function() {
|
||||||
done(new Error("Unexpected success"));
|
done(new Error("Unexpected success"));
|
||||||
}).otherwise(function(err) {
|
}).otherwise(function(err) {
|
||||||
@ -312,7 +315,7 @@ describe("red/server", function() {
|
|||||||
var addModule = sinon.stub(redNodes,"addModule",function(md) {
|
var addModule = sinon.stub(redNodes,"addModule",function(md) {
|
||||||
return when.resolve(nodeInfo);
|
return when.resolve(nodeInfo);
|
||||||
});
|
});
|
||||||
|
|
||||||
server.installModule("this_wont_exist").then(function(info) {
|
server.installModule("this_wont_exist").then(function(info) {
|
||||||
info.should.eql(nodeInfo);
|
info.should.eql(nodeInfo);
|
||||||
commsMessages.should.have.length(1);
|
commsMessages.should.have.length(1);
|
||||||
@ -347,7 +350,7 @@ describe("red/server", function() {
|
|||||||
var exec = sinon.stub(child_process,"exec",function(cmd,opt,cb) {
|
var exec = sinon.stub(child_process,"exec",function(cmd,opt,cb) {
|
||||||
cb(new Error("test_error"),"","");
|
cb(new Error("test_error"),"","");
|
||||||
});
|
});
|
||||||
|
|
||||||
server.uninstallModule("this_wont_exist").then(function() {
|
server.uninstallModule("this_wont_exist").then(function() {
|
||||||
done(new Error("Unexpected success"));
|
done(new Error("Unexpected success"));
|
||||||
}).otherwise(function(err) {
|
}).otherwise(function(err) {
|
||||||
@ -366,7 +369,7 @@ describe("red/server", function() {
|
|||||||
cb(null,"","");
|
cb(null,"","");
|
||||||
});
|
});
|
||||||
var exists = sinon.stub(fs,"existsSync", function(fn) { return true; });
|
var exists = sinon.stub(fs,"existsSync", function(fn) { return true; });
|
||||||
|
|
||||||
server.uninstallModule("this_wont_exist").then(function(info) {
|
server.uninstallModule("this_wont_exist").then(function(info) {
|
||||||
info.should.eql(nodeInfo);
|
info.should.eql(nodeInfo);
|
||||||
commsMessages.should.have.length(1);
|
commsMessages.should.have.length(1);
|
||||||
@ -382,5 +385,5 @@ describe("red/server", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user