From abe033d7a1592baa9ee655f9b4c313f0c030d188 Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Wed, 29 Jun 2022 14:12:28 +0200 Subject: [PATCH 1/5] Handle errors from example node and clarified options about credentials and properties --- credentials/HttpBinApi.credentials.ts | 40 ++++++++++----------------- nodes/ExampleNode/ExampleNode.node.ts | 24 ++++++++++++++-- package.json | 4 +-- 3 files changed, 38 insertions(+), 30 deletions(-) diff --git a/credentials/HttpBinApi.credentials.ts b/credentials/HttpBinApi.credentials.ts index 8ccd44f..62f170b 100644 --- a/credentials/HttpBinApi.credentials.ts +++ b/credentials/HttpBinApi.credentials.ts @@ -1,4 +1,5 @@ import { + IAuthenticateGeneric, ICredentialDataDecryptedObject, ICredentialTestRequest, ICredentialType, @@ -17,12 +18,6 @@ export class HttpBinApi implements ICredentialType { type: 'string', default: '', }, - // { - // displayName: "API Key", - // name: "apiKey", - // type: "string", - // default: "", - // }, { displayName: 'Domain', name: 'domain', @@ -31,26 +26,21 @@ export class HttpBinApi implements ICredentialType { }, ]; - // authenticate = { - // type: "headerAuth", - // properties: { - // name: "api-key", - // value: "={{$credentials.apiKey}}", - // }, - // } as IAuthenticateHeaderAuth; - - authenticate = async ( - credentials: ICredentialDataDecryptedObject, - requestOptions: IHttpRequestOptions, - ): Promise => { - const headers = requestOptions.headers || {}; - const authentication = { Authorization: `Bearer ${credentials.token}` }; - Object.assign(requestOptions, { - headers: { ...authentication, ...headers }, - }); - return requestOptions; - }; + // This allows the credential to be used by other parts of n8n + // stating how this credential is injected as part of the request + // An example is the Http Request node that can make generic calls + // reusing this credential + authenticate = { + type: 'generic', + properties: { + header: { + name: 'api-key', + value: '={{$credentials.apiKey}}', + }, + }, + } as IAuthenticateGeneric; + // The block below tells how this credential can be tested test: ICredentialTestRequest = { request: { baseURL: '={{$credentials?.domain}}', diff --git a/nodes/ExampleNode/ExampleNode.node.ts b/nodes/ExampleNode/ExampleNode.node.ts index 3bbadf4..73f568e 100644 --- a/nodes/ExampleNode/ExampleNode.node.ts +++ b/nodes/ExampleNode/ExampleNode.node.ts @@ -28,6 +28,10 @@ export class ExampleNode implements INodeType { ], }; + // The function below is responsible for actually doing whatever this node + // is supposed to do. In this case, we're just appending the `myString` property + // with whatever the user has entered. + // You can make async calls and use `await`. async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); @@ -38,10 +42,24 @@ export class ExampleNode implements INodeType { // value the parameter "myString" resolves to. // (This could be a different value for each item in case it contains an expression) for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { - myString = this.getNodeParameter('myString', itemIndex, '') as string; - item = items[itemIndex]; + try { + myString = this.getNodeParameter('myString', itemIndex, '') as string; + item = items[itemIndex]; + + item.json['myString'] = myString; + } catch (error) { + + // This node should never fail but we want to showcase how + // to handle errors. + if (this.continueOnFail()) { + items.push({json: this.getInputData(itemIndex)[0].json, error}); + } else { + // Adding `itemIndex` allows other workflows to handle this error + if (error.context) error.context.itemIndex = itemIndex; + throw error; + } + } - item.json['myString'] = myString; } return this.prepareOutputData(items); diff --git a/package.json b/package.json index 72720bc..fadce8b 100644 --- a/package.json +++ b/package.json @@ -42,8 +42,8 @@ "@typescript-eslint/parser": "^5.29.0", "eslint-plugin-n8n-nodes-base": "~1.1.1", "gulp": "^4.0.2", - "n8n-core": "~0.122.1", - "n8n-workflow": "~0.104.0", + "n8n-core": "^0.124.0", + "n8n-workflow": "^0.106.0", "prettier": "^2.7.1", "tslint": "^6.1.2", "typescript": "~4.6.0" From ba83500857b44e82c88a9af656bffe8493b430b0 Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Wed, 29 Jun 2022 14:54:53 +0200 Subject: [PATCH 2/5] Add correct error output with context --- nodes/ExampleNode/ExampleNode.node.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/nodes/ExampleNode/ExampleNode.node.ts b/nodes/ExampleNode/ExampleNode.node.ts index 73f568e..81e9d53 100644 --- a/nodes/ExampleNode/ExampleNode.node.ts +++ b/nodes/ExampleNode/ExampleNode.node.ts @@ -1,5 +1,5 @@ import { IExecuteFunctions } from 'n8n-core'; -import { INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow'; +import { INodeExecutionData, INodeType, INodeTypeDescription, NodeOperationError } from 'n8n-workflow'; export class ExampleNode implements INodeType { description: INodeTypeDescription = { @@ -55,8 +55,13 @@ export class ExampleNode implements INodeType { items.push({json: this.getInputData(itemIndex)[0].json, error}); } else { // Adding `itemIndex` allows other workflows to handle this error - if (error.context) error.context.itemIndex = itemIndex; - throw error; + if (error.context) { + error.context.itemIndex = itemIndex; + throw error; + } + throw new NodeOperationError(this.getNode(), error.message, { + itemIndex, + }); } } From 26f52ed166c9730d22d857957354e3944288f26e Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Wed, 29 Jun 2022 14:55:31 +0200 Subject: [PATCH 3/5] Add clarifying comment --- nodes/ExampleNode/ExampleNode.node.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nodes/ExampleNode/ExampleNode.node.ts b/nodes/ExampleNode/ExampleNode.node.ts index 81e9d53..d7e111f 100644 --- a/nodes/ExampleNode/ExampleNode.node.ts +++ b/nodes/ExampleNode/ExampleNode.node.ts @@ -55,7 +55,9 @@ export class ExampleNode implements INodeType { items.push({json: this.getInputData(itemIndex)[0].json, error}); } else { // Adding `itemIndex` allows other workflows to handle this error - if (error.context) { + if (error.context) { + // If the error thrown already contains the context property, + // only append the itemIndex error.context.itemIndex = itemIndex; throw error; } From de7c4dbed10ad06b1028a34d27929318f537c85d Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Wed, 29 Jun 2022 16:27:55 +0200 Subject: [PATCH 4/5] Fix credential usage and error output --- credentials/HttpBinApi.credentials.ts | 7 ++----- nodes/ExampleNode/ExampleNode.node.ts | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/credentials/HttpBinApi.credentials.ts b/credentials/HttpBinApi.credentials.ts index 62f170b..805b83d 100644 --- a/credentials/HttpBinApi.credentials.ts +++ b/credentials/HttpBinApi.credentials.ts @@ -1,9 +1,7 @@ import { IAuthenticateGeneric, - ICredentialDataDecryptedObject, ICredentialTestRequest, ICredentialType, - IHttpRequestOptions, INodeProperties, } from 'n8n-workflow'; @@ -33,9 +31,8 @@ export class HttpBinApi implements ICredentialType { authenticate = { type: 'generic', properties: { - header: { - name: 'api-key', - value: '={{$credentials.apiKey}}', + headers: { + Authorization: '={{"Bearer " + $credentials.token}}', }, }, } as IAuthenticateGeneric; diff --git a/nodes/ExampleNode/ExampleNode.node.ts b/nodes/ExampleNode/ExampleNode.node.ts index d7e111f..29fbcfc 100644 --- a/nodes/ExampleNode/ExampleNode.node.ts +++ b/nodes/ExampleNode/ExampleNode.node.ts @@ -61,7 +61,7 @@ export class ExampleNode implements INodeType { error.context.itemIndex = itemIndex; throw error; } - throw new NodeOperationError(this.getNode(), error.message, { + throw new NodeOperationError(this.getNode(), error, { itemIndex, }); } From 7ac69f816eb5dd43c53b7317f864ddb92b1401b9 Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Wed, 29 Jun 2022 17:25:14 +0200 Subject: [PATCH 5/5] Improve example credential --- credentials/ExampleCredentials.credentials.ts | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/credentials/ExampleCredentials.credentials.ts b/credentials/ExampleCredentials.credentials.ts index 2484f47..fdd9ebf 100644 --- a/credentials/ExampleCredentials.credentials.ts +++ b/credentials/ExampleCredentials.credentials.ts @@ -1,4 +1,4 @@ -import { ICredentialType, NodePropertyTypes } from 'n8n-workflow'; +import { IAuthenticateGeneric, ICredentialTestRequest, ICredentialType, NodePropertyTypes } from 'n8n-workflow'; export class ExampleCredentials implements ICredentialType { name = 'exampleCredentials'; @@ -8,16 +8,44 @@ export class ExampleCredentials implements ICredentialType { // Properties can be defined exactly in the same way // as node properties. { - displayName: 'User', - name: 'user', + displayName: 'User Name', + name: 'username', type: 'string' as NodePropertyTypes, default: '', }, { - displayName: 'Access Token', - name: 'accessToken', + displayName: 'Password', + name: 'password', type: 'string' as NodePropertyTypes, + typeOptions: { + password: true, + }, default: '', }, ]; + + // This credential is currently not used by any node directly + // but the HTTP Request node can use it to make requests. + // The credential is also testable due to the `test` property below + authenticate = { + type: 'generic', + properties: { + auth: { + username: '={{ $credentials.username }}', + password: '={{ $credentials.password }}', + }, + qs: { + // Send this as part of the query string + n8n: 'rocks', + } + }, + } as IAuthenticateGeneric; + + // The block below tells how this credential can be tested + test: ICredentialTestRequest = { + request: { + baseURL: 'https://example.com/', + url: '', + }, + }; }