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

Tidy up flow/util

This commit is contained in:
Nick O'Leary 2023-06-23 15:48:06 +01:00
parent f196493402
commit 3209777aba
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9

View File

@ -13,16 +13,22 @@
* 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 clone = require("clone"); const clone = require("clone");
var redUtil = require("@node-red/util").util; const redUtil = require("@node-red/util").util;
var Log = require("@node-red/util").log; const Log = require("@node-red/util").log;
var subflowInstanceRE = /^subflow:(.+)$/; const typeRegistry = require("@node-red/registry");
var typeRegistry = require("@node-red/registry"); const subflowInstanceRE = /^subflow:(.+)$/;
const credentials = require("../nodes/credentials");
let _runtime = null; 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);
}
}
function diffNodes(oldNode,newNode) { function diffNodes(oldNode,newNode) {
if (oldNode == null) { if (oldNode == null) {
@ -121,6 +127,12 @@ async function evaluateEnvProperties(flow, env, credentials) {
return evaluatedEnv 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) { async function createNode(flow,config) {
var newNode = null; var newNode = null;
var type = config.type; var type = config.type;
@ -317,259 +329,207 @@ function parseConfig(config) {
}); });
return flow; return flow;
} }
function getEnvVar(k) {
if (!envVarExcludes[k]) {
return process.env[k];
}
return undefined;
}
function diffConfigs(oldConfig, newConfig) {
var id;
var node;
var nn;
var wires;
var j,k;
module.exports = { if (!oldConfig) {
init: function(runtime) { oldConfig = {
_runtime = runtime; flows:{},
envVarExcludes = {}; allNodes:{}
if (runtime.settings.hasOwnProperty('envVarExcludes') && Array.isArray(runtime.settings.envVarExcludes)) {
runtime.settings.envVarExcludes.forEach(v => envVarExcludes[v] = true);
} }
}, }
getEnvVar: function(k) { var changedSubflows = {};
if (!envVarExcludes[k]) {
return process.env[k]; 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];
} }
return undefined; }
},
diffNodes, // Look for tabs that have been disabled
mapEnvVarProperties, for (id in oldConfig.flows) {
evaluateEnvProperties, if (oldConfig.flows.hasOwnProperty(id) && newConfig.flows.hasOwnProperty(id)) {
parseConfig, var originalState = oldConfig.flows[id].disabled||false;
var newState = newConfig.flows[id].disabled||false;
diffConfigs: function(oldConfig, newConfig) { if (originalState !== newState) {
var id; changedTabs[id] = true;
var node; if (originalState) {
var nn; added[id] = oldConfig.allNodes[id];
var wires; } else {
var j,k; removed[id] = oldConfig.allNodes[id];
if (!oldConfig) {
oldConfig = {
flows:{},
allNodes:{}
}
}
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];
}
} }
} }
} }
}
for (id in oldConfig.allNodes) { for (id in oldConfig.allNodes) {
if (oldConfig.allNodes.hasOwnProperty(id)) { if (oldConfig.allNodes.hasOwnProperty(id)) {
node = oldConfig.allNodes[id]; node = oldConfig.allNodes[id];
if (node.type !== 'tab') { if (node.type !== 'tab') {
// build the map of what this node was previously wired to // 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];
}
}
} 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;
}
// 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) { if (node.wires) {
linkMap[node.id] = linkMap[node.id] || []; linkMap[node.id] = linkMap[node.id] || [];
for (j=0;j<node.wires.length;j++) { for (j=0;j<node.wires.length;j++) {
wires = node.wires[j]; wires = node.wires[j];
for (k=0;k<wires.length;k++) { for (k=0;k<wires.length;k++) {
if (linkMap[node.id].indexOf(wires[k]) === -1) { linkMap[node.id].push(wires[k]);
linkMap[node.id].push(wires[k]); nn = oldConfig.allNodes[wires[k]];
}
nn = newConfig.allNodes[wires[k]];
if (nn) { if (nn) {
linkMap[nn.id] = linkMap[nn.id] || []; linkMap[nn.id] = linkMap[nn.id] || [];
if (linkMap[nn.id].indexOf(node.id) === -1) { linkMap[nn.id].push(node.id);
linkMap[nn.id].push(node.id);
}
} }
} }
} }
} }
// This node has been added // This node has been removed or its flow disabled
if (!oldConfig.allNodes.hasOwnProperty(id)) { if (removed[node.z] || !newConfig.allNodes.hasOwnProperty(id)) {
added[id] = node; removed[id] = node;
// Mark the container as changed // Mark the container as changed
if (newConfig.allNodes[added[id].z]) { if (!removed[node.z] && newConfig.allNodes[removed[id].z]) {
changed[added[id].z] = newConfig.allNodes[added[id].z]; changed[removed[id].z] = newConfig.allNodes[removed[id].z];
if (changed[added[id].z].type === "subflow") { if (changed[removed[id].z].type === "subflow") {
changedSubflows[added[id].z] = changed[added[id].z]; changedSubflows[removed[id].z] = changed[removed[id].z];
delete added[id]; //delete removed[id];
}
}
} 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;
}
// 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];
}
}
} }
} }
} }
} }
} }
}
var madeChange; // Look for added nodes
// Loop through the nodes looking for references to changed config nodes for (id in newConfig.allNodes) {
// Repeat the loop if anything is marked as changed as it may need to be if (newConfig.allNodes.hasOwnProperty(id)) {
// propagated to parent nodes. node = newConfig.allNodes[id];
// TODO: looping through all nodes every time is a bit inefficient - could be more targeted // build the map of what this node is now wired to
do { if (node.wires) {
madeChange = false; linkMap[node.id] = linkMap[node.id] || [];
for (id in newConfig.allNodes) { for (j=0;j<node.wires.length;j++) {
if (newConfig.allNodes.hasOwnProperty(id)) { wires = node.wires[j];
node = newConfig.allNodes[id]; for (k=0;k<wires.length;k++) {
for (var prop in node) { if (linkMap[node.id].indexOf(wires[k]) === -1) {
if (node.hasOwnProperty(prop) && prop != "z" && prop != "id" && prop != "wires") { linkMap[node.id].push(wires[k]);
// This node has a property that references a changed/removed node }
// Assume it is a config node change and mark this node as nn = newConfig.allNodes[wires[k]];
// changed. if (nn) {
linkMap[nn.id] = linkMap[nn.id] || [];
var changeOrigin = changed[node[prop]]; if (linkMap[nn.id].indexOf(node.id) === -1) {
if (changeOrigin || removed[node[prop]]) { linkMap[nn.id].push(node.id);
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];
}
}
}
} }
} }
} }
} }
} }
} while (madeChange===true) // 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];
}
}
}
}
}
// Find any nodes that exist on a subflow template and remove from changed var madeChange;
// list as the parent subflow will now be marked as containing a change // 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) { for (id in newConfig.allNodes) {
if (newConfig.allNodes.hasOwnProperty(id)) { if (newConfig.allNodes.hasOwnProperty(id)) {
node = newConfig.allNodes[id]; node = newConfig.allNodes[id];
if (newConfig.allNodes[node.z] && newConfig.allNodes[node.z].type === "subflow") { for (var prop in node) {
delete changed[node.id]; 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.
// Recursively mark all instances of changed subflows as changed var changeOrigin = changed[node[prop]];
var changedSubflowStack = Object.keys(changedSubflows); if (changeOrigin || removed[node[prop]]) {
while (changedSubflowStack.length > 0) { if (!changed[node.id]) {
var subflowId = changedSubflowStack.pop(); if (changeOrigin &&
for (id in newConfig.allNodes) { (prop === "g") &&
if (newConfig.allNodes.hasOwnProperty(id)) { (changeOrigin.type === "group")) {
node = newConfig.allNodes[id]; var oldNode = oldConfig.allNodes[node.id];
if (node.type === 'subflow:'+subflowId) { // ignore change of group node
if (!changed[node.id]) { // if group of this node not changed
changed[node.id] = node; if (oldNode &&
if (!changed[changed[node.id].z] && newConfig.allNodes[changed[node.id].z]) { (node.g === oldNode.g)) {
changed[changed[node.id].z] = newConfig.allNodes[changed[node.id].z]; continue;
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 madeChange = true;
changedSubflowStack.push(changed[node.id].z); changed[node.id] = node;
delete changed[node.id]; // 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];
}
} }
} }
} }
@ -577,58 +537,96 @@ module.exports = {
} }
} }
} }
} while (madeChange===true)
var diff = { // Find any nodes that exist on a subflow template and remove from changed
added:Object.keys(added), // list as the parent subflow will now be marked as containing a change
changed:Object.keys(changed), for (id in newConfig.allNodes) {
removed:Object.keys(removed), if (newConfig.allNodes.hasOwnProperty(id)) {
rewired:Object.keys(wiringChanged), node = newConfig.allNodes[id];
linked:[] if (newConfig.allNodes[node.z] && newConfig.allNodes[node.z].type === "subflow") {
delete changed[node.id];
}
} }
}
// Traverse the links of all modified nodes to mark the connected nodes // Recursively mark all instances of changed subflows as changed
var modifiedNodes = diff.added.concat(diff.changed).concat(diff.removed).concat(diff.rewired); var changedSubflowStack = Object.keys(changedSubflows);
var visited = {}; while (changedSubflowStack.length > 0) {
while (modifiedNodes.length > 0) { var subflowId = changedSubflowStack.pop();
node = modifiedNodes.pop(); for (id in newConfig.allNodes) {
if (!visited[node]) { if (newConfig.allNodes.hasOwnProperty(id)) {
visited[node] = true; node = newConfig.allNodes[id];
if (linkMap[node]) { if (node.type === 'subflow:'+subflowId) {
if (!changed[node] && !added[node] && !removed[node] && !wiringChanged[node]) { if (!changed[node.id]) {
diff.linked.push(node); 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];
}
}
} }
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||""
// );
// }
return diff; 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
* Create a new instance of a node var modifiedNodes = diff.added.concat(diff.changed).concat(diff.removed).concat(diff.rewired);
* @param {Flow} flow The containing flow var visited = {};
* @param {object} config The node configuration object while (modifiedNodes.length > 0) {
* @return {Node} The instance of the node node = modifiedNodes.pop();
*/ if (!visited[node]) {
createNode: createNode, 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||""
// );
// }
return diff;
}
module.exports = {
init,
createNode,
parseConfig,
diffConfigs,
diffNodes,
getEnvVar,
mapEnvVarProperties,
evaluateEnvProperties evaluateEnvProperties
} }