diff --git a/README.md b/README.md index de15bc49..6c3df64e 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ CAMUNDA_TOKEN_SCOPE Here is an example of doing this via the constructor, rather than via the environment: -````typescript +```typescript import { Camunda8 } from '@camunda8/sdk' const c8 = new Camunda8({ @@ -168,4 +168,20 @@ export CAMUNDA_CONSOLE_CLIENT_ID='e-JdgKfJy9hHSXzi' export CAMUNDA_CONSOLE_CLIENT_SECRET='DT8Pe-ANC6e3Je_ptLyzZvBNS0aFwaIV' export CAMUNDA_CONSOLE_BASE_URL='https://api.cloud.camunda.io' export CAMUNDA_CONSOLE_OAUTH_AUDIENCE='api.cloud.camunda.io' -```` +``` + +## Debugging + +The SDK uses the [`debug`](https://github.com/debug-js/debug) library. To enable debugging output, set a value for the `DEBUG` environment variable. The value is a comma-separated list of debugging namespaces. The SDK has the following namespaces: + +| Value | Component | +| ---------------------- | -------------------- | +| `camunda:adminconsole` | Admin Console API | +| `camunda:modeler` | Modeler API | +| `camunda:operate` | Operate API | +| `camunda:optimize` | Optimize API | +| `camunda:tasklist` | Tasklist API | +| `camunda:oauth` | OAuth Token Exchange | +| `camunda:grpc` | Zeebe gRPC channel | +| `camunda:worker` | Zeebe Worker | +| `camunda:zeebeclient` | Zeebe Client | diff --git a/package.json b/package.json index 44fdea55..e4e06729 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,12 @@ "compile": "tsc --project tsconfig.json", "docs": "rm -rf ./docs && typedoc", "test": "jest --detectOpenHandles --testPathIgnorePatterns integration local-integration disconnection multitenancy", - "test:integration": "jest --runInBand --testPathIgnorePatterns disconnection --testPathIgnorePatterns multitenancy --detectOpenHandles --verbose true -u", - "test:multitenancy": "jest --runInBand --testPathIgnorePatterns disconnection --testPathIgnorePatterns admin - --detectOpenHandles --verbose true -u", + "test:integration": "jest --runInBand --testPathIgnorePatterns disconnection --testPathIgnorePatterns __tests__/config --testPathIgnorePatterns multitenancy --detectOpenHandles --verbose true -u", + "test:multitenancy": "jest --runInBand --testPathIgnorePatterns disconnection --testPathIgnorePatterns admin --testPathIgnorePatterns __tests__/config - --detectOpenHandles --verbose true -u", "test:local": "jest --runInBand --verbose true --detectOpenHandles local-integration -u", - "test:local-integration": "jest --runInBand --detectOpenHandles --verbose --testPathIgnorePatterns disconnection --testPathIgnorePatterns admin --testPathIgnorePatterns multitenancy -u", - "test:docker": "jest --runInBand --testPathIgnorePatterns disconnection local-integration --detectOpenHandles --verbose true", - "test:disconnect": "jest --runInBand --verbose true --detectOpenHandles disconnection", + "test:local-integration": "jest --runInBand --detectOpenHandles --verbose --testPathIgnorePatterns disconnection --testPathIgnorePatterns admin --testPathIgnorePatterns multitenancy --testPathIgnorePatterns __tests__/config -u", + "test:docker": "jest --runInBand --testPathIgnorePatterns disconnection --testPathIgnorePatterns __tests__/config local-integration --detectOpenHandles --verbose true", + "test:disconnect": "jest --runInBand --verbose true --detectOpenHandles --testPathIgnorePatterns __tests__/config disconnection", "test&docs": "npm test && npm run docs", "publish": "npm run build && npm run test && lerna publish", "commit": "cz", @@ -72,7 +72,12 @@ }, "preset": "ts-jest", "testEnvironment": "node", - "globalSetup": "<rootDir>/src/lib/jest.globalSetup.ts" + "testPathIgnorePatterns": [ + "/node_modules/", + "__tests__/config/*.ts" + ], + "globalSetup": "<rootDir>/src/__tests__/config/jest.globalSetup.ts", + "globalTeardown": "<rootDir>/src/__tests__/config/jest.globalTeardown.ts" }, "config": { "commitizen": { diff --git a/src/lib/jest.globalSetup.ts b/src/__tests__/config/jest.cleanup.ts similarity index 73% rename from src/lib/jest.globalSetup.ts rename to src/__tests__/config/jest.cleanup.ts index d581ac77..981edfc5 100644 --- a/src/lib/jest.globalSetup.ts +++ b/src/__tests__/config/jest.cleanup.ts @@ -9,36 +9,42 @@ require('tsconfig-paths').register() import { OperateApiClient } from 'operate' import { BpmnParser, ZeebeGrpcClient } from 'zeebe' -export default async () => { +export const cleanUp = async () => { console.log('_dirname', __dirname) console.log(process.cwd()) // Your cleanup process here. console.log( 'Running global setup: cleanup test process instances before all tests...' ) - const filePath = path.join(__dirname, '..', '__tests__', 'testdata') + const filePath = path.join(__dirname, '..', 'testdata') const files = fs .readdirSync(filePath) .map((file) => path.join(filePath, file)) const bpmn = BpmnParser.parseBpmn(files) // eslint-disable-next-line @typescript-eslint/no-explicit-any const processIds = (bpmn as any[]).map( - (b) => b['bpmn:definitions']?.['bpmn:process']?.['@_id'] + (b) => b?.['bpmn:definitions']?.['bpmn:process']?.['@_id'] ) const operate = new OperateApiClient() - const zeebe = new ZeebeGrpcClient() + const zeebe = new ZeebeGrpcClient({ + config: { + zeebeGrpcSettings: { ZEEBE_CLIENT_LOG_LEVEL: 'NONE' }, + }, + }) for (const id of processIds) { if (id) { const res = await operate.searchProcessInstances({ filter: { bpmnProcessId: id, state: 'ACTIVE' }, }) const instancesKeys = res.items.map((instance) => instance.key) - console.log(`Canceling ${instancesKeys.length} instances for ${id}`) + if (instancesKeys.length > 0) { + console.log(`Cancelling ${instancesKeys.length} instances for ${id}`) + } for (const key of instancesKeys) { try { await zeebe.cancelProcessInstance(key) } catch (e) { - console.log('Error canceling process instance', key) + console.log('Error cancelling process instance', key) console.log(e) } } diff --git a/src/__tests__/config/jest.globalSetup.ts b/src/__tests__/config/jest.globalSetup.ts new file mode 100644 index 00000000..f68b8615 --- /dev/null +++ b/src/__tests__/config/jest.globalSetup.ts @@ -0,0 +1,3 @@ +import { cleanUp } from './jest.cleanup' + +export default async () => cleanUp() diff --git a/src/__tests__/config/jest.globalTeardown.ts b/src/__tests__/config/jest.globalTeardown.ts new file mode 100644 index 00000000..f68b8615 --- /dev/null +++ b/src/__tests__/config/jest.globalTeardown.ts @@ -0,0 +1,3 @@ +import { cleanUp } from './jest.cleanup' + +export default async () => cleanUp() diff --git a/src/__tests__/tasklist/tasklist.integration.spec.ts b/src/__tests__/tasklist/tasklist.integration.spec.ts index 9a3a7935..339a2b84 100644 --- a/src/__tests__/tasklist/tasklist.integration.spec.ts +++ b/src/__tests__/tasklist/tasklist.integration.spec.ts @@ -1,5 +1,6 @@ import { join } from 'path' +import { OperateApiClient } from 'operate' import { CreateProcessInstanceResponse, DeployResourceResponse, @@ -42,7 +43,7 @@ describe('TasklistApiClient', () => { interests: ['golf', 'frisbee'], }, }) - await delay(10000) // we wait here to allow Tasklist to do its thing + await delay(13000) // we wait here to allow Tasklist to do its thing }) afterEach(async () => { @@ -101,6 +102,14 @@ describe('TasklistApiClient', () => { describe('Write operations', () => { it('can claim a task', async () => { const tasklist = new TasklistApiClient() + expect(p).toBeTruthy() + const operate = new OperateApiClient() + const res = await operate + .getProcessInstance(p!.processInstanceKey) + .catch((e) => { + console.log('Error getting process instance', e) + }) + expect(res).toBeTruthy() const tasks = await tasklist.searchTasks({ state: 'CREATED' }) const taskid = tasks[0].id expect(tasks.length).toBeGreaterThan(0) diff --git a/src/__tests__/zeebe/integration/Worker-Failure-Retries.spec.ts b/src/__tests__/zeebe/integration/Worker-Failure-Retries.spec.ts index 513e1e93..4be2e8c5 100644 --- a/src/__tests__/zeebe/integration/Worker-Failure-Retries.spec.ts +++ b/src/__tests__/zeebe/integration/Worker-Failure-Retries.spec.ts @@ -52,9 +52,9 @@ test('Decrements the retries count by default', async () => { return job.fail('Some reason') } expect(job.retries).toBe(99) - resolve(null) return job.complete().then(async (res) => { await worker.close() + resolve(null) return res }) }, diff --git a/src/__tests__/zeebe/multitenancy/createProcessInstance-mt.spec.ts b/src/__tests__/zeebe/multitenancy/createProcessInstance-mt.spec.ts index a6663c32..ff6fbc61 100644 --- a/src/__tests__/zeebe/multitenancy/createProcessInstance-mt.spec.ts +++ b/src/__tests__/zeebe/multitenancy/createProcessInstance-mt.spec.ts @@ -56,7 +56,7 @@ test('Will throw an error if no tenantId is provided when starting a process ins bpmnProcessId, variables: {}, }) - client.cancelProcessInstance(p.bpmnProcessId) + client.cancelProcessInstance(p.processInstanceKey) } catch (e) { // eslint-disable-next-line @typescript-eslint/no-explicit-any expect((e as any).code).toBe(3) diff --git a/src/admin/lib/AdminApiClient.ts b/src/admin/lib/AdminApiClient.ts index b8b3980f..14eb4a50 100644 --- a/src/admin/lib/AdminApiClient.ts +++ b/src/admin/lib/AdminApiClient.ts @@ -14,7 +14,7 @@ import { IOAuthProvider } from '../../oauth' import * as Dto from './AdminDto' -const debug = d('consoleapi') +const debug = d('camunda:adminconsole') export class AdminApiClient { private userAgentString: string @@ -45,9 +45,20 @@ export class AdminApiClient { certificateAuthority, }, hooks: { - beforeRequest: [ - (options: unknown) => { - debug('beforeRequest', options) + beforeError: [ + (error) => { + const { request } = error + if (request) { + debug(`Error in request to ${request.options.url.href}`) + debug( + `Request headers: ${JSON.stringify(request.options.headers)}` + ) + debug(`Error: ${error.code} - ${error.message}`) + + // Attach more contextual information to the error object + error.message += ` (request to ${request.options.url.href})` + } + return error }, ], }, diff --git a/src/modeler/lib/ModelerAPIClient.ts b/src/modeler/lib/ModelerAPIClient.ts index 76a7732c..cccdc735 100644 --- a/src/modeler/lib/ModelerAPIClient.ts +++ b/src/modeler/lib/ModelerAPIClient.ts @@ -12,7 +12,7 @@ import { IOAuthProvider } from 'oauth' import * as Dto from './ModelerDto' -const debug = d('modelerapi') +const debug = d('camunda:modeler') const API_VERSION = 'v1' @@ -43,6 +43,23 @@ export class ModelerApiClient { certificateAuthority, }, responseType: 'json', + hooks: { + beforeError: [ + (error) => { + const { request } = error + if (request) { + debug(`Error in request to ${request.options.url.href}`) + debug( + `Request headers: ${JSON.stringify(request.options.headers)}` + ) + debug(`Error: ${error.code} - ${error.message}`) + // Attach more contextual information to the error object + error.message += ` (request to ${request.options.url.href})` + } + return error + }, + ], + }, }) debug(`baseUrl: ${prefixUrl}`) } diff --git a/src/oauth/lib/NullAuthProvider.ts b/src/oauth/lib/NullAuthProvider.ts index 6a30e00a..eb089bfd 100644 --- a/src/oauth/lib/NullAuthProvider.ts +++ b/src/oauth/lib/NullAuthProvider.ts @@ -3,7 +3,7 @@ import { IOAuthProvider } from 'oauth' import { TokenGrantAudienceType } from './IOAuthProvider' -const d = debug('oauth') +const d = debug('camunda:oauth') export class NullAuthProvider implements IOAuthProvider { public async getToken(audience: TokenGrantAudienceType): Promise<string> { diff --git a/src/operate/lib/OperateApiClient.ts b/src/operate/lib/OperateApiClient.ts index 3a0506fe..977a54e3 100644 --- a/src/operate/lib/OperateApiClient.ts +++ b/src/operate/lib/OperateApiClient.ts @@ -1,3 +1,4 @@ +import { debug as d } from 'debug' import got from 'got' import { CamundaEnvironmentConfigurator, @@ -30,6 +31,8 @@ import { parseSearchResults } from './parseSearchResults' const OPERATE_API_VERSION = 'v1' +const debug = d('camunda:operate') + type JSONDoc = { [key: string]: string | boolean | number | JSONDoc } type EnhanceWithTenantIdIfMissing<T> = T extends { filter: { tenantId: string | undefined } @@ -91,6 +94,24 @@ export class OperateApiClient { https: { certificateAuthority, }, + hooks: { + beforeError: [ + (error) => { + const { request } = error + if (request) { + debug(`Error in request to ${request.options.url.href}`) + debug( + `Request headers: ${JSON.stringify(request.options.headers)}` + ) + debug(`Error: ${error.code} - ${error.message}`) + + // Attach more contextual information to the error object + error.message += ` (request to ${request.options.url.href})` + } + return error + }, + ], + }, }) this.tenantId = config.CAMUNDA_TENANT_ID } diff --git a/src/optimize/lib/OptimizeApiClient.ts b/src/optimize/lib/OptimizeApiClient.ts index 14b63c39..0442e421 100644 --- a/src/optimize/lib/OptimizeApiClient.ts +++ b/src/optimize/lib/OptimizeApiClient.ts @@ -1,4 +1,4 @@ -import d from 'debug' +import { debug as d } from 'debug' import got from 'got' import { CamundaEnvironmentConfigurator, @@ -23,7 +23,7 @@ import { } from './APIObjects' import { ReportResults } from './ReportResults' -const debug = d('optimizeapi') +const debug = d('camunda:optimize') /** * @description The high-level API client for Optimize. @@ -76,9 +76,20 @@ export class OptimizeApiClient { certificateAuthority, }, hooks: { - beforeRequest: [ - (options: unknown) => { - debug('beforeRequest', options) + beforeError: [ + (error) => { + const { request } = error + if (request) { + debug(`Error in request to ${request.options.url.href}`) + debug( + `Request headers: ${JSON.stringify(request.options.headers)}` + ) + debug(`Error: ${error.code} - ${error.message}`) + + // Attach more contextual information to the error object + error.message += ` (request to ${request.options.url.href})` + } + return error }, ], }, diff --git a/src/tasklist/lib/TasklistApiClient.ts b/src/tasklist/lib/TasklistApiClient.ts index f0331f8c..3daf0cc5 100644 --- a/src/tasklist/lib/TasklistApiClient.ts +++ b/src/tasklist/lib/TasklistApiClient.ts @@ -29,7 +29,7 @@ import { } from './TasklistDto' import { JSONDoc, encodeTaskVariablesForAPIRequest } from './utils' -const trace = debug('tasklist:rest') +const trace = debug('camunda:tasklist') const TASKLIST_API_VERSION = 'v1' @@ -77,6 +77,24 @@ export class TasklistApiClient { https: { certificateAuthority, }, + hooks: { + beforeError: [ + (error) => { + const { request } = error + if (request) { + debug(`Error in request to ${request.options.url.href}`) + debug( + `Request headers: ${JSON.stringify(request.options.headers)}` + ) + debug(`Error: ${error.code} - ${error.message}`) + + // Attach more contextual information to the error object + error.message += ` (request to ${request.options.url.href})` + } + return error + }, + ], + }, }) trace(`prefixUrl: ${prefixUrl}`) } @@ -228,9 +246,9 @@ export class TasklistApiClient { /** * @description Assign a task with taskId to assignee or the active user. * @throws Status 400 - An error is returned when the task is not active (not in the CREATED state). - * @throws Status 400 - An error is returned when task was already assigned, except the case when JWT authentication token used and allowOverrideAssignment = true. - * @throws Status 403 - An error is returned when user doesn't have the permission to assign another user to this task. - * @throws Status 404 - An error is returned when the task with the taskId is not found. + * Status 400 - An error is returned when task was already assigned, except the case when JWT authentication token used and allowOverrideAssignment = true. + * Status 403 - An error is returned when user doesn't have the permission to assign another user to this task. + * Status 404 - An error is returned when the task with the taskId is not found. */ public async assignTask({ taskId, diff --git a/src/zeebe/lib/GrpcClient.ts b/src/zeebe/lib/GrpcClient.ts index effd6084..ebd09250 100644 --- a/src/zeebe/lib/GrpcClient.ts +++ b/src/zeebe/lib/GrpcClient.ts @@ -19,7 +19,7 @@ import { packageVersion } from './GetPackageVersion' import { GrpcError } from './GrpcError' import { Loglevel, ZBCustomLogger } from './interfaces-published-contract' -const debug = d('grpc') +const debug = d('camunda:grpc') export interface GrpcClientExtendedOptions { longPoll?: MaybeTimeDuration diff --git a/src/zeebe/lib/ZBWorkerBase.ts b/src/zeebe/lib/ZBWorkerBase.ts index 41132b24..a927e9d9 100644 --- a/src/zeebe/lib/ZBWorkerBase.ts +++ b/src/zeebe/lib/ZBWorkerBase.ts @@ -24,7 +24,7 @@ import { ZBClientOptions } from './interfaces-published-contract' import { parseVariablesAndCustomHeadersToJSON } from '.' -const debug = d('worker') +const debug = d('camunda:worker') debug('Loaded ZBWorkerBase') const MIN_ACTIVE_JOBS_RATIO_BEFORE_ACTIVATING_JOBS = 0.3 diff --git a/src/zeebe/lib/cancelProcesses.ts b/src/zeebe/lib/cancelProcesses.ts index e35fa1e0..416f26f5 100644 --- a/src/zeebe/lib/cancelProcesses.ts +++ b/src/zeebe/lib/cancelProcesses.ts @@ -6,27 +6,36 @@ export async function cancelProcesses(processDefinitionKey: string) { if (!operate) { return } - const processes = await operate.searchProcessInstances({ - filter: { - processDefinitionKey, - }, - }) - await Promise.all( - processes.items.map((item) => { - return operate.deleteProcessInstance(item.key).catch((e) => { - console.log(`Failed to delete process ${item.key}`) - console.log(e) - }) + const processes = await operate + .searchProcessInstances({ + filter: { + processDefinitionKey, + }, + }) + .catch((e) => { + console.log( + `Failed to search for process instances for ${processDefinitionKey}` + ) + console.log(e) }) - ) + if (processes) { + await Promise.all( + processes.items.map((item) => { + return operate.deleteProcessInstance(item.key).catch((e) => { + console.log(`Failed to delete process ${item.key}`) + console.log(e) + }) + }) + ) + } } function createClient() { try { return new OperateApiClient() } catch (e: unknown) { - // console.log(e.message) - // console.log(`Running without access to Operate`) + console.log((e as Error).message) + console.log(`Running without access to Operate`) return null } } diff --git a/src/zeebe/zb/ZeebeGrpcClient.ts b/src/zeebe/zb/ZeebeGrpcClient.ts index 5cd51aab..c8c0b5c4 100644 --- a/src/zeebe/zb/ZeebeGrpcClient.ts +++ b/src/zeebe/zb/ZeebeGrpcClient.ts @@ -42,7 +42,7 @@ import { Utils } from '../lib/utils' import { ZBBatchWorker } from './ZBBatchWorker' import { ZBWorker } from './ZBWorker' -const debug = d('client') +const debug = d('camunda:zeebeclient') const idColors = [ chalk.yellow,