@5minds/node-red-contrib-processcube 1.16.1-develop-36f966-mh90fmsz → 1.16.1-feature-91fab0-mhvqo2et
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 +145 -1
- package/package.json +1 -1
package/externaltask-input.js
CHANGED
|
@@ -1,9 +1,128 @@
|
|
|
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
|
+
console.log(`[DEBUG] markSended - flowNodeInstanceId: ${this.flowNodeInstanceId}, nodeId: ${nodeId}, before: ${JSON.stringify(this.nodeStades[nodeId])}`);
|
|
15
|
+
this.nodeStades[nodeId].gotSend = true;
|
|
16
|
+
console.log(`[DEBUG] markSended - after: ${JSON.stringify(this.nodeStades[nodeId])}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
markCompleted(nodeId) {
|
|
20
|
+
if (!this.nodeStades[nodeId]) {
|
|
21
|
+
this.nodeStades[nodeId] = { gotSend: false, gotCompleted: false };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log(`[DEBUG] markCompleted - flowNodeInstanceId: ${this.flowNodeInstanceId}, nodeId: ${nodeId}, before: ${JSON.stringify(this.nodeStades[nodeId])}`);
|
|
25
|
+
this.nodeStades[nodeId].gotCompleted = true;
|
|
26
|
+
console.log(`[DEBUG] markCompleted - after: ${JSON.stringify(this.nodeStades[nodeId])}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
checkIfCompletedWithoutSend(nodeId) {
|
|
30
|
+
const nodeState = this.nodeStades[nodeId];
|
|
31
|
+
const result = (nodeState && nodeState.gotCompleted && !nodeState.gotSend);
|
|
32
|
+
|
|
33
|
+
console.log(`[DEBUG] checkIfCompletedWithoutSend - flowNodeInstanceId: ${this.flowNodeInstanceId}, nodeId: ${nodeId}, nodeState: ${JSON.stringify(nodeState)}, result: ${result}`);
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
3
38
|
module.exports = function (RED) {
|
|
4
39
|
|
|
5
40
|
const os = require('os');
|
|
6
41
|
|
|
42
|
+
// Global dictionary for tracking external tasks by flowNodeInstanceId
|
|
43
|
+
const globalExternalTaskStates = {};
|
|
44
|
+
|
|
45
|
+
const raiseExternalTaskError = (flowNodeInstanceId, etwInputNodeId, nodeId) => {
|
|
46
|
+
const fullNode = RED.nodes.getNode(nodeId);
|
|
47
|
+
|
|
48
|
+
const wires = fullNode?.wires;
|
|
49
|
+
const hasConnectedOutputs = wires && wires.some(wireArray => wireArray && wireArray.length > 0);
|
|
50
|
+
|
|
51
|
+
console.log(`[DEBUG] raiseExternalTaskError called for flowNodeInstanceId: ${flowNodeInstanceId}, nodeId: ${nodeId}, hasConnectedOutputs: ${hasConnectedOutputs}`);
|
|
52
|
+
|
|
53
|
+
if (hasConnectedOutputs) {
|
|
54
|
+
const inputNode = RED.nodes.getNode(etwInputNodeId);
|
|
55
|
+
|
|
56
|
+
if (inputNode && inputNode.eventEmitter) {
|
|
57
|
+
const errorMessage = `Node ${nodeId} (${fullNode.name || fullNode.type}) completed without sending output`;
|
|
58
|
+
const error = new Error(errorMessage);
|
|
59
|
+
error.errorCode = 'NODE_NO_OUTPUT';
|
|
60
|
+
error.errorDetails = RED.util.encodeObject({
|
|
61
|
+
flowNodeInstanceId: flowNodeInstanceId,
|
|
62
|
+
nodeId: nodeId,
|
|
63
|
+
nodeName: fullNode.name,
|
|
64
|
+
nodeType: fullNode.type
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
console.log(`[DEBUG] Emitting error event for flowNodeInstanceId: ${flowNodeInstanceId}, error: ${errorMessage}`);
|
|
68
|
+
inputNode.eventEmitter.emit(`handle-${flowNodeInstanceId}`, error, true);
|
|
69
|
+
} else {
|
|
70
|
+
console.log(`[DEBUG] Cannot raise error - inputNode or eventEmitter not found for etwInputNodeId: ${etwInputNodeId}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Example synchronous onSend hook
|
|
76
|
+
RED.hooks.add("onSend", (sendEvents) => {
|
|
77
|
+
for (const sendEvent of sendEvents) {
|
|
78
|
+
|
|
79
|
+
// Call send method on ExternalTaskState if this message has a flowNodeInstanceId
|
|
80
|
+
if (sendEvent.msg?.flowNodeInstanceId) {
|
|
81
|
+
let externalTaskNodeStates = globalExternalTaskStates[sendEvent.msg.flowNodeInstanceId];
|
|
82
|
+
|
|
83
|
+
console.log(`[DEBUG] onSend - flowNodeInstanceId: ${sendEvent.msg.flowNodeInstanceId}, nodeId: ${sendEvent.source.node.id}, stateExists: ${!!externalTaskNodeStates}`);
|
|
84
|
+
|
|
85
|
+
if (!externalTaskNodeStates) {
|
|
86
|
+
console.log(`[DEBUG] onSend - Creating NEW ExternalTaskNodeStates for flowNodeInstanceId: ${sendEvent.msg.flowNodeInstanceId}`);
|
|
87
|
+
externalTaskNodeStates = new ExternalTaskNodeStates(sendEvent.msg.flowNodeInstanceId);
|
|
88
|
+
globalExternalTaskStates[sendEvent.msg.flowNodeInstanceId] = externalTaskNodeStates;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
externalTaskNodeStates.markSended(sendEvent.source.node.id)
|
|
92
|
+
|
|
93
|
+
if (externalTaskNodeStates.checkIfCompletedWithoutSend(sendEvent.source.node.id)) {
|
|
94
|
+
console.log(`[DEBUG] onSend - Node completed without send detected! Raising error for nodeId: ${sendEvent.source.node.id}`);
|
|
95
|
+
raiseExternalTaskError(sendEvent.msg.flowNodeInstanceId, sendEvent.msg.etw_input_node_id, sendEvent.source.node.id);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const onCompleted = (completeEvent) => {
|
|
102
|
+
|
|
103
|
+
// Check if this is an external task message
|
|
104
|
+
if (completeEvent.msg?.flowNodeInstanceId) {
|
|
105
|
+
let externalTaskNodeStates = globalExternalTaskStates[completeEvent.msg.flowNodeInstanceId];
|
|
106
|
+
|
|
107
|
+
console.log(`[DEBUG] onComplete - flowNodeInstanceId: ${completeEvent.msg.flowNodeInstanceId}, nodeId: ${completeEvent.node.id}, stateExists: ${!!externalTaskNodeStates}`);
|
|
108
|
+
|
|
109
|
+
if (!externalTaskNodeStates) {
|
|
110
|
+
console.log(`[DEBUG] onComplete - Creating NEW ExternalTaskNodeStates for flowNodeInstanceId: ${completeEvent.msg.flowNodeInstanceId}`);
|
|
111
|
+
externalTaskNodeStates = new ExternalTaskNodeStates(completeEvent.msg.flowNodeInstanceId);
|
|
112
|
+
globalExternalTaskStates[completeEvent.msg.flowNodeInstanceId] = externalTaskNodeStates;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
externalTaskNodeStates.markCompleted(completeEvent.node.id);
|
|
116
|
+
|
|
117
|
+
if (externalTaskNodeStates.checkIfCompletedWithoutSend(completeEvent.node.id)) {
|
|
118
|
+
console.log(`[DEBUG] onComplete - Node completed without send detected! Raising error for nodeId: ${completeEvent.node.id}`);
|
|
119
|
+
raiseExternalTaskError(completeEvent.msg.flowNodeInstanceId, completeEvent.msg.etw_input_node_id, completeEvent.node.id);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
RED.hooks.add("onComplete", onCompleted);
|
|
125
|
+
|
|
7
126
|
function ExternalTaskInput(config) {
|
|
8
127
|
RED.nodes.createNode(this, config);
|
|
9
128
|
var node = this;
|
|
@@ -349,15 +468,34 @@ module.exports = function (RED) {
|
|
|
349
468
|
return;
|
|
350
469
|
}
|
|
351
470
|
const etwCallback = async (payload, externalTask) => {
|
|
471
|
+
|
|
472
|
+
console.log(`[DEBUG] etwCallback - NEW External Task received! flowNodeInstanceId: ${externalTask.flowNodeInstanceId}, processInstanceId: ${externalTask.processInstanceId}`);
|
|
473
|
+
console.log(`[DEBUG] etwCallback - Creating NEW ExternalTaskNodeStates for flowNodeInstanceId: ${externalTask.flowNodeInstanceId}`);
|
|
474
|
+
globalExternalTaskStates[externalTask.flowNodeInstanceId] = new ExternalTaskNodeStates(externalTask.flowNodeInstanceId);
|
|
475
|
+
|
|
352
476
|
const saveHandleCallback = (data, callback, msg) => {
|
|
353
477
|
try {
|
|
354
478
|
callback(data);
|
|
355
479
|
node.log(`send to engine *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}', topic '${node.topic}' and *processInstanceId* ${externalTask.processInstanceId}`);
|
|
480
|
+
|
|
481
|
+
// Remove ExternalTaskState from global dictionary
|
|
482
|
+
if (globalExternalTaskStates[externalTask.flowNodeInstanceId]) {
|
|
483
|
+
console.log(`[DEBUG] saveHandleCallback SUCCESS - Deleting ExternalTaskNodeStates for flowNodeInstanceId: ${externalTask.flowNodeInstanceId}`);
|
|
484
|
+
delete globalExternalTaskStates[externalTask.flowNodeInstanceId];
|
|
485
|
+
}
|
|
486
|
+
|
|
356
487
|
node.setFinishHandlingTaskStatus(externalTask);
|
|
357
488
|
} catch (error) {
|
|
489
|
+
// Remove ExternalTaskState from global dictionary on error as well
|
|
490
|
+
if (globalExternalTaskStates[externalTask.flowNodeInstanceId]) {
|
|
491
|
+
console.log(`[DEBUG] saveHandleCallback ERROR - Deleting ExternalTaskNodeStates for flowNodeInstanceId: ${externalTask.flowNodeInstanceId}, error: ${error?.message}`);
|
|
492
|
+
delete globalExternalTaskStates[externalTask.flowNodeInstanceId];
|
|
493
|
+
}
|
|
494
|
+
|
|
358
495
|
node.setErrorFinishHandlingTaskStatus(externalTask, error);
|
|
359
496
|
msg.error = error;
|
|
360
497
|
node.error(`failed send to engine *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}', topic '${node.topic}' and *processInstanceId* ${externalTask.processInstanceId}: ${error?.message}`, msg);
|
|
498
|
+
callback(error);
|
|
361
499
|
}
|
|
362
500
|
};
|
|
363
501
|
|
|
@@ -378,6 +516,7 @@ module.exports = function (RED) {
|
|
|
378
516
|
};
|
|
379
517
|
|
|
380
518
|
const handleErrorTask = (error) => {
|
|
519
|
+
console.log(`[DEBUG] handleErrorTask - flowNodeInstanceId: ${externalTask.flowNodeInstanceId}, errorCode: ${error?.errorCode}, errorMessage: ${error?.message}`);
|
|
381
520
|
node.log(
|
|
382
521
|
`handle error event for *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}' and *processInstanceId* '${externalTask.processInstanceId}' on *msg._msgid* '${error.errorDetails?._msgid}'.`
|
|
383
522
|
);
|
|
@@ -390,6 +529,8 @@ module.exports = function (RED) {
|
|
|
390
529
|
};
|
|
391
530
|
|
|
392
531
|
node.eventEmitter.once(`handle-${externalTask.flowNodeInstanceId}`, (msg, isError = false) => {
|
|
532
|
+
console.log(`[DEBUG] eventEmitter handle event - flowNodeInstanceId: ${externalTask.flowNodeInstanceId}, isError: ${isError}, msgId: ${msg._msgid}`);
|
|
533
|
+
|
|
393
534
|
try {
|
|
394
535
|
msg.etw_finished_at = new Date().toISOString();
|
|
395
536
|
|
|
@@ -397,7 +538,7 @@ module.exports = function (RED) {
|
|
|
397
538
|
msg.etw_duration = new Date(msg.etw_finished_at) - new Date(msg.etw_started_at);
|
|
398
539
|
}
|
|
399
540
|
} catch (error) {
|
|
400
|
-
node.error(`failed to calculate duration: ${error?.message}`, msg);
|
|
541
|
+
node.error(`failed to calculate duration: ${error?.message}`, msg);
|
|
401
542
|
}
|
|
402
543
|
|
|
403
544
|
node.log(
|
|
@@ -406,8 +547,10 @@ module.exports = function (RED) {
|
|
|
406
547
|
|
|
407
548
|
|
|
408
549
|
if (isError) {
|
|
550
|
+
console.log(`[DEBUG] Routing to handleErrorTask`);
|
|
409
551
|
handleErrorTask(msg);
|
|
410
552
|
} else {
|
|
553
|
+
console.log(`[DEBUG] Routing to handleFinishTask`);
|
|
411
554
|
handleFinishTask(msg);
|
|
412
555
|
}
|
|
413
556
|
});
|
|
@@ -429,6 +572,7 @@ module.exports = function (RED) {
|
|
|
429
572
|
);
|
|
430
573
|
|
|
431
574
|
node.send(msg);
|
|
575
|
+
console.log(`[DEBUG] etwCallback - Sent message for flowNodeInstanceId: ${externalTask.flowNodeInstanceId}, msgId: ${msg._msgid}`);
|
|
432
576
|
});
|
|
433
577
|
};
|
|
434
578
|
|