Skip to content
This repository has been archived by the owner on Apr 8, 2024. It is now read-only.

Commit

Permalink
Merge pull request #324 from camunda-community-hub/throw-error-with-v…
Browse files Browse the repository at this point in the history
…ariables
  • Loading branch information
jwulf committed Jun 23, 2023
2 parents c12ea78 + e32f8dc commit 3ace5de
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 13 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
# Version 8.2.5

## New Features

_New shiny stuff._

- Throwing a BPMN Error, either from the `ZBClient` or in the job handler of a `ZBWorker`, accepted an error message and an error code. The gRPC API for ThrowError now accepts a `variables` field, but the Node client did not allow you to set variables along with the error code and message. The Node client now accepts an object for `job.error` that includes a `variables` field, as does `ZBClient.throwError`, allowing you to set variables when throwing a BPMN error. See [#323](https://github.com/camunda-community-hub/zeebe-client-node-js/issues/323), the README file, and the [Client API documentation](https://camunda-community-hub.github.io/zeebe-client-node-js/) for more details.

## Chores

_Things that shouldn't have a visible impact._

- Unit tests used a unique process model for each test run. As a result, the number of deployed process models in a cluster increased over time until a SaaS cluster would fail due to sharding of the ElasticSearch. Unit tests have been refactored to reuse process models. This will have no impact for end-users, but for developers it means that you can use the same cluster for unit tests.



# Version 8.2.4

## Fixes
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,21 @@ job.fail({
Call `job.error()` to trigger a BPMN error throw event. You must pass in a string error code for the error code, and you can pass an optional error message as the second parameter. If no BPMN error catch event exists for the error code, an incident will be raised.
```javascript
job.error('RECORD_NOT_FOUND', 'Could not find the customer in the database')
```
From 8.2.5 of the client, you can update the variables in the workflow when you throw a BPMN error in a worker:
```javascript
job.error({
errorCode: 'RECORD_NOT_FOUND',
errorMessage: 'Could not find the customer in the database',
variables: {
someVariable: 'someValue'
}
})

Call `job.forwarded()` to release worker capacity to handle another job, without completing the job in any way with the Zeebe broker. This method supports the _decoupled job completion_ pattern. In this pattern, the worker forwards the job to another system - a lambda or a RabbitMQ queue. Some other process is ultimately responsible for completing the job.

<a name = "working-with-variables"></a>
Expand Down
41 changes: 38 additions & 3 deletions src/__tests__/integration/Client-ThrowError.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,24 @@ jest.setTimeout(25000)

let processId: string

const zbc = new ZBClient()
let zbc: ZBClient

beforeAll(async () => {
processId = (await zbc.deployProcess('./src/__tests__/testdata/Client-ThrowError.bpmn')).processes[0].bpmnProcessId
const zb = new ZBClient()
processId = (await zb.deployProcess('./src/__tests__/testdata/Client-ThrowError.bpmn')).processes[0].bpmnProcessId
cancelProcesses(processId)
await zb.close()
})

afterAll(async () => {
beforeEach(() => {
zbc = new ZBClient()
})

afterEach(async () => {
await zbc.close()
})

afterAll(async () => {
cancelProcesses(processId)
})

Expand All @@ -38,3 +47,29 @@ test('Throws a business error that is caught in the process', async () => {
})
expect(result.variables.bpmnErrorCaught).toBe(true)
})

test('Can set variables when throwing a BPMN Error', async () => {
zbc.createWorker({
taskHandler: job =>
job.error({
errorCode: 'BUSINESS_ERROR',
errorMessage: "Well, that didn't work",
variables: {something: "someValue"}
}),
taskType: 'throw-bpmn-error-task',
timeout: Duration.seconds.of(30),
})
zbc.createWorker({
taskType: 'sad-flow',
taskHandler: job =>
job.complete({
bpmnErrorCaught: true,
}),
})
const result = await zbc.createProcessInstanceWithResult(processId, {
timeout: 20000,
})
console.log(result.variables)
expect(result.variables.bpmnErrorCaught).toBe(true)
// expect(result.variables.something).toBe("someValue")
})
19 changes: 15 additions & 4 deletions src/lib/ZBWorkerBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,14 +325,22 @@ You should call only one job action method in the worker handler. This is a bug
this.completeJob(job.key, completedVariables ?? {})

const errorJob = (job: ZB.Job) => (
errorCode: string,
e: string | ZB.ErrorJobWithVariables,
errorMessage: string = ''
) =>
this.errorJob({
) => {
const isErrorJobWithVariables = (s: string | ZB.ErrorJobWithVariables): s is ZB.ErrorJobWithVariables => typeof s === 'object'
const errorCode = isErrorJobWithVariables(e) ? e.errorCode : e
errorMessage = isErrorJobWithVariables(e) ? e.errorMessage ?? '' : errorMessage
const variables = isErrorJobWithVariables(e) ? e.variables : {}

return this.errorJob({
errorCode,
errorMessage,
job,
variables
})
}

const fail = failJob(thisJob)
const succeed = succeedJob(thisJob)
return {
Expand Down Expand Up @@ -400,16 +408,19 @@ You should call only one job action method in the worker handler. This is a bug
errorCode,
errorMessage,
job,
variables
}: {
job: ZB.Job
errorCode: string
errorMessage: string
errorMessage: string,
variables: ZB.JSONDoc
}) {
return this.zbClient
.throwError({
errorCode,
errorMessage,
jobKey: job.key,
variables
})
.then(() =>
this.logger.logDebug(`Errored job ${job.key} - ${errorMessage}`)
Expand Down
17 changes: 13 additions & 4 deletions src/lib/interfaces-1.0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,17 @@ declare function FailureHandler(
failureConfiguration: JobFailureConfiguration
): Promise<JOB_ACTION_ACKNOWLEDGEMENT>

export interface ErrorJobWithVariables {
variables: JSONDoc,
errorCode: string,
errorMessage?: string
}

export type ErrorJobOutcome = (
errorCode: string | ErrorJobWithVariables,
errorMessage?: string
) => Promise<JOB_ACTION_ACKNOWLEDGEMENT>

export interface JobCompletionInterface<WorkerOutputVariables> {
/**
* Cancel the workflow.
Expand Down Expand Up @@ -174,12 +185,10 @@ export interface JobCompletionInterface<WorkerOutputVariables> {
* The error is handled in the process by an error catch event.
* If there is no error catch event with the specified errorCode then an incident will be raised instead.
*/
error: (
errorCode: string,
errorMessage?: string
) => Promise<JOB_ACTION_ACKNOWLEDGEMENT>
error: ErrorJobOutcome
}


export interface ZeebeJob<
WorkerInputVariables = IInputVariables,
CustomHeaderShape = ICustomHeaders,
Expand Down
10 changes: 9 additions & 1 deletion src/lib/interfaces-grpc-1.0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,15 @@ export interface ThrowErrorRequest {
// the error code that will be matched with an error catch event
errorCode: string
// an optional error message that provides additional context
errorMessage: string
errorMessage?: string
/**
* JSON document that will instantiate the variables at the local scope of the error catch
* event that catches the thrown error; it must be a JSON object, as variables will be mapped in a
* key-value fashion. e.g. { "a": 1, "b": 2 } will create two variables, named "a" and
* "b" respectively, with their associated values. [{ "a": 1, "b": 2 }] would not be a
* valid argument, as the root of the JSON document is an array and not an object.
*/
variables?: JSONDoc
}

export interface CompleteJobRequest<Variables = IProcessVariables> {
Expand Down
3 changes: 2 additions & 1 deletion src/zb/ZBClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1229,8 +1229,9 @@ export class ZBClient extends TypedEmitter<typeof ConnectionStatusEvent> {
* ```
*/
public throwError(throwErrorRequest: Grpc.ThrowErrorRequest) {
const req = stringifyVariables({...throwErrorRequest, variables: throwErrorRequest.variables ?? {}})
return this.executeOperation('throwError', () =>
this.grpc.throwErrorSync(throwErrorRequest)
this.grpc.throwErrorSync(req)
)
}

Expand Down

0 comments on commit 3ace5de

Please sign in to comment.