1
1
mirror of https://github.com/n8n-io/n8n.git synced 2024-10-26 13:29:14 +03:00

🔀 Merge branch 'feature/mautic-node' of https://github.com/RicardoE105/n8n into RicardoE105-feature/mautic-node

This commit is contained in:
Jan Oberhauser 2020-06-13 16:42:37 +02:00
commit 5430a72209
5 changed files with 809 additions and 33 deletions

View File

@ -0,0 +1,55 @@
import {
ICredentialType,
NodePropertyTypes,
} from 'n8n-workflow';
export class MauticOAuth2Api implements ICredentialType {
name = 'mauticOAuth2Api';
extends = [
'oAuth2Api',
];
displayName = 'Mautic OAuth2 API';
properties = [
{
displayName: 'URL',
name: 'url',
type: 'string' as NodePropertyTypes,
default: '',
placeholder: 'https://name.mautic.net',
},
{
displayName: 'Authorization URL',
name: 'authUrl',
type: 'string' as NodePropertyTypes,
default: '',
placeholder: 'https://name.mautic.net/oauth/v2/authorize',
required: true,
},
{
displayName: 'Access Token URL',
name: 'accessTokenUrl',
type: 'string' as NodePropertyTypes,
default: '',
placeholder: 'https://name.mautic.net/oauth/v2/token',
required: true,
},
{
displayName: 'Scope',
name: 'scope',
type: 'hidden' as NodePropertyTypes,
default: '',
},
{
displayName: 'Auth URI Query Parameters',
name: 'authQueryParameters',
type: 'hidden' as NodePropertyTypes,
default: '',
},
{
displayName: 'Authentication',
name: 'authentication',
type: 'hidden' as NodePropertyTypes,
default: 'header',
},
];
}

View File

