@5minds/node-red-contrib-processcube 1.16.1-feature-5e019f-mhojvcvu → 1.16.1-feature-b8d70b-mhvx8c1e
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-error.js +3 -3
- package/externaltask-input.js +196 -44
- package/package.json +1 -2
- package/externaltask-inject.html +0 -123
- package/externaltask-inject.js +0 -98
package/externaltask-error.js
CHANGED
|
@@ -3,10 +3,10 @@ module.exports = function (RED) {
|
|
|
3
3
|
RED.nodes.createNode(this, config);
|
|
4
4
|
var node = this;
|
|
5
5
|
|
|
6
|
-
node.on('input', function (msg) {
|
|
6
|
+
node.on('input', function (msg) {
|
|
7
7
|
const flowNodeInstanceId = msg.flowNodeInstanceId;
|
|
8
8
|
const etw_input_node_id = msg.etw_input_node_id;
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
if (!etw_input_node_id) {
|
|
11
11
|
node.error('Error: The message did not contain the required etw_input_node_id.');
|
|
12
12
|
} else {
|
|
@@ -24,7 +24,7 @@ module.exports = function (RED) {
|
|
|
24
24
|
if (msgError.code) {
|
|
25
25
|
errorCode = msgError.code;
|
|
26
26
|
errorMessage = msgError.message;
|
|
27
|
-
}
|
|
27
|
+
}
|
|
28
28
|
} else if (msg.errorCode) {
|
|
29
29
|
errorCode = msg.errorCode;
|
|
30
30
|
errorMessage = msg.errorMessage;
|
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;
|
|
@@ -165,60 +284,68 @@ module.exports = function (RED) {
|
|
|
165
284
|
};
|
|
166
285
|
|
|
167
286
|
const onPreDeliver = (sendEvent) => {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const sourceNode = sendEvent?.source?.node;
|
|
171
|
-
const destinationNode = sendEvent?.destination?.node;
|
|
287
|
+
try {
|
|
288
|
+
if (node.isHandling() && node.ownMessage(sendEvent.msg)) {
|
|
172
289
|
|
|
173
|
-
|
|
290
|
+
const sourceNode = sendEvent?.source?.node;
|
|
291
|
+
const destinationNode = sendEvent?.destination?.node;
|
|
174
292
|
|
|
175
|
-
|
|
293
|
+
node._step = `${destinationNode.name || destinationNode.type}`;
|
|
176
294
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
295
|
+
node.showStatus();
|
|
296
|
+
|
|
297
|
+
const debugMsg = {
|
|
298
|
+
event: 'enter',
|
|
299
|
+
sourceName: sourceNode.name,
|
|
300
|
+
sourceType: sourceNode.type,
|
|
301
|
+
destinationNodeName: destinationNode.name,
|
|
302
|
+
destinationNodeType: destinationNode.type,
|
|
303
|
+
timestamp: new Date().toISOString(),
|
|
304
|
+
};
|
|
185
305
|
|
|
186
|
-
|
|
306
|
+
node.traceExecution(debugMsg);
|
|
187
307
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
308
|
+
if (process.env.NODE_RED_ETW_STEP_LOGGING == 'true' || process.env.NODERED_ETW_STEP_LOGGING == 'true') {
|
|
309
|
+
node._trace = `'${sourceNode.name || sourceNode.type}'->'${destinationNode.name || destinationNode.type}'`;
|
|
310
|
+
node.log(`preDeliver: ${node._trace}`);
|
|
311
|
+
}
|
|
191
312
|
}
|
|
313
|
+
} catch (error) {
|
|
314
|
+
node.error(`Error in onPreDeliver: ${error?.message}`, { error: error });
|
|
192
315
|
}
|
|
193
316
|
};
|
|
194
|
-
RED.hooks.add(
|
|
317
|
+
RED.hooks.add(`preDeliver.etw-input-${node.id}`, onPreDeliver);
|
|
195
318
|
|
|
196
319
|
const onPostDeliver = (sendEvent) => {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
320
|
+
try {
|
|
321
|
+
if (node.isHandling() && node.ownMessage(sendEvent.msg)) {
|
|
322
|
+
const sourceNode = sendEvent?.source?.node;
|
|
323
|
+
const destinationNode = sendEvent?.destination?.node;
|
|
324
|
+
|
|
325
|
+
node.decrMsgOnNode(sourceNode, sendEvent.msg);
|
|
326
|
+
node.incMsgOnNode(destinationNode, sendEvent.msg);
|
|
327
|
+
|
|
328
|
+
const debugMsg = {
|
|
329
|
+
event: 'exit',
|
|
330
|
+
sourceName: sourceNode.name,
|
|
331
|
+
sourceType: sourceNode.type,
|
|
332
|
+
destinationNodeName: destinationNode.name,
|
|
333
|
+
destinationNodeType: destinationNode.type,
|
|
334
|
+
timestamp: new Date().toISOString(),
|
|
335
|
+
};
|
|
212
336
|
|
|
213
|
-
|
|
337
|
+
node.traceExecution(debugMsg);
|
|
214
338
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
339
|
+
if (process.env.NODE_RED_ETW_STEP_LOGGING == 'true' || process.env.NODERED_ETW_STEP_LOGGING == 'true') {
|
|
340
|
+
node._trace = `'${sourceNode.name || sourceNode.type}'->'${destinationNode.name || destinationNode.type}'`;
|
|
341
|
+
node.log(`postDeliver: ${node._trace}`);
|
|
342
|
+
}
|
|
218
343
|
}
|
|
344
|
+
} catch (error) {
|
|
345
|
+
node.error(`Error in onPostDeliver: ${error?.message}`, { error: error });
|
|
219
346
|
}
|
|
220
347
|
};
|
|
221
|
-
RED.hooks.add(
|
|
348
|
+
RED.hooks.add(`postDeliver.etw-input-${node.id}`, onPostDeliver);
|
|
222
349
|
|
|
223
350
|
node.setSubscribedStatus = () => {
|
|
224
351
|
this._subscribed = true;
|
|
@@ -349,15 +476,34 @@ module.exports = function (RED) {
|
|
|
349
476
|
return;
|
|
350
477
|
}
|
|
351
478
|
const etwCallback = async (payload, externalTask) => {
|
|
479
|
+
|
|
480
|
+
console.log(`[DEBUG] etwCallback - NEW External Task received! flowNodeInstanceId: ${externalTask.flowNodeInstanceId}, processInstanceId: ${externalTask.processInstanceId}`);
|
|
481
|
+
console.log(`[DEBUG] etwCallback - Creating NEW ExternalTaskNodeStates for flowNodeInstanceId: ${externalTask.flowNodeInstanceId}`);
|
|
482
|
+
globalExternalTaskStates[externalTask.flowNodeInstanceId] = new ExternalTaskNodeStates(externalTask.flowNodeInstanceId);
|
|
483
|
+
|
|
352
484
|
const saveHandleCallback = (data, callback, msg) => {
|
|
353
485
|
try {
|
|
354
486
|
callback(data);
|
|
355
487
|
node.log(`send to engine *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}', topic '${node.topic}' and *processInstanceId* ${externalTask.processInstanceId}`);
|
|
488
|
+
|
|
489
|
+
// Remove ExternalTaskState from global dictionary
|
|
490
|
+
if (globalExternalTaskStates[externalTask.flowNodeInstanceId]) {
|
|
491
|
+
console.log(`[DEBUG] saveHandleCallback SUCCESS - Deleting ExternalTaskNodeStates for flowNodeInstanceId: ${externalTask.flowNodeInstanceId}`);
|
|
492
|
+
delete globalExternalTaskStates[externalTask.flowNodeInstanceId];
|
|
493
|
+
}
|
|
494
|
+
|
|
356
495
|
node.setFinishHandlingTaskStatus(externalTask);
|
|
357
496
|
} catch (error) {
|
|
497
|
+
// Remove ExternalTaskState from global dictionary on error as well
|
|
498
|
+
if (globalExternalTaskStates[externalTask.flowNodeInstanceId]) {
|
|
499
|
+
console.log(`[DEBUG] saveHandleCallback ERROR - Deleting ExternalTaskNodeStates for flowNodeInstanceId: ${externalTask.flowNodeInstanceId}, error: ${error?.message}`);
|
|
500
|
+
delete globalExternalTaskStates[externalTask.flowNodeInstanceId];
|
|
501
|
+
}
|
|
502
|
+
|
|
358
503
|
node.setErrorFinishHandlingTaskStatus(externalTask, error);
|
|
359
504
|
msg.error = error;
|
|
360
505
|
node.error(`failed send to engine *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}', topic '${node.topic}' and *processInstanceId* ${externalTask.processInstanceId}: ${error?.message}`, msg);
|
|
506
|
+
callback(error);
|
|
361
507
|
}
|
|
362
508
|
};
|
|
363
509
|
|
|
@@ -378,6 +524,7 @@ module.exports = function (RED) {
|
|
|
378
524
|
};
|
|
379
525
|
|
|
380
526
|
const handleErrorTask = (error) => {
|
|
527
|
+
console.log(`[DEBUG] handleErrorTask - flowNodeInstanceId: ${externalTask.flowNodeInstanceId}, errorCode: ${error?.errorCode}, errorMessage: ${error?.message}`);
|
|
381
528
|
node.log(
|
|
382
529
|
`handle error event for *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}' and *processInstanceId* '${externalTask.processInstanceId}' on *msg._msgid* '${error.errorDetails?._msgid}'.`
|
|
383
530
|
);
|
|
@@ -390,6 +537,8 @@ module.exports = function (RED) {
|
|
|
390
537
|
};
|
|
391
538
|
|
|
392
539
|
node.eventEmitter.once(`handle-${externalTask.flowNodeInstanceId}`, (msg, isError = false) => {
|
|
540
|
+
console.log(`[DEBUG] eventEmitter handle event - flowNodeInstanceId: ${externalTask.flowNodeInstanceId}, isError: ${isError}, msgId: ${msg._msgid}`);
|
|
541
|
+
|
|
393
542
|
try {
|
|
394
543
|
msg.etw_finished_at = new Date().toISOString();
|
|
395
544
|
|
|
@@ -397,7 +546,7 @@ module.exports = function (RED) {
|
|
|
397
546
|
msg.etw_duration = new Date(msg.etw_finished_at) - new Date(msg.etw_started_at);
|
|
398
547
|
}
|
|
399
548
|
} catch (error) {
|
|
400
|
-
node.error(`failed to calculate duration: ${error?.message}`, msg);
|
|
549
|
+
node.error(`failed to calculate duration: ${error?.message}`, msg);
|
|
401
550
|
}
|
|
402
551
|
|
|
403
552
|
node.log(
|
|
@@ -406,8 +555,10 @@ module.exports = function (RED) {
|
|
|
406
555
|
|
|
407
556
|
|
|
408
557
|
if (isError) {
|
|
558
|
+
console.log(`[DEBUG] Routing to handleErrorTask`);
|
|
409
559
|
handleErrorTask(msg);
|
|
410
560
|
} else {
|
|
561
|
+
console.log(`[DEBUG] Routing to handleFinishTask`);
|
|
411
562
|
handleFinishTask(msg);
|
|
412
563
|
}
|
|
413
564
|
});
|
|
@@ -429,6 +580,7 @@ module.exports = function (RED) {
|
|
|
429
580
|
);
|
|
430
581
|
|
|
431
582
|
node.send(msg);
|
|
583
|
+
console.log(`[DEBUG] etwCallback - Sent message for flowNodeInstanceId: ${externalTask.flowNodeInstanceId}, msgId: ${msg._msgid}`);
|
|
432
584
|
});
|
|
433
585
|
};
|
|
434
586
|
|
|
@@ -488,12 +640,12 @@ module.exports = function (RED) {
|
|
|
488
640
|
|
|
489
641
|
node.on('close', () => {
|
|
490
642
|
try {
|
|
491
|
-
RED.hooks.remove('preDeliver', onPreDeliver);
|
|
492
|
-
RED.hooks.remove('postDeliver', onPostDeliver);
|
|
493
643
|
externalTaskWorker.stop();
|
|
644
|
+
RED.hooks.remove(`preDeliver.etw-input-${node.id}`);
|
|
645
|
+
RED.hooks.remove(`postDeliver.etw-input-${node.id}`);
|
|
494
646
|
node.log('External Task Worker closed.');
|
|
495
|
-
} catch {
|
|
496
|
-
node.error(
|
|
647
|
+
} catch (error) {
|
|
648
|
+
node.error(`Client close failed: ${JSON.stringify(error)}`);
|
|
497
649
|
}
|
|
498
650
|
});
|
|
499
651
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@5minds/node-red-contrib-processcube",
|
|
3
|
-
"version": "1.16.1-feature-
|
|
3
|
+
"version": "1.16.1-feature-b8d70b-mhvx8c1e",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Node-RED nodes for ProcessCube",
|
|
6
6
|
"scripts": {
|
|
@@ -39,7 +39,6 @@
|
|
|
39
39
|
"DataobjectInstanceQuery": "dataobject-instance-query.js",
|
|
40
40
|
"EndEventFinishedListener": "endevent-finished-listener.js",
|
|
41
41
|
"externaltaskInput": "externaltask-input.js",
|
|
42
|
-
"externaltaskInject": "externaltask-inject.js",
|
|
43
42
|
"externaltaskOutput": "externaltask-output.js",
|
|
44
43
|
"externaltaskError": "externaltask-error.js",
|
|
45
44
|
"externaltaskEventListener": "externaltask-event-listener.js",
|
package/externaltask-inject.html
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
<script type="text/javascript">
|
|
2
|
-
RED.nodes.registerType('externaltask-inject', {
|
|
3
|
-
category: 'ProcessCube',
|
|
4
|
-
color: '#02AFD6',
|
|
5
|
-
defaults: {
|
|
6
|
-
name: { value: '' },
|
|
7
|
-
payloadType: { value: 'json' },
|
|
8
|
-
payload: { value: '{}' },
|
|
9
|
-
taskType: { value: 'json' },
|
|
10
|
-
task: { value: '{}' }
|
|
11
|
-
},
|
|
12
|
-
inputs: 0,
|
|
13
|
-
outputs: 1,
|
|
14
|
-
icon: 'inject.svg',
|
|
15
|
-
label: function () {
|
|
16
|
-
return this.name || 'externaltask-inject';
|
|
17
|
-
},
|
|
18
|
-
button: {
|
|
19
|
-
onclick: function() {
|
|
20
|
-
var node = this;
|
|
21
|
-
|
|
22
|
-
$.ajax({
|
|
23
|
-
type: "POST",
|
|
24
|
-
url: "externaltask-inject/trigger/" + node.id,
|
|
25
|
-
success: function(resp) {
|
|
26
|
-
RED.notify("Message injected", "success");
|
|
27
|
-
},
|
|
28
|
-
error: function(xhr, status, err) {
|
|
29
|
-
RED.notify("Error injecting message: " + err, "error");
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
oneditprepare: function () {
|
|
35
|
-
// Initialize the typed input for payload
|
|
36
|
-
$('#node-input-payload').typedInput({
|
|
37
|
-
default: 'json',
|
|
38
|
-
types: ['str', 'num', 'bool', 'json', 'jsonata', 'flow', 'global']
|
|
39
|
-
});
|
|
40
|
-
// Restore the saved type for payload from hidden input
|
|
41
|
-
var payloadType = $('#node-input-payloadType').val();
|
|
42
|
-
if (payloadType) {
|
|
43
|
-
$('#node-input-payload').typedInput('type', payloadType);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Initialize the typed input for task
|
|
47
|
-
$('#node-input-task').typedInput({
|
|
48
|
-
default: 'json',
|
|
49
|
-
types: ['str', 'num', 'bool', 'json', 'jsonata', 'flow', 'global']
|
|
50
|
-
});
|
|
51
|
-
// Restore the saved type for task from hidden input
|
|
52
|
-
var taskType = $('#node-input-taskType').val();
|
|
53
|
-
if (taskType) {
|
|
54
|
-
$('#node-input-task').typedInput('type', taskType);
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
oneditsave: function () {
|
|
58
|
-
var payloadValue = $('#node-input-payload').typedInput('value');
|
|
59
|
-
var payloadType = $('#node-input-payload').typedInput('type');
|
|
60
|
-
this.payload = payloadValue;
|
|
61
|
-
this.payloadType = payloadType;
|
|
62
|
-
// Persist type to hidden input field
|
|
63
|
-
$('#node-input-payloadType').val(payloadType);
|
|
64
|
-
|
|
65
|
-
var taskValue = $('#node-input-task').typedInput('value');
|
|
66
|
-
var taskType = $('#node-input-task').typedInput('type');
|
|
67
|
-
this.task = taskValue;
|
|
68
|
-
this.taskType = taskType;
|
|
69
|
-
// Persist type to hidden input field
|
|
70
|
-
$('#node-input-taskType').val(taskType);
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
</script>
|
|
74
|
-
|
|
75
|
-
<script type="text/html" data-template-name="externaltask-inject">
|
|
76
|
-
<div class="form-row">
|
|
77
|
-
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
78
|
-
<input type="text" id="node-input-name" placeholder="Name" />
|
|
79
|
-
</div>
|
|
80
|
-
<div class="form-row">
|
|
81
|
-
<label for="node-input-payload">msg.payload</label>
|
|
82
|
-
<input type="hidden" id="node-input-payloadType">
|
|
83
|
-
<input type="text" id="node-input-payload" style="width: 70%;">
|
|
84
|
-
</div>
|
|
85
|
-
<div class="form-row">
|
|
86
|
-
<label for="node-input-task">msg.task</label>
|
|
87
|
-
<input type="hidden" id="node-input-taskType">
|
|
88
|
-
<input type="text" id="node-input-task" style="width: 70%;">
|
|
89
|
-
</div>
|
|
90
|
-
</script>
|
|
91
|
-
|
|
92
|
-
<script type="text/markdown" data-help-name="externaltask-inject">
|
|
93
|
-
A lightweight alternative to the standard Node-RED inject node for testing external task workflows. Use this node to inject test messages directly into an external task workflow without needing a real external task from the ProcessCube engine.
|
|
94
|
-
|
|
95
|
-
This node is ideal for small-scale testing and debugging of external task workflows. For larger-scale testing with test cases or CI/CD integration, use the **externaltask-testing** node instead.
|
|
96
|
-
|
|
97
|
-
## Features
|
|
98
|
-
|
|
99
|
-
- Injects messages with configurable payload and task metadata
|
|
100
|
-
- Automatically generates `flowNodeInstanceId` and `processInstanceId` if not provided
|
|
101
|
-
- Sets required fields for compatibility with externaltask-output and externaltask-error nodes
|
|
102
|
-
- Supports multiple value types (JSON, string, number, boolean, JSONata, flow/global context)
|
|
103
|
-
|
|
104
|
-
## Configs
|
|
105
|
-
|
|
106
|
-
: name (string) : The name of the node
|
|
107
|
-
: payload (string | {}) : The payload to inject into the workflow
|
|
108
|
-
: task (string | {}) : The task object to inject
|
|
109
|
-
|
|
110
|
-
## Outputs
|
|
111
|
-
|
|
112
|
-
: payload (string | {}) : The injected payload with metadata (flowNodeInstanceId, processInstanceId, task)
|
|
113
|
-
: task (object) : The injected task object
|
|
114
|
-
: flowNodeInstanceId (string) : Unique identifier for the external task
|
|
115
|
-
: processInstanceId (string) : Reference to the process instance
|
|
116
|
-
: etw_input_node_id (string) : Reference to the injecting node
|
|
117
|
-
: etw_started_at (string) : ISO timestamp of when the task was injected
|
|
118
|
-
|
|
119
|
-
## References
|
|
120
|
-
|
|
121
|
-
- [The ProcessCube© Developer Network](https://processcube.io) - All documentation for the ProcessCube© platform
|
|
122
|
-
- [ProcessCube© LowCode Integration](https://processcube.io/docs/node-red) - LowCode integration in ProcessCube©
|
|
123
|
-
</script>
|
package/externaltask-inject.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
const { v4: uuidv4 } = require('uuid');
|
|
2
|
-
const EventEmitter = require('node:events');
|
|
3
|
-
|
|
4
|
-
module.exports = function (RED) {
|
|
5
|
-
function ExternalTaskInject(config) {
|
|
6
|
-
RED.nodes.createNode(this, config);
|
|
7
|
-
var node = this;
|
|
8
|
-
node.config = config;
|
|
9
|
-
node.eventEmitter = new EventEmitter();
|
|
10
|
-
|
|
11
|
-
node.on('input', function (msg, send, done) {
|
|
12
|
-
msg.flowNodeInstanceId = msg.flowNodeInstanceId ?? uuidv4();
|
|
13
|
-
msg.processInstanceId = msg.processInstanceId ?? uuidv4();
|
|
14
|
-
|
|
15
|
-
if (!msg.task) {
|
|
16
|
-
// Use configured task if available, otherwise create default
|
|
17
|
-
if (config.task) {
|
|
18
|
-
msg.task = config.task;
|
|
19
|
-
} else {
|
|
20
|
-
msg.task = {
|
|
21
|
-
flowNodeInstanceId: msg.flowNodeInstanceId,
|
|
22
|
-
processInstanceId: msg.processInstanceId,
|
|
23
|
-
task: {}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
msg.etw_started_at = new Date().toISOString();
|
|
28
|
-
msg.etw_input_node_id = node.id;
|
|
29
|
-
|
|
30
|
-
send(msg);
|
|
31
|
-
if (done) done();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
RED.nodes.registerType('externaltask-inject', ExternalTaskInject);
|
|
37
|
-
|
|
38
|
-
// Helper function to parse typed values
|
|
39
|
-
function parseTypedValue(value, type) {
|
|
40
|
-
switch(type) {
|
|
41
|
-
case 'str':
|
|
42
|
-
return value || '';
|
|
43
|
-
case 'num':
|
|
44
|
-
return Number(value) || 0;
|
|
45
|
-
case 'bool':
|
|
46
|
-
return value === 'true' || value === true;
|
|
47
|
-
case 'json':
|
|
48
|
-
try {
|
|
49
|
-
return JSON.parse(value || '{}');
|
|
50
|
-
} catch(parseErr) {
|
|
51
|
-
console.error("Invalid JSON: " + parseErr.message);
|
|
52
|
-
return {};
|
|
53
|
-
}
|
|
54
|
-
case 'jsonata':
|
|
55
|
-
// JSONata expressions would need to be evaluated in Node-RED context
|
|
56
|
-
// For now, treat as string
|
|
57
|
-
return value || '';
|
|
58
|
-
case 'flow':
|
|
59
|
-
case 'global':
|
|
60
|
-
// These are context references - store as string for now
|
|
61
|
-
return value || '';
|
|
62
|
-
default:
|
|
63
|
-
return value || '';
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// HTTP endpoint to handle button clicks
|
|
68
|
-
RED.httpAdmin.post('/externaltask-inject/trigger/:id', function(req, res) {
|
|
69
|
-
var node = RED.nodes.getNode(req.params.id);
|
|
70
|
-
if (node !== null && typeof node !== 'undefined') {
|
|
71
|
-
try {
|
|
72
|
-
var payloadValue = node.config.payload;
|
|
73
|
-
var payloadType = node.config.payloadType || 'json';
|
|
74
|
-
var payloadData = parseTypedValue(payloadValue, payloadType);
|
|
75
|
-
|
|
76
|
-
var taskValue = node.config.task;
|
|
77
|
-
var taskType = node.config.taskType || 'json';
|
|
78
|
-
var taskData = parseTypedValue(taskValue, taskType);
|
|
79
|
-
|
|
80
|
-
// Create a message with the configured payload and task
|
|
81
|
-
var msg = {
|
|
82
|
-
payload: payloadData,
|
|
83
|
-
task: taskData,
|
|
84
|
-
_msgid: uuidv4()
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
// Trigger the node's input handler with the configured message
|
|
88
|
-
node.receive(msg);
|
|
89
|
-
res.sendStatus(200);
|
|
90
|
-
} catch(err) {
|
|
91
|
-
res.sendStatus(500);
|
|
92
|
-
node.error("Error injecting message: " + err.message);
|
|
93
|
-
}
|
|
94
|
-
} else {
|
|
95
|
-
res.sendStatus(404);
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
};
|