From d2756de090f2628f9025ba2f4436870e67576367 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Sun, 20 Mar 2022 15:13:18 -0400 Subject: [PATCH] feat(Mailjet Node): Add credential tests and support for sandbox, JSON parameters & variables (#2987) * Add Variables JSON to Mailjet Batch send * :zap: Improvements * :zap: Add credential verification * :zap: Small improvement Co-authored-by: Marcin Koziej --- .../MailjetEmailApi.credentials.ts | 7 ++ .../nodes/Mailjet/EmailDescription.ts | 89 ++++++++++++++++++- .../nodes/Mailjet/GenericFunctions.ts | 53 ++++++++++- .../nodes-base/nodes/Mailjet/Mailjet.node.ts | 59 ++++++++++-- 4 files changed, 197 insertions(+), 11 deletions(-) diff --git a/packages/nodes-base/credentials/MailjetEmailApi.credentials.ts b/packages/nodes-base/credentials/MailjetEmailApi.credentials.ts index c43f0bb1a0c5b..cefcc89aebfd8 100644 --- a/packages/nodes-base/credentials/MailjetEmailApi.credentials.ts +++ b/packages/nodes-base/credentials/MailjetEmailApi.credentials.ts @@ -20,5 +20,12 @@ export class MailjetEmailApi implements ICredentialType { type: 'string', default: '', }, + { + displayName: 'Sandbox Mode', + name: 'sandboxMode', + type: 'boolean', + default: false, + description: 'Allow to run the API call in a Sandbox mode, where all validations of the payload will be done without delivering the message', + }, ]; } diff --git a/packages/nodes-base/nodes/Mailjet/EmailDescription.ts b/packages/nodes-base/nodes/Mailjet/EmailDescription.ts index bd378ca0b9ff2..c0721f51448e0 100644 --- a/packages/nodes-base/nodes/Mailjet/EmailDescription.ts +++ b/packages/nodes-base/nodes/Mailjet/EmailDescription.ts @@ -1,4 +1,6 @@ -import { INodeProperties } from 'n8n-workflow'; +import { + INodeProperties, +} from 'n8n-workflow'; export const emailOperations: INodeProperties[] = [ { @@ -25,7 +27,6 @@ export const emailOperations: INodeProperties[] = [ }, ], default: 'send', - description: 'The operation to perform.', }, ]; @@ -120,6 +121,22 @@ export const emailFields: INodeProperties[] = [ default: '', description: 'HTML text message of email.', }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + displayOptions: { + show: { + resource: [ + 'email', + ], + operation: [ + 'send', + ], + }, + }, + }, { displayName: 'Additional Fields', name: 'additionalFields', @@ -226,6 +243,29 @@ export const emailFields: INodeProperties[] = [ }, ], }, + { + displayName: 'Variables (JSON)', + name: 'variablesJson', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + displayOptions: { + show: { + resource: [ + 'email', + ], + operation: [ + 'send', + ], + jsonParameters: [ + true, + ], + }, + }, + default: '', + description: 'HTML text message of email.', + }, { displayName: 'Variables', name: 'variablesUi', @@ -241,6 +281,9 @@ export const emailFields: INodeProperties[] = [ operation: [ 'send', ], + jsonParameters: [ + false, + ], }, }, placeholder: 'Add Variable', @@ -327,6 +370,22 @@ export const emailFields: INodeProperties[] = [ }, }, }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + displayOptions: { + show: { + resource: [ + 'email', + ], + operation: [ + 'sendTemplate', + ], + }, + }, + }, { displayName: 'Additional Fields', name: 'additionalFields', @@ -420,6 +479,9 @@ export const emailFields: INodeProperties[] = [ operation: [ 'sendTemplate', ], + jsonParameters: [ + false, + ], }, }, placeholder: 'Add Variable', @@ -445,4 +507,27 @@ export const emailFields: INodeProperties[] = [ }, ], }, + { + displayName: 'Variables (JSON)', + name: 'variablesJson', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + displayOptions: { + show: { + resource: [ + 'email', + ], + operation: [ + 'sendTemplate', + ], + jsonParameters: [ + true, + ], + }, + }, + default: '', + description: 'HTML text message of email.', + }, ]; diff --git a/packages/nodes-base/nodes/Mailjet/GenericFunctions.ts b/packages/nodes-base/nodes/Mailjet/GenericFunctions.ts index fc663eb463eca..49c2b72eca812 100644 --- a/packages/nodes-base/nodes/Mailjet/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Mailjet/GenericFunctions.ts @@ -9,6 +9,8 @@ import { } from 'n8n-core'; import { + ICredentialDataDecryptedObject, + ICredentialTestFunctions, IDataObject, IHookFunctions, JsonObject, @@ -16,7 +18,7 @@ import { } from 'n8n-workflow'; export async function mailjetApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any - const emailApiCredentials = await this.getCredentials('mailjetEmailApi'); + const emailApiCredentials = await this.getCredentials('mailjetEmailApi') as { apiKey: string, secretKey: string, sandboxMode: boolean }; let options: OptionsWithUri = { headers: { Accept: 'application/json', @@ -33,8 +35,12 @@ export async function mailjetApiRequest(this: IExecuteFunctions | IExecuteSingle delete options.body; } if (emailApiCredentials !== undefined) { - const base64Credentials = Buffer.from(`${emailApiCredentials.apiKey}:${emailApiCredentials.secretKey}`).toString('base64'); - options.headers!['Authorization'] = `Basic ${base64Credentials}`; + const { sandboxMode } = emailApiCredentials; + Object.assign(body, { SandboxMode: sandboxMode }); + options.auth = { + username: emailApiCredentials.apiKey, + password: emailApiCredentials.secretKey, + }; } else { const smsApiCredentials = await this.getCredentials('mailjetSmsApi'); options.headers!['Authorization'] = `Bearer ${smsApiCredentials!.token}`; @@ -65,6 +71,47 @@ export async function mailjetApiRequestAllItems(this: IExecuteFunctions | IHookF return returnData; } +export async function validateCredentials( + this: ICredentialTestFunctions, + decryptedCredentials: ICredentialDataDecryptedObject, +): Promise { // tslint:disable-line:no-any + const credentials = decryptedCredentials; + + const { + apiKey, + secretKey, + } = credentials as { + apiKey: string, + secretKey: string, + }; + + const options: OptionsWithUri = { + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + auth: { + username: apiKey, + password: secretKey, + }, + method: 'GET', + uri: `https://api.mailjet.com/v3/REST/template`, + json: true, + }; + + return await this.helpers.request(options); +} + +export function validateJSON(json: string | undefined): IDataObject | undefined { // tslint:disable-line:no-any + let result; + try { + result = JSON.parse(json!); + } catch (exception) { + result = undefined; + } + return result; +} + export interface IMessage { From?: { Email?: string, Name?: string }; Subject?: string; diff --git a/packages/nodes-base/nodes/Mailjet/Mailjet.node.ts b/packages/nodes-base/nodes/Mailjet/Mailjet.node.ts index a63d4492ae71c..9881ae3046d09 100644 --- a/packages/nodes-base/nodes/Mailjet/Mailjet.node.ts +++ b/packages/nodes-base/nodes/Mailjet/Mailjet.node.ts @@ -3,17 +3,25 @@ import { } from 'n8n-core'; import { + ICredentialDataDecryptedObject, + ICredentialsDecrypted, + ICredentialTestFunctions, IDataObject, ILoadOptionsFunctions, + INodeCredentialTestResult, INodeExecutionData, INodePropertyOptions, INodeType, INodeTypeDescription, + JsonObject, + NodeOperationError, } from 'n8n-workflow'; import { IMessage, mailjetApiRequest, + validateCredentials, + validateJSON, } from './GenericFunctions'; import { @@ -25,7 +33,6 @@ import { smsFields, smsOperations, } from './SmsDescription'; - export class Mailjet implements INodeType { description: INodeTypeDescription = { displayName: 'Mailjet', @@ -44,6 +51,7 @@ export class Mailjet implements INodeType { { name: 'mailjetEmailApi', required: true, + testedBy: 'mailjetEmailApiTest', displayOptions: { show: { resource: [ @@ -90,6 +98,25 @@ export class Mailjet implements INodeType { }; methods = { + credentialTest: { + async mailjetEmailApiTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise { + try { + await validateCredentials.call(this, credential.data as ICredentialDataDecryptedObject); + } catch (error) { + const err = error as JsonObject; + if (err.statusCode === 401) { + return { + status: 'Error', + message: `Invalid credentials`, + }; + } + } + return { + status: 'OK', + message: 'Authentication successful', + }; + }, + }, loadOptions: { // Get all the available custom fields to display them to user so that he can // select them easily @@ -126,7 +153,7 @@ export class Mailjet implements INodeType { const subject = this.getNodeParameter('subject', i) as string; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; const toEmail = (this.getNodeParameter('toEmail', i) as string).split(',') as string[]; - const variables = (this.getNodeParameter('variablesUi', i) as IDataObject).variablesValues as IDataObject[]; + const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean; const body: IMessage = { From: { @@ -144,11 +171,21 @@ export class Mailjet implements INodeType { Email: email, }); } - if (variables) { + + if (jsonParameters) { + const variablesJson = this.getNodeParameter('variablesJson', i) as string; + const parsedJson = validateJSON(variablesJson); + if (parsedJson === undefined) { + throw new NodeOperationError(this.getNode(),`Parameter 'Variables (JSON)' has a invalid JSON`); + } + body.Variables = parsedJson; + } else { + const variables = (this.getNodeParameter('variablesUi', i) as IDataObject).variablesValues as IDataObject[] || []; for (const variable of variables) { body.Variables![variable.name as string] = variable.value; } } + if (htmlBody) { body.HTMLPart = htmlBody; } @@ -201,9 +238,9 @@ export class Mailjet implements INodeType { const fromEmail = this.getNodeParameter('fromEmail', i) as string; const templateId = parseInt(this.getNodeParameter('templateId', i) as string, 10); const subject = this.getNodeParameter('subject', i) as string; - const variables = (this.getNodeParameter('variablesUi', i) as IDataObject).variablesValues as IDataObject[]; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; const toEmail = (this.getNodeParameter('toEmail', i) as string).split(',') as string[]; + const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean; const body: IMessage = { From: { @@ -222,11 +259,21 @@ export class Mailjet implements INodeType { Email: email, }); } - if (variables) { + + if (jsonParameters) { + const variablesJson = this.getNodeParameter('variablesJson', i) as string; + const parsedJson = validateJSON(variablesJson); + if (parsedJson === undefined) { + throw new NodeOperationError(this.getNode(), `Parameter 'Variables (JSON)' has a invalid JSON`); + } + body.Variables = parsedJson; + } else { + const variables = (this.getNodeParameter('variablesUi', i) as IDataObject).variablesValues as IDataObject[] || []; for (const variable of variables) { body.Variables![variable.name as string] = variable.value; } } + if (additionalFields.bccEmail) { const bccEmail = (additionalFields.bccEmail as string).split(',') as string[]; for (const email of bccEmail) { @@ -289,7 +336,7 @@ export class Mailjet implements INodeType { } } catch (error) { if (this.continueOnFail()) { - returnData.push({ error: error.message }); + returnData.push({ error: (error as JsonObject).message }); continue; } throw error;