@5minds/node-red-contrib-processcube 0.15.0-fix-error-in-process-instance-query-f9f0d2-lz03zzg4 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
package/Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM node:20 as builder
1
+ FROM node:20 AS builder
2
2
 
3
3
  COPY ./ /src/node-red-contrib-processcube
4
4
 
@@ -5,12 +5,17 @@ services:
5
5
  context: .
6
6
  ports:
7
7
  - "1880:1880"
8
+ - "9229:9229"
8
9
  environment:
9
10
  - TZ=Europe/Berlin
10
11
  - ENGINE_URL=http://engine:8000
11
12
  - FLOWS=/nodered/node-red-contrib-processcube-flows.json
12
13
  volumes:
13
14
  - ./nodered/:/nodered/
15
+ - ./examples/:/examples/
16
+ depends_on:
17
+ - engine
18
+ - authority
14
19
 
15
20
  engine:
16
21
  image: 5minds/processcube_engine:2024-1
@@ -19,9 +24,16 @@ services:
19
24
  volumes:
20
25
  - ./processes:/processes:ro
21
26
  environment:
27
+ application__name: Engine for Node-RED contrib
22
28
  iam__baseUrl: http://authority:11560
23
29
  iam__allowAnonymousRootAccess: true
24
30
  command: --seed-dir=/processes --port 8000
31
+ depends_on:
32
+ - authority
33
+ healthcheck:
34
+ test: bash -c "[ -f /tmp/healthy ]"
35
+ timeout: 1s
36
+ retries: 20
25
37
 
26
38
  authority:
27
39
  image: 5minds/processcube_authority:3.0.4
@@ -0,0 +1,252 @@
1
+ [
2
+ {
3
+ "id": "a23d2e782beb66f4",
4
+ "type": "tab",
5
+ "label": "External Task Sample",
6
+ "disabled": false,
7
+ "info": "",
8
+ "env": []
9
+ },
10
+ {
11
+ "id": "70d7a70e375b162a",
12
+ "type": "group",
13
+ "z": "a23d2e782beb66f4",
14
+ "style": {
15
+ "stroke": "#999999",
16
+ "stroke-opacity": "1",
17
+ "fill": "none",
18
+ "fill-opacity": "1",
19
+ "label": true,
20
+ "label-position": "nw",
21
+ "color": "#a4a4a4"
22
+ },
23
+ "nodes": [
24
+ "2991a5e6df2b87d2",
25
+ "911fde53bcbb7e3f",
26
+ "d56bb7cd73fd220c",
27
+ "15a5f64b9e2e05fc"
28
+ ],
29
+ "x": 34,
30
+ "y": 79,
31
+ "w": 512,
32
+ "h": 142
33
+ },
34
+ {
35
+ "id": "31cb6729aac0ba46",
36
+ "type": "group",
37
+ "z": "a23d2e782beb66f4",
38
+ "style": {
39
+ "stroke": "#999999",
40
+ "stroke-opacity": "1",
41
+ "fill": "none",
42
+ "fill-opacity": "1",
43
+ "label": true,
44
+ "label-position": "nw",
45
+ "color": "#a4a4a4"
46
+ },
47
+ "nodes": [
48
+ "18e05d562d48f32d",
49
+ "49aee03a7885fae8",
50
+ "e4555d9019cd3f47",
51
+ "bd68c4bdc53b8f80",
52
+ "fb87f79852b22e4a",
53
+ "1ba1e62c9935255f",
54
+ "b49f226ba865e164"
55
+ ],
56
+ "x": 34,
57
+ "y": 259,
58
+ "w": 812,
59
+ "h": 202
60
+ },
61
+ {
62
+ "id": "2991a5e6df2b87d2",
63
+ "type": "externaltask-input",
64
+ "z": "a23d2e782beb66f4",
65
+ "g": "70d7a70e375b162a",
66
+ "name": "Test",
67
+ "engine": "42e6796dddd9d4db",
68
+ "topic": "Test",
69
+ "x": 110,
70
+ "y": 180,
71
+ "wires": [
72
+ [
73
+ "d56bb7cd73fd220c"
74
+ ]
75
+ ]
76
+ },
77
+ {
78
+ "id": "911fde53bcbb7e3f",
79
+ "type": "externaltask-output",
80
+ "z": "a23d2e782beb66f4",
81
+ "g": "70d7a70e375b162a",
82
+ "name": "Test Ende",
83
+ "x": 460,
84
+ "y": 180,
85
+ "wires": []
86
+ },
87
+ {
88
+ "id": "d56bb7cd73fd220c",
89
+ "type": "delay",
90
+ "z": "a23d2e782beb66f4",
91
+ "g": "70d7a70e375b162a",
92
+ "name": "",
93
+ "pauseType": "delay",
94
+ "timeout": "1",
95
+ "timeoutUnits": "seconds",
96
+ "rate": "1",
97
+ "nbRateUnits": "1",
98
+ "rateUnits": "second",
99
+ "randomFirst": "1",
100
+ "randomLast": "5",
101
+ "randomUnits": "seconds",
102
+ "drop": false,
103
+ "allowrate": false,
104
+ "outputs": 1,
105
+ "x": 280,
106
+ "y": 180,
107
+ "wires": [
108
+ [
109
+ "911fde53bcbb7e3f"
110
+ ]
111
+ ]
112
+ },
113
+ {
114
+ "id": "18e05d562d48f32d",
115
+ "type": "externaltask-input",
116
+ "z": "a23d2e782beb66f4",
117
+ "g": "31cb6729aac0ba46",
118
+ "name": "SampleError",
119
+ "engine": "42e6796dddd9d4db",
120
+ "topic": "SampleError",
121
+ "x": 130,
122
+ "y": 360,
123
+ "wires": [
124
+ [
125
+ "49aee03a7885fae8"
126
+ ]
127
+ ]
128
+ },
129
+ {
130
+ "id": "49aee03a7885fae8",
131
+ "type": "delay",
132
+ "z": "a23d2e782beb66f4",
133
+ "g": "31cb6729aac0ba46",
134
+ "name": "",
135
+ "pauseType": "delay",
136
+ "timeout": "5",
137
+ "timeoutUnits": "seconds",
138
+ "rate": "1",
139
+ "nbRateUnits": "1",
140
+ "rateUnits": "second",
141
+ "randomFirst": "1",
142
+ "randomLast": "5",
143
+ "randomUnits": "seconds",
144
+ "drop": false,
145
+ "allowrate": false,
146
+ "outputs": 1,
147
+ "x": 320,
148
+ "y": 360,
149
+ "wires": [
150
+ [
151
+ "bd68c4bdc53b8f80"
152
+ ]
153
+ ]
154
+ },
155
+ {
156
+ "id": "e4555d9019cd3f47",
157
+ "type": "catch",
158
+ "z": "a23d2e782beb66f4",
159
+ "g": "31cb6729aac0ba46",
160
+ "name": "",
161
+ "scope": "group",
162
+ "uncaught": false,
163
+ "x": 510,
164
+ "y": 420,
165
+ "wires": [
166
+ [
167
+ "fb87f79852b22e4a"
168
+ ]
169
+ ]
170
+ },
171
+ {
172
+ "id": "bd68c4bdc53b8f80",
173
+ "type": "function",
174
+ "z": "a23d2e782beb66f4",
175
+ "g": "31cb6729aac0ba46",
176
+ "name": "raise Error",
177
+ "func": "throw Error(\"hello error\");\n\nreturn msg;",
178
+ "outputs": 1,
179
+ "timeout": 0,
180
+ "noerr": 0,
181
+ "initialize": "",
182
+ "finalize": "",
183
+ "libs": [],
184
+ "x": 510,
185
+ "y": 360,
186
+ "wires": [
187
+ [
188
+ "1ba1e62c9935255f"
189
+ ]
190
+ ]
191
+ },
192
+ {
193
+ "id": "fb87f79852b22e4a",
194
+ "type": "externaltask-error",
195
+ "z": "a23d2e782beb66f4",
196
+ "g": "31cb6729aac0ba46",
197
+ "name": "Send Error to Engine",
198
+ "error": "MyErrorCode",
199
+ "x": 720,
200
+ "y": 420,
201
+ "wires": [
202
+ []
203
+ ]
204
+ },
205
+ {
206
+ "id": "1ba1e62c9935255f",
207
+ "type": "externaltask-output",
208
+ "z": "a23d2e782beb66f4",
209
+ "g": "31cb6729aac0ba46",
210
+ "name": "",
211
+ "x": 710,
212
+ "y": 360,
213
+ "wires": []
214
+ },
215
+ {
216
+ "id": "15a5f64b9e2e05fc",
217
+ "type": "comment",
218
+ "z": "a23d2e782beb66f4",
219
+ "g": "70d7a70e375b162a",
220
+ "name": "Simple External Task handling",
221
+ "info": "",
222
+ "x": 180,
223
+ "y": 120,
224
+ "wires": []
225
+ },
226
+ {
227
+ "id": "b49f226ba865e164",
228
+ "type": "comment",
229
+ "z": "a23d2e782beb66f4",
230
+ "g": "31cb6729aac0ba46",
231
+ "name": "Error handling with external tasks",
232
+ "info": "",
233
+ "x": 190,
234
+ "y": 300,
235
+ "wires": []
236
+ },
237
+ {
238
+ "id": "799125e21de02f13",
239
+ "type": "comment",
240
+ "z": "a23d2e782beb66f4",
241
+ "name": "For Testing, deploy \"External-Task-Sample.bpmn\" and start the process.",
242
+ "info": "",
243
+ "x": 300,
244
+ "y": 40,
245
+ "wires": []
246
+ },
247
+ {
248
+ "id": "42e6796dddd9d4db",
249
+ "type": "processcube-engine-config",
250
+ "url": "http://engine:8000"
251
+ }
252
+ ]
@@ -26,6 +26,20 @@
26
26
  </div>
27
27
  </script>
28
28
 
29
- <script type="text/html" data-help-name="externaltask-error">
30
- <p>A node which error response to an External Task of https://processcube.io</p>
29
+ <script type="text/markdown" data-help-name="externaltask-error">
30
+ Used for reporting errors in the processing of external tasks. The error
31
+ code `Error` in the configuration is forwarded to the ProcessCube engine for
32
+ handling within _Error-Boundary-Events_.
33
+
34
+ ## Inputs
35
+
36
+ : msg (Object) : Passed as `ErrorDetails` to the engine
37
+ : Error (string) : From the configuration
38
+ : Message (string) : The caught exception message
39
+ : StackTrace (string) : The stack trace of the exception
40
+
41
+ ### References
42
+
43
+ - [The ProcessCube Developer Network](https://processcube.io) - All documentation for the ProcessCube&copy; platform
44
+ - [Node-RED Integration in ProcessCube&copy;](https://processcube.io/docs/node-red) - Node-RED integration in ProcessCube&copy;
31
45
  </script>
@@ -7,24 +7,22 @@ module.exports = function (RED) {
7
7
  var eventEmitter = flowContext.get('emitter');
8
8
 
9
9
  node.on('input', function (msg) {
10
- const externalTaskId = msg.externalTaskId;
10
+ const flowNodeInstanceId = msg.flowNodeInstanceId;
11
11
 
12
- let error = msg.error;
12
+ let msgError = msg.error;
13
13
 
14
- if (error === undefined) {
15
- error.message = 'An error occurred';
16
- error.source = msg.payload;
14
+ if (msgError === undefined) {
15
+ msgError.message = 'An error occurred';
17
16
  }
18
17
 
19
- msg.payload = {
20
- error: {
21
- errorCode: config.error,
22
- errorMessage: error.message,
23
- errorDetails: error.source,
24
- },
25
- };
18
+ const error = new Error(msgError.message);
19
+ error.errorCode = config.error;
20
+ error.errorDetails = RED.util.encodeObject(msg);
26
21
 
27
- eventEmitter.emit(`error-${externalTaskId}`, msg.payload);
22
+ msg.errorCode = config.error;
23
+ msg.errorMessage = msgError.message;
24
+
25
+ eventEmitter.emit(`handle-${flowNodeInstanceId}`, error, true);
28
26
 
29
27
  node.send(msg);
30
28
  });
@@ -31,6 +31,23 @@
31
31
  </div>
32
32
  </script>
33
33
 
34
- <script type="text/html" data-help-name="externaltask-input">
35
- <p>A node which subscribes to an External Task Topic of https://processcube.io</p>
34
+ <script type="text/markdown" data-help-name="externaltask-input">
35
+ Waiting for external tasks that correspond to the `Topic` configured in
36
+ the connected ProcessCube Engine for processing.
37
+
38
+ ## Outputs
39
+
40
+ : payload (string) : Defines the input of the external task token
41
+ : task (object) : The external task object
42
+ : flowNodeInstanceId : The Id of the external task, which is needed to complete the task
43
+
44
+ ### Details
45
+
46
+ - To finish the external task the `externaltask-output` node is required.
47
+ - For handling a error while executing a flow as external task the `externaltask-error` node is required.
48
+
49
+ ### References
50
+
51
+ - [The ProcessCube Developer Network](https://processcube.io) - All documentation for the ProcessCube&copy; Plattform
52
+ - [Node-RED Integration in ProcessCube&copy;](https://processcube.io/docs/node-red) - Node-RED Integration in ProcessCube&copy;
36
53
  </script>
@@ -1,44 +1,24 @@
1
- const process = require('process');
2
1
  const EventEmitter = require('node:events');
3
2
 
4
- const engine_client = require('@5minds/processcube_engine_client');
5
-
6
3
  function showStatus(node, msgCounter) {
7
4
  if (msgCounter >= 1) {
8
- node.status({ fill: 'blue', shape: 'dot', text: `handling tasks ${msgCounter}` });
5
+ node.status({ fill: 'blue', shape: 'dot', text: `handling tasks ${msgCounter}.` });
9
6
  } else {
10
- node.status({ fill: 'blue', shape: 'ring', text: `subcribed ${msgCounter}` });
7
+ node.status({ fill: 'blue', shape: 'ring', text: `subcribed.` });
11
8
  }
12
9
  }
13
10
 
14
- function decrCounter(msgCounter) {
15
- msgCounter--;
16
-
17
- if (msgCounter < 0) {
18
- msgCounter = 0;
19
- }
20
-
21
- return msgCounter;
22
- }
11
+ const started_external_tasks = {};
23
12
 
24
13
  module.exports = function (RED) {
25
14
  function ExternalTaskInput(config) {
26
15
  RED.nodes.createNode(this, config);
27
16
  var node = this;
28
- var msgCounter = 0;
29
17
  var flowContext = node.context().flow;
30
- var nodeContext = node.context();
31
18
 
32
19
  this.engine = this.server = RED.nodes.getNode(config.engine);
33
20
 
34
- const engineUrl = this.engine?.url || process.env.ENGINE_URL || 'http://engine:8000';
35
-
36
- var client = nodeContext.get('client');
37
-
38
- if (!client) {
39
- nodeContext.set('client', new engine_client.EngineClient(engineUrl));
40
- client = nodeContext.get('client');
41
- }
21
+ const client = this.engine.getEngineClient();
42
22
 
43
23
  var eventEmitter = flowContext.get('emitter');
44
24
 
@@ -49,33 +29,78 @@ module.exports = function (RED) {
49
29
 
50
30
  client.externalTasks
51
31
  .subscribeToExternalTaskTopic(config.topic, async (payload, externalTask) => {
52
- msgCounter++;
32
+ const saveHandleCallback = (data, callback) => {
33
+ try {
34
+ callback(data);
35
+ } catch (error) {
36
+ node.error(`Error in callback 'saveHandleCallback': ${error.message}`);
37
+ }
38
+ };
53
39
 
54
40
  return await new Promise((resolve, reject) => {
55
- // TODO: once ist 2x gebunden
56
- eventEmitter.once(`finish-${externalTask.flowNodeInstanceId}`, (result) => {
57
- msgCounter = decrCounter(msgCounter);
58
-
59
- showStatus(node, msgCounter);
60
- resolve(result);
41
+ const handleFinishTask = (msg) => {
42
+ let result = RED.util.encodeObject(msg.payload);
43
+
44
+ node.log(
45
+ `handle event for *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}' and *processInstanceId* ${externalTask.processInstanceId} with result ${result} on msg._msgid ${msg._msgid}.`,
46
+ );
47
+
48
+ if (externalTask.flowNodeInstanceId) {
49
+ delete started_external_tasks[externalTask.flowNodeInstanceId];
50
+ }
51
+
52
+ showStatus(node, Object.keys(started_external_tasks).length);
53
+
54
+ //resolve(result);
55
+ saveHandleCallback(result, resolve);
56
+ };
57
+
58
+ const handleErrorTask = (msg) => {
59
+ node.log(
60
+ `handle error event for *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}' and *processInstanceId* '${externalTask.processInstanceId}' on *msg._msgid* '${msg._msgid}'.`,
61
+ );
62
+
63
+ if (externalTask.flowNodeInstanceId) {
64
+ delete started_external_tasks[externalTask.flowNodeInstanceId];
65
+ }
66
+
67
+ showStatus(node, Object.keys(started_external_tasks).length);
68
+
69
+ // TODO: with reject, the default error handling is proceed
70
+ // SEE: https://github.com/5minds/ProcessCube.Engine.Client.ts/blob/develop/src/ExternalTaskWorker.ts#L180
71
+ // reject(result);
72
+ //resolve(msg);
73
+ saveHandleCallback(msg, resolve);
74
+ };
75
+
76
+ eventEmitter.once(`handle-${externalTask.flowNodeInstanceId}`, (msg, isError = false) => {
77
+ node.log(
78
+ `handle event for *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}' and *processInstanceId* '${externalTask.processInstanceId}' with *msg._msgid* '${msg._msgid}' and *isError* '${isError}'`,
79
+ );
80
+
81
+ if (isError) {
82
+ handleErrorTask(msg);
83
+ } else {
84
+ handleFinishTask(msg);
85
+ }
61
86
  });
62
87
 
63
- eventEmitter.once(`error-${externalTask.flowNodeInstanceId}`, (msg) => {
64
- msgCounter = decrCounter(msgCounter);
65
- showStatus(node, msgCounter);
88
+ started_external_tasks[externalTask.flowNodeInstanceId] = externalTask;
66
89
 
67
- var result = msg.payload ? msg.payload : msg;
90
+ showStatus(node, Object.keys(started_external_tasks).length);
68
91
 
69
- reject(result);
70
- });
92
+ let msg = {
93
+ _msgid: RED.util.generateId(),
94
+ task: RED.util.encodeObject(externalTask),
95
+ payload: payload,
96
+ flowNodeInstanceId: externalTask.flowNodeInstanceId,
97
+ };
71
98
 
72
- showStatus(node, msgCounter);
99
+ node.log(
100
+ `Received *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}' and *processInstanceId* '${externalTask.processInstanceId}' with *msg._msgid* '${msg._msgid}'`,
101
+ );
73
102
 
74
- node.send({
75
- topic: externalTask.topic,
76
- payload: payload,
77
- externalTaskId: externalTask.flowNodeInstanceId,
78
- });
103
+ node.send(msg);
79
104
  });
80
105
  })
81
106
  .then(async (externalTaskWorker) => {
@@ -85,16 +110,47 @@ module.exports = function (RED) {
85
110
  node.server.registerOnIdentityChanged((identity) => {
86
111
  externalTaskWorker.identity = identity;
87
112
  });
88
- await externalTaskWorker.start();
89
113
 
90
- node.on('close', async () => {
114
+ // export type WorkerErrorHandler = (errorType: 'fetchAndLock' | 'extendLock' | 'processExternalTask' | 'finishExternalTask', error: Error, externalTask?: ExternalTask<any>) => void;
115
+ externalTaskWorker.onWorkerError((errorType, error, externalTask) => {
116
+ switch (errorType) {
117
+ case 'extendLock':
118
+ case 'finishExternalTask':
119
+ case 'processExternalTask':
120
+ node.error(
121
+ `Worker error ${errorType} for *external task flowNodeInstanceId* '${externalTask.flowNodeInstanceId}' and *processInstanceId* '${externalTask.processInstanceId}': ${error.message}`,
122
+ );
123
+
124
+ if (externalTask) {
125
+ delete started_external_tasks[externalTask.flowNodeInstanceId];
126
+ }
127
+
128
+ showStatus(node, Object.keys(started_external_tasks).length);
129
+ break;
130
+ default:
131
+ node.error(`Worker error ${errorType}: ${error.message}`);
132
+ break;
133
+ }
134
+ });
135
+
136
+ try {
137
+ externalTaskWorker.start();
138
+ } catch (error) {
139
+ node.error(`Worker start 'externalTaskWorker.start' failed: ${error.message}`);
140
+ }
141
+
142
+ node.on('close', () => {
91
143
  try {
92
144
  externalTaskWorker.stop();
93
145
  } catch {
94
- console.warn('Client close failed');
146
+ node.error('Client close failed');
95
147
  }
96
148
  });
149
+ })
150
+ .catch((error) => {
151
+ node.error(`Error in subscribeToExternalTaskTopic: ${error.message}`);
97
152
  });
98
153
  }
154
+
99
155
  RED.nodes.registerType('externaltask-input', ExternalTaskInput);
100
156
  };
@@ -21,6 +21,16 @@
21
21
  </div>
22
22
  </script>
23
23
 
24
- <script type="text/html" data-help-name="externaltask-output">
25
- <p>A node which response to an External Task of https://processcube.io</p>
24
+ <script type="text/markdown" data-help-name="externaltask-output">
25
+ Used to complete the external task. The `msg.payload` is returned to the ProcessCube
26
+ engine as the result of the external task.
27
+
28
+ ## Inputs
29
+
30
+ : payload (Object) : Returned to the ProcessCube engine as the result of the external task
31
+
32
+ ### References
33
+
34
+ - [The ProcessCube Developer Network](https://processcube.io) - All documentation for the ProcessCube&copy; platform
35
+ - [Node-RED Integration in ProcessCube&copy;](https://processcube.io/docs/node-red) - Node-RED integration in ProcessCube&copy;
26
36
  </script>
@@ -7,13 +7,13 @@ module.exports = function (RED) {
7
7
  var eventEmitter = flowContext.get('emitter');
8
8
 
9
9
  node.on('input', function (msg) {
10
- const externalTaskId = msg.externalTaskId;
10
+ const flowNodeInstanceId = msg.flowNodeInstanceId;
11
11
 
12
- if (!externalTaskId) {
12
+ if (!flowNodeInstanceId) {
13
13
  node.error('Error: The message did not contain the required external task id.', msg);
14
14
  }
15
15
 
16
- eventEmitter.emit(`finish-${externalTaskId}`, msg.payload);
16
+ eventEmitter.emit(`handle-${flowNodeInstanceId}`, msg, false);
17
17
  });
18
18
  }
19
19
  RED.nodes.registerType('externaltask-output', ExternalTaskOutput);
@@ -6,18 +6,10 @@ module.exports = function (RED) {
6
6
  function MessageEventTrigger(config) {
7
7
  RED.nodes.createNode(this, config);
8
8
  var node = this;
9
- var nodeContext = node.context();
10
9
 
11
10
  this.engine = this.server = RED.nodes.getNode(config.engine);
12
11
 
13
- const engineUrl = this.engine?.url || process.env.ENGINE_URL || 'http://engine:8000';
14
-
15
- var client = nodeContext.get('client');
16
-
17
- if (!client) {
18
- nodeContext.set('client', new engine_client.EngineClient(engineUrl));
19
- client = nodeContext.get('client');
20
- }
12
+ const client = this.engine.getEngineClient();
21
13
 
22
14
  node.on('input', function (msg) {
23
15
  client.events
@@ -30,7 +22,11 @@ module.exports = function (RED) {
30
22
  msg.payload = result;
31
23
 
32
24
  node.send(msg);
33
- node.status({ fill: 'blue', shape: 'dot', text: `message event triggered` });
25
+ node.status({
26
+ fill: 'blue',
27
+ shape: 'dot',
28
+ text: `message event triggered`,
29
+ });
34
30
  })
35
31
  .catch((error) => {
36
32
  node.error(error);