mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 04:47:29 -08:00
✨ Add Emelia nodes (#1455)
* Emelia node added * Minor improvements on Emelia nodes * Fix nodes and credentials listing * Fix multi-line imports * Apply cosmetic changes to node description * Apply cosmetic changes to node execute method * Fix linting details * Apply cosmetic changes to trigger node * Replace PNG with SVG icon * Bring generic functions in line with codebase * Refactor resources and add operations * Fix typo in GraphQL call function * Add campaign description * Add contact list description * ⚡ Improvements * ⚡ Minor improvements to Emelia Nodes Co-authored-by: Charles LECALIER <clecalie@student.42.fr> Co-authored-by: ricardo <ricardoespinoza105@gmail.com> Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
parent
4dce9e2cd1
commit
4d4ab7943b
18
packages/nodes-base/credentials/EmeliaApi.credentials.ts
Normal file
18
packages/nodes-base/credentials/EmeliaApi.credentials.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class EmeliaApi implements ICredentialType {
|
||||
name = 'emeliaApi';
|
||||
displayName = 'Emelia API';
|
||||
documentationUrl = 'emelia';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'API Key',
|
||||
name: 'apiKey',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
326
packages/nodes-base/nodes/Emelia/CampaignDescription.ts
Normal file
326
packages/nodes-base/nodes/Emelia/CampaignDescription.ts
Normal file
|
@ -0,0 +1,326 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const campaignOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
default: 'get',
|
||||
description: 'Operation to perform',
|
||||
options: [
|
||||
{
|
||||
name: 'Add Contact',
|
||||
value: 'addContact',
|
||||
},
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
},
|
||||
{
|
||||
name: 'Pause',
|
||||
value: 'pause',
|
||||
},
|
||||
{
|
||||
name: 'Start',
|
||||
value: 'start',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const campaignFields = [
|
||||
// ----------------------------------
|
||||
// campaign: addContact
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Campaign ID',
|
||||
name: 'campaignId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getCampaigns',
|
||||
},
|
||||
default: [],
|
||||
required: true,
|
||||
description: 'The ID of the campaign to add the contact to.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'addContact',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Contact Email',
|
||||
name: 'contactEmail',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'The email of the contact to add to the campaign.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'addContact',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'addContact',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Custom Fields',
|
||||
name: 'customFieldsUi',
|
||||
placeholder: 'Add Custom Field',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
description: 'Filter by custom fields ',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'customFieldsValues',
|
||||
displayName: 'Custom Field',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Field Name',
|
||||
name: 'fieldName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
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.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'First Name',
|
||||
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.',
|
||||
},
|
||||
{
|
||||
displayName: 'Last Contacted',
|
||||
name: 'lastContacted',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Last contacted date of the contact to add.',
|
||||
},
|
||||
{
|
||||
displayName: 'Last Open',
|
||||
name: 'lastOpen',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Last opened date of the contact to add.',
|
||||
},
|
||||
{
|
||||
displayName: 'Last Replied',
|
||||
name: 'lastReplied',
|
||||
type: 'string',
|
||||
default: '',
|
||||
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.',
|
||||
},
|
||||
{
|
||||
displayName: 'Phone Number',
|
||||
name: 'phoneNumber',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Phone number of the contact to add.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// campaign: create
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Campaign Name',
|
||||
name: 'campaignName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'The name of the campaign to create.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// campaign: get
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Campaign ID',
|
||||
name: 'campaignId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'The ID of the campaign to retrieve.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// campaign: getAll
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Return all results.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 100,
|
||||
description: 'The number of results to return.',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// campaign: pause
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Campaign ID',
|
||||
name: 'campaignId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'The ID of the campaign to pause.<br>The campaign must be in RUNNING mode.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'pause',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// campaign: start
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Campaign ID',
|
||||
name: 'campaignId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'The ID of the campaign to start.<br>Email provider and contacts must be set.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'start',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
] as INodeProperties[];
|
221
packages/nodes-base/nodes/Emelia/ContactListDescription.ts
Normal file
221
packages/nodes-base/nodes/Emelia/ContactListDescription.ts
Normal file
|
@ -0,0 +1,221 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const contactListOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
default: 'get',
|
||||
description: 'Operation to perform',
|
||||
options: [
|
||||
{
|
||||
name: 'Add',
|
||||
value: 'add',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contactList',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const contactListFields = [
|
||||
// ----------------------------------
|
||||
// contactList: add
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Contact List ID',
|
||||
name: 'contactListId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getContactLists',
|
||||
},
|
||||
default: [],
|
||||
required: true,
|
||||
description: 'The ID of the contact list to add the contact to.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contactList',
|
||||
],
|
||||
operation: [
|
||||
'add',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Contact Email',
|
||||
name: 'contactEmail',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'The email of the contact to add to the contact list.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contactList',
|
||||
],
|
||||
operation: [
|
||||
'add',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contactList',
|
||||
],
|
||||
operation: [
|
||||
'add',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Custom Fields',
|
||||
name: 'customFieldsUi',
|
||||
placeholder: 'Add Custom Field',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
description: 'Filter by custom fields ',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'customFieldsValues',
|
||||
displayName: 'Custom Field',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Field Name',
|
||||
name: 'fieldName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
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.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'First Name',
|
||||
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.',
|
||||
},
|
||||
{
|
||||
displayName: 'Last Contacted',
|
||||
name: 'lastContacted',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'Last contacted date of the contact to add.',
|
||||
},
|
||||
{
|
||||
displayName: 'Last Open',
|
||||
name: 'lastOpen',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
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.',
|
||||
},
|
||||
{
|
||||
displayName: 'Mails Sent',
|
||||
name: 'mailsSent',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
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.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// contactList: getAll
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Return all results.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contactList',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 100,
|
||||
description: 'The number of results to return.',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contactList',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
] as INodeProperties[];
|
387
packages/nodes-base/nodes/Emelia/Emelia.node.ts
Normal file
387
packages/nodes-base/nodes/Emelia/Emelia.node.ts
Normal file
|
@ -0,0 +1,387 @@
|
|||
import {
|
||||
IExecuteFunctions
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
emeliaGraphqlRequest,
|
||||
loadResource,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
campaignFields,
|
||||
campaignOperations,
|
||||
} from './CampaignDescription';
|
||||
|
||||
import {
|
||||
contactListFields,
|
||||
contactListOperations,
|
||||
} from './ContactListDescription';
|
||||
|
||||
import {
|
||||
isEmpty,
|
||||
} from 'lodash';
|
||||
|
||||
export class Emelia implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Emelia',
|
||||
name: 'emelia',
|
||||
icon: 'file:emelia.svg',
|
||||
group: ['input'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}',
|
||||
description: 'Consume the Emelia API',
|
||||
defaults: {
|
||||
name: 'Emelia',
|
||||
color: '#e18063',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'emeliaApi',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Campaign',
|
||||
value: 'campaign',
|
||||
},
|
||||
{
|
||||
name: 'Contact List',
|
||||
value: 'contactList',
|
||||
},
|
||||
],
|
||||
default: 'campaign',
|
||||
required: true,
|
||||
description: 'The resource to operate on.',
|
||||
},
|
||||
...campaignOperations,
|
||||
...campaignFields,
|
||||
...contactListOperations,
|
||||
...contactListFields,
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
async getCampaigns(this: ILoadOptionsFunctions) {
|
||||
return loadResource.call(this, 'campaign');
|
||||
},
|
||||
|
||||
async getContactLists(this: ILoadOptionsFunctions) {
|
||||
return loadResource.call(this, 'contactList');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
const resource = this.getNodeParameter('resource', 0);
|
||||
const operation = this.getNodeParameter('operation', 0);
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
|
||||
try {
|
||||
|
||||
if (resource === 'campaign') {
|
||||
|
||||
// **********************************
|
||||
// campaign
|
||||
// **********************************
|
||||
|
||||
if (operation === 'addContact') {
|
||||
|
||||
// ----------------------------------
|
||||
// campaign: addContact
|
||||
// ----------------------------------
|
||||
|
||||
const contact = {
|
||||
email: this.getNodeParameter('contactEmail', i) as string,
|
||||
};
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (!isEmpty(additionalFields)) {
|
||||
Object.assign(contact, additionalFields);
|
||||
}
|
||||
|
||||
if (additionalFields.customFieldsUi) {
|
||||
const customFields = (additionalFields.customFieldsUi as IDataObject || {}).customFieldsValues as IDataObject[] || [];
|
||||
const data = customFields.reduce((obj, value) => Object.assign(obj, { [`${value.fieldName}`]: value.value }), {});
|
||||
Object.assign(contact, data);
|
||||
//@ts-ignore
|
||||
delete contact.customFieldsUi;
|
||||
}
|
||||
|
||||
const responseData = await emeliaGraphqlRequest.call(this, {
|
||||
query: `
|
||||
mutation AddContactToCampaignHook($id: ID!, $contact: JSON!) {
|
||||
addContactToCampaignHook(id: $id, contact: $contact)
|
||||
}`,
|
||||
operationName: 'AddContactToCampaignHook',
|
||||
variables: {
|
||||
id: this.getNodeParameter('campaignId', i),
|
||||
contact,
|
||||
},
|
||||
});
|
||||
|
||||
returnData.push({ contactId: responseData.data.addContactToCampaignHook });
|
||||
|
||||
} else if (operation === 'create') {
|
||||
|
||||
// ----------------------------------
|
||||
// campaign: create
|
||||
// ----------------------------------
|
||||
|
||||
const responseData = await emeliaGraphqlRequest.call(this, {
|
||||
operationName: 'createCampaign',
|
||||
query: `
|
||||
mutation createCampaign($name: String!) {
|
||||
createCampaign(name: $name) {
|
||||
_id
|
||||
name
|
||||
status
|
||||
createdAt
|
||||
provider
|
||||
startAt
|
||||
estimatedEnd
|
||||
}
|
||||
}`,
|
||||
variables: {
|
||||
name: this.getNodeParameter('campaignName', i),
|
||||
},
|
||||
});
|
||||
|
||||
returnData.push(responseData.data.createCampaign);
|
||||
|
||||
} else if (operation === 'get') {
|
||||
|
||||
// ----------------------------------
|
||||
// campaign: get
|
||||
// ----------------------------------
|
||||
|
||||
const responseData = await emeliaGraphqlRequest.call(this, {
|
||||
query: `
|
||||
query campaign($id: ID!){
|
||||
campaign(id: $id){
|
||||
_id
|
||||
name
|
||||
status
|
||||
createdAt
|
||||
schedule{
|
||||
dailyContact
|
||||
dailyLimit
|
||||
minInterval
|
||||
maxInterval
|
||||
trackLinks
|
||||
trackOpens
|
||||
timeZone
|
||||
days
|
||||
start
|
||||
end
|
||||
eventToStopMails
|
||||
}
|
||||
provider
|
||||
startAt
|
||||
recipients{
|
||||
total_count
|
||||
}
|
||||
estimatedEnd
|
||||
}
|
||||
}`,
|
||||
operationName: 'campaign',
|
||||
variables: {
|
||||
id: this.getNodeParameter('campaignId', i),
|
||||
},
|
||||
});
|
||||
|
||||
returnData.push(responseData.data.campaign);
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------
|
||||
// campaign: getAll
|
||||
// ----------------------------------
|
||||
|
||||
const responseData = await emeliaGraphqlRequest.call(this, {
|
||||
query: `
|
||||
query all_campaigns {
|
||||
all_campaigns {
|
||||
_id
|
||||
name
|
||||
status
|
||||
createdAt
|
||||
stats {
|
||||
mailsSent
|
||||
uniqueOpensPercent
|
||||
opens
|
||||
linkClickedPercent
|
||||
repliedPercent
|
||||
bouncedPercent
|
||||
unsubscribePercent
|
||||
progressPercent
|
||||
}
|
||||
}
|
||||
}`,
|
||||
operationName: 'all_campaigns',
|
||||
});
|
||||
|
||||
let campaigns = responseData.data.all_campaigns;
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', i);
|
||||
|
||||
if (!returnAll) {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
campaigns = campaigns.slice(0, limit);
|
||||
}
|
||||
|
||||
returnData.push(...campaigns);
|
||||
|
||||
} else if (operation === 'pause') {
|
||||
|
||||
// ----------------------------------
|
||||
// campaign: pause
|
||||
// ----------------------------------
|
||||
|
||||
await emeliaGraphqlRequest.call(this, {
|
||||
query: `
|
||||
mutation pauseCampaign($id: ID!) {
|
||||
pauseCampaign(id: $id)
|
||||
}`,
|
||||
operationName: 'pauseCampaign',
|
||||
variables: {
|
||||
id: this.getNodeParameter('campaignId', i),
|
||||
},
|
||||
});
|
||||
|
||||
returnData.push({ success: true });
|
||||
|
||||
} else if (operation === 'start') {
|
||||
|
||||
// ----------------------------------
|
||||
// campaign: start
|
||||
// ----------------------------------
|
||||
|
||||
await emeliaGraphqlRequest.call(this, {
|
||||
query: `
|
||||
mutation startCampaign($id: ID!) {
|
||||
startCampaign(id: $id)
|
||||
}`,
|
||||
operationName: 'startCampaign',
|
||||
variables: {
|
||||
id: this.getNodeParameter('campaignId', i),
|
||||
},
|
||||
});
|
||||
|
||||
returnData.push({ success: true });
|
||||
|
||||
}
|
||||
|
||||
} else if (resource === 'contactList') {
|
||||
|
||||
// **********************************
|
||||
// ContactList
|
||||
// **********************************
|
||||
|
||||
if (operation === 'add') {
|
||||
|
||||
// ----------------------------------
|
||||
// contactList: add
|
||||
// ----------------------------------
|
||||
|
||||
const contact = {
|
||||
email: this.getNodeParameter('contactEmail', i) as string,
|
||||
};
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (!isEmpty(additionalFields)) {
|
||||
Object.assign(contact, additionalFields);
|
||||
}
|
||||
|
||||
if (additionalFields.customFieldsUi) {
|
||||
const customFields = (additionalFields.customFieldsUi as IDataObject || {}).customFieldsValues as IDataObject[] || [];
|
||||
const data = customFields.reduce((obj, value) => Object.assign(obj, { [`${value.fieldName}`]: value.value }), {});
|
||||
Object.assign(contact, data);
|
||||
//@ts-ignore
|
||||
delete contact.customFieldsUi;
|
||||
}
|
||||
|
||||
const responseData = await emeliaGraphqlRequest.call(this, {
|
||||
query: `
|
||||
mutation AddContactsToListHook($id: ID!, $contact: JSON!) {
|
||||
addContactsToListHook(id: $id, contact: $contact)
|
||||
}`,
|
||||
operationName: 'AddContactsToListHook',
|
||||
variables: {
|
||||
id: this.getNodeParameter('contactListId', i),
|
||||
contact,
|
||||
},
|
||||
});
|
||||
|
||||
returnData.push({ contactId: responseData.data.addContactsToListHook });
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------
|
||||
// contactList: getAll
|
||||
// ----------------------------------
|
||||
|
||||
const responseData = await emeliaGraphqlRequest.call(this, {
|
||||
query: `
|
||||
query contact_lists{
|
||||
contact_lists{
|
||||
_id
|
||||
name
|
||||
contactCount
|
||||
fields
|
||||
usedInCampaign
|
||||
}
|
||||
}`,
|
||||
operationName: 'contact_lists',
|
||||
});
|
||||
|
||||
let contactLists = responseData.data.contact_lists;
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', i);
|
||||
|
||||
if (!returnAll) {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
contactLists = contactLists.slice(0, limit);
|
||||
}
|
||||
|
||||
returnData.push(...contactLists);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ error: error.message });
|
||||
continue;
|
||||
}
|
||||
|
||||
throw error;
|
||||
|
||||
}
|
||||
}
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
180
packages/nodes-base/nodes/Emelia/EmeliaTrigger.node.ts
Normal file
180
packages/nodes-base/nodes/Emelia/EmeliaTrigger.node.ts
Normal file
|
@ -0,0 +1,180 @@
|
|||
import {
|
||||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
IWebhookFunctions,
|
||||
IWebhookResponseData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
emeliaApiRequest,
|
||||
emeliaGraphqlRequest,
|
||||
} from './GenericFunctions';
|
||||
|
||||
interface Campaign {
|
||||
_id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class EmeliaTrigger implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Emelia Trigger',
|
||||
name: 'emeliaTrigger',
|
||||
icon: 'file:emelia.svg',
|
||||
group: ['trigger'],
|
||||
version: 1,
|
||||
description: 'Handle Emelia campaign activity events via webhooks',
|
||||
defaults: {
|
||||
name: 'Emelia Trigger',
|
||||
color: '#e18063',
|
||||
},
|
||||
inputs: [],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'emeliaApi',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
webhooks: [
|
||||
{
|
||||
name: 'default',
|
||||
httpMethod: 'POST',
|
||||
responseMode: 'onReceived',
|
||||
path: 'webhook',
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Campaign',
|
||||
name: 'campaignId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getCampaigns',
|
||||
},
|
||||
required: true,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Events',
|
||||
name: 'events',
|
||||
type: 'multiOptions',
|
||||
required: true,
|
||||
default: [],
|
||||
options: [
|
||||
{
|
||||
name: 'Email Bounced',
|
||||
value: 'bounced',
|
||||
},
|
||||
{
|
||||
name: 'Email Opened',
|
||||
value: 'opened',
|
||||
},
|
||||
{
|
||||
name: 'Email Replied',
|
||||
value: 'replied',
|
||||
},
|
||||
{
|
||||
name: 'Email Sent',
|
||||
value: 'sent',
|
||||
},
|
||||
{
|
||||
name: 'Link Clicked',
|
||||
value: 'clicked',
|
||||
},
|
||||
{
|
||||
name: 'Unsubscribed Contact',
|
||||
value: 'unsubscribed',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
async getCampaigns(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const responseData = await emeliaGraphqlRequest.call(this, {
|
||||
query: `
|
||||
query GetCampaigns {
|
||||
campaigns {
|
||||
_id
|
||||
name
|
||||
}
|
||||
}`,
|
||||
operationName: 'GetCampaigns',
|
||||
variables: '{}',
|
||||
});
|
||||
|
||||
return responseData.data.campaigns.map(
|
||||
(campaign: Campaign) => ({
|
||||
name: campaign.name,
|
||||
value: campaign._id,
|
||||
}),
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
webhookMethods = {
|
||||
default: {
|
||||
async checkExists(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookUrl = this.getNodeWebhookUrl('default') as string;
|
||||
const campaignId = this.getNodeParameter('campaignId') as string;
|
||||
const { webhooks } = await emeliaApiRequest.call(this, 'GET', '/webhook');
|
||||
for (const webhook of webhooks) {
|
||||
if (webhook.url === webhookUrl && webhook.campaignId === campaignId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
async create(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookUrl = this.getNodeWebhookUrl('default') as string;
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
const events = this.getNodeParameter('events') as string[];
|
||||
|
||||
const campaignId = this.getNodeParameter('campaignId') as string;
|
||||
const body = {
|
||||
hookUrl: webhookUrl,
|
||||
events: events.map(e => e.toUpperCase()),
|
||||
campaignId,
|
||||
};
|
||||
|
||||
const { webhookId } = await emeliaApiRequest.call(this, 'POST', '/webhook/webhook', body);
|
||||
webhookData.webhookId = webhookId;
|
||||
return true;
|
||||
},
|
||||
async delete(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
const webhookUrl = this.getNodeWebhookUrl('default') as string;
|
||||
const campaignId = this.getNodeParameter('campaignId') as string;
|
||||
|
||||
try {
|
||||
const body = {
|
||||
hookUrl: webhookUrl,
|
||||
campaignId,
|
||||
};
|
||||
await emeliaApiRequest.call(this, 'DELETE', '/webhook/webhook', body);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
delete webhookData.webhookId;
|
||||
return true;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
|
||||
const req = this.getRequestObject();
|
||||
return {
|
||||
workflowData: [
|
||||
this.helpers.returnJsonArray(req.body),
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
104
packages/nodes-base/nodes/Emelia/GenericFunctions.ts
Normal file
104
packages/nodes-base/nodes/Emelia/GenericFunctions.ts
Normal file
|
@ -0,0 +1,104 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IHookFunctions,
|
||||
INodePropertyOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
/**
|
||||
* Make an authenticated GraphQL request to Emelia.
|
||||
*/
|
||||
export async function emeliaGraphqlRequest(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||
body: object = {},
|
||||
) {
|
||||
const response = await emeliaApiRequest.call(this, 'POST', '/graphql', body);
|
||||
|
||||
if (response.errors) {
|
||||
throw new Error(`Emelia error message: ${response.errors[0].message}`);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an authenticated REST API request to Emelia, used for trigger node.
|
||||
*/
|
||||
export async function emeliaApiRequest(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions,
|
||||
method: string,
|
||||
endpoint: string,
|
||||
body: object = {},
|
||||
qs: object = {},
|
||||
) {
|
||||
const { apiKey } = this.getCredentials('emeliaApi') as { apiKey: string };
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
Authorization: apiKey,
|
||||
},
|
||||
method,
|
||||
body,
|
||||
qs,
|
||||
uri: `https://graphql.emelia.io${endpoint}`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
return await this.helpers.request!.call(this, options);
|
||||
|
||||
} catch (error) {
|
||||
|
||||
if (error?.response?.body?.error) {
|
||||
const { error: errorMessage } = error.response.body;
|
||||
throw new Error(
|
||||
`Emelia error response [${error.statusCode}]: ${errorMessage}`,
|
||||
);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load resources so that the user can select them easily.
|
||||
*/
|
||||
export async function loadResource(
|
||||
this: ILoadOptionsFunctions,
|
||||
resource: 'campaign' | 'contactList',
|
||||
): Promise<INodePropertyOptions[]> {
|
||||
const mapping: { [key in 'campaign' | 'contactList']: { query: string, key: string } } = {
|
||||
campaign: {
|
||||
query: `
|
||||
query GetCampaigns {
|
||||
campaigns {
|
||||
_id
|
||||
name
|
||||
}
|
||||
}`,
|
||||
key: 'campaigns',
|
||||
},
|
||||
contactList: {
|
||||
query: `
|
||||
query GetContactLists {
|
||||
contact_lists {
|
||||
_id
|
||||
name
|
||||
}
|
||||
}`,
|
||||
key: 'contact_lists',
|
||||
},
|
||||
};
|
||||
|
||||
const responseData = await emeliaGraphqlRequest.call(this, { query: mapping[resource].query });
|
||||
|
||||
return responseData.data[mapping[resource].key].map((campaign: { name: string, _id: string }) => ({
|
||||
name: campaign.name,
|
||||
value: campaign._id,
|
||||
}));
|
||||
|
||||
}
|
1
packages/nodes-base/nodes/Emelia/emelia.svg
Normal file
1
packages/nodes-base/nodes/Emelia/emelia.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg viewBox="-2 2 45 45" xmlns="http://www.w3.org/2000/svg"><path d="M29.714 6.539H9.078a2.109 2.109 0 00-2.007 2.762l5.484 16.863 4.219-1.817a2.69 2.69 0 01-.024-.34 2.641 2.641 0 111.023 2.081 2416.71 2416.71 0 01-4.415 2.545l5.156 15.852a.865.865 0 001.645 0l11.48-35.293a2.026 2.026 0 00-1.925-2.653z" opacity="0.9" fill="#f4b454"></path><path d="M37.154 21.261L1.071 12.577a.865.865 0 00-.861 1.4l10.909 12.8 5.656-2.436a2.694 2.694 0 01-.024-.34 2.641 2.641 0 111.023 2.081 2178.15 2178.15 0 01-4.86 2.8l11.291 13.255a2.11 2.11 0 003.4-.264l10.8-17.586a2.026 2.026 0 00-1.251-3.026z" fill="#ef6d4a" opacity="0.9"></path><path d="M37.636 12.577L1.553 21.261A2.025 2.025 0 00.301 24.29l3.472 5.656 13-5.6a2.689 2.689 0 01-.024-.34 2.641 2.641 0 111.023 2.081c-3.1 1.791-9.073 5.234-12.108 6.94l5.432 8.848a2.11 2.11 0 003.4.264l23.995-28.161a.865.865 0 00-.855-1.401z" fill="#20354c" opacity="0.9"></path></svg>
|
After Width: | Height: | Size: 917 B |
|
@ -70,6 +70,7 @@
|
|||
"dist/credentials/DropboxApi.credentials.js",
|
||||
"dist/credentials/DropboxOAuth2Api.credentials.js",
|
||||
"dist/credentials/EgoiApi.credentials.js",
|
||||
"dist/credentials/EmeliaApi.credentials.js",
|
||||
"dist/credentials/EventbriteApi.credentials.js",
|
||||
"dist/credentials/EventbriteOAuth2Api.credentials.js",
|
||||
"dist/credentials/FacebookGraphApi.credentials.js",
|
||||
|
@ -317,6 +318,8 @@
|
|||
"dist/nodes/Egoi/Egoi.node.js",
|
||||
"dist/nodes/EmailReadImap.node.js",
|
||||
"dist/nodes/EmailSend.node.js",
|
||||
"dist/nodes/Emelia/Emelia.node.js",
|
||||
"dist/nodes/Emelia/EmeliaTrigger.node.js",
|
||||
"dist/nodes/ErrorTrigger.node.js",
|
||||
"dist/nodes/Eventbrite/EventbriteTrigger.node.js",
|
||||
"dist/nodes/ExecuteCommand.node.js",
|
||||
|
|
Loading…
Reference in a new issue