@ -226,6 +226,94 @@ export const contactFields = [
},
},
options: [
{
displayName: 'Address',
name: 'addressUi',
placeholder: 'Address',
type: 'fixedCollection',
typeOptions: {
multipleValues: false,
},
default: {},
options: [
{
name: 'addressValues',
displayName: 'Address',
values: [
{
displayName: 'Address Line 1',
name: 'address1',
type: 'string',
default: '',
},
{
displayName: 'Address Line 2',
name: 'address2',
type: 'string',
default: '',
},
{
displayName: 'City',
name: 'city',
type: 'string',
default: '',
},
{
displayName: 'State',
name: 'state',
type: 'string',
default: '',
},
{
displayName: 'Country',
name: 'country',
type: 'string',
default: '',
},
{
displayName: 'Zip Code',
name: 'zipCode',
type: 'string',
default: '',
},
],
},
],
},
{
displayName: 'B2B or B2C',
name: 'b2bOrb2c',
type: 'options',
options: [
{
name: 'B2B',
value: 'B2B',
},
{
name: 'B2C',
value: 'B2C',
},
],
default: '',
},
{
displayName: 'CRM ID',
name: 'crmId',
type: 'string',
default: '',
},
{
displayName: 'Fax',
name: 'fax',
type: 'string',
default: '',
},
{
displayName: 'Has Purchased',
name: 'hasPurchased',
type: 'boolean',
default: false,
},
{
displayName: 'IP Address',
name: 'ipAddress',
@ -240,6 +328,12 @@ export const contactFields = [
default: '',
description: 'Date/time in UTC;',
},
{
displayName: 'Mobile',
name: 'mobile',
type: 'string',
default: '',
},
{
displayName: 'Owner ID',
name: 'ownerId',
@ -247,6 +341,112 @@ export const contactFields = [
default: '',
description: 'ID of a Mautic user to assign this contact to',
},
{
displayName: 'Phone',
name: 'phone',
type: 'string',
default: '',
},
{
displayName: 'Prospect or Customer',
name: 'prospectOrCustomer',
type: 'options',
options: [
{
name: 'Prospect',
value: 'Prospect',
},
{
name: 'Customer',
value: 'Customer',
},
],
default: '',
},
{
displayName: 'Sandbox',
name: 'sandbox',
type: 'boolean',
default: false,
},
{
displayName: 'Stage',
name: 'stage',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getStages',
},
default: '',
},
{
displayName: 'Tags',
name: 'tags',
type: 'multiOptions',
typeOptions: {
loadOptionsMethod: 'getTags',
},
default: '',
},
{
displayName: 'Social Media',
name: 'socialMediaUi',
placeholder: 'Social Media',
type: 'fixedCollection',
typeOptions: {
multipleValues: false,
},
default: {},
options: [
{
name: 'socialMediaValues',
displayName: 'Social Media',
values: [
{
displayName: 'Facebook',
name: 'facebook',
type: 'string',
default: '',
},
{
displayName: 'Foursquare',
name: 'foursquare',
type: 'string',
default: '',
},
{
displayName: 'Instagram',
name: 'instagram',
type: 'string',
default: '',
},
{
displayName: 'LinkedIn',
name: 'linkedIn',
type: 'string',
default: '',
},
{
displayName: 'Skype',
name: 'skype',
type: 'string',
default: '',
},
{
displayName: 'Twitter',
name: 'twitter',
type: 'string',
default: '',
},
],
},
],
},
{
displayName: 'Website',
name: 'website',
type: 'string',
default: '',
},
],
},
@ -318,6 +518,103 @@ export const contactFields = [
default: '',
description: 'Contact parameters',
},
{
displayName: 'Address',
name: 'addressUi',
placeholder: 'Address',
type: 'fixedCollection',
typeOptions: {
multipleValues: false,
},
displayOptions: {
show: {
'/jsonParameters': [
false,
],
},
},
default: {},
options: [
{
name: 'addressValues',
displayName: 'Address',
values: [
{
displayName: 'Address Line 1',
name: 'address1',
type: 'string',
default: '',
},
{
displayName: 'Address Line 2',
name: 'address2',
type: 'string',
default: '',
},
{
displayName: 'City',
name: 'city',
type: 'string',
default: '',
},
{
displayName: 'State',
name: 'state',
type: 'string',
default: '',
},
{
displayName: 'Country',
name: 'country',
type: 'string',
default: '',
},
{
displayName: 'Zip Code',
name: 'zipCode',
type: 'string',
default: '',
},
],
},
],
},
{
displayName: 'B2B or B2C',
name: 'b2bOrb2c',
type: 'options',
displayOptions: {
show: {
'/jsonParameters': [
false,
],
},
},
options: [
{
name: 'B2B',
value: 'B2B',
},
{
name: 'B2C',
value: 'B2C',
},
],
default: '',
},
{
displayName: 'CRM ID',
name: 'crmId',
type: 'string',
displayOptions: {
show: {
'/jsonParameters': [
false,
],
},
},
default: '',
},
{
displayName: 'Email',
name: 'email',
@ -332,6 +629,19 @@ export const contactFields = [
default: '',
description: 'Email address of the contact.',
},
{
displayName: 'Fax',
name: 'fax',
type: 'string',
displayOptions: {
show: {
'/jsonParameters': [
false,
],
},
},
default: '',
},
{
displayName: 'First Name',
name: 'firstName',
@ -346,6 +656,47 @@ export const contactFields = [
default: '',
description: 'First Name',
},
{
displayName: 'Has Purchased',
name: 'hasPurchased',
type: 'boolean',
displayOptions: {
show: {
'/jsonParameters': [
false,
],
},
},
default: false,
},
{
displayName: 'IP Address',
name: 'ipAddress',
type: 'string',
displayOptions: {
show: {
'/jsonParameters': [
false,
],
},
},
default: '',
description: 'IP address to associate with the contact',
},
{
displayName: 'Last Active',
name: 'lastActive',
type: 'dateTime',
displayOptions: {
show: {
'/jsonParameters': [
false,
],
},
},
default: '',
description: 'Date/time in UTC;',
},
{
displayName: 'Last Name',
name: 'lastName',
@ -360,6 +711,60 @@ export const contactFields = [
default: '',
description: 'LastName',
},
{
displayName: 'Mobile',
name: 'mobile',
type: 'string',
displayOptions: {
show: {
'/jsonParameters': [
false,
],
},
},
default: '',
},
{
displayName: 'Owner ID',
name: 'ownerId',
type: 'string',
displayOptions: {
show: {
'/jsonParameters': [
false,
],
},
},
default: '',
description: 'ID of a Mautic user to assign this contact to',
},
{
displayName: 'Phone',
name: 'phone',
type: 'string',
displayOptions: {
show: {
'/jsonParameters': [
false,
],
},
},
default: '',
},
{
displayName: 'Position',
name: 'position',
type: 'string',
displayOptions: {
show: {
'/jsonParameters': [
false,
],
},
},
default: '',
description: 'Position',
},
{
displayName: 'Primary Company',
name: 'company',
@ -378,9 +783,9 @@ export const contactFields = [
description: 'Primary company',
},
{
displayName: 'Position',
name: 'position',
type: 'string',
displayName: 'Prospect or Customer',
name: 'prospectOrCustomer',
type: 'options',
displayOptions: {
show: {
'/jsonParameters': [
@ -388,8 +793,62 @@ export const contactFields = [
],
},
},
options: [
{
name: 'Prospect',
value: 'Prospect',
},
{
name: 'Customer',
value: 'Customer',
},
],
default: '',
},
{
displayName: 'Sandbox',
name: 'sandbox',
type: 'boolean',
displayOptions: {
show: {
'/jsonParameters': [
false,
],
},
},
default: false,
},
{
displayName: 'Stage',
name: 'stage',
type: 'options',
displayOptions: {
show: {
'/jsonParameters': [
false,
],
},
},
typeOptions: {
loadOptionsMethod: 'getStages',
},
default: '',
},
{
displayName: 'Tags',
name: 'tags',
type: 'multiOptions',
displayOptions: {
show: {
'/jsonParameters': [
false,
],
},
},
typeOptions: {
loadOptionsMethod: 'getTags',
},
default: '',
description: 'Position',
},
{
displayName: 'Title',
@ -405,27 +864,94 @@ export const contactFields = [
default: '',
description: 'Title',
},
{
displayName: 'Social Media',
name: 'socialMediaUi',
placeholder: 'Social Media',
type: 'fixedCollection',
displayOptions: {
show: {
'/jsonParameters': [
false,
],
},
},
typeOptions: {
multipleValues: false,
},
default: {},
options: [
{
name: 'socialMediaValues',
displayName: 'Social Media',
values: [
{
displayName: 'Facebook',
name: 'facebook',
type: 'string',
default: '',
},
{
displayName: 'Foursquare',
name: 'foursquare',
type: 'string',
default: '',
},
{
displayName: 'Instagram',
name: 'instagram',
type: 'string',
default: '',
},
{
displayName: 'LinkedIn',
name: 'linkedIn',
type: 'string',
default: '',
},
{
displayName: 'Skype',
name: 'skype',
type: 'string',
default: '',
},
{
displayName: 'Twitter',
name: 'twitter',
type: 'string',
default: '',
},
],
},
],
},
{
displayName: 'Website',
name: 'website',
type: 'string',
displayOptions: {
show: {
'/jsonParameters': [
false,
],
},
},
default: '',
},
{
displayName: 'IP Address',
name: 'ipAddress',
type: 'string',
displayOptions: {
show: {
'/jsonParameters': [
false,
],
},
},
default: '',
description: 'IP address to associate with the contact',
},
{
displayName: 'Last Active',
name: 'lastActive',
type: 'dateTime',
default: '',
description: 'Date/time in UTC;',
},
{
displayName: 'Owner ID',
name: 'ownerId',
type: 'string',
default: '',
description: 'ID of a Mautic user to assign this contact to',
},
],
},

View File

@ -10,7 +10,6 @@ import {
import {
IDataObject,
} from 'n8n-workflow';
import { errors } from 'imap-simple';
interface OMauticErrorResponse {
errors: Array<{
@ -19,7 +18,7 @@ interface OMauticErrorResponse {
}>;
}
function getErrors(error: OMauticErrorResponse): string {
export function getErrors(error: OMauticErrorResponse): string {
const returnErrors: string[] = [];
for (const errorItem of error.errors) {
@ -31,23 +30,40 @@ function getErrors(error: OMauticErrorResponse): string {
export async function mauticApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query?: IDataObject, uri?: string): Promise<any> { // tslint:disable-line:no-any
const credentials = this.getCredentials('mauticApi');
if (credentials === undefined) {
throw new Error('No credentials got returned!');
}
const base64Key = Buffer.from(`${credentials.username}:${credentials.password}`).toString('base64');
const authenticationMethod = this.getNodeParameter('authentication', 0, 'credentials') as string;
const options: OptionsWithUri = {
headers: { Authorization: `Basic ${base64Key}` },
headers: {},
method,
qs: query,
uri: uri || `${credentials.url}/api${endpoint}`,
uri: uri || `/api${endpoint}`,
body,
json: true
};
try {
const returnData = await this.helpers.request!(options);
if (returnData.error) {
try {
let returnData;
if (authenticationMethod === 'credentials') {
const credentials = this.getCredentials('mauticApi') as IDataObject;
const base64Key = Buffer.from(`${credentials.username}:${credentials.password}`).toString('base64');
options.headers!.Authorization = `Basic ${base64Key}`;
options.uri = `${credentials.url}${options.uri}`;
//@ts-ignore
returnData = await this.helpers.request(options);
} else {
const credentials = this.getCredentials('mauticOAuth2Api') as IDataObject;
options.uri = `${credentials.url}${options.uri}`;
//@ts-ignore
returnData = await this.helpers.requestOAuth2.call(this, 'mauticOAuth2Api', options);
}
if (returnData.errors) {
// They seem to to sometimes return 200 status but still error.
throw new Error(getErrors(returnData));
}

View File

@ -1,5 +1,3 @@
import { snakeCase } from 'change-case';
import {
IExecuteFunctions,
} from 'n8n-core';
@ -15,12 +13,18 @@ import {
mauticApiRequest,
mauticApiRequestAllItems,
validateJSON,
getErrors,
} from './GenericFunctions';
import {
contactFields,
contactOperations,
} from './ContactDescription';
import {
snakeCase,
} from 'change-case';
export class Mautic implements INodeType {
description: INodeTypeDescription = {
displayName: 'Mautic',
@ -40,9 +44,43 @@ export class Mautic implements INodeType {
{
name: 'mauticApi',
required: true,
}
displayOptions: {
show: {
authentication: [
'credentials',
],
},
},
},
{
name: 'mauticOAuth2Api',
required: true,
displayOptions: {
show: {
authentication: [
'oAuth2',
],
},
},
},
],
properties: [
{
displayName: 'Authentication',
name: 'authentication',
type: 'options',
options: [
{
name: 'Credentials',
value: 'credentials',
},
{
name: 'OAuth2',
value: 'oAuth2',
},
],
default: 'credentials',
},
{
displayName: 'Resource',
name: 'resource',
@ -77,6 +115,32 @@ export class Mautic implements INodeType {
}
return returnData;
},
// Get all the available tags to display them to user so that he can
// select them easily
async getTags(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const tags = await mauticApiRequestAllItems.call(this, 'tags', 'GET', '/tags');
for (const tag of tags) {
returnData.push({
name: tag.tag,
value: tag.tag,
});
}
return returnData;
},
// Get all the available stages to display them to user so that he can
// select them easily
async getStages(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const stages = await mauticApiRequestAllItems.call(this, 'stages', 'GET', '/stages');
for (const stage of stages) {
returnData.push({
name: stage.name,
value: stage.id,
});
}
return returnData;
},
},
};
@ -124,6 +188,62 @@ export class Mautic implements INodeType {
if (additionalFields.ownerId) {
body.ownerId = additionalFields.ownerId as string;
}
if (additionalFields.addressUi) {
const addressValues = (additionalFields.addressUi as IDataObject).addressValues as IDataObject;
if (addressValues) {
body.address1 = addressValues.address1 as string;
body.address2 = addressValues.address2 as string;
body.city = addressValues.city as string;
body.state = addressValues.state as string;
body.country = addressValues.country as string;
body.zipcode = addressValues.zipCode as string;
}
}
if (additionalFields.socialMediaUi) {
const socialMediaValues = (additionalFields.socialMediaUi as IDataObject).socialMediaValues as IDataObject;
if (socialMediaValues) {
body.facebook = socialMediaValues.facebook as string;
body.foursquare = socialMediaValues.foursquare as string;
body.instagram = socialMediaValues.instagram as string;
body.linkedin = socialMediaValues.linkedIn as string;
body.skype = socialMediaValues.skype as string;
body.twitter = socialMediaValues.twitter as string;
}
}
if (additionalFields.b2bOrb2c) {
body.b2b_or_b2c = additionalFields.b2bOrb2c as string;
}
if (additionalFields.crmId) {
body.crm_id = additionalFields.crmId as string;
}
if (additionalFields.fax) {
body.fax = additionalFields.fax as string;
}
if (additionalFields.hasPurchased) {
body.haspurchased = additionalFields.hasPurchased as boolean;
}
if (additionalFields.mobile) {
body.mobile = additionalFields.mobile as string;
}
if (additionalFields.phone) {
body.phone = additionalFields.phone as string;
}
if (additionalFields.prospectOrCustomer) {
body.prospect_or_customer = additionalFields.prospectOrCustomer as string;
}
if (additionalFields.sandbox) {
body.sandbox = additionalFields.sandbox as boolean;
}
if (additionalFields.stage) {
body.stage = additionalFields.stage as string;
}
if (additionalFields.tags) {
body.tags = additionalFields.tags as string;
}
if (additionalFields.website) {
body.website = additionalFields.website as string;
}
responseData = await mauticApiRequest.call(this, 'POST', '/contacts/new', body);
responseData = responseData.contact;
}
@ -167,6 +287,61 @@ export class Mautic implements INodeType {
if (updateFields.ownerId) {
body.ownerId = updateFields.ownerId as string;
}
if (updateFields.addressUi) {
const addressValues = (updateFields.addressUi as IDataObject).addressValues as IDataObject;
if (addressValues) {
body.address1 = addressValues.address1 as string;
body.address2 = addressValues.address2 as string;
body.city = addressValues.city as string;
body.state = addressValues.state as string;
body.country = addressValues.country as string;
body.zipcode = addressValues.zipCode as string;
}
}
if (updateFields.socialMediaUi) {
const socialMediaValues = (updateFields.socialMediaUi as IDataObject).socialMediaValues as IDataObject;
if (socialMediaValues) {
body.facebook = socialMediaValues.facebook as string;
body.foursquare = socialMediaValues.foursquare as string;
body.instagram = socialMediaValues.instagram as string;
body.linkedin = socialMediaValues.linkedIn as string;
body.skype = socialMediaValues.skype as string;
body.twitter = socialMediaValues.twitter as string;
}
}
if (updateFields.b2bOrb2c) {
body.b2b_or_b2c = updateFields.b2bOrb2c as string;
}
if (updateFields.crmId) {
body.crm_id = updateFields.crmId as string;
}
if (updateFields.fax) {
body.fax = updateFields.fax as string;
}
if (updateFields.hasPurchased) {
body.haspurchased = updateFields.hasPurchased as boolean;
}
if (updateFields.mobile) {
body.mobile = updateFields.mobile as string;
}
if (updateFields.phone) {
body.phone = updateFields.phone as string;
}
if (updateFields.prospectOrCustomer) {
body.prospect_or_customer = updateFields.prospectOrCustomer as string;
}
if (updateFields.sandbox) {
body.sandbox = updateFields.sandbox as boolean;
}
if (updateFields.stage) {
body.stage = updateFields.stage as string;
}
if (updateFields.tags) {
body.tags = updateFields.tags as string;
}
if (updateFields.website) {
body.website = updateFields.website as string;
}
responseData = await mauticApiRequest.call(this, 'PATCH', `/contacts/${contactId}/edit`, body);
responseData = responseData.contact;
}
@ -193,6 +368,9 @@ export class Mautic implements INodeType {
qs.limit = this.getNodeParameter('limit', i) as number;
qs.start = 0;
responseData = await mauticApiRequest.call(this, 'GET', '/contacts', {}, qs);
if (responseData.errors) {
throw new Error(getErrors(responseData));
}
responseData = responseData.contacts;
responseData = Object.values(responseData);
}

View File

@ -85,6 +85,7 @@
"dist/credentials/MandrillApi.credentials.js",
"dist/credentials/MattermostApi.credentials.js",
"dist/credentials/MauticApi.credentials.js",
"dist/credentials/MauticOAuth2Api.credentials.js",
"dist/credentials/MessageBirdApi.credentials.js",
"dist/credentials/MicrosoftExcelOAuth2Api.credentials.js",
"dist/credentials/MicrosoftOAuth2Api.credentials.js",