@5minds/node-red-contrib-processcube 1.16.1 → 1.16.2-develop-646cf1-mhvz5a84
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/externaltask-input.js +168 -44
- package/package.json +1 -1
package/externaltask-input.js
CHANGED
|
@@ -1,9 +1,110 @@
|
|
|
1
1
|
const EventEmitter = require('node:events');
|
|
2
2
|
|
|
3
|
+
class ExternalTaskNodeStates {
|
|
4
|
+
constructor(flowNodeInstanceId) {
|
|
5
|
+
this.flowNodeInstanceId = flowNodeInstanceId;
|
|
6
|
+
this.nodeStades = {}; // Track send calls per nodeId
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
markSended(nodeId) {
|
|
10
|
+
if (!this.nodeStades[nodeId]) {
|
|
11
|
+
this.nodeStades[nodeId] = { gotSend: false, gotCompleted: false };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
this.nodeStades[nodeId].gotSend = true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
markCompleted(nodeId) {
|
|
18
|
+
if (!this.nodeStades[nodeId]) {
|
|
19
|
+
this.nodeStades[nodeId] = { gotSend: false, gotCompleted: false };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.nodeStades[nodeId].gotCompleted = true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
checkIfCompletedWithoutSend(nodeId) {
|
|
26
|
+
const nodeState = this.nodeStades[nodeId];
|
|
27
|
+
const result = (nodeState && nodeState.gotCompleted && !nodeState.gotSend);
|
|
28
|
+
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
3
33
|
module.exports = function (RED) {
|
|
4
34
|
|
|
5
35
|
const os = require('os');
|
|
6
36
|
|
|
37
|
+
// Global dictionary for tracking external tasks by flowNodeInstanceId
|
|
38
|
+
const globalExternalTaskStates = {};
|
|
39
|
+
|
|
40
|
+
const raiseExternalTaskError = (flowNodeInstanceId, etwInputNodeId, nodeId) => {
|
|
41
|
+
const fullNode = RED.nodes.getNode(nodeId);
|
|
42
|
+
|
|
43
|
+
const wires = fullNode?.wires;
|
|
44
|
+
const hasConnectedOutputs = wires && wires.some(wireArray => wireArray && wireArray.length > 0);
|
|
45
|
+
|
|
46
|
+
if (hasConnectedOutputs) {
|
|
47
|
+
const inputNode = RED.nodes.getNode(etwInputNodeId);
|
|
48
|
+
|
|
49
|
+
if (inputNode && inputNode.eventEmitter) {
|
|
50
|
+
const errorMessage = `Node ${nodeId} (${fullNode.name || fullNode.type}) completed without sending output`;
|
|
51
|
+
const error = new Error(errorMessage);
|
|
52
|
+
error.errorCode = 'NODE_NO_OUTPUT';
|
|
53
|
+
error.errorDetails = RED.util.encodeObject({
|
|
54
|
+
flowNodeInstanceId: flowNodeInstanceId,
|
|
55
|
+
nodeId: nodeId,
|
|
56
|
+
nodeName: fullNode.name,
|
|
57
|
+
nodeType: fullNode.type
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
inputNode.eventEmitter.emit(`handle-${flowNodeInstanceId}`, error, true);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Example synchronous onSend hook
|
|
66
|
+
RED.hooks.add("onSend", (sendEvents) => {
|
|
67
|
+
for (const sendEvent of sendEvents) {
|
|
68
|
+
|
|
69
|
+
// Call send method on ExternalTaskState if this message has a flowNodeInstanceId
|
|
70
|
+
if (sendEvent.msg?.flowNodeInstanceId) {
|
|
71
|
+
let externalTaskNodeStates = globalExternalTaskStates[sendEvent.msg.flowNodeInstanceId];
|
|
72
|
+
|
|
73
|
+
if (!externalTaskNodeStates) {
|
|
74
|
+
externalTaskNodeStates = new ExternalTaskNodeStates(sendEvent.msg.flowNodeInstanceId);
|
|
75
|
+
globalExternalTaskStates[sendEvent.msg.flowNodeInstanceId] = externalTaskNodeStates;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
externalTaskNodeStates.markSended(sendEvent.source.node.id)
|
|
79
|
+
|
|
80
|
+
if (externalTaskNodeStates.checkIfCompletedWithoutSend(sendEvent.source.node.id)) {
|
|
81
|
+
raiseExternalTaskError(sendEvent.msg.flowNodeInstanceId, sendEvent.msg.etw_input_node_id, sendEvent.source.node.id);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const onCompleted = (completeEvent) => {
|
|
88
|
+
|
|
89
|
+
// Check if this is an external task message
|
|
90
|
+
if (completeEvent.msg?.flowNodeInstanceId) {
|
|
91
|
+
let externalTaskNodeStates = globalExternalTaskStates[completeEvent.msg.flowNodeInstanceId];
|
|
92
|
+
|
|
93
|
+
if (!externalTaskNodeStates) {
|
|
94
|
+
externalTaskNodeStates = new ExternalTaskNodeStates(completeEvent.msg.flowNodeInstanceId);
|
|
95
|
+
globalExternalTaskStates[completeEvent.msg.flowNodeInstanceId] = externalTaskNodeStates;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
externalTaskNodeStates.markCompleted(completeEvent.node.id);
|
|
99
|
+
|
|
100
|
+
if (externalTaskNodeStates.checkIfCompletedWithoutSend(completeEvent.node.id)) {
|
|
101
|
+
raiseExternalTaskError(completeEvent.msg.flowNodeInstanceId, completeEvent.msg.etw_input_node_id, completeEvent.node.id);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
RED.hooks.add("onComplete", onCompleted);
|
|
107
|
+
|
|
7
108
|
function ExternalTaskInput(config) {
|
|
8
109
|
RED.nodes.createNode(this, config);
|
|
9
110
|
var node = this;
|
|
@@ -165,60 +266,68 @@ module.exports = function (RED) {
|
|
|
165
266
|
};
|
|
166
267
|
|
|
167
268
|
const onPreDeliver = (sendEvent) => {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const sourceNode = sendEvent?.source?.node;
|
|
171
|
-
const destinationNode = sendEvent?.destination?.node;
|
|
269
|
+
try {
|
|
270
|
+
if (node.isHandling() && node.ownMessage(sendEvent.msg)) {
|
|
172
271
|
|
|
173
|
-
|
|
272
|
+
const sourceNode = sendEvent?.source?.node;
|
|
273
|
+
const destinationNode = sendEvent?.destination?.node;
|
|
174
274
|
|
|
175
|
-
|
|
275
|
+
node._step = `${destinationNode.name || destinationNode.type}`;
|
|
176
276
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
277
|
+
node.showStatus();
|
|
278
|
+
|
|
279
|
+
const debugMsg = {
|
|
280
|
+
event: 'enter',
|
|
281
|
+
sourceName: sourceNode.name,
|
|
282
|
+
sourceType: sourceNode.type,
|
|
283
|
+
destinationNodeName: destinationNode.name,
|
|
284
|
+
destinationNodeType: destinationNode.type,
|
|
285
|
+
timestamp: new Date().toISOString(),
|
|
286
|
+
};
|
|
185
287
|
|
|
186
|
-
|
|
288
|
+
node.traceExecution(debugMsg);
|
|
187
289
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
290
|
+
if (process.env.NODE_RED_ETW_STEP_LOGGING == 'true' || process.env.NODERED_ETW_STEP_LOGGING == 'true') {
|
|
291
|
+
node._trace = `'${sourceNode.name || sourceNode.type}'->'${destinationNode.name || destinationNode.type}'`;
|
|
292
|
+
node.log(`preDeliver: ${node._trace}`);
|
|
293
|
+
}
|
|
191
294
|
}
|
|
295
|
+
} catch (error) {
|
|
296
|
+
node.error(`Error in onPreDeliver: ${error?.message}`, { error: error });
|
|
192
297
|
}
|
|
193
298
|
};
|
|
194
|
-
RED.hooks.add(
|
|
299
|
+
RED.hooks.add(`preDeliver.etw-input-${node.id}`, onPreDeliver);
|
|
195
300
|
|
|
196
301
|
const onPostDeliver = (sendEvent) => {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
302
|
+
try {
|
|
303
|
+
if (node.isHandling() && node.ownMessage(sendEvent.msg)) {
|
|
304
|
+
const sourceNode = sendEvent?.source?.node;
|
|
305
|
+
const destinationNode = sendEvent?.destination?.node;
|
|
306
|
+
|
|
307
|
+
node.decrMsgOnNode(sourceNode, sendEvent.msg);
|
|
308
|
+
node.incMsgOnNode(destinationNode, sendEvent.msg);
|
|
309
|
+
|
|
310
|
+
const debugMsg = {
|
|
311
|
+
event: 'exit',
|
|
312
|
+
sourceName: sourceNode.name,
|
|
313
|
+
sourceType: sourceNode.type,
|
|
314
|
+
destinationNodeName: destinationNode.name,
|
|
315
|
+
destinationNodeType: destinationNode.type,
|
|
316
|
+
timestamp: new Date().toISOString(),
|
|
317
|
+
};
|
|
212
318
|
|
|
213
|
-
|
|
319
|
+
node.traceExecution(debugMsg);
|
|
214
320
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
321
|
+
if (process.env.NODE_RED_ETW_STEP_LOGGING == 'true' || process.env.NODERED_ETW_STEP_LOGGING == 'true') {
|
|
322
|
+
node._trace = `'${sourceNode.name || sourceNode.type}'->'${destinationNode.name || destinationNode.type}'`;
|
|
323
|
+
node.log(`postDeliver: ${node._trace}`);
|
|
324
|
+
}
|
|
218
325
|
}
|
|
326
|
+
} catch (error) {
|
|
327
|
+
node.error(`Error in onPostDeliver: ${error?.message}`, { error: error });
|
|
219
328
|
}
|
|
220
329
|
};
|
|
221
|
-
RED.hooks.add(
|
|
330
|
+
RED.hooks.add(`postDeliver.etw-input-${node.id}`, onPostDeliver);
|
|
222
331
|
|
|
223
332
|
node.setSubscribedStatus = () => {
|
|
224
333
|
this._subscribed = true;
|
|
@@ -349,15 +458,30 @@ module.exports = function (RED) {
|
|
|
349
458
|
return;
|
|
350
459
|
}
|
|
351
460
|
const etwCallback = async (payload, externalTask) => {
|
|
461
|
+
|
|
462
|
+
globalExternalTaskStates[externalTask.flowNodeInstanceId] = new ExternalTaskNodeStates(externalTask.flowNodeInstanceId);
|
|
463
|
+
|
|
352
464
|
const saveHandleCallback = (data, callback, msg) => {
|
|
353
465
|
try {
|
|
354
466
|
callback(data);
|
|
355
467
|
node.log(`send to engine *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}', topic '${node.topic}' and *processInstanceId* ${externalTask.processInstanceId}`);
|
|
468
|
+
|
|
469
|
+
// Remove ExternalTaskState from global dictionary
|
|
470
|
+
if (globalExternalTaskStates[externalTask.flowNodeInstanceId]) {
|
|
471
|
+
delete globalExternalTaskStates[externalTask.flowNodeInstanceId];
|
|
472
|
+
}
|
|
473
|
+
|
|
356
474
|
node.setFinishHandlingTaskStatus(externalTask);
|
|
357
475
|
} catch (error) {
|
|
476
|
+
// Remove ExternalTaskState from global dictionary on error as well
|
|
477
|
+
if (globalExternalTaskStates[externalTask.flowNodeInstanceId]) {
|
|
478
|
+
delete globalExternalTaskStates[externalTask.flowNodeInstanceId];
|
|
479
|
+
}
|
|
480
|
+
|
|
358
481
|
node.setErrorFinishHandlingTaskStatus(externalTask, error);
|
|
359
482
|
msg.error = error;
|
|
360
483
|
node.error(`failed send to engine *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}', topic '${node.topic}' and *processInstanceId* ${externalTask.processInstanceId}: ${error?.message}`, msg);
|
|
484
|
+
callback(error);
|
|
361
485
|
}
|
|
362
486
|
};
|
|
363
487
|
|
|
@@ -397,7 +521,7 @@ module.exports = function (RED) {
|
|
|
397
521
|
msg.etw_duration = new Date(msg.etw_finished_at) - new Date(msg.etw_started_at);
|
|
398
522
|
}
|
|
399
523
|
} catch (error) {
|
|
400
|
-
node.error(`failed to calculate duration: ${error?.message}`, msg);
|
|
524
|
+
node.error(`failed to calculate duration: ${error?.message}`, msg);
|
|
401
525
|
}
|
|
402
526
|
|
|
403
527
|
node.log(
|
|
@@ -488,12 +612,12 @@ module.exports = function (RED) {
|
|
|
488
612
|
|
|
489
613
|
node.on('close', () => {
|
|
490
614
|
try {
|
|
491
|
-
RED.hooks.remove('preDeliver', onPreDeliver);
|
|
492
|
-
RED.hooks.remove('postDeliver', onPostDeliver);
|
|
493
615
|
externalTaskWorker.stop();
|
|
616
|
+
RED.hooks.remove(`preDeliver.etw-input-${node.id}`);
|
|
617
|
+
RED.hooks.remove(`postDeliver.etw-input-${node.id}`);
|
|
494
618
|
node.log('External Task Worker closed.');
|
|
495
|
-
} catch {
|
|
496
|
-
node.error(
|
|
619
|
+
} catch (error) {
|
|
620
|
+
node.error(`Client close failed: ${JSON.stringify(error)}`);
|
|
497
621
|
}
|
|
498
622
|
});
|
|
499
623
|
})
|