@5minds/node-red-contrib-processcube 2.0.0-feature-629c78-m2dq1ygt → 7.6.0-develop-51b534-mjy4rzoh
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/README.md +3 -58
- package/check-authorization.html +138 -0
- package/check-authorization.js +27 -0
- package/dataobject-instance-query.html +141 -0
- package/dataobject-instance-query.js +45 -0
- package/endevent-finished-listener.js +14 -25
- package/examples/Check-Authorization-Sample.json +109 -0
- package/examples/Dataobject-Instance-Query-Sample.json +109 -0
- package/externaltask-error.html +8 -3
- package/externaltask-error.js +43 -29
- package/externaltask-event-listener.js +11 -28
- package/externaltask-input.html +48 -3
- package/externaltask-input.js +581 -114
- package/externaltask-output.js +20 -16
- package/icons/data-object-query.svg +5 -0
- package/message-event-trigger.html +1 -1
- package/message-event-trigger.js +8 -7
- package/package.json +74 -67
- package/process-event-listener.js +166 -225
- package/process-start.html +6 -0
- package/process-start.js +29 -6
- package/process-terminate.html +1 -1
- package/process-terminate.js +7 -5
- package/processcube-engine-config.html +25 -7
- package/processcube-engine-config.js +25 -135
- package/processcube-google-docs-mail-template.html +150 -0
- package/processcube-google-docs-mail-template.js +158 -0
- package/processdefinition-deploy.html +44 -0
- package/processdefinition-deploy.js +28 -0
- package/processdefinition-query.html +18 -13
- package/processdefinition-query.js +33 -31
- package/processinstance-delete-advanced.html +82 -0
- package/processinstance-delete-advanced.js +33 -0
- package/processinstance-delete.html +60 -8
- package/processinstance-delete.js +84 -30
- package/processinstance-query.html +116 -109
- package/processinstance-query.js +28 -5
- package/signal-event-trigger.js +8 -6
- package/usertask-event-listener.html +123 -1
- package/usertask-event-listener.js +30 -45
- package/usertask-input.html +119 -0
- package/usertask-input.js +7 -9
- package/usertask-output.js +15 -8
- package/wait-for-usertask.html +122 -6
- package/wait-for-usertask.js +44 -47
- package/.github/workflows/build-and-publish.yml +0 -72
- package/.processcube/authority/config/config.json +0 -36
- package/.processcube/authority/config/upeSeedingData.json +0 -12
- package/Dockerfile +0 -9
- package/doc_generator/_process_instances_query.md +0 -115
- package/doc_generator/generator.js +0 -41
- package/doc_generator/generator_with_swagger.js +0 -72
- package/doc_generator/package-lock.json +0 -176
- package/doc_generator/package.json +0 -15
- package/doc_generator/query_template.mustache +0 -20
- package/doc_generator/swagger.json +0 -4110
- package/docker-compose.yml +0 -44
- package/nodered/flows.json +0 -2156
- package/nodered/flows_cred.json +0 -3
- package/nodered/settings.js +0 -562
- package/nodered/static/ProcessCube_Logo.svg +0 -53
- package/processes/Call-Activity-Sample.bpmn +0 -88
- package/processes/External-Task-Auth-Sample.bpmn +0 -82
- package/processes/External-Task-Sample.bpmn +0 -94
- package/processes/SampleEvent.bpmn +0 -73
- package/processes/User-Task-Auth-Sample.bpmn +0 -63
- package/processes/User-Task-Sample.bpmn +0 -76
- package/processes/Wait-For-Usertask.bpmn +0 -74
package/externaltask-input.js
CHANGED
|
@@ -1,170 +1,637 @@
|
|
|
1
1
|
const EventEmitter = require('node:events');
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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;
|
|
8
23
|
}
|
|
9
|
-
}
|
|
10
24
|
|
|
11
|
-
|
|
25
|
+
checkIfCompletedWithoutSend(nodeId) {
|
|
26
|
+
const nodeState = this.nodeStades[nodeId];
|
|
27
|
+
const result = (nodeState && nodeState.gotCompleted && !nodeState.gotSend);
|
|
28
|
+
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
12
32
|
|
|
13
33
|
module.exports = function (RED) {
|
|
34
|
+
|
|
35
|
+
const os = require('os');
|
|
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) && !completeEvent.error) {
|
|
101
|
+
|
|
102
|
+
raiseExternalTaskError(completeEvent.msg.flowNodeInstanceId, completeEvent.msg.etw_input_node_id, completeEvent.node.id);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
RED.hooks.add("onComplete", onCompleted);
|
|
108
|
+
|
|
14
109
|
function ExternalTaskInput(config) {
|
|
15
110
|
RED.nodes.createNode(this, config);
|
|
16
111
|
var node = this;
|
|
17
|
-
var flowContext = node.context().flow;
|
|
18
112
|
|
|
19
|
-
|
|
113
|
+
node.started_external_tasks = {};
|
|
114
|
+
|
|
115
|
+
node.engine = RED.nodes.getNode(config.engine);
|
|
20
116
|
|
|
21
|
-
|
|
117
|
+
node.eventEmitter = new EventEmitter();
|
|
22
118
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
119
|
+
let options = RED.util.evaluateNodeProperty(config.workerConfig, config.workerConfigType, node);
|
|
120
|
+
let topic = node.topic = RED.util.evaluateNodeProperty(config.topic, config.topicType, node)
|
|
121
|
+
this.workername = RED.util.evaluateNodeProperty(config.workername, config.workernameType, node);
|
|
122
|
+
node._traces = config.traces || [];
|
|
123
|
+
|
|
124
|
+
if (!options['workerId']) {
|
|
125
|
+
|
|
126
|
+
if (!this.workername) {
|
|
127
|
+
this.workername = `nodered:${process.env.NODERED_NAME || ''}-host:${os.hostname()}-pid:${process.pid}-id:${node.id}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
options['workerId'] = this.workername;
|
|
26
131
|
}
|
|
27
132
|
|
|
28
|
-
|
|
133
|
+
if (!options['lockDuration'] && (process.env.NODE_RED_ETW_LOCK_DURATION || process.env.NODERED_ETW_LOCK_DURATION)) {
|
|
134
|
+
options['lockDuration'] = parseInt(process.env.NODE_RED_ETW_LOCK_DURATION || process.env.NODERED_ETW_LOCK_DURATION) || undefined;
|
|
135
|
+
}
|
|
29
136
|
|
|
30
|
-
if (!
|
|
31
|
-
|
|
32
|
-
eventEmitter = flowContext.get('emitter');
|
|
137
|
+
if (!options['longpollingTimeout']) {
|
|
138
|
+
options['longpollingTimeout'] = parseInt(process.env.NODE_RED_ETW_LONGPOLLING_TIMEOUT || process.env.NODERED_ETW_LONGPOLLING_TIMEOUT) || undefined;
|
|
33
139
|
}
|
|
34
140
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
141
|
+
if (!options['idleTimeout']) {
|
|
142
|
+
options['idleTimeout'] = parseInt(process.env.NODE_RED_ETW_IDLE_TIMEOUT || process.env.NODERED_ETW_IDLE_TIMEOUT) || undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
node._subscribed = true;
|
|
146
|
+
node._subscribed_error = null;
|
|
147
|
+
node._trace = '';
|
|
148
|
+
node._step = '';
|
|
149
|
+
node._tracking_nodes = {};
|
|
150
|
+
node._join_inputs = {};
|
|
151
|
+
node._tracking_for_etw = {};
|
|
152
|
+
|
|
153
|
+
node.isHandling = () => {
|
|
154
|
+
return Object.keys(node.started_external_tasks).length > 0;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
node.ownMessage = (msg) => {
|
|
158
|
+
return msg.etw_input_node_id === node.id;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
node.clearTracking = (msg) => {
|
|
162
|
+
if (msg.flowNodeInstanceId) {
|
|
163
|
+
node._tracking_for_etw[msg.flowNodeInstanceId].forEach((theNode) => {
|
|
164
|
+
node.decrMsgOnNode(theNode, msg);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
node.incMsgOnNode = (theNode, msg) => {
|
|
170
|
+
if (node.id === theNode.id) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!node._tracking_nodes[theNode.id]) {
|
|
175
|
+
node._tracking_nodes[theNode.id] = {
|
|
176
|
+
node: theNode,
|
|
177
|
+
count: 1,
|
|
178
|
+
};
|
|
179
|
+
} else {
|
|
180
|
+
node._tracking_nodes[theNode.id].count++;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// bei nodes vom type 'join' müssen die eingänge gezählt werden,
|
|
184
|
+
// dass diese dann wieder beim verlassen am ausgang gesamt entfernt werden müssem
|
|
185
|
+
if (theNode.type === 'join') {
|
|
186
|
+
if (!node._join_inputs[theNode.id]) {
|
|
187
|
+
node._join_inputs[theNode.id] = {};
|
|
41
188
|
}
|
|
42
|
-
};
|
|
43
189
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
190
|
+
if (!node._join_inputs[theNode.id][msg.flowNodeInstanceId]) {
|
|
191
|
+
node._join_inputs[theNode.id][msg.flowNodeInstanceId] = 1;
|
|
192
|
+
} else {
|
|
193
|
+
node._join_inputs[theNode.id][msg.flowNodeInstanceId]++;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!node._tracking_for_etw[msg.flowNodeInstanceId]) {
|
|
198
|
+
node._tracking_for_etw[msg.flowNodeInstanceId] = [];
|
|
199
|
+
node._tracking_for_etw[msg.flowNodeInstanceId].push(theNode);
|
|
200
|
+
} else {
|
|
201
|
+
node._tracking_for_etw[msg.flowNodeInstanceId].push(theNode);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
theNode.status({ fill: 'blue', shape: 'dot', text: `tasks(${node._tracking_nodes[theNode.id].count})` });
|
|
205
|
+
};
|
|
47
206
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
207
|
+
node.decrMsgOnNode = (theNode, msg) => {
|
|
208
|
+
if (node.id === theNode.id) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
51
211
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
212
|
+
// bei nodes vom type 'join' müssen die eingänge gezählt werden,
|
|
213
|
+
// dass diese dann wieder beim verlassen am ausgang gesamt entfernt werden müssen
|
|
214
|
+
let dec_count = 1;
|
|
55
215
|
|
|
56
|
-
|
|
216
|
+
if (theNode.type === 'join') {
|
|
217
|
+
if (!node._join_inputs[theNode.id]) {
|
|
218
|
+
node._join_inputs[theNode.id] = {};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (node._join_inputs[theNode.id][msg.flowNodeInstanceId]) {
|
|
222
|
+
dec_count = node._join_inputs[theNode.id][msg.flowNodeInstanceId];
|
|
223
|
+
delete node._join_inputs[theNode.id][msg.flowNodeInstanceId];
|
|
224
|
+
}
|
|
225
|
+
}
|
|
57
226
|
|
|
58
|
-
|
|
59
|
-
|
|
227
|
+
if (!node._tracking_nodes[theNode.id]) {
|
|
228
|
+
node._tracking_nodes[theNode.id] = {
|
|
229
|
+
node: theNode,
|
|
230
|
+
count: 0,
|
|
60
231
|
};
|
|
232
|
+
} else {
|
|
233
|
+
//node._tracking_nodes[theNode.id].count--;
|
|
234
|
+
node._tracking_nodes[theNode.id].count =- dec_count;
|
|
61
235
|
|
|
62
|
-
|
|
63
|
-
node.
|
|
64
|
-
|
|
65
|
-
|
|
236
|
+
if (node._tracking_nodes[theNode.id].count <= 0) {
|
|
237
|
+
node._tracking_nodes[theNode.id].count = 0;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
66
240
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
241
|
+
if (node._tracking_for_etw[msg.flowNodeInstanceId]) {
|
|
242
|
+
const count_nodes = node._tracking_for_etw[msg.flowNodeInstanceId].filter(item => item !== theNode)
|
|
243
|
+
|
|
244
|
+
if (count_nodes <= 0) {
|
|
245
|
+
delete node._tracking_for_etw[msg.flowNodeInstanceId];
|
|
246
|
+
}
|
|
247
|
+
}
|
|
70
248
|
|
|
71
|
-
|
|
249
|
+
theNode.status({ fill: 'blue', shape: 'dot', text: `tasks(${node._tracking_nodes[theNode.id].count})` });
|
|
250
|
+
};
|
|
72
251
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
252
|
+
node.traceExecution = (msg) => {
|
|
253
|
+
if ((node._traces || []).includes(msg.event)) {
|
|
254
|
+
|
|
255
|
+
const message = {
|
|
256
|
+
id: node.id,
|
|
257
|
+
z: node.z,
|
|
258
|
+
_alias: node._alias,
|
|
259
|
+
path: node._flow.path,
|
|
260
|
+
name: node.name,
|
|
261
|
+
topic: node.topic,
|
|
262
|
+
msg: msg
|
|
78
263
|
};
|
|
79
264
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
);
|
|
265
|
+
RED.comms.publish("debug", message);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
84
268
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
269
|
+
const onPreDeliver = (sendEvent) => {
|
|
270
|
+
try {
|
|
271
|
+
if (node.isHandling() && node.ownMessage(sendEvent.msg)) {
|
|
272
|
+
|
|
273
|
+
const sourceNode = sendEvent?.source?.node;
|
|
274
|
+
const destinationNode = sendEvent?.destination?.node;
|
|
275
|
+
|
|
276
|
+
node._step = `${destinationNode.name || destinationNode.type}`;
|
|
277
|
+
|
|
278
|
+
node.showStatus();
|
|
279
|
+
|
|
280
|
+
const debugMsg = {
|
|
281
|
+
event: 'enter',
|
|
282
|
+
sourceName: sourceNode.name,
|
|
283
|
+
sourceType: sourceNode.type,
|
|
284
|
+
destinationNodeName: destinationNode.name,
|
|
285
|
+
destinationNodeType: destinationNode.type,
|
|
286
|
+
timestamp: new Date().toISOString(),
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
node.traceExecution(debugMsg);
|
|
290
|
+
|
|
291
|
+
if (process.env.NODE_RED_ETW_STEP_LOGGING == 'true' || process.env.NODERED_ETW_STEP_LOGGING == 'true') {
|
|
292
|
+
node._trace = `'${sourceNode.name || sourceNode.type}'->'${destinationNode.name || destinationNode.type}'`;
|
|
293
|
+
node.log(`preDeliver: ${node._trace}`);
|
|
89
294
|
}
|
|
90
|
-
}
|
|
295
|
+
}
|
|
296
|
+
} catch (error) {
|
|
297
|
+
node.error(`Error in onPreDeliver: ${error?.message}`, { error: error });
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
RED.hooks.add(`preDeliver.etw-input-${node.id}`, onPreDeliver);
|
|
301
|
+
|
|
302
|
+
const onPostDeliver = (sendEvent) => {
|
|
303
|
+
try {
|
|
304
|
+
if (node.isHandling() && node.ownMessage(sendEvent.msg)) {
|
|
305
|
+
const sourceNode = sendEvent?.source?.node;
|
|
306
|
+
const destinationNode = sendEvent?.destination?.node;
|
|
307
|
+
|
|
308
|
+
node.decrMsgOnNode(sourceNode, sendEvent.msg);
|
|
309
|
+
node.incMsgOnNode(destinationNode, sendEvent.msg);
|
|
310
|
+
|
|
311
|
+
const debugMsg = {
|
|
312
|
+
event: 'exit',
|
|
313
|
+
sourceName: sourceNode.name,
|
|
314
|
+
sourceType: sourceNode.type,
|
|
315
|
+
destinationNodeName: destinationNode.name,
|
|
316
|
+
destinationNodeType: destinationNode.type,
|
|
317
|
+
timestamp: new Date().toISOString(),
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
node.traceExecution(debugMsg);
|
|
321
|
+
|
|
322
|
+
if (process.env.NODE_RED_ETW_STEP_LOGGING == 'true' || process.env.NODERED_ETW_STEP_LOGGING == 'true') {
|
|
323
|
+
node._trace = `'${sourceNode.name || sourceNode.type}'->'${destinationNode.name || destinationNode.type}'`;
|
|
324
|
+
node.log(`postDeliver: ${node._trace}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
} catch (error) {
|
|
328
|
+
node.error(`Error in onPostDeliver: ${error?.message}`, { error: error });
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
RED.hooks.add(`postDeliver.etw-input-${node.id}`, onPostDeliver);
|
|
91
332
|
|
|
92
|
-
|
|
333
|
+
node.setSubscribedStatus = () => {
|
|
334
|
+
this._subscribed = true;
|
|
335
|
+
this._subscribed_error = null;
|
|
336
|
+
this.showStatus();
|
|
337
|
+
};
|
|
93
338
|
|
|
94
|
-
|
|
339
|
+
node.setUnsubscribedStatus = (error) => {
|
|
340
|
+
this._subscribed = false;
|
|
341
|
+
this._subscribed_error = error;
|
|
95
342
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
payload: payload,
|
|
100
|
-
flowNodeInstanceId: externalTask.flowNodeInstanceId,
|
|
101
|
-
processInstanceId: externalTask.processInstanceId
|
|
102
|
-
};
|
|
343
|
+
const info = `subscription failed (topic: ${node.topic}) [error: ${error?.message}].`;
|
|
344
|
+
|
|
345
|
+
this.error(info, JSON.stringify(error));
|
|
103
346
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
);
|
|
347
|
+
this.showStatus();
|
|
348
|
+
};
|
|
107
349
|
|
|
108
|
-
|
|
109
|
-
|
|
350
|
+
node.setStartHandlingTaskStatus = (externalTask) => {
|
|
351
|
+
this._subscribed = true;
|
|
352
|
+
this._subscribed_error = null;
|
|
353
|
+
this.started_external_tasks[externalTask.flowNodeInstanceId] = externalTask;
|
|
354
|
+
|
|
355
|
+
const debugMsg = {
|
|
356
|
+
event: 'start',
|
|
357
|
+
topic: node.topic,
|
|
358
|
+
flowNodeInstanceId: externalTask.flowNodeInstanceId,
|
|
359
|
+
timestamp: new Date().toISOString(),
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
node.traceExecution(debugMsg);
|
|
363
|
+
|
|
364
|
+
this.showStatus();
|
|
110
365
|
};
|
|
111
366
|
|
|
112
|
-
|
|
367
|
+
node.setFinishHandlingTaskStatus = (externalTask) => {
|
|
368
|
+
if (externalTask.flowNodeInstanceId) {
|
|
369
|
+
delete this.started_external_tasks[externalTask.flowNodeInstanceId];
|
|
370
|
+
node._trace = '';
|
|
371
|
+
node._step = '';
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
this._subscribed = true;
|
|
375
|
+
this._subscribed_error = null;
|
|
376
|
+
|
|
377
|
+
const debugMsg = {
|
|
378
|
+
event: 'finish',
|
|
379
|
+
topic: node.topic,
|
|
380
|
+
flowNodeInstanceId: externalTask.flowNodeInstanceId,
|
|
381
|
+
timestamp: new Date().toISOString(),
|
|
382
|
+
};
|
|
113
383
|
|
|
114
|
-
|
|
115
|
-
options = RED.util.evaluateNodeProperty(config.workerConfig, 'json', node);
|
|
116
|
-
}
|
|
384
|
+
node.traceExecution(debugMsg);
|
|
117
385
|
|
|
118
|
-
|
|
119
|
-
.
|
|
120
|
-
|
|
121
|
-
node.status({ fill: 'blue', shape: 'ring', text: 'subcribed' });
|
|
386
|
+
this.clearTracking(externalTask); // as msg
|
|
387
|
+
this.showStatus();
|
|
388
|
+
};
|
|
122
389
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
390
|
+
node.setErrorFinishHandlingTaskStatus = (externalTask, error) => {
|
|
391
|
+
if (externalTask.flowNodeInstanceId) {
|
|
392
|
+
delete this.started_external_tasks[externalTask.flowNodeInstanceId];
|
|
393
|
+
}
|
|
127
394
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
switch (errorType) {
|
|
131
|
-
case 'extendLock':
|
|
132
|
-
case 'finishExternalTask':
|
|
133
|
-
case 'processExternalTask':
|
|
134
|
-
node.error(
|
|
135
|
-
`Worker error ${errorType} for *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}' and *processInstanceId* '${externalTask.processInstanceId}': ${error.message}`
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
if (externalTask) {
|
|
139
|
-
delete started_external_tasks[externalTask.flowNodeInstanceId];
|
|
140
|
-
}
|
|
395
|
+
this._subscribed_error = error;
|
|
396
|
+
this.error(`finished task failed (topic: ${node.topic}).`);
|
|
141
397
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
398
|
+
const debugMsg = {
|
|
399
|
+
event: 'error',
|
|
400
|
+
topic: node.topic,
|
|
401
|
+
flowNodeInstanceId: externalTask.flowNodeInstanceId,
|
|
402
|
+
timestamp: new Date().toISOString(),
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
node.traceExecution(debugMsg);
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
this.showStatus();
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
node.sendStatus = (status, message) => {
|
|
412
|
+
RED.events.emit("processcube:healthcheck:update", {
|
|
413
|
+
nodeId: node.id,
|
|
414
|
+
status: status,
|
|
415
|
+
nodeName: `topic: ${node.topic}`,
|
|
416
|
+
nodeType: 'externaltask-input',
|
|
417
|
+
message: message
|
|
418
|
+
});
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
node.showStatus = () => {
|
|
422
|
+
const msgCounter = Object.keys(this.started_external_tasks).length;
|
|
423
|
+
|
|
424
|
+
if (this._subscribed === false) {
|
|
425
|
+
this.status({ fill: 'red', shape: 'ring', text: `subscription failed (${msgCounter})` });
|
|
426
|
+
|
|
427
|
+
this.sendStatus('NotOk', `subscription failed (${msgCounter})`);
|
|
428
|
+
} else {
|
|
429
|
+
if (msgCounter >= 1) {
|
|
430
|
+
if (node._step) {
|
|
431
|
+
this.status({ fill: 'blue', shape: 'dot', text: `tasks(${msgCounter}) ->'${node._step}'` });
|
|
432
|
+
this.sendStatus('Ok', `tasks(${msgCounter}) ->'${node._step}'.`);
|
|
433
|
+
} else {
|
|
434
|
+
this.status({ fill: 'blue', shape: 'dot', text: `tasks(${msgCounter})` });
|
|
435
|
+
this.sendStatus('Ok', `tasks(${msgCounter})`);
|
|
148
436
|
}
|
|
149
|
-
|
|
437
|
+
this.log(`handling tasks ${msgCounter}.`);
|
|
438
|
+
} else {
|
|
439
|
+
this.status({ fill: 'green', shape: 'ring', text: `subcribed` });
|
|
440
|
+
this.sendStatus('Ok', `subcribed`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
};
|
|
150
444
|
|
|
445
|
+
const register = async () => {
|
|
446
|
+
if (node.etw) {
|
|
151
447
|
try {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
448
|
+
node.etw.stop();
|
|
449
|
+
node.log(`old etw closed: ${JSON.stringify(node.etw)}`);
|
|
450
|
+
} catch (e) {
|
|
451
|
+
node.log(`cant close etw: ${JSON.stringify(node.etw)}`);
|
|
155
452
|
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const client = node.engine.engineClient;
|
|
456
|
+
|
|
457
|
+
if (!client) {
|
|
458
|
+
node.error('No engine configured.', {});
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
const etwCallback = async (payload, externalTask) => {
|
|
462
|
+
|
|
463
|
+
globalExternalTaskStates[externalTask.flowNodeInstanceId] = new ExternalTaskNodeStates(externalTask.flowNodeInstanceId);
|
|
464
|
+
|
|
465
|
+
const saveHandleCallback = (data, callback, msg) => {
|
|
466
|
+
try {
|
|
467
|
+
callback(data);
|
|
468
|
+
node.log(`send to engine *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}', topic '${node.topic}' and *processInstanceId* ${externalTask.processInstanceId}`);
|
|
469
|
+
|
|
470
|
+
// Remove ExternalTaskState from global dictionary
|
|
471
|
+
if (globalExternalTaskStates[externalTask.flowNodeInstanceId]) {
|
|
472
|
+
delete globalExternalTaskStates[externalTask.flowNodeInstanceId];
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
node.setFinishHandlingTaskStatus(externalTask);
|
|
476
|
+
} catch (error) {
|
|
477
|
+
// Remove ExternalTaskState from global dictionary on error as well
|
|
478
|
+
if (globalExternalTaskStates[externalTask.flowNodeInstanceId]) {
|
|
479
|
+
delete globalExternalTaskStates[externalTask.flowNodeInstanceId];
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
node.setErrorFinishHandlingTaskStatus(externalTask, error);
|
|
483
|
+
msg.error = error;
|
|
484
|
+
node.error(`failed send to engine *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}', topic '${node.topic}' and *processInstanceId* ${externalTask.processInstanceId}: ${error?.message}`, msg);
|
|
485
|
+
callback(error);
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
return await new Promise((resolve, reject) => {
|
|
490
|
+
const handleFinishTask = (msg) => {
|
|
491
|
+
let result = RED.util.encodeObject(msg.payload);
|
|
492
|
+
|
|
493
|
+
// remote msg and format from result
|
|
494
|
+
delete result.format;
|
|
495
|
+
delete result.msg;
|
|
496
|
+
|
|
497
|
+
node.log(
|
|
498
|
+
`handle event for *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}' and *processInstanceId* ${externalTask.processInstanceId} with result ${JSON.stringify(result)} on msg._msgid ${msg._msgid}.`
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
//resolve(result);
|
|
502
|
+
saveHandleCallback(result, resolve, msg);
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
const handleErrorTask = (error) => {
|
|
506
|
+
node.log(
|
|
507
|
+
`handle error event for *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}' and *processInstanceId* '${externalTask.processInstanceId}' on *msg._msgid* '${error.errorDetails?._msgid}'.`
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
// TODO: with reject, the default error handling is proceed
|
|
511
|
+
// SEE: https://github.com/5minds/ProcessCube.Engine.Client.ts/blob/develop/src/ExternalTaskWorker.ts#L180
|
|
512
|
+
// reject(result);
|
|
513
|
+
//resolve(msg);
|
|
514
|
+
saveHandleCallback(error, resolve, error);
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
node.eventEmitter.once(`handle-${externalTask.flowNodeInstanceId}`, (msg, isError = false) => {
|
|
518
|
+
try {
|
|
519
|
+
msg.etw_finished_at = new Date().toISOString();
|
|
520
|
+
|
|
521
|
+
if (msg.etw_started_at) {
|
|
522
|
+
msg.etw_duration = new Date(msg.etw_finished_at) - new Date(msg.etw_started_at);
|
|
523
|
+
}
|
|
524
|
+
} catch (error) {
|
|
525
|
+
node.error(`failed to calculate duration: ${error?.message}`, msg);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
node.log(
|
|
529
|
+
`handle event for *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}' and *processInstanceId* '${externalTask.processInstanceId}' with *msg._msgid* '${msg._msgid}' and *isError* '${isError}' *duration* '${msg.etw_duration}' mSek`
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
if (isError) {
|
|
534
|
+
handleErrorTask(msg);
|
|
535
|
+
} else {
|
|
536
|
+
handleFinishTask(msg);
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
node.setStartHandlingTaskStatus(externalTask);
|
|
541
|
+
|
|
542
|
+
let msg = {
|
|
543
|
+
_msgid: RED.util.generateId(),
|
|
544
|
+
task: RED.util.encodeObject(externalTask),
|
|
545
|
+
payload: payload,
|
|
546
|
+
flowNodeInstanceId: externalTask.flowNodeInstanceId,
|
|
547
|
+
processInstanceId: externalTask.processInstanceId,
|
|
548
|
+
etw_input_node_id: node.id,
|
|
549
|
+
etw_started_at: new Date().toISOString()
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
node.log(
|
|
553
|
+
`Received *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}' and *processInstanceId* '${externalTask.processInstanceId}' with *msg._msgid* '${msg._msgid}'`
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
node.send(msg);
|
|
557
|
+
});
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
client.externalTasks
|
|
561
|
+
.subscribeToExternalTaskTopic(topic, etwCallback, options)
|
|
562
|
+
.then(async (externalTaskWorker) => {
|
|
563
|
+
node.status({ fill: 'blue', shape: 'ring', text: 'subcribed' });
|
|
564
|
+
|
|
565
|
+
node.etw = externalTaskWorker;
|
|
566
|
+
|
|
567
|
+
externalTaskWorker.onHeartbeat((event, external_task_id) => {
|
|
568
|
+
node.setSubscribedStatus();
|
|
569
|
+
|
|
570
|
+
if (process.env.NODE_RED_ETW_HEARTBEAT_LOGGING == 'true' || process.env.NODERED_ETW_HEARTBEAT_LOGGING == 'true') {
|
|
571
|
+
if (external_task_id) {
|
|
572
|
+
this.log(`subscription (heartbeat:topic ${node.topic}, ${event} for ${external_task_id}).`);
|
|
573
|
+
} else {
|
|
574
|
+
this.log(`subscription (heartbeat:topic ${node.topic}, ${event}).`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
externalTaskWorker.onWorkerError((errorType, error, externalTask) => {
|
|
580
|
+
switch (errorType) {
|
|
581
|
+
case 'extendLock':
|
|
582
|
+
case 'finishExternalTask':
|
|
583
|
+
case 'processExternalTask':
|
|
584
|
+
node.error(
|
|
585
|
+
`Worker error ${errorType} for *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}' and *processInstanceId* '${externalTask.processInstanceId}': ${error?.message}`
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
node.setUnsubscribedStatus(error);
|
|
589
|
+
|
|
590
|
+
if (process.env.NODE_RED_ETW_STOP_IF_FAILED == 'true' || process.env.NODERED_ETW_STOP_IF_FAILED == 'true') {
|
|
591
|
+
// abort the external task MM: waiting for a fix in the client.ts
|
|
592
|
+
externalTaskWorker.abortExternalTaskIfPresent(externalTask.id);
|
|
593
|
+
// mark the external task as finished, cause it is gone
|
|
594
|
+
node.setFinishHandlingTaskStatus(externalTask);
|
|
595
|
+
node.log(`Cancel external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}' and *processInstanceId* '${externalTask.processInstanceId}'.`)
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
break;
|
|
599
|
+
case 'fetchAndLock':
|
|
600
|
+
node.setUnsubscribedStatus(error);
|
|
601
|
+
break;
|
|
602
|
+
default:
|
|
603
|
+
// reduce noise error logs
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
});
|
|
156
607
|
|
|
157
|
-
node.on('close', () => {
|
|
158
608
|
try {
|
|
159
|
-
externalTaskWorker.
|
|
160
|
-
} catch {
|
|
161
|
-
node.error('
|
|
609
|
+
externalTaskWorker.start();
|
|
610
|
+
} catch (error) {
|
|
611
|
+
node.error(`Worker start 'externalTaskWorker.start' failed: ${error.message}`, {});
|
|
162
612
|
}
|
|
613
|
+
|
|
614
|
+
node.on('close', () => {
|
|
615
|
+
try {
|
|
616
|
+
externalTaskWorker.stop();
|
|
617
|
+
RED.hooks.remove(`preDeliver.etw-input-${node.id}`);
|
|
618
|
+
RED.hooks.remove(`postDeliver.etw-input-${node.id}`);
|
|
619
|
+
node.log('External Task Worker closed.');
|
|
620
|
+
} catch (error) {
|
|
621
|
+
node.error(`Client close failed: ${JSON.stringify(error)}`);
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
})
|
|
625
|
+
.catch((error) => {
|
|
626
|
+
node.error(`Error in subscribeToExternalTaskTopic: ${error.message}`, {});
|
|
163
627
|
});
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
if (node.engine) {
|
|
631
|
+
register().catch((error) => {
|
|
632
|
+
node.error(error, {});
|
|
167
633
|
});
|
|
634
|
+
}
|
|
168
635
|
}
|
|
169
636
|
|
|
170
637
|
RED.nodes.registerType('externaltask-input', ExternalTaskInput);
|