@node-red/runtime 3.1.0-beta.3 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/flows/util.js CHANGED
@@ -13,23 +13,32 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  **/
16
- var clone = require("clone");
17
- var redUtil = require("@node-red/util").util;
18
- var Log = require("@node-red/util").log;
19
- var subflowInstanceRE = /^subflow:(.+)$/;
20
- var typeRegistry = require("@node-red/registry");
21
- const credentials = require("../nodes/credentials");
16
+ const clone = require("clone");
17
+ const redUtil = require("@node-red/util").util;
18
+ const Log = require("@node-red/util").log;
19
+ const typeRegistry = require("@node-red/registry");
20
+ const subflowInstanceRE = /^subflow:(.+)$/;
22
21
 
23
22
  let _runtime = null;
23
+ let envVarExcludes = {};
24
24
 
25
- var envVarExcludes = {};
25
+ function init(runtime) {
26
+ _runtime = runtime;
27
+ envVarExcludes = {};
28
+ if (runtime.settings.hasOwnProperty('envVarExcludes') && Array.isArray(runtime.settings.envVarExcludes)) {
29
+ runtime.settings.envVarExcludes.forEach(v => envVarExcludes[v] = true);
30
+ }
31
+ }
26
32
 
27
33
  function diffNodes(oldNode,newNode) {
28
34
  if (oldNode == null) {
29
35
  return true;
30
36
  }
31
- var oldKeys = Object.keys(oldNode).filter(function(p) { return p != "x" && p != "y" && p != "wires" });
32
- var newKeys = Object.keys(newNode).filter(function(p) { return p != "x" && p != "y" && p != "wires" });
37
+ const keyFilter = p => p != 'x' && p != 'y' && p != 'wires'
38
+ const groupKeyFilter = p => keyFilter(p) && p != 'nodes' && p != 'style' && p != 'w' && p != 'h'
39
+ var oldKeys = Object.keys(oldNode).filter(oldNode.type === 'group' ? groupKeyFilter : keyFilter);
40
+ var newKeys = Object.keys(newNode).filter(newNode.type === 'group' ? groupKeyFilter : keyFilter);
41
+
33
42
  if (oldKeys.length != newKeys.length) {
34
43
  return true;
35
44
  }
@@ -70,8 +79,64 @@ function mapEnvVarProperties(obj,prop,flow,config) {
70
79
  }
71
80
  }
72
81
 
82
+ async function evaluateEnvProperties(flow, env, credentials) {
83
+ const pendingEvaluations = []
84
+ const evaluatedEnv = {}
85
+ const envTypes = []
86
+ for (let i = 0; i < env.length; i++) {
87
+ let { name, value, type } = env[i]
88
+ if (type === "env") {
89
+ // Do env types last as they may include references to other env vars
90
+ // at this level which need to be resolved before they can be looked-up
91
+ envTypes.push(env[i])
92
+ } else if (type === "bool") {
93
+ value = (value === "true") || (value === true);
94
+ } else if (type === "cred") {
95
+ if (credentials.hasOwnProperty(name)) {
96
+ value = credentials[name];
97
+ }
98
+ } else if (type ==='jsonata') {
99
+ pendingEvaluations.push(new Promise((resolve, _) => {
100
+ redUtil.evaluateNodeProperty(value, 'jsonata', {_flow: flow}, null, (err, result) => {
101
+ if (!err) {
102
+ evaluatedEnv[name] = result
103
+ }
104
+ resolve()
105
+ });
106
+ }))
107
+ } else {
108
+ value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null);
109
+ }
110
+ evaluatedEnv[name] = value
111
+ }
112
+ if (pendingEvaluations.length > 0) {
113
+ await Promise.all(pendingEvaluations)
114
+ }
115
+ for (let i = 0; i < envTypes.length; i++) {
116
+ let { name, value, type } = envTypes[i]
117
+ // If an env-var wants to lookup itself, delegate straight to the parent
118
+ // https://github.com/node-red/node-red/issues/2099
119
+ if (value === name) {
120
+ value = `$parent.${name}`
121
+ }
122
+ if (evaluatedEnv.hasOwnProperty(value)) {
123
+ value = evaluatedEnv[value]
124
+ } else {
125
+ value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null);
126
+ }
127
+ evaluatedEnv[name] = value
128
+ }
129
+
130
+ return evaluatedEnv
131
+ }
73
132
 
74
- function createNode(flow,config) {
133
+ /**
134
+ * Create a new instance of a node
135
+ * @param {Flow} flow The containing flow
136
+ * @param {object} config The node configuration object
137
+ * @return {Node} The instance of the node
138
+ */
139
+ async function createNode(flow,config) {
75
140
  var newNode = null;
76
141
  var type = config.type;
77
142
  try {
@@ -140,7 +205,7 @@ function createNode(flow,config) {
140
205
  // This allows nodes inside the subflow to get ahold of each other
141
206
  // such as a node accessing its config node
142
207
  flow.subflowInstanceNodes[config.id] = subflow
143
- subflow.start();
208
+ await subflow.start();
144
209
  return subflow.node;
145
210
  }
146
211
  } catch(err) {
@@ -150,375 +215,343 @@ function createNode(flow,config) {
150
215
  }
151
216
 
152
217
  function parseConfig(config) {
153
- var flow = {};
154
- flow.allNodes = {};
155
- flow.subflows = {};
156
- flow.configs = {};
157
- flow.flows = {};
158
- flow.groups = {};
159
- flow.missingTypes = [];
160
-
161
- config.forEach(function(n) {
162
- flow.allNodes[n.id] = clone(n);
163
- if (n.type === 'tab') {
164
- flow.flows[n.id] = n;
165
- flow.flows[n.id].subflows = {};
166
- flow.flows[n.id].configs = {};
167
- flow.flows[n.id].nodes = {};
168
- }
169
- if (n.type === 'group') {
170
- flow.groups[n.id] = n;
171
- }
172
- });
173
-
174
- // TODO: why a separate forEach? this can be merged with above
175
- config.forEach(function(n) {
176
- if (n.type === 'subflow') {
177
- flow.subflows[n.id] = n;
178
- flow.subflows[n.id].configs = {};
179
- flow.subflows[n.id].nodes = {};
180
- flow.subflows[n.id].instances = [];
181
- }
182
- });
183
- var linkWires = {};
184
- var linkOutNodes = [];
185
- config.forEach(function(n) {
186
- if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') {
187
- var subflowDetails = subflowInstanceRE.exec(n.type);
188
-
189
- if ( (subflowDetails && !flow.subflows[subflowDetails[1]]) || (!subflowDetails && !typeRegistry.get(n.type)) ) {
190
- if (flow.missingTypes.indexOf(n.type) === -1) {
191
- flow.missingTypes.push(n.type);
192
- }
193
- }
194
- var container = null;
195
- if (flow.flows[n.z]) {
196
- container = flow.flows[n.z];
197
- } else if (flow.subflows[n.z]) {
198
- container = flow.subflows[n.z];
199
- }
200
- if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) {
201
- if (subflowDetails) {
202
- var subflowType = subflowDetails[1]
203
- n.subflow = subflowType;
204
- if (flow.subflows[subflowType]) {
205
- flow.subflows[subflowType].instances.push(n)
206
- }
207
- }
208
- if (container) {
209
- container.nodes[n.id] = n;
210
- }
211
- } else {
212
- if (container) {
213
- container.configs[n.id] = n;
214
- } else {
215
- flow.configs[n.id] = n;
216
- flow.configs[n.id]._users = [];
217
- }
218
- }
219
- if (n.type === 'link in' && n.links) {
220
- // Ensure wires are present in corresponding link out nodes
221
- n.links.forEach(function(id) {
222
- linkWires[id] = linkWires[id]||{};
223
- linkWires[id][n.id] = true;
224
- })
225
- } else if (n.type === 'link out' && n.links) {
226
- linkWires[n.id] = linkWires[n.id]||{};
227
- n.links.forEach(function(id) {
228
- linkWires[n.id][id] = true;
229
- })
230
- linkOutNodes.push(n);
231
- }
232
- }
233
- });
234
- linkOutNodes.forEach(function(n) {
235
- var links = linkWires[n.id];
236
- var targets = Object.keys(links);
237
- n.wires = [targets];
238
- });
239
-
240
-
241
- var addedTabs = {};
242
- config.forEach(function(n) {
243
- if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') {
244
- for (var prop in n) {
245
- if (n.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== 'type' && prop !== '_users' && flow.configs.hasOwnProperty(n[prop])) {
246
- // This property references a global config node
247
- flow.configs[n[prop]]._users.push(n.id)
248
- }
249
- }
250
- if (n.z && !flow.subflows[n.z]) {
251
-
252
- if (!flow.flows[n.z]) {
253
- flow.flows[n.z] = {type:'tab',id:n.z};
254
- flow.flows[n.z].subflows = {};
255
- flow.flows[n.z].configs = {};
256
- flow.flows[n.z].nodes = {};
257
- addedTabs[n.z] = flow.flows[n.z];
258
- }
259
- if (addedTabs[n.z]) {
260
- if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) {
261
- addedTabs[n.z].nodes[n.id] = n;
262
- } else {
263
- addedTabs[n.z].configs[n.id] = n;
264
- }
265
- }
266
- }
267
- }
268
- });
269
- return flow;
270
- }
218
+ var flow = {};
219
+ flow.allNodes = {};
220
+ flow.subflows = {};
221
+ flow.configs = {};
222
+ flow.flows = {};
223
+ flow.missingTypes = [];
224
+
225
+ config.forEach(function (n) {
226
+ flow.allNodes[n.id] = clone(n);
227
+ if (n.type === 'tab') {
228
+ flow.flows[n.id] = n;
229
+ flow.flows[n.id].subflows = {};
230
+ flow.flows[n.id].configs = {};
231
+ flow.flows[n.id].nodes = {};
232
+ flow.flows[n.id].groups = {};
233
+ } else if (n.type === 'subflow') {
234
+ flow.subflows[n.id] = n;
235
+ flow.subflows[n.id].configs = {};
236
+ flow.subflows[n.id].nodes = {};
237
+ flow.subflows[n.id].groups = {};
238
+ flow.subflows[n.id].instances = [];
239
+ }
240
+ });
271
241
 
272
- function getGlobalEnv(name) {
273
- const nodes = _runtime.nodes;
274
- if (!nodes) {
275
- return null;
276
- }
277
- const gconf = nodes.getGlobalConfig();
278
- const env = gconf ? gconf.env : null;
279
-
280
- if (env) {
281
- const cred = (gconf ? credentials.get(gconf.id) : null) || {
282
- map: {}
283
- };
284
- const map = cred.map;
285
-
286
- for (let i = 0; i < env.length; i++) {
287
- const item = env[i];
288
- if (item.name === name) {
289
- if (item.type === "cred") {
290
- return {
291
- name: name,
292
- value: map[name],
293
- type: "cred"
294
- };
242
+ var linkWires = {};
243
+ var linkOutNodes = [];
244
+ config.forEach(function (n) {
245
+ if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') {
246
+ var subflowDetails = subflowInstanceRE.exec(n.type);
247
+
248
+ if ((subflowDetails && !flow.subflows[subflowDetails[1]]) || (!subflowDetails && !typeRegistry.get(n.type))) {
249
+ if (flow.missingTypes.indexOf(n.type) === -1) {
250
+ flow.missingTypes.push(n.type);
295
251
  }
296
- return item;
297
252
  }
298
- }
299
- }
300
- return null;
301
- }
302
-
303
- module.exports = {
304
- init: function(runtime) {
305
- _runtime = runtime;
306
- envVarExcludes = {};
307
- if (runtime.settings.hasOwnProperty('envVarExcludes') && Array.isArray(runtime.settings.envVarExcludes)) {
308
- runtime.settings.envVarExcludes.forEach(v => envVarExcludes[v] = true);
309
- }
310
- },
311
- getEnvVar: function(k) {
312
- if (!envVarExcludes[k]) {
313
- const item = getGlobalEnv(k);
314
- if (item) {
315
- const val = redUtil.evaluateNodeProperty(item.value, item.type, null, null, null);
316
- return val;
253
+ var container = null;
254
+ if (flow.flows[n.z]) {
255
+ container = flow.flows[n.z];
256
+ } else if (flow.subflows[n.z]) {
257
+ container = flow.subflows[n.z];
317
258
  }
318
- return process.env[k];
319
- }
320
- return undefined;
321
- },
322
- diffNodes: diffNodes,
323
- mapEnvVarProperties: mapEnvVarProperties,
324
-
325
- parseConfig: parseConfig,
326
-
327
- diffConfigs: function(oldConfig, newConfig) {
328
- var id;
329
- var node;
330
- var nn;
331
- var wires;
332
- var j,k;
333
-
334
- if (!oldConfig) {
335
- oldConfig = {
336
- flows:{},
337
- allNodes:{}
259
+ if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) {
260
+ if (subflowDetails) {
261
+ var subflowType = subflowDetails[1]
262
+ n.subflow = subflowType;
263
+ if (flow.subflows[subflowType]) {
264
+ flow.subflows[subflowType].instances.push(n)
265
+ }
266
+ }
267
+ if (container) {
268
+ container.nodes[n.id] = n;
269
+ }
270
+ } else {
271
+ if (container) {
272
+ container.configs[n.id] = n;
273
+ } else {
274
+ flow.configs[n.id] = n;
275
+ flow.configs[n.id]._users = [];
276
+ }
338
277
  }
339
- }
340
- var changedSubflows = {};
341
-
342
- var added = {};
343
- var removed = {};
344
- var changed = {};
345
- var wiringChanged = {};
346
-
347
- var linkMap = {};
348
-
349
- var changedTabs = {};
350
-
351
- // Look for tabs that have been removed
352
- for (id in oldConfig.flows) {
353
- if (oldConfig.flows.hasOwnProperty(id) && (!newConfig.flows.hasOwnProperty(id))) {
354
- removed[id] = oldConfig.allNodes[id];
278
+ if (n.type === 'link in' && n.links) {
279
+ // Ensure wires are present in corresponding link out nodes
280
+ n.links.forEach(function (id) {
281
+ linkWires[id] = linkWires[id] || {};
282
+ linkWires[id][n.id] = true;
283
+ })
284
+ } else if (n.type === 'link out' && n.links) {
285
+ linkWires[n.id] = linkWires[n.id] || {};
286
+ n.links.forEach(function (id) {
287
+ linkWires[n.id][id] = true;
288
+ })
289
+ linkOutNodes.push(n);
290
+ }
291
+ } else if (n.type === 'group') {
292
+ const parentContainer = flow.flows[n.z] || flow.subflows[n.z]
293
+ if (parentContainer) {
294
+ parentContainer.groups[n.id] = n
355
295
  }
356
296
  }
357
-
358
- // Look for tabs that have been disabled
359
- for (id in oldConfig.flows) {
360
- if (oldConfig.flows.hasOwnProperty(id) && newConfig.flows.hasOwnProperty(id)) {
361
- var originalState = oldConfig.flows[id].disabled||false;
362
- var newState = newConfig.flows[id].disabled||false;
363
- if (originalState !== newState) {
364
- changedTabs[id] = true;
365
- if (originalState) {
366
- added[id] = oldConfig.allNodes[id];
297
+ });
298
+ linkOutNodes.forEach(function (n) {
299
+ var links = linkWires[n.id];
300
+ var targets = Object.keys(links);
301
+ n.wires = [targets];
302
+ });
303
+
304
+
305
+ var addedTabs = {};
306
+ config.forEach(function (n) {
307
+ if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') {
308
+ for (var prop in n) {
309
+ if (n.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== 'type' && prop !== '_users' && flow.configs.hasOwnProperty(n[prop])) {
310
+ // This property references a global config node
311
+ flow.configs[n[prop]]._users.push(n.id)
312
+ }
313
+ }
314
+ if (n.z && !flow.subflows[n.z]) {
315
+
316
+ if (!flow.flows[n.z]) {
317
+ flow.flows[n.z] = { type: 'tab', id: n.z };
318
+ flow.flows[n.z].subflows = {};
319
+ flow.flows[n.z].configs = {};
320
+ flow.flows[n.z].nodes = {};
321
+ addedTabs[n.z] = flow.flows[n.z];
322
+ }
323
+ if (addedTabs[n.z]) {
324
+ if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) {
325
+ addedTabs[n.z].nodes[n.id] = n;
367
326
  } else {
368
- removed[id] = oldConfig.allNodes[id];
327
+ addedTabs[n.z].configs[n.id] = n;
369
328
  }
370
329
  }
371
330
  }
372
331
  }
332
+ });
333
+ return flow;
334
+ }
335
+ function getEnvVar(k) {
336
+ if (!envVarExcludes[k]) {
337
+ return process.env[k];
338
+ }
339
+ return undefined;
340
+ }
341
+ function diffConfigs(oldConfig, newConfig) {
342
+ var id;
343
+ var node;
344
+ var nn;
345
+ var wires;
346
+ var j,k;
347
+
348
+ if (!oldConfig) {
349
+ oldConfig = {
350
+ flows:{},
351
+ allNodes:{}
352
+ }
353
+ }
354
+ var changedSubflows = {};
355
+
356
+ var added = {};
357
+ var removed = {};
358
+ var changed = {};
359
+ var flowChanged = {};
360
+ var wiringChanged = {};
361
+ var globalConfigChanged = false;
362
+ var linkMap = {};
363
+ var allNestedGroups = []
364
+
365
+ // Look for tabs that have been removed
366
+ for (id in oldConfig.flows) {
367
+ if (oldConfig.flows.hasOwnProperty(id) && (!newConfig.flows.hasOwnProperty(id))) {
368
+ removed[id] = oldConfig.allNodes[id];
369
+ }
370
+ }
373
371
 
374
- for (id in oldConfig.allNodes) {
375
- if (oldConfig.allNodes.hasOwnProperty(id)) {
376
- node = oldConfig.allNodes[id];
377
- if (node.type !== 'tab') {
378
- // build the map of what this node was previously wired to
379
- if (node.wires) {
380
- linkMap[node.id] = linkMap[node.id] || [];
381
- for (j=0;j<node.wires.length;j++) {
382
- wires = node.wires[j];
383
- for (k=0;k<wires.length;k++) {
384
- linkMap[node.id].push(wires[k]);
385
- nn = oldConfig.allNodes[wires[k]];
386
- if (nn) {
387
- linkMap[nn.id] = linkMap[nn.id] || [];
388
- linkMap[nn.id].push(node.id);
389
- }
372
+ // Look for tabs that have been disabled
373
+ for (id in oldConfig.flows) {
374
+ if (oldConfig.flows.hasOwnProperty(id) && newConfig.flows.hasOwnProperty(id)) {
375
+ var originalState = oldConfig.flows[id].disabled||false;
376
+ var newState = newConfig.flows[id].disabled||false;
377
+ if (originalState !== newState) {
378
+ if (originalState) {
379
+ added[id] = oldConfig.allNodes[id];
380
+ } else {
381
+ removed[id] = oldConfig.allNodes[id];
382
+ }
383
+ }
384
+ }
385
+ }
386
+
387
+ for (id in oldConfig.allNodes) {
388
+ if (oldConfig.allNodes.hasOwnProperty(id)) {
389
+ node = oldConfig.allNodes[id];
390
+ if (node.type !== 'tab') {
391
+ // build the map of what this node was previously wired to
392
+ if (node.wires) {
393
+ linkMap[node.id] = linkMap[node.id] || [];
394
+ for (j=0;j<node.wires.length;j++) {
395
+ wires = node.wires[j];
396
+ for (k=0;k<wires.length;k++) {
397
+ linkMap[node.id].push(wires[k]);
398
+ nn = oldConfig.allNodes[wires[k]];
399
+ if (nn) {
400
+ linkMap[nn.id] = linkMap[nn.id] || [];
401
+ linkMap[nn.id].push(node.id);
390
402
  }
391
403
  }
392
404
  }
393
- // This node has been removed or its flow disabled
394
- if (removed[node.z] || !newConfig.allNodes.hasOwnProperty(id)) {
395
- removed[id] = node;
396
- // Mark the container as changed
397
- if (!removed[node.z] && newConfig.allNodes[removed[id].z]) {
398
- changed[removed[id].z] = newConfig.allNodes[removed[id].z];
399
- if (changed[removed[id].z].type === "subflow") {
400
- changedSubflows[removed[id].z] = changed[removed[id].z];
401
- //delete removed[id];
402
- }
405
+ }
406
+ // This node has been removed or its flow disabled
407
+ if (removed[node.z] || !newConfig.allNodes.hasOwnProperty(id)) {
408
+ removed[id] = node;
409
+ // Mark the container as changed
410
+ if (!removed[node.z] && newConfig.allNodes[removed[id].z]) {
411
+ changed[removed[id].z] = newConfig.allNodes[removed[id].z];
412
+ if (changed[removed[id].z].type === "subflow") {
413
+ changedSubflows[removed[id].z] = changed[removed[id].z];
414
+ //delete removed[id];
403
415
  }
416
+ }
417
+ } else {
418
+ if (added[node.z]) {
419
+ added[id] = node;
404
420
  } else {
405
- if (added[node.z]) {
406
- added[id] = node;
407
- } else {
408
- var currentState = node.d;
409
- var newState = newConfig.allNodes[id].d;
410
- if (!currentState && newState) {
411
- removed[id] = node;
421
+ var currentState = node.d;
422
+ var newState = newConfig.allNodes[id].d;
423
+ if (!currentState && newState) {
424
+ removed[id] = node;
425
+ }
426
+ // This node has a material configuration change
427
+ if (diffNodes(node,newConfig.allNodes[id]) || newConfig.allNodes[id].credentials) {
428
+ changed[id] = newConfig.allNodes[id];
429
+ if (changed[id].type === "subflow") {
430
+ changedSubflows[id] = changed[id];
412
431
  }
413
- // This node has a material configuration change
414
- if (diffNodes(node,newConfig.allNodes[id]) || newConfig.allNodes[id].credentials) {
415
- changed[id] = newConfig.allNodes[id];
416
- if (changed[id].type === "subflow") {
417
- changedSubflows[id] = changed[id];
418
- }
419
- // Mark the container as changed
420
- if (newConfig.allNodes[changed[id].z]) {
421
- changed[changed[id].z] = newConfig.allNodes[changed[id].z];
422
- if (changed[changed[id].z].type === "subflow") {
423
- changedSubflows[changed[id].z] = changed[changed[id].z];
424
- delete changed[id];
425
- }
432
+ // Mark the container as changed
433
+ if (newConfig.allNodes[changed[id].z]) {
434
+ changed[changed[id].z] = newConfig.allNodes[changed[id].z];
435
+ if (changed[changed[id].z].type === "subflow") {
436
+ changedSubflows[changed[id].z] = changed[changed[id].z];
437
+ delete changed[id];
426
438
  }
427
439
  }
428
- // This node's wiring has changed
429
- if (!redUtil.compareObjects(node.wires,newConfig.allNodes[id].wires)) {
430
- wiringChanged[id] = newConfig.allNodes[id];
431
- // Mark the container as changed
432
- if (newConfig.allNodes[wiringChanged[id].z]) {
433
- changed[wiringChanged[id].z] = newConfig.allNodes[wiringChanged[id].z];
434
- if (changed[wiringChanged[id].z].type === "subflow") {
435
- changedSubflows[wiringChanged[id].z] = changed[wiringChanged[id].z];
436
- delete wiringChanged[id];
437
- }
440
+ if (newConfig.allNodes[id].type === 'global-config') {
441
+ globalConfigChanged = true
442
+ }
443
+ }
444
+ // This node's wiring has changed
445
+ if (!redUtil.compareObjects(node.wires,newConfig.allNodes[id].wires)) {
446
+ wiringChanged[id] = newConfig.allNodes[id];
447
+ // Mark the container as changed
448
+ if (newConfig.allNodes[wiringChanged[id].z]) {
449
+ changed[wiringChanged[id].z] = newConfig.allNodes[wiringChanged[id].z];
450
+ if (changed[wiringChanged[id].z].type === "subflow") {
451
+ changedSubflows[wiringChanged[id].z] = changed[wiringChanged[id].z];
452
+ delete wiringChanged[id];
438
453
  }
439
454
  }
440
455
  }
441
456
  }
442
457
  }
458
+ } else if (!removed[id]) {
459
+ if (JSON.stringify(node.env) !== JSON.stringify(newConfig.allNodes[id].env)) {
460
+ flowChanged[id] = newConfig.allNodes[id];
461
+ }
443
462
  }
444
463
  }
445
- // Look for added nodes
446
- for (id in newConfig.allNodes) {
447
- if (newConfig.allNodes.hasOwnProperty(id)) {
448
- node = newConfig.allNodes[id];
449
- // build the map of what this node is now wired to
450
- if (node.wires) {
451
- linkMap[node.id] = linkMap[node.id] || [];
452
- for (j=0;j<node.wires.length;j++) {
453
- wires = node.wires[j];
454
- for (k=0;k<wires.length;k++) {
455
- if (linkMap[node.id].indexOf(wires[k]) === -1) {
456
- linkMap[node.id].push(wires[k]);
464
+ }
465
+ // Look for added nodes
466
+ for (id in newConfig.allNodes) {
467
+ if (newConfig.allNodes.hasOwnProperty(id)) {
468
+ node = newConfig.allNodes[id];
469
+ if (node.type === 'group') {
470
+ if (node.g) {
471
+ allNestedGroups.push(node)
472
+ }
473
+ if (changed[node.id]) {
474
+ if (node.nodes) {
475
+ node.nodes.forEach(nid => {
476
+ if (!changed[nid]) {
477
+ changed[nid] = true
457
478
  }
458
- nn = newConfig.allNodes[wires[k]];
459
- if (nn) {
460
- linkMap[nn.id] = linkMap[nn.id] || [];
461
- if (linkMap[nn.id].indexOf(node.id) === -1) {
462
- linkMap[nn.id].push(node.id);
463
- }
479
+ })
480
+ }
481
+ }
482
+ }
483
+ // build the map of what this node is now wired to
484
+ if (node.wires) {
485
+ linkMap[node.id] = linkMap[node.id] || [];
486
+ for (j=0;j<node.wires.length;j++) {
487
+ wires = node.wires[j];
488
+ for (k=0;k<wires.length;k++) {
489
+ if (linkMap[node.id].indexOf(wires[k]) === -1) {
490
+ linkMap[node.id].push(wires[k]);
491
+ }
492
+ nn = newConfig.allNodes[wires[k]];
493
+ if (nn) {
494
+ linkMap[nn.id] = linkMap[nn.id] || [];
495
+ if (linkMap[nn.id].indexOf(node.id) === -1) {
496
+ linkMap[nn.id].push(node.id);
464
497
  }
465
498
  }
466
499
  }
467
500
  }
468
- // This node has been added
469
- if (!oldConfig.allNodes.hasOwnProperty(id)) {
470
- added[id] = node;
471
- // Mark the container as changed
472
- if (newConfig.allNodes[added[id].z]) {
473
- changed[added[id].z] = newConfig.allNodes[added[id].z];
474
- if (changed[added[id].z].type === "subflow") {
475
- changedSubflows[added[id].z] = changed[added[id].z];
476
- delete added[id];
477
- }
501
+ }
502
+ // This node has been added
503
+ if (!oldConfig.allNodes.hasOwnProperty(id)) {
504
+ added[id] = node;
505
+ // Mark the container as changed
506
+ if (newConfig.allNodes[added[id].z]) {
507
+ changed[added[id].z] = newConfig.allNodes[added[id].z];
508
+ if (changed[added[id].z].type === "subflow") {
509
+ changedSubflows[added[id].z] = changed[added[id].z];
510
+ delete added[id];
478
511
  }
479
512
  }
480
513
  }
481
514
  }
515
+ }
482
516
 
483
- var madeChange;
484
- // Loop through the nodes looking for references to changed config nodes
485
- // Repeat the loop if anything is marked as changed as it may need to be
486
- // propagated to parent nodes.
487
- // TODO: looping through all nodes every time is a bit inefficient - could be more targeted
488
- do {
489
- madeChange = false;
490
- for (id in newConfig.allNodes) {
491
- if (newConfig.allNodes.hasOwnProperty(id)) {
492
- node = newConfig.allNodes[id];
493
- for (var prop in node) {
494
- if (node.hasOwnProperty(prop) && prop != "z" && prop != "id" && prop != "wires") {
495
- // This node has a property that references a changed/removed node
496
- // Assume it is a config node change and mark this node as
497
- // changed.
498
-
499
- var changeOrigin = changed[node[prop]];
500
- if (changeOrigin || removed[node[prop]]) {
501
- if (!changed[node.id]) {
502
- if (changeOrigin &&
503
- (prop === "g") &&
504
- (changeOrigin.type === "group")) {
505
- var oldNode = oldConfig.allNodes[node.id];
506
- // ignore change of group node
507
- // if group of this node not changed
508
- if (oldNode &&
509
- (node.g === oldNode.g)) {
510
- continue;
511
- }
517
+ var madeChange;
518
+ // Loop through the nodes looking for references to changed config nodes
519
+ // Repeat the loop if anything is marked as changed as it may need to be
520
+ // propagated to parent nodes.
521
+ // TODO: looping through all nodes every time is a bit inefficient - could be more targeted
522
+ do {
523
+ madeChange = false;
524
+ for (id in newConfig.allNodes) {
525
+ if (newConfig.allNodes.hasOwnProperty(id)) {
526
+ node = newConfig.allNodes[id];
527
+ for (var prop in node) {
528
+ if (node.hasOwnProperty(prop) && prop != "z" && prop != "id" && prop != "wires") {
529
+ // This node has a property that references a changed/removed node
530
+ // Assume it is a config node change and mark this node as
531
+ // changed.
532
+
533
+ var changeOrigin = changed[node[prop]];
534
+ if (changeOrigin || removed[node[prop]]) {
535
+ if (!changed[node.id]) {
536
+ if (changeOrigin &&
537
+ (prop === "g") &&
538
+ (changeOrigin.type === "group")) {
539
+ var oldNode = oldConfig.allNodes[node.id];
540
+ // ignore change of group node
541
+ // if group of this node not changed
542
+ if (oldNode &&
543
+ (node.g === oldNode.g)) {
544
+ continue;
512
545
  }
513
- madeChange = true;
514
- changed[node.id] = node;
515
- // This node exists within subflow template
516
- // Mark the template as having changed
517
- if (newConfig.allNodes[node.z]) {
518
- changed[node.z] = newConfig.allNodes[node.z];
519
- if (changed[node.z].type === "subflow") {
520
- changedSubflows[node.z] = changed[node.z];
521
- }
546
+ }
547
+ madeChange = true;
548
+ changed[node.id] = node;
549
+ // This node exists within subflow template
550
+ // Mark the template as having changed
551
+ if (newConfig.allNodes[node.z]) {
552
+ changed[node.z] = newConfig.allNodes[node.z];
553
+ if (changed[node.z].type === "subflow") {
554
+ changedSubflows[node.z] = changed[node.z];
522
555
  }
523
556
  }
524
557
  }
@@ -526,94 +559,123 @@ module.exports = {
526
559
  }
527
560
  }
528
561
  }
529
- } while (madeChange===true)
562
+ }
563
+ } while (madeChange===true)
564
+
565
+ // Find any nodes that exist on a subflow template and remove from changed
566
+ // list as the parent subflow will now be marked as containing a change
567
+ for (id in newConfig.allNodes) {
568
+ if (newConfig.allNodes.hasOwnProperty(id)) {
569
+ node = newConfig.allNodes[id];
570
+ if (newConfig.allNodes[node.z] && newConfig.allNodes[node.z].type === "subflow") {
571
+ delete changed[node.id];
572
+ }
573
+ }
574
+ }
530
575
 
531
- // Find any nodes that exist on a subflow template and remove from changed
532
- // list as the parent subflow will now be marked as containing a change
533
- for (id in newConfig.allNodes) {
534
- if (newConfig.allNodes.hasOwnProperty(id)) {
535
- node = newConfig.allNodes[id];
536
- if (newConfig.allNodes[node.z] && newConfig.allNodes[node.z].type === "subflow") {
537
- delete changed[node.id];
538
- }
576
+ // Recursively mark all children of changed groups as changed
577
+ do {
578
+ madeChange = false
579
+ for (let i = 0; i < allNestedGroups.length; i++) {
580
+ const group = allNestedGroups[i]
581
+ if (!changed[group.id] && group.g && changed[group.g]) {
582
+ changed[group.id] = true
583
+ madeChange = true
584
+ }
585
+ if (changed[group.id] && group.nodes) {
586
+ group.nodes.forEach(nid => {
587
+ if (!changed[nid]) {
588
+ changed[nid] = true
589
+ madeChange = true
590
+ }
591
+ })
539
592
  }
540
593
  }
594
+ } while(madeChange)
541
595
 
542
- // Recursively mark all instances of changed subflows as changed
543
- var changedSubflowStack = Object.keys(changedSubflows);
544
- while (changedSubflowStack.length > 0) {
545
- var subflowId = changedSubflowStack.pop();
546
- for (id in newConfig.allNodes) {
547
- if (newConfig.allNodes.hasOwnProperty(id)) {
548
- node = newConfig.allNodes[id];
549
- if (node.type === 'subflow:'+subflowId) {
550
- if (!changed[node.id]) {
551
- changed[node.id] = node;
552
- if (!changed[changed[node.id].z] && newConfig.allNodes[changed[node.id].z]) {
553
- changed[changed[node.id].z] = newConfig.allNodes[changed[node.id].z];
554
- if (newConfig.allNodes[changed[node.id].z].type === "subflow") {
555
- // This subflow instance is inside a subflow. Add the
556
- // containing subflow to the stack to mark
557
- changedSubflowStack.push(changed[node.id].z);
558
- delete changed[node.id];
559
- }
596
+ // Recursively mark all instances of changed subflows as changed
597
+ var changedSubflowStack = Object.keys(changedSubflows);
598
+ while (changedSubflowStack.length > 0) {
599
+ var subflowId = changedSubflowStack.pop();
600
+ for (id in newConfig.allNodes) {
601
+ if (newConfig.allNodes.hasOwnProperty(id)) {
602
+ node = newConfig.allNodes[id];
603
+ if (node.type === 'subflow:'+subflowId) {
604
+ if (!changed[node.id]) {
605
+ changed[node.id] = node;
606
+ if (!changed[changed[node.id].z] && newConfig.allNodes[changed[node.id].z]) {
607
+ changed[changed[node.id].z] = newConfig.allNodes[changed[node.id].z];
608
+ if (newConfig.allNodes[changed[node.id].z].type === "subflow") {
609
+ // This subflow instance is inside a subflow. Add the
610
+ // containing subflow to the stack to mark
611
+ changedSubflowStack.push(changed[node.id].z);
612
+ delete changed[node.id];
560
613
  }
561
614
  }
562
615
  }
563
616
  }
564
617
  }
565
618
  }
619
+ }
566
620
 
567
- var diff = {
568
- added:Object.keys(added),
569
- changed:Object.keys(changed),
570
- removed:Object.keys(removed),
571
- rewired:Object.keys(wiringChanged),
572
- linked:[]
573
- }
574
621
 
575
- // Traverse the links of all modified nodes to mark the connected nodes
576
- var modifiedNodes = diff.added.concat(diff.changed).concat(diff.removed).concat(diff.rewired);
577
- var visited = {};
578
- while (modifiedNodes.length > 0) {
579
- node = modifiedNodes.pop();
580
- if (!visited[node]) {
581
- visited[node] = true;
582
- if (linkMap[node]) {
583
- if (!changed[node] && !added[node] && !removed[node] && !wiringChanged[node]) {
584
- diff.linked.push(node);
585
- }
586
- modifiedNodes = modifiedNodes.concat(linkMap[node]);
622
+
623
+ var diff = {
624
+ added:Object.keys(added),
625
+ changed:Object.keys(changed),
626
+ removed:Object.keys(removed),
627
+ rewired:Object.keys(wiringChanged),
628
+ linked:[],
629
+ flowChanged: Object.keys(flowChanged),
630
+ globalConfigChanged
631
+ }
632
+
633
+ // Traverse the links of all modified nodes to mark the connected nodes
634
+ var modifiedNodes = diff.added.concat(diff.changed).concat(diff.removed).concat(diff.rewired);
635
+ var visited = {};
636
+ while (modifiedNodes.length > 0) {
637
+ node = modifiedNodes.pop();
638
+ if (!visited[node]) {
639
+ visited[node] = true;
640
+ if (linkMap[node]) {
641
+ if (!changed[node] && !added[node] && !removed[node] && !wiringChanged[node]) {
642
+ diff.linked.push(node);
587
643
  }
644
+ modifiedNodes = modifiedNodes.concat(linkMap[node]);
588
645
  }
589
646
  }
590
- // console.log(diff);
591
- // for (id in newConfig.allNodes) {
592
- // console.log(
593
- // (added[id]?"a":(changed[id]?"c":" "))+(wiringChanged[id]?"w":" ")+(diff.linked.indexOf(id)!==-1?"l":" "),
594
- // newConfig.allNodes[id].type.padEnd(10),
595
- // id.padEnd(16),
596
- // (newConfig.allNodes[id].z||"").padEnd(16),
597
- // newConfig.allNodes[id].name||newConfig.allNodes[id].label||""
598
- // );
599
- // }
600
- // for (id in removed) {
601
- // console.log(
602
- // "- "+(diff.linked.indexOf(id)!==-1?"~":" "),
603
- // id,
604
- // oldConfig.allNodes[id].type,
605
- // oldConfig.allNodes[id].name||oldConfig.allNodes[id].label||""
606
- // );
607
- // }
608
-
609
- return diff;
610
- },
611
-
612
- /**
613
- * Create a new instance of a node
614
- * @param {Flow} flow The containing flow
615
- * @param {object} config The node configuration object
616
- * @return {Node} The instance of the node
617
- */
618
- createNode: createNode
647
+ }
648
+ // console.log(diff);
649
+ // for (id in newConfig.allNodes) {
650
+ // if (added[id] || changed[id] || wiringChanged[id] || diff.linked.indexOf(id)!==-1) {
651
+ // console.log(
652
+ // (added[id]?"a":(changed[id]?"c":" "))+(wiringChanged[id]?"w":" ")+(diff.linked.indexOf(id)!==-1?"l":" "),
653
+ // newConfig.allNodes[id].type.padEnd(10),
654
+ // id.padEnd(16),
655
+ // (newConfig.allNodes[id].z||"").padEnd(16),
656
+ // newConfig.allNodes[id].name||newConfig.allNodes[id].label||""
657
+ // );
658
+ // }
659
+ // }
660
+ // for (id in removed) {
661
+ // console.log(
662
+ // "- "+(diff.linked.indexOf(id)!==-1?"~":" "),
663
+ // id,
664
+ // oldConfig.allNodes[id].type,
665
+ // oldConfig.allNodes[id].name||oldConfig.allNodes[id].label||""
666
+ // );
667
+ // }
668
+
669
+ return diff;
670
+ }
671
+
672
+ module.exports = {
673
+ init,
674
+ createNode,
675
+ parseConfig,
676
+ diffConfigs,
677
+ diffNodes,
678
+ getEnvVar,
679
+ mapEnvVarProperties,
680
+ evaluateEnvProperties
619
681
  }