diff --git a/packages/nodes-base/nodes/Emelia/CampaignDescription.ts b/packages/nodes-base/nodes/Emelia/CampaignDescription.ts index 2fc0826e12..df2b46f77b 100644 --- a/packages/nodes-base/nodes/Emelia/CampaignDescription.ts +++ b/packages/nodes-base/nodes/Emelia/CampaignDescription.ts @@ -9,6 +9,7 @@ export const campaignOperations: INodeProperties[] = [ type: 'options', default: 'get', description: 'Operation to perform', + noDataExpression: true, options: [ { name: 'Add Contact', @@ -18,6 +19,10 @@ export const campaignOperations: INodeProperties[] = [ name: 'Create', value: 'create', }, + { + name: 'Duplicate', + value: 'duplicate', + }, { name: 'Get', value: 'get', @@ -58,7 +63,7 @@ export const campaignFields: INodeProperties[] = [ }, default: [], required: true, - description: 'The ID of the campaign to add the contact to.', + description: 'The ID of the campaign to add the contact to', displayOptions: { show: { resource: [ @@ -76,7 +81,7 @@ export const campaignFields: INodeProperties[] = [ type: 'string', required: true, default: '', - description: 'The email of the contact to add to the campaign.', + description: 'The email of the contact to add to the campaign', displayOptions: { show: { resource: [ @@ -113,7 +118,7 @@ export const campaignFields: INodeProperties[] = [ typeOptions: { multipleValues: true, }, - description: 'Filter by custom fields ', + description: 'Filter by custom fields', default: {}, options: [ { @@ -125,14 +130,14 @@ export const campaignFields: INodeProperties[] = [ name: 'fieldName', type: 'string', default: '', - description: 'The name of the field to add custom field to.', + description: 'The name of the field to add custom field to', }, { displayName: 'Value', name: 'value', type: 'string', default: '', - description: 'The value to set on custom field.', + description: 'The value to set on custom field', }, ], }, @@ -143,49 +148,49 @@ export const campaignFields: INodeProperties[] = [ name: 'firstName', type: 'string', default: '', - description: 'First name of the contact to add.', + description: 'First name of the contact to add', + }, + { + displayName: 'Last Contacted', + name: 'lastContacted', + type: 'dateTime', + default: '', + description: 'Last contacted date of the contact to add', }, { displayName: 'Last Name', name: 'lastName', type: 'string', default: '', - description: 'Last name of the contact to add.', - }, - { - displayName: 'Last Contacted', - name: 'lastContacted', - type: 'string', - default: '', - description: 'Last contacted date of the contact to add.', + description: 'Last name of the contact to add', }, { displayName: 'Last Open', name: 'lastOpen', - type: 'string', + type: 'dateTime', default: '', - description: 'Last opened date of the contact to add.', + description: 'Last opened date of the contact to add', }, { displayName: 'Last Replied', name: 'lastReplied', - type: 'string', + type: 'dateTime', default: '', - description: 'Last replied date of the contact to add.', + description: 'Last replied date of the contact to add', }, { displayName: 'Mails Sent', name: 'mailsSent', type: 'number', default: 0, - description: 'Number of emails sent to the contact to add.', + description: 'Number of emails sent to the contact to add', }, { displayName: 'Phone Number', name: 'phoneNumber', type: 'string', default: '', - description: 'Phone number of the contact to add.', + description: 'Phone number of the contact to add', }, ], }, @@ -199,7 +204,7 @@ export const campaignFields: INodeProperties[] = [ type: 'string', required: true, default: '', - description: 'The name of the campaign to create.', + description: 'The name of the campaign to create', displayOptions: { show: { resource: [ @@ -221,7 +226,7 @@ export const campaignFields: INodeProperties[] = [ type: 'string', default: '', required: true, - description: 'The ID of the campaign to retrieve.', + description: 'The ID of the campaign to retrieve', displayOptions: { show: { resource: [ @@ -242,7 +247,7 @@ export const campaignFields: INodeProperties[] = [ name: 'returnAll', type: 'boolean', default: false, - description: 'Return all results.', + description: 'Whether to return all results or only up to a given limit', displayOptions: { show: { resource: [ @@ -259,7 +264,7 @@ export const campaignFields: INodeProperties[] = [ name: 'limit', type: 'number', default: 100, - description: 'The number of results to return.', + description: 'Max number of results to return', typeOptions: { minValue: 1, maxValue: 100, @@ -323,4 +328,93 @@ export const campaignFields: INodeProperties[] = [ }, }, + // ---------------------------------- + // campaign: duplicate + // ---------------------------------- + { + displayName: 'Campaign ID', + name: 'campaignId', + type: 'options', + default: '', + required: true, + description: 'The ID of the campaign to duplicate', + typeOptions: { + loadOptionsMethod: 'getCampaigns', + }, + displayOptions: { + show: { + resource: [ + 'campaign', + ], + operation: [ + 'duplicate', + ], + }, + }, + }, + { + displayName: 'New Campaign Name', + name: 'campaignName', + type: 'string', + required: true, + default: '', + description: 'The name of the new campaign to create', + displayOptions: { + show: { + resource: [ + 'campaign', + ], + operation: [ + 'duplicate', + ], + }, + }, + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'duplicate', + ], + resource: [ + 'campaign', + ], + }, + }, + options: [ + { + displayName: 'Copy Contacts', + name: 'copyContacts', + type: 'boolean', + default: false, + description: 'Whether to copy all the contacts from the original campaign', + }, + { + displayName: 'Copy Email Provider', + name: 'copyProvider', + type: 'boolean', + default: true, + description: 'Whether to set the same email provider than the original campaign', + }, + { + displayName: 'Copy Email Sequence', + name: 'copyMails', + type: 'boolean', + default: true, + description: 'Whether to copy all the steps of the email sequence from the original campaign', + }, + { + displayName: 'Copy Global Settings', + name: 'copySettings', + type: 'boolean', + default: true, + description: 'Whether to copy all the general settings from the original campaign', + }, + ], + }, ]; diff --git a/packages/nodes-base/nodes/Emelia/ContactListDescription.ts b/packages/nodes-base/nodes/Emelia/ContactListDescription.ts index a303f39937..f48d9c7c49 100644 --- a/packages/nodes-base/nodes/Emelia/ContactListDescription.ts +++ b/packages/nodes-base/nodes/Emelia/ContactListDescription.ts @@ -7,8 +7,9 @@ export const contactListOperations: INodeProperties[] = [ displayName: 'Operation', name: 'operation', type: 'options', - default: 'get', + default: 'getAll', description: 'Operation to perform', + noDataExpression: true, options: [ { name: 'Add', @@ -42,7 +43,7 @@ export const contactListFields: INodeProperties[] = [ }, default: [], required: true, - description: 'The ID of the contact list to add the contact to.', + description: 'The ID of the contact list to add the contact to', displayOptions: { show: { resource: [ @@ -60,7 +61,7 @@ export const contactListFields: INodeProperties[] = [ type: 'string', required: true, default: '', - description: 'The email of the contact to add to the contact list.', + description: 'The email of the contact to add to the contact list', displayOptions: { show: { resource: [ @@ -97,7 +98,7 @@ export const contactListFields: INodeProperties[] = [ typeOptions: { multipleValues: true, }, - description: 'Filter by custom fields ', + description: 'Filter by custom fields', default: {}, options: [ { @@ -109,14 +110,14 @@ export const contactListFields: INodeProperties[] = [ name: 'fieldName', type: 'string', default: '', - description: 'The name of the field to add custom field to.', + description: 'The name of the field to add custom field to', }, { displayName: 'Value', name: 'value', type: 'string', default: '', - description: 'The value to set on custom field.', + description: 'The value to set on custom field', }, ], }, @@ -127,49 +128,49 @@ export const contactListFields: INodeProperties[] = [ name: 'firstName', type: 'string', default: '', - description: 'First name of the contact to add.', - }, - { - displayName: 'Last Name', - name: 'lastName', - type: 'string', - default: '', - description: 'Last name of the contact to add.', + description: 'First name of the contact to add', }, { displayName: 'Last Contacted', name: 'lastContacted', type: 'dateTime', default: '', - description: 'Last contacted date of the contact to add.', + description: 'Last contacted date of the contact to add', + }, + { + displayName: 'Last Name', + name: 'lastName', + type: 'string', + default: '', + description: 'Last name of the contact to add', }, { displayName: 'Last Open', name: 'lastOpen', type: 'dateTime', default: '', - description: 'Last opened date of the contact to add.', + description: 'Last opened date of the contact to add', }, { displayName: 'Last Replied', name: 'lastReplied', type: 'dateTime', default: '', - description: 'Last replied date of the contact to add.', + description: 'Last replied date of the contact to add', }, { displayName: 'Mails Sent', name: 'mailsSent', type: 'number', default: 0, - description: 'Number of emails sent to the contact to add.', + description: 'Number of emails sent to the contact to add', }, { displayName: 'Phone Number', name: 'phoneNumber', type: 'string', default: '', - description: 'Phone number of the contact to add.', + description: 'Phone number of the contact to add', }, ], }, @@ -182,7 +183,7 @@ export const contactListFields: INodeProperties[] = [ name: 'returnAll', type: 'boolean', default: false, - description: 'Return all results.', + description: 'Whether to return all results or only up to a given limit', displayOptions: { show: { resource: [ @@ -199,7 +200,7 @@ export const contactListFields: INodeProperties[] = [ name: 'limit', type: 'number', default: 100, - description: 'The number of results to return.', + description: 'Max number of results to return', typeOptions: { minValue: 1, maxValue: 100, diff --git a/packages/nodes-base/nodes/Emelia/Emelia.node.ts b/packages/nodes-base/nodes/Emelia/Emelia.node.ts index 53c0c4d603..d2d54e4dd1 100644 --- a/packages/nodes-base/nodes/Emelia/Emelia.node.ts +++ b/packages/nodes-base/nodes/Emelia/Emelia.node.ts @@ -7,10 +7,12 @@ import { ILoadOptionsFunctions, INodeExecutionData, INodeType, - INodeTypeDescription + INodeTypeDescription, + JsonObject } from 'n8n-workflow'; import { + emeliaApiTest, emeliaGraphqlRequest, loadResource, } from './GenericFunctions'; @@ -36,7 +38,7 @@ export class Emelia implements INodeType { icon: 'file:emelia.svg', group: ['input'], version: 1, - subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}', + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Consume the Emelia API', defaults: { name: 'Emelia', @@ -47,6 +49,7 @@ export class Emelia implements INodeType { { name: 'emeliaApi', required: true, + testedBy: 'emeliaApiTest', }, ], properties: [ @@ -54,6 +57,7 @@ export class Emelia implements INodeType { displayName: 'Resource', name: 'resource', type: 'options', + noDataExpression: true, options: [ { name: 'Campaign', @@ -66,7 +70,7 @@ export class Emelia implements INodeType { ], default: 'campaign', required: true, - description: 'The resource to operate on.', + description: 'The resource to operate on', }, ...campaignOperations, ...campaignFields, @@ -76,6 +80,10 @@ export class Emelia implements INodeType { }; methods = { + credentialTest: { + emeliaApiTest, + }, + loadOptions: { async getCampaigns(this: ILoadOptionsFunctions) { return loadResource.call(this, 'campaign'); @@ -290,6 +298,46 @@ export class Emelia implements INodeType { returnData.push({ success: true }); + } else if (operation === 'duplicate') { + + // ---------------------------------- + // campaign: duplicate + // ---------------------------------- + + const options = this.getNodeParameter('options', i) as IDataObject; + const variables = { + fromId: this.getNodeParameter('campaignId', i), + name: this.getNodeParameter('campaignName', i), + copySettings: true, + copyMails: true, + copyContacts: false, + copyProvider: true, + ...options, + }; + const { data: { duplicateCampaign } } = await emeliaGraphqlRequest.call(this, { + query: ` + mutation duplicateCampaign( + $fromId: ID! + $name: String! + $copySettings: Boolean! + $copyMails: Boolean! + $copyContacts: Boolean! + $copyProvider: Boolean! + ) { + duplicateCampaign( + fromId: $fromId + name: $name + copySettings: $copySettings + copyMails: $copyMails + copyContacts: $copyContacts + copyProvider: $copyProvider + ) + }`, + operationName: 'duplicateCampaign', + variables, + }); + + returnData.push({ _id: duplicateCampaign }); } } else if (resource === 'contactList') { @@ -373,7 +421,7 @@ export class Emelia implements INodeType { } catch (error) { if (this.continueOnFail()) { - returnData.push({ error: error.message }); + returnData.push({ error: (error as JsonObject).message }); continue; } diff --git a/packages/nodes-base/nodes/Emelia/EmeliaTrigger.node.ts b/packages/nodes-base/nodes/Emelia/EmeliaTrigger.node.ts index e9023b5107..c418248f96 100644 --- a/packages/nodes-base/nodes/Emelia/EmeliaTrigger.node.ts +++ b/packages/nodes-base/nodes/Emelia/EmeliaTrigger.node.ts @@ -10,6 +10,7 @@ import { import { emeliaApiRequest, + emeliaApiTest, emeliaGraphqlRequest, } from './GenericFunctions'; @@ -23,6 +24,7 @@ export class EmeliaTrigger implements INodeType { displayName: 'Emelia Trigger', name: 'emeliaTrigger', icon: 'file:emelia.svg', + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', group: ['trigger'], version: 1, description: 'Handle Emelia campaign activity events via webhooks', @@ -35,6 +37,7 @@ export class EmeliaTrigger implements INodeType { { name: 'emeliaApi', required: true, + testedBy: 'emeliaApiTest', }, ], webhooks: [ @@ -93,6 +96,10 @@ export class EmeliaTrigger implements INodeType { }; methods = { + credentialTest: { + emeliaApiTest, + }, + loadOptions: { async getCampaigns(this: ILoadOptionsFunctions): Promise { const responseData = await emeliaGraphqlRequest.call(this, { diff --git a/packages/nodes-base/nodes/Emelia/GenericFunctions.ts b/packages/nodes-base/nodes/Emelia/GenericFunctions.ts index 745dcd5228..a1feb5efa1 100644 --- a/packages/nodes-base/nodes/Emelia/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Emelia/GenericFunctions.ts @@ -4,8 +4,12 @@ import { } from 'n8n-core'; import { + ICredentialsDecrypted, + ICredentialTestFunctions, IHookFunctions, + INodeCredentialTestResult, INodePropertyOptions, + JsonObject, NodeApiError, } from 'n8n-workflow'; @@ -51,7 +55,7 @@ export async function emeliaApiRequest( try { return await this.helpers.request!.call(this, options); } catch (error) { - throw new NodeApiError(this.getNode(), error); + throw new NodeApiError(this.getNode(), (error as JsonObject)); } } @@ -91,5 +95,47 @@ export async function loadResource( name: campaign.name, value: campaign._id, })); - +} + +export async function emeliaApiTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise { + const credentials = credential.data; + + const body = { + query: ` + query all_campaigns { + all_campaigns { + _id + name + status + createdAt + stats { + mailsSent + } + } + }`, + operationName: 'all_campaigns', + }; + + const options = { + headers: { + Authorization: credentials?.apiKey, + }, + method: 'POST', + body, + uri: `https://graphql.emelia.io/graphql`, + json: true, + }; + + try { + await this.helpers.request!(options); + } catch (error) { + return { + status: 'Error', + message: `Connection details not valid: ${(error as JsonObject).message}`, + }; + } + return { + status: 'OK', + message: 'Authentication successful!', + }; }