@node-red/runtime
Advanced tools
Comparing version 3.1.0-beta.3 to 3.1.0-beta.4
@@ -17,6 +17,6 @@ /** | ||
var clone = require("clone"); | ||
var redUtil = require("@node-red/util").util; | ||
const clone = require("clone"); | ||
const redUtil = require("@node-red/util").util; | ||
const events = require("@node-red/util").events; | ||
var flowUtil = require("./util"); | ||
const flowUtil = require("./util"); | ||
const context = require('../nodes/context'); | ||
@@ -26,7 +26,8 @@ const hooks = require("@node-red/util").hooks; | ||
var Subflow; | ||
var Log; | ||
let Subflow; | ||
let Log; | ||
let Group; | ||
var nodeCloseTimeout = 15000; | ||
var asyncMessageDelivery = true; | ||
let nodeCloseTimeout = 15000; | ||
let asyncMessageDelivery = true; | ||
@@ -57,2 +58,4 @@ /** | ||
this.id = this.flow.id || "global"; | ||
this.groups = {} | ||
this.groupOrder = [] | ||
this.activeNodes = {}; | ||
@@ -65,2 +68,7 @@ this.subflowInstanceNodes = {}; | ||
this.context = context.getFlowContext(this.id,this.parent.id); | ||
// env is an array of env definitions | ||
// _env is an object for direct lookup of env name -> value | ||
this.env = this.flow.env | ||
this._env = {} | ||
} | ||
@@ -143,3 +151,3 @@ | ||
*/ | ||
start(diff) { | ||
async start(diff) { | ||
this.trace("start "+this.TYPE+" ["+this.path+"]"); | ||
@@ -153,2 +161,48 @@ var node; | ||
if (this.isGlobalFlow) { | ||
// This is the global flow. It needs to go find the `global-config` | ||
// node and extract any env properties from it | ||
const configNodes = Object.keys(this.flow.configs); | ||
for (let i = 0; i < configNodes.length; i++) { | ||
const node = this.flow.configs[configNodes[i]] | ||
if (node.type === 'global-config' && node.env) { | ||
const nodeEnv = await flowUtil.evaluateEnvProperties(this, node.env, credentials.get(node.id)) | ||
this._env = { ...this._env, ...nodeEnv } | ||
} | ||
} | ||
} | ||
if (this.env) { | ||
this._env = { ...this._env, ...await flowUtil.evaluateEnvProperties(this, this.env, credentials.get(this.id)) } | ||
} | ||
// Initialise the group objects. These must be done in the right order | ||
// starting from outer-most to inner-most so that the parent hierarchy | ||
// is maintained. | ||
this.groups = {} | ||
this.groupOrder = [] | ||
const groupIds = Object.keys(this.flow.groups || {}) | ||
while (groupIds.length > 0) { | ||
const id = groupIds.shift() | ||
const groupDef = this.flow.groups[id] | ||
if (!groupDef.g || this.groups[groupDef.g]) { | ||
// The parent of this group is available - either another group | ||
// or the top-level flow (this) | ||
const parent = this.groups[groupDef.g] || this | ||
this.groups[groupDef.id] = new Group(parent, groupDef) | ||
this.groupOrder.push(groupDef.id) | ||
} else { | ||
// Try again once we've processed the other groups | ||
groupIds.push(id) | ||
} | ||
} | ||
for (let i = 0; i < this.groupOrder.length; i++) { | ||
// Start the groups in the right order so they | ||
// can setup their env vars knowning their parent | ||
// will have been started | ||
await this.groups[this.groupOrder[i]].start() | ||
} | ||
var configNodes = Object.keys(this.flow.configs); | ||
@@ -186,3 +240,3 @@ var configNodeAttempts = {}; | ||
if (readyToCreate) { | ||
newNode = flowUtil.createNode(this,node); | ||
newNode = await flowUtil.createNode(this,node); | ||
if (newNode) { | ||
@@ -213,3 +267,3 @@ this.activeNodes[id] = newNode; | ||
if (!this.activeNodes[id]) { | ||
newNode = flowUtil.createNode(this,node); | ||
newNode = await flowUtil.createNode(this,node); | ||
if (newNode) { | ||
@@ -232,3 +286,3 @@ this.activeNodes[id] = newNode; | ||
this.subflowInstanceNodes[id] = subflow; | ||
subflow.start(); | ||
await subflow.start(); | ||
this.activeNodes[id] = subflow.node; | ||
@@ -416,4 +470,3 @@ | ||
getGroupNode(id) { | ||
const groups = this.global.groups; | ||
return groups[id]; | ||
return this.groups[id]; | ||
} | ||
@@ -429,91 +482,4 @@ | ||
/*! | ||
* Get value of environment variable defined in group node. | ||
* @param {String} group - group node | ||
* @param {String} name - name of variable | ||
* @return {Object} object containing the value in val property or null if not defined | ||
*/ | ||
getGroupEnvSetting(node, group, name) { | ||
if (group) { | ||
if (name === "NR_GROUP_NAME") { | ||
return [{ | ||
val: group.name | ||
}, null]; | ||
} | ||
if (name === "NR_GROUP_ID") { | ||
return [{ | ||
val: group.id | ||
}, null]; | ||
} | ||
if (group.credentials === undefined) { | ||
group.credentials = credentials.get(group.id) || {}; | ||
} | ||
if (!name.startsWith("$parent.")) { | ||
if (group.env) { | ||
if (!group._env) { | ||
const envs = group.env; | ||
const entries = envs.map((env) => { | ||
if (env.type === "cred") { | ||
const cred = group.credentials; | ||
if (cred.hasOwnProperty(env.name)) { | ||
env.value = cred[env.name]; | ||
} | ||
} | ||
return [env.name, env]; | ||
}); | ||
group._env = Object.fromEntries(entries); | ||
} | ||
const env = group._env[name]; | ||
if (env) { | ||
let value = env.value; | ||
const type = env.type; | ||
if ((type !== "env") || | ||
(value !== name)) { | ||
if (type === "env") { | ||
value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}"); | ||
} | ||
if (type === "bool") { | ||
const val | ||
= ((value === "true") || | ||
(value === true)); | ||
return [{ | ||
val: val | ||
}, null]; | ||
} | ||
if (type === "cred") { | ||
return [{ | ||
val: value | ||
}, null]; | ||
} | ||
try { | ||
var val = redUtil.evaluateNodeProperty(value, type, node, null, null); | ||
return [{ | ||
val: val | ||
}, null]; | ||
} | ||
catch (e) { | ||
this.error(e); | ||
return [null, null]; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
else { | ||
name = name.substring(8); | ||
} | ||
if (group.g) { | ||
const parent = this.getGroupNode(group.g); | ||
return this.getGroupEnvSetting(node, parent, name); | ||
} | ||
} | ||
return [null, name]; | ||
} | ||
/** | ||
* Get a flow setting value. This currently automatically defers to the parent | ||
* flow which, as defined in ./index.js returns `process.env[key]`. | ||
* This lays the groundwork for Subflow to have instance-specific settings | ||
* Get a flow setting value. | ||
* @param {[type]} key [description] | ||
@@ -530,50 +496,10 @@ * @return {[type]} [description] | ||
} | ||
if (flow.credentials === undefined) { | ||
flow.credentials = credentials.get(flow.id) || {}; | ||
} | ||
if (flow.env) { | ||
if (!key.startsWith("$parent.")) { | ||
if (!flow._env) { | ||
const envs = flow.env; | ||
const entries = envs.map((env) => { | ||
if (env.type === "cred") { | ||
const cred = flow.credentials; | ||
if (cred.hasOwnProperty(env.name)) { | ||
env.value = cred[env.name]; | ||
} | ||
} | ||
return [env.name, env] | ||
}); | ||
flow._env = Object.fromEntries(entries); | ||
} | ||
const env = flow._env[key]; | ||
if (env) { | ||
let value = env.value; | ||
const type = env.type; | ||
if ((type !== "env") || (value !== key)) { | ||
if (type === "env") { | ||
value = value.replace(new RegExp("\\${"+key+"}","g"),"${$parent."+key+"}"); | ||
} | ||
try { | ||
if (type === "bool") { | ||
const val = ((value === "true") || | ||
(value === true)); | ||
return val; | ||
} | ||
if (type === "cred") { | ||
return value; | ||
} | ||
var val = redUtil.evaluateNodeProperty(value, type, null, null, null); | ||
return val; | ||
} | ||
catch (e) { | ||
this.error(e); | ||
} | ||
} | ||
} | ||
if (!key.startsWith("$parent.")) { | ||
if (this._env.hasOwnProperty(key)) { | ||
return this._env[key] | ||
} | ||
else { | ||
} else { | ||
key = key.substring(8); | ||
} | ||
} | ||
// Delegate to the parent flow. | ||
return this.parent.getSetting(key); | ||
@@ -616,6 +542,5 @@ } | ||
if (node.users.hasOwnProperty(userNode)) { | ||
node.users[userNode]._flow.handleStatus(node,statusMessage,node.users[userNode],true); | ||
handled = node.users[userNode]._flow.handleStatus(node,statusMessage,node.users[userNode],true) || handled; | ||
} | ||
} | ||
handled = true; | ||
} else { | ||
@@ -634,6 +559,6 @@ const candidateNodes = []; | ||
// Reporting node inside a group. Calculate the distance between it and the status node | ||
let containingGroup = this.global.groups[reportingNode.g] | ||
let containingGroup = this.groups[reportingNode.g] | ||
while (containingGroup && containingGroup.id !== targetStatusNode.g) { | ||
distance++ | ||
containingGroup = this.global.groups[containingGroup.g] | ||
containingGroup = this.groups[containingGroup.g] | ||
} | ||
@@ -705,6 +630,5 @@ if (!containingGroup && targetStatusNode.g && targetStatusNode.scope === 'group') { | ||
if (node.users.hasOwnProperty(userNode)) { | ||
node.users[userNode]._flow.handleError(node,logMessage,msg,node.users[userNode]); | ||
handled = node.users[userNode]._flow.handleError(node,logMessage,msg,node.users[userNode]) || handled; | ||
} | ||
} | ||
handled = true; | ||
} else { | ||
@@ -724,6 +648,6 @@ const candidateNodes = []; | ||
// Reporting node inside a group. Calculate the distance between it and the catch node | ||
let containingGroup = this.global.groups[reportingNode.g] | ||
let containingGroup = this.groups[reportingNode.g] | ||
while (containingGroup && containingGroup.id !== targetCatchNode.g) { | ||
distance++ | ||
containingGroup = this.global.groups[containingGroup.g] | ||
containingGroup = this.groups[containingGroup.g] | ||
} | ||
@@ -878,3 +802,3 @@ if (!containingGroup && targetCatchNode.g && targetCatchNode.scope === 'group') { | ||
sendEvent.destination.node = flow.getNode(sendEvent.destination.id); | ||
if (sendEvent.destination.node) { | ||
if (sendEvent.destination.node && typeof sendEvent.destination.node === 'object') { | ||
if (sendEvent.cloneMessage) { | ||
@@ -929,7 +853,8 @@ sendEvent.msg = redUtil.cloneMessage(sendEvent.msg); | ||
Subflow = require("./Subflow"); | ||
Group = require("./Group").Group | ||
}, | ||
create: function(parent,global,conf) { | ||
return new Flow(parent,global,conf); | ||
return new Flow(parent,global,conf) | ||
}, | ||
Flow: Flow | ||
} |
@@ -274,2 +274,6 @@ /** | ||
type = type || "full"; | ||
if (diff && diff.globalConfigChanged) { | ||
type = 'full' | ||
} | ||
started = true; | ||
@@ -363,3 +367,3 @@ state = 'start' | ||
// This flow is not disabled, nor is it currently active, so create it | ||
activeFlows[id] = Flow.create(flowAPI,activeFlowConfig,activeFlowConfig.flows[id]); | ||
activeFlows[id] = Flow.create(activeFlows['global'],activeFlowConfig,activeFlowConfig.flows[id]); | ||
log.debug("red/nodes/flows.start : starting flow : "+id); | ||
@@ -384,3 +388,3 @@ } else { | ||
// This flow didn't previously exist, so create it | ||
activeFlows[id] = Flow.create(flowAPI,activeFlowConfig,activeFlowConfig.flows[id]); | ||
activeFlows[id] = Flow.create(activeFlows['global'],activeFlowConfig,activeFlowConfig.flows[id]); | ||
log.debug("red/nodes/flows.start : starting flow : "+id); | ||
@@ -397,3 +401,3 @@ } | ||
try { | ||
activeFlows[id].start(diff); | ||
await activeFlows[id].start(diff); | ||
// Create a map of node id to flow id and also a subflowInstance lookup map | ||
@@ -439,3 +443,4 @@ var activeNodes = activeFlows[id].getActiveNodes(); | ||
rewired:[], | ||
linked:[] | ||
linked:[], | ||
flowChanged:[] | ||
}; | ||
@@ -449,2 +454,5 @@ if (!muteLog) { | ||
} | ||
if (diff.globalConfigChanged) { | ||
type = 'full' | ||
} | ||
started = false; | ||
@@ -473,3 +481,3 @@ state = 'stop' | ||
if (activeFlows.hasOwnProperty(id)) { | ||
var flowStateChanged = diff && (diff.added.indexOf(id) !== -1 || diff.removed.indexOf(id) !== -1); | ||
var flowStateChanged = diff && (diff.flowChanged.indexOf(id) !== -1 || diff.added.indexOf(id) !== -1 || diff.removed.indexOf(id) !== -1); | ||
log.debug("red/nodes/flows.stop : stopping flow : "+id); | ||
@@ -794,13 +802,2 @@ promises.push(activeFlows[id].stop(flowStateChanged?null:stopList,removedList)); | ||
function getGlobalConfig() { | ||
let gconf = null; | ||
eachNode((n) => { | ||
if (n.type === "global-config") { | ||
gconf = n; | ||
} | ||
}); | ||
return gconf; | ||
} | ||
module.exports = { | ||
@@ -818,6 +815,3 @@ init: init, | ||
eachNode: eachNode, | ||
getGlobalConfig: getGlobalConfig, | ||
/** | ||
@@ -824,0 +818,0 @@ * Gets the current flow configuration |
@@ -122,3 +122,3 @@ /** | ||
var env = []; | ||
var env = {}; | ||
if (this.subflowDef.env) { | ||
@@ -149,3 +149,3 @@ this.subflowDef.env.forEach(e => { | ||
} | ||
this.env = env; | ||
this.env = Object.values(env); | ||
} | ||
@@ -161,3 +161,3 @@ | ||
*/ | ||
start(diff) { | ||
async start(diff) { | ||
var self = this; | ||
@@ -316,3 +316,3 @@ // Create a subflow node to accept inbound messages and route appropriately | ||
} | ||
super.start(diff); | ||
return super.start(diff); | ||
} | ||
@@ -342,64 +342,31 @@ | ||
* Get environment variable of subflow | ||
* @param {String} name name of env var | ||
* @param {String} key name of env var | ||
* @return {Object} val value of env var | ||
*/ | ||
getSetting(name) { | ||
if (!/^\$parent\./.test(name)) { | ||
var env = this.env; | ||
if (env && env.hasOwnProperty(name)) { | ||
var val = env[name]; | ||
// If this is an env type property we need to be careful not | ||
// to get into lookup loops. | ||
// 1. if the value to lookup is the same as this one, go straight to parent | ||
// 2. otherwise, check if it is a compound env var ("foo $(bar)") | ||
// and if so, substitute any instances of `name` with $parent.name | ||
// See https://github.com/node-red/node-red/issues/2099 | ||
if (val.type !== 'env' || val.value !== name) { | ||
let value = val.value; | ||
var type = val.type; | ||
if (type === 'env') { | ||
value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}"); | ||
} | ||
try { | ||
return evaluateInputValue(value, type, this.node); | ||
} | ||
catch (e) { | ||
this.error(e); | ||
return undefined; | ||
} | ||
} else { | ||
// This _is_ an env property pointing at itself - go to parent | ||
} | ||
} | ||
} else { | ||
// name starts $parent. ... so delegate to parent automatically | ||
name = name.substring(8); | ||
} | ||
getSetting(key) { | ||
const node = this.subflowInstance; | ||
if (node) { | ||
if (name === "NR_NODE_NAME") { | ||
if (key === "NR_NODE_NAME" || key === "NR_SUBFLOW_NAME") { | ||
return node.name; | ||
} | ||
if (name === "NR_NODE_ID") { | ||
if (key === "NR_NODE_ID" || key === "NR_SUBFLOW_ID") { | ||
return node.id; | ||
} | ||
if (name === "NR_NODE_PATH") { | ||
if (key === "NR_NODE_PATH" || key === "NR_SUBFLOW_PATH") { | ||
return node._path; | ||
} | ||
} | ||
if (node.g) { | ||
const group = this.getGroupNode(node.g); | ||
const [result, newName] = this.getGroupEnvSetting(node, group, name); | ||
if (result) { | ||
return result.val; | ||
if (!key.startsWith("$parent.")) { | ||
if (this._env.hasOwnProperty(key)) { | ||
return this._env[key] | ||
} | ||
name = newName; | ||
} else { | ||
key = key.substring(8); | ||
} | ||
var parent = this.parent; | ||
if (parent) { | ||
var val = parent.getSetting(name); | ||
return val; | ||
// Push the request up to the parent. | ||
// Unlike a Flow, the parent of a Subflow could be a Group | ||
if (node.g) { | ||
return this.parent.getGroupNode(node.g).getSetting(key) | ||
} | ||
return undefined; | ||
return this.parent.getSetting(key) | ||
} | ||
@@ -406,0 +373,0 @@ |
@@ -16,12 +16,18 @@ /** | ||
**/ | ||
var clone = require("clone"); | ||
var redUtil = require("@node-red/util").util; | ||
var Log = require("@node-red/util").log; | ||
var subflowInstanceRE = /^subflow:(.+)$/; | ||
var typeRegistry = require("@node-red/registry"); | ||
const credentials = require("../nodes/credentials"); | ||
const clone = require("clone"); | ||
const redUtil = require("@node-red/util").util; | ||
const Log = require("@node-red/util").log; | ||
const typeRegistry = require("@node-red/registry"); | ||
const subflowInstanceRE = /^subflow:(.+)$/; | ||
let _runtime = null; | ||
let envVarExcludes = {}; | ||
var envVarExcludes = {}; | ||
function init(runtime) { | ||
_runtime = runtime; | ||
envVarExcludes = {}; | ||
if (runtime.settings.hasOwnProperty('envVarExcludes') && Array.isArray(runtime.settings.envVarExcludes)) { | ||
runtime.settings.envVarExcludes.forEach(v => envVarExcludes[v] = true); | ||
} | ||
} | ||
@@ -32,4 +38,7 @@ function diffNodes(oldNode,newNode) { | ||
} | ||
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" }); | ||
const keyFilter = p => p != 'x' && p != 'y' && p != 'wires' | ||
const groupKeyFilter = p => keyFilter(p) && p != 'nodes' && p != 'style' && p != 'w' && p != 'h' | ||
var oldKeys = Object.keys(oldNode).filter(oldNode.type === 'group' ? groupKeyFilter : keyFilter); | ||
var newKeys = Object.keys(newNode).filter(newNode.type === 'group' ? groupKeyFilter : keyFilter); | ||
if (oldKeys.length != newKeys.length) { | ||
@@ -75,4 +84,60 @@ return true; | ||
async function evaluateEnvProperties(flow, env, credentials) { | ||
const pendingEvaluations = [] | ||
const evaluatedEnv = {} | ||
const envTypes = [] | ||
for (let i = 0; i < env.length; i++) { | ||
let { name, value, type } = env[i] | ||
if (type === "env") { | ||
// Do env types last as they may include references to other env vars | ||
// at this level which need to be resolved before they can be looked-up | ||
envTypes.push(env[i]) | ||
} else if (type === "bool") { | ||
value = (value === "true") || (value === true); | ||
} else if (type === "cred") { | ||
if (credentials.hasOwnProperty(name)) { | ||
value = credentials[name]; | ||
} | ||
} else if (type ==='jsonata') { | ||
pendingEvaluations.push(new Promise((resolve, _) => { | ||
redUtil.evaluateNodeProperty(value, 'jsonata', {_flow: flow}, null, (err, result) => { | ||
if (!err) { | ||
evaluatedEnv[name] = result | ||
} | ||
resolve() | ||
}); | ||
})) | ||
} else { | ||
value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null); | ||
} | ||
evaluatedEnv[name] = value | ||
} | ||
if (pendingEvaluations.length > 0) { | ||
await Promise.all(pendingEvaluations) | ||
} | ||
for (let i = 0; i < envTypes.length; i++) { | ||
let { name, value, type } = envTypes[i] | ||
// If an env-var wants to lookup itself, delegate straight to the parent | ||
// https://github.com/node-red/node-red/issues/2099 | ||
if (value === name) { | ||
value = `$parent.${name}` | ||
} | ||
if (evaluatedEnv.hasOwnProperty(value)) { | ||
value = evaluatedEnv[value] | ||
} else { | ||
value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null); | ||
} | ||
evaluatedEnv[name] = value | ||
} | ||
function createNode(flow,config) { | ||
return evaluatedEnv | ||
} | ||
/** | ||
* Create a new instance of a node | ||
* @param {Flow} flow The containing flow | ||
* @param {object} config The node configuration object | ||
* @return {Node} The instance of the node | ||
*/ | ||
async function createNode(flow,config) { | ||
var newNode = null; | ||
@@ -146,3 +211,3 @@ var type = config.type; | ||
flow.subflowInstanceNodes[config.id] = subflow | ||
subflow.start(); | ||
await subflow.start(); | ||
return subflow.node; | ||
@@ -157,287 +222,237 @@ } | ||
function parseConfig(config) { | ||
var flow = {}; | ||
flow.allNodes = {}; | ||
flow.subflows = {}; | ||
flow.configs = {}; | ||
flow.flows = {}; | ||
flow.groups = {}; | ||
flow.missingTypes = []; | ||
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 = {}; | ||
} | ||
if (n.type === 'group') { | ||
flow.groups[n.id] = n; | ||
} | ||
}); | ||
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 = {}; | ||
flow.flows[n.id].groups = {}; | ||
} else if (n.type === 'subflow') { | ||
flow.subflows[n.id] = n; | ||
flow.subflows[n.id].configs = {}; | ||
flow.subflows[n.id].nodes = {}; | ||
flow.subflows[n.id].groups = {}; | ||
flow.subflows[n.id].instances = []; | ||
} | ||
}); | ||
// TODO: why a separate forEach? this can be merged with above | ||
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 = []; | ||
} | ||
}); | ||
var linkWires = {}; | ||
var linkOutNodes = []; | ||
config.forEach(function(n) { | ||
if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') { | ||
var subflowDetails = subflowInstanceRE.exec(n.type); | ||
var linkWires = {}; | ||
var linkOutNodes = []; | ||
config.forEach(function (n) { | ||
if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') { | ||
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); | ||
} | ||
} | ||
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; | ||
if (flow.subflows[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 = []; | ||
} | ||
} | ||
if (n.type === 'link in' && n.links) { | ||
// Ensure wires are present in corresponding link out nodes | ||
n.links.forEach(function(id) { | ||
linkWires[id] = linkWires[id]||{}; | ||
linkWires[id][n.id] = true; | ||
}) | ||
} else if (n.type === 'link out' && n.links) { | ||
linkWires[n.id] = linkWires[n.id]||{}; | ||
n.links.forEach(function(id) { | ||
linkWires[n.id][id] = true; | ||
}) | ||
linkOutNodes.push(n); | ||
} | ||
} | ||
}); | ||
linkOutNodes.forEach(function(n) { | ||
var links = linkWires[n.id]; | ||
var targets = Object.keys(links); | ||
n.wires = [targets]; | ||
}); | ||
if ((subflowDetails && !flow.subflows[subflowDetails[1]]) || (!subflowDetails && !typeRegistry.get(n.type))) { | ||
if (flow.missingTypes.indexOf(n.type) === -1) { | ||
flow.missingTypes.push(n.type); | ||
} | ||
} | ||
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; | ||
if (flow.subflows[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 = []; | ||
} | ||
} | ||
if (n.type === 'link in' && n.links) { | ||
// Ensure wires are present in corresponding link out nodes | ||
n.links.forEach(function (id) { | ||
linkWires[id] = linkWires[id] || {}; | ||
linkWires[id][n.id] = true; | ||
}) | ||
} else if (n.type === 'link out' && n.links) { | ||
linkWires[n.id] = linkWires[n.id] || {}; | ||
n.links.forEach(function (id) { | ||
linkWires[n.id][id] = true; | ||
}) | ||
linkOutNodes.push(n); | ||
} | ||
} else if (n.type === 'group') { | ||
const parentContainer = flow.flows[n.z] || flow.subflows[n.z] | ||
if (parentContainer) { | ||
parentContainer.groups[n.id] = n | ||
} | ||
} | ||
}); | ||
linkOutNodes.forEach(function (n) { | ||
var links = linkWires[n.id]; | ||
var targets = Object.keys(links); | ||
n.wires = [targets]; | ||
}); | ||
var addedTabs = {}; | ||
config.forEach(function(n) { | ||
if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') { | ||
for (var prop in n) { | ||
if (n.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== 'type' && prop !== '_users' && flow.configs.hasOwnProperty(n[prop])) { | ||
// This property references a global config node | ||
flow.configs[n[prop]]._users.push(n.id) | ||
} | ||
} | ||
if (n.z && !flow.subflows[n.z]) { | ||
var addedTabs = {}; | ||
config.forEach(function (n) { | ||
if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') { | ||
for (var prop in n) { | ||
if (n.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== 'type' && prop !== '_users' && flow.configs.hasOwnProperty(n[prop])) { | ||
// This property references a global config node | ||
flow.configs[n[prop]]._users.push(n.id) | ||
} | ||
} | ||
if (n.z && !flow.subflows[n.z]) { | ||
if (!flow.flows[n.z]) { | ||
flow.flows[n.z] = {type:'tab',id:n.z}; | ||
flow.flows[n.z].subflows = {}; | ||
flow.flows[n.z].configs = {}; | ||
flow.flows[n.z].nodes = {}; | ||
addedTabs[n.z] = flow.flows[n.z]; | ||
} | ||
if (addedTabs[n.z]) { | ||
if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) { | ||
addedTabs[n.z].nodes[n.id] = n; | ||
} else { | ||
addedTabs[n.z].configs[n.id] = n; | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
return flow; | ||
} | ||
function getGlobalEnv(name) { | ||
const nodes = _runtime.nodes; | ||
if (!nodes) { | ||
return null; | ||
} | ||
const gconf = nodes.getGlobalConfig(); | ||
const env = gconf ? gconf.env : null; | ||
if (env) { | ||
const cred = (gconf ? credentials.get(gconf.id) : null) || { | ||
map: {} | ||
}; | ||
const map = cred.map; | ||
for (let i = 0; i < env.length; i++) { | ||
const item = env[i]; | ||
if (item.name === name) { | ||
if (item.type === "cred") { | ||
return { | ||
name: name, | ||
value: map[name], | ||
type: "cred" | ||
}; | ||
if (!flow.flows[n.z]) { | ||
flow.flows[n.z] = { type: 'tab', id: n.z }; | ||
flow.flows[n.z].subflows = {}; | ||
flow.flows[n.z].configs = {}; | ||
flow.flows[n.z].nodes = {}; | ||
addedTabs[n.z] = flow.flows[n.z]; | ||
} | ||
return item; | ||
if (addedTabs[n.z]) { | ||
if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) { | ||
addedTabs[n.z].nodes[n.id] = n; | ||
} else { | ||
addedTabs[n.z].configs[n.id] = n; | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
return flow; | ||
} | ||
function getEnvVar(k) { | ||
if (!envVarExcludes[k]) { | ||
return process.env[k]; | ||
} | ||
return null; | ||
return undefined; | ||
} | ||
function diffConfigs(oldConfig, newConfig) { | ||
var id; | ||
var node; | ||
var nn; | ||
var wires; | ||
var j,k; | ||
module.exports = { | ||
init: function(runtime) { | ||
_runtime = runtime; | ||
envVarExcludes = {}; | ||
if (runtime.settings.hasOwnProperty('envVarExcludes') && Array.isArray(runtime.settings.envVarExcludes)) { | ||
runtime.settings.envVarExcludes.forEach(v => envVarExcludes[v] = true); | ||
if (!oldConfig) { | ||
oldConfig = { | ||
flows:{}, | ||
allNodes:{} | ||
} | ||
}, | ||
getEnvVar: function(k) { | ||
if (!envVarExcludes[k]) { | ||
const item = getGlobalEnv(k); | ||
if (item) { | ||
const val = redUtil.evaluateNodeProperty(item.value, item.type, null, null, null); | ||
return val; | ||
} | ||
return process.env[k]; | ||
} | ||
return undefined; | ||
}, | ||
diffNodes: diffNodes, | ||
mapEnvVarProperties: mapEnvVarProperties, | ||
} | ||
var changedSubflows = {}; | ||
parseConfig: parseConfig, | ||
var added = {}; | ||
var removed = {}; | ||
var changed = {}; | ||
var flowChanged = {}; | ||
var wiringChanged = {}; | ||
var globalConfigChanged = false; | ||
var linkMap = {}; | ||
var allNestedGroups = [] | ||
diffConfigs: function(oldConfig, newConfig) { | ||
var id; | ||
var node; | ||
var nn; | ||
var wires; | ||
var j,k; | ||
if (!oldConfig) { | ||
oldConfig = { | ||
flows:{}, | ||
allNodes:{} | ||
} | ||
// Look for tabs that have been removed | ||
for (id in oldConfig.flows) { | ||
if (oldConfig.flows.hasOwnProperty(id) && (!newConfig.flows.hasOwnProperty(id))) { | ||
removed[id] = oldConfig.allNodes[id]; | ||
} | ||
var changedSubflows = {}; | ||
} | ||
var added = {}; | ||
var removed = {}; | ||
var changed = {}; | ||
var wiringChanged = {}; | ||
var linkMap = {}; | ||
var changedTabs = {}; | ||
// Look for tabs that have been removed | ||
for (id in oldConfig.flows) { | ||
if (oldConfig.flows.hasOwnProperty(id) && (!newConfig.flows.hasOwnProperty(id))) { | ||
removed[id] = oldConfig.allNodes[id]; | ||
} | ||
} | ||
// Look for tabs that have been disabled | ||
for (id in oldConfig.flows) { | ||
if (oldConfig.flows.hasOwnProperty(id) && newConfig.flows.hasOwnProperty(id)) { | ||
var originalState = oldConfig.flows[id].disabled||false; | ||
var newState = newConfig.flows[id].disabled||false; | ||
if (originalState !== newState) { | ||
changedTabs[id] = true; | ||
if (originalState) { | ||
added[id] = oldConfig.allNodes[id]; | ||
} else { | ||
removed[id] = oldConfig.allNodes[id]; | ||
} | ||
// Look for tabs that have been disabled | ||
for (id in oldConfig.flows) { | ||
if (oldConfig.flows.hasOwnProperty(id) && newConfig.flows.hasOwnProperty(id)) { | ||
var originalState = oldConfig.flows[id].disabled||false; | ||
var newState = newConfig.flows[id].disabled||false; | ||
if (originalState !== newState) { | ||
if (originalState) { | ||
added[id] = oldConfig.allNodes[id]; | ||
} else { | ||
removed[id] = oldConfig.allNodes[id]; | ||
} | ||
} | ||
} | ||
} | ||
for (id in oldConfig.allNodes) { | ||
if (oldConfig.allNodes.hasOwnProperty(id)) { | ||
node = oldConfig.allNodes[id]; | ||
if (node.type !== 'tab') { | ||
// 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); | ||
} | ||
for (id in oldConfig.allNodes) { | ||
if (oldConfig.allNodes.hasOwnProperty(id)) { | ||
node = oldConfig.allNodes[id]; | ||
if (node.type !== 'tab') { | ||
// 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 or its flow disabled | ||
if (removed[node.z] || !newConfig.allNodes.hasOwnProperty(id)) { | ||
removed[id] = node; | ||
// Mark the container as changed | ||
if (!removed[node.z] && 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]; | ||
} | ||
} | ||
// This node has been removed or its flow disabled | ||
if (removed[node.z] || !newConfig.allNodes.hasOwnProperty(id)) { | ||
removed[id] = node; | ||
// Mark the container as changed | ||
if (!removed[node.z] && 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 { | ||
if (added[node.z]) { | ||
added[id] = node; | ||
} else { | ||
if (added[node.z]) { | ||
added[id] = node; | ||
} else { | ||
var currentState = node.d; | ||
var newState = newConfig.allNodes[id].d; | ||
if (!currentState && newState) { | ||
removed[id] = node; | ||
var currentState = node.d; | ||
var newState = newConfig.allNodes[id].d; | ||
if (!currentState && newState) { | ||
removed[id] = node; | ||
} | ||
// 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]; | ||
} | ||
// 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]; | ||
} | ||
// 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]; | ||
} | ||
if (newConfig.allNodes[id].type === 'global-config') { | ||
globalConfigChanged = true | ||
} | ||
} | ||
// 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]; | ||
} | ||
@@ -448,23 +463,41 @@ } | ||
} | ||
} else if (!removed[id]) { | ||
if (JSON.stringify(node.env) !== JSON.stringify(newConfig.allNodes[id].env)) { | ||
flowChanged[id] = newConfig.allNodes[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]); | ||
} | ||
// Look for added nodes | ||
for (id in newConfig.allNodes) { | ||
if (newConfig.allNodes.hasOwnProperty(id)) { | ||
node = newConfig.allNodes[id]; | ||
if (node.type === 'group') { | ||
if (node.g) { | ||
allNestedGroups.push(node) | ||
} | ||
if (changed[node.id]) { | ||
if (node.nodes) { | ||
node.nodes.forEach(nid => { | ||
if (!changed[nid]) { | ||
changed[nid] = true | ||
} | ||
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); | ||
} | ||
}) | ||
} | ||
} | ||
} | ||
// 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); | ||
} | ||
@@ -474,12 +507,12 @@ } | ||
} | ||
// 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]; | ||
} | ||
} | ||
// 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]; | ||
} | ||
@@ -489,42 +522,42 @@ } | ||
} | ||
} | ||
var madeChange; | ||
// Loop through the nodes looking for references to changed config nodes | ||
// Repeat the loop if anything is marked as changed as it may need to be | ||
// propagated to parent nodes. | ||
// TODO: looping through all nodes every time is a bit inefficient - could be more targeted | ||
do { | ||
madeChange = false; | ||
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. | ||
var madeChange; | ||
// Loop through the nodes looking for references to changed config nodes | ||
// Repeat the loop if anything is marked as changed as it may need to be | ||
// propagated to parent nodes. | ||
// TODO: looping through all nodes every time is a bit inefficient - could be more targeted | ||
do { | ||
madeChange = false; | ||
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. | ||
var changeOrigin = changed[node[prop]]; | ||
if (changeOrigin || removed[node[prop]]) { | ||
if (!changed[node.id]) { | ||
if (changeOrigin && | ||
(prop === "g") && | ||
(changeOrigin.type === "group")) { | ||
var oldNode = oldConfig.allNodes[node.id]; | ||
// ignore change of group node | ||
// if group of this node not changed | ||
if (oldNode && | ||
(node.g === oldNode.g)) { | ||
continue; | ||
} | ||
var changeOrigin = changed[node[prop]]; | ||
if (changeOrigin || removed[node[prop]]) { | ||
if (!changed[node.id]) { | ||
if (changeOrigin && | ||
(prop === "g") && | ||
(changeOrigin.type === "group")) { | ||
var oldNode = oldConfig.allNodes[node.id]; | ||
// ignore change of group node | ||
// if group of this node not changed | ||
if (oldNode && | ||
(node.g === oldNode.g)) { | ||
continue; | ||
} | ||
madeChange = true; | ||
changed[node.id] = node; | ||
// This node exists within subflow template | ||
// Mark the template as having changed | ||
if (newConfig.allNodes[node.z]) { | ||
changed[node.z] = newConfig.allNodes[node.z]; | ||
if (changed[node.z].type === "subflow") { | ||
changedSubflows[node.z] = changed[node.z]; | ||
} | ||
} | ||
madeChange = true; | ||
changed[node.id] = node; | ||
// This node exists within subflow template | ||
// Mark the template as having changed | ||
if (newConfig.allNodes[node.z]) { | ||
changed[node.z] = newConfig.allNodes[node.z]; | ||
if (changed[node.z].type === "subflow") { | ||
changedSubflows[node.z] = changed[node.z]; | ||
} | ||
@@ -537,33 +570,53 @@ } | ||
} | ||
} while (madeChange===true) | ||
} | ||
} while (madeChange===true) | ||
// Find any nodes that exist on a subflow template and remove from changed | ||
// list as the parent subflow will now be marked as containing a change | ||
// Find any nodes that exist on a subflow template and remove from changed | ||
// list as the parent subflow will now be marked as containing a change | ||
for (id in newConfig.allNodes) { | ||
if (newConfig.allNodes.hasOwnProperty(id)) { | ||
node = newConfig.allNodes[id]; | ||
if (newConfig.allNodes[node.z] && newConfig.allNodes[node.z].type === "subflow") { | ||
delete changed[node.id]; | ||
} | ||
} | ||
} | ||
// Recursively mark all children of changed groups as changed | ||
do { | ||
madeChange = false | ||
for (let i = 0; i < allNestedGroups.length; i++) { | ||
const group = allNestedGroups[i] | ||
if (!changed[group.id] && group.g && changed[group.g]) { | ||
changed[group.id] = true | ||
madeChange = true | ||
} | ||
if (changed[group.id] && group.nodes) { | ||
group.nodes.forEach(nid => { | ||
if (!changed[nid]) { | ||
changed[nid] = true | ||
madeChange = true | ||
} | ||
}) | ||
} | ||
} | ||
} while(madeChange) | ||
// 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 (newConfig.allNodes[node.z] && newConfig.allNodes[node.z].type === "subflow") { | ||
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]; | ||
} | ||
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]; | ||
} | ||
@@ -575,55 +628,64 @@ } | ||
} | ||
} | ||
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]); | ||
var diff = { | ||
added:Object.keys(added), | ||
changed:Object.keys(changed), | ||
removed:Object.keys(removed), | ||
rewired:Object.keys(wiringChanged), | ||
linked:[], | ||
flowChanged: Object.keys(flowChanged), | ||
globalConfigChanged | ||
} | ||
// 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]); | ||
} | ||
} | ||
// console.log(diff); | ||
// for (id in newConfig.allNodes) { | ||
// console.log( | ||
// (added[id]?"a":(changed[id]?"c":" "))+(wiringChanged[id]?"w":" ")+(diff.linked.indexOf(id)!==-1?"l":" "), | ||
// newConfig.allNodes[id].type.padEnd(10), | ||
// id.padEnd(16), | ||
// (newConfig.allNodes[id].z||"").padEnd(16), | ||
// 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||"" | ||
// ); | ||
// } | ||
} | ||
// console.log(diff); | ||
// for (id in newConfig.allNodes) { | ||
// if (added[id] || changed[id] || wiringChanged[id] || diff.linked.indexOf(id)!==-1) { | ||
// console.log( | ||
// (added[id]?"a":(changed[id]?"c":" "))+(wiringChanged[id]?"w":" ")+(diff.linked.indexOf(id)!==-1?"l":" "), | ||
// newConfig.allNodes[id].type.padEnd(10), | ||
// id.padEnd(16), | ||
// (newConfig.allNodes[id].z||"").padEnd(16), | ||
// 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; | ||
}, | ||
return diff; | ||
} | ||
/** | ||
* Create a new instance of a node | ||
* @param {Flow} flow The containing flow | ||
* @param {object} config The node configuration object | ||
* @return {Node} The instance of the node | ||
*/ | ||
createNode: createNode | ||
module.exports = { | ||
init, | ||
createNode, | ||
parseConfig, | ||
diffConfigs, | ||
diffNodes, | ||
getEnvVar, | ||
mapEnvVarProperties, | ||
evaluateEnvProperties | ||
} |
@@ -208,3 +208,2 @@ /** | ||
getContext: context.get, | ||
getGlobalConfig: flows.getGlobalConfig, | ||
@@ -211,0 +210,0 @@ clearContext: context.clear, |
{ | ||
"name": "@node-red/runtime", | ||
"version": "3.1.0-beta.3", | ||
"version": "3.1.0-beta.4", | ||
"license": "Apache-2.0", | ||
@@ -19,4 +19,4 @@ "main": "./lib/index.js", | ||
"dependencies": { | ||
"@node-red/registry": "3.1.0-beta.3", | ||
"@node-red/util": "3.1.0-beta.3", | ||
"@node-red/registry": "3.1.0-beta.4", | ||
"@node-red/util": "3.1.0-beta.4", | ||
"async-mutex": "0.4.0", | ||
@@ -23,0 +23,0 @@ "clone": "2.1.2", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
56
13759
595934
+ Added@node-red/registry@3.1.0-beta.4(transitive)
+ Added@node-red/util@3.1.0-beta.4(transitive)
- Removed@node-red/registry@3.1.0-beta.3(transitive)
- Removed@node-red/util@3.1.0-beta.3(transitive)
Updated@node-red/util@3.1.0-beta.4