From f8223c6e98bebd0ad788f18d2af3101609486c2b Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Thu, 10 Sep 2020 15:56:15 -0400 Subject: [PATCH] :zap: Add Freshdesk contacts resource (#938) * Freshdesk - add contact integration * fix linter errors * :zap: Small improvements Co-authored-by: Yonatan Rosemarin --- .../nodes/Freshdesk/ContactDescription.ts | 431 ++++++++++++++++++ .../nodes/Freshdesk/ContactInterface.ts | 24 + .../nodes/Freshdesk/Freshdesk.node.ts | 80 +++- .../nodes/Freshdesk/GenericFunctions.ts | 8 +- 4 files changed, 538 insertions(+), 5 deletions(-) create mode 100644 packages/nodes-base/nodes/Freshdesk/ContactDescription.ts create mode 100644 packages/nodes-base/nodes/Freshdesk/ContactInterface.ts diff --git a/packages/nodes-base/nodes/Freshdesk/ContactDescription.ts b/packages/nodes-base/nodes/Freshdesk/ContactDescription.ts new file mode 100644 index 0000000000..e5dd39e3d4 --- /dev/null +++ b/packages/nodes-base/nodes/Freshdesk/ContactDescription.ts @@ -0,0 +1,431 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const contactOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'contact', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new contact', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a contact', + }, + { + name: 'Get', + value: 'get', + description: 'Get a contact', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all contacts', + }, + { + name: 'Update', + value: 'update', + description: 'Update a contact', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const contactFields = [ + + /* -------------------------------------------------------------------------- */ + /* contact:create/update */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Name', + name: 'name', + type: 'string', + placeholder: '', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'contact', + ], + }, + }, + default: '', + description: 'Name of the contact.', + required: true, + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'contact', + ], + }, + }, + description: `Primary email address of the contact. If you want to associate
+ additional email(s) with this contact, use the other_emails attribute.`, + }, + { + displayName: 'Contact ID', + name: 'contactId', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'update', + ], + }, + }, + required: true, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'create', + 'update', + ], + resource: [ + 'contact', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Address', + name: 'address', + type: 'string', + default: '', + description: 'Address of the contact.', + }, + // { + // displayName: 'Avatar', + // name: 'avatar', + // type: '', + // default: '', + // description: `Avatar image of the contact The maximum file size is 5MB
+ // and the supported file types are .jpg, .jpeg, .jpe, and .png.`, + // }, + { + displayName: 'Company ID', + name: 'company_id', + type: 'number', + default: '', + description: 'ID of the primary company to which this contact belongs.', + }, + { + displayName: 'Custom Fields', + name: 'customFields', + type: 'fixedCollection', + placeholder: 'Add Custom Field', + typeOptions: { + multipleValues: true, + }, + description: `Key value pairs containing the name and value of the custom field.
+ Only dates in the format YYYY-MM-DD are accepted as input for custom date fields.`, + default: {}, + options: [ + { + displayName: 'Custom Field', + name: 'customField', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: `Custom Field\'s name.`, + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: `Custom Field\'s values.`, + }, + ] + }, + ], + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + description: 'A small description of the contact.', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + displayOptions: { + show: { + '/operation': [ + 'update', + ], + }, + }, + description: `Primary email address of the contact. If you want to associate
+ additional email(s) with this contact, use the other_emails attribute.`, + }, + { + displayName: 'Job Title', + name: 'job_title', + type: 'string', + default: '', + description: 'Job title of the contact.', + }, + { + displayName: 'Language', + name: 'language', + type: 'string', + default: '', + description: `Language of the contact. Default language is "en".
+ This attribute can only be set if the Multiple Language feature is
+ enabled (Garden plan and above).`, + }, + { + displayName: 'Mobile', + name: 'mobile', + type: 'string', + default: '', + description: 'Mobile number of the contact.', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + displayOptions: { + show: { + '/operation': [ + 'update', + ], + }, + }, + description: 'Name of the contact.', + }, + { + displayName: 'Other Companies', + name: 'other_companies', + type: 'string', + default: '', + typeOptions: { + multipleValues: true, + }, + placeholder: 'Add Company', + description: `Additional companies associated with the contact.
+ This attribute can only be set if the Multiple Companies feature
+ is enabled (Estate plan and above).`, + }, + { + displayName: 'Other Emails', + name: 'other_emails', + type: 'string', + default: '', + typeOptions: { + multipleValues: true, + }, + placeholder: 'Add Email', + description: 'Additional emails associated with the contact.', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + description: 'Telephone number of the contact.', + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: '', + typeOptions: { + multipleValues: true, + }, + description: 'Tags associated with this contact.', + }, + { + displayName: 'Time Zone', + name: 'time_zone', + type: 'string', + default: '', + description: `Time zone of the contact. Default value is the time zone of the domain.
+ This attribute can only be set if the Multiple Time Zone feature is enabled (Garden plan and above)];`, + }, + { + displayName: 'Twitter ID', + name: 'twitter_id', + type: 'string', + default: '', + description: 'Twitter handle of the contact.', + }, + { + displayName: 'Unique External ID', + name: 'unique_external_id', + type: 'string', + default: '', + description: 'External ID of the contact.', + }, + { + displayName: 'View All Tickets', + name: 'view_all_tickets', + type: 'boolean', + default: '', + description: `Set to true if the contact can see all the tickets
+ that are associated with the company to which he belong.`, + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* contact:delete */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Contact ID', + name: 'contactId', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'delete', + ], + }, + }, + required: true, + }, + /* -------------------------------------------------------------------------- */ + /* contact:get */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Contact ID', + name: 'contactId', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'get', + ], + }, + }, + required: true, + }, + /* -------------------------------------------------------------------------- */ + /* contact:getAll */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'contact', + ], + }, + }, + options: [ + { + displayName: 'Company ID', + name: 'company_id', + type: 'number', + default: '', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + }, + { + displayName: 'Mobile', + name: 'mobile', + type: 'string', + default: '', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + }, + { + displayName: 'State', + name: 'state', + type: 'options', + default: '', + options: [ + { + name: 'Blocked', + value: 'blocked', + }, + { + name: 'Deleted', + value: 'deleted', + }, + { + name: 'Unverified', + value: 'unverified', + }, + { + name: 'Verified', + value: 'verified', + }, + ], + }, + { + displayName: 'Updated Since', + name: 'updated_since', + type: 'dateTime', + default: '', + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Freshdesk/ContactInterface.ts b/packages/nodes-base/nodes/Freshdesk/ContactInterface.ts new file mode 100644 index 0000000000..431f1589fe --- /dev/null +++ b/packages/nodes-base/nodes/Freshdesk/ContactInterface.ts @@ -0,0 +1,24 @@ +import { + IDataObject, +} from 'n8n-workflow'; + +export interface ICreateContactBody { + address?: string; + // avatar?: object; + company_id?: number; + custom_fields?: IDataObject; + description?: string; + email?: string; + job_title?: string; + language?: string; + mobile?: string; + name?: string; + other_companies?: string[]; + other_emails?: string[]; + phone?: string; + tags?: string[]; + time_zone?: string; + twitter_id?: string; + unique_external_id?: string; + view_all_tickets?: boolean; +} diff --git a/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts b/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts index 3e9217d3a3..eba94626b8 100644 --- a/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts +++ b/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts @@ -6,9 +6,11 @@ import { ILoadOptionsFunctions, INodePropertyOptions, } from 'n8n-workflow'; + import { IExecuteFunctions, } from 'n8n-core'; + import { freshdeskApiRequest, freshdeskApiRequestAllItems, @@ -16,6 +18,18 @@ import { capitalize } from './GenericFunctions'; +import { + ICreateContactBody, +} from './ContactInterface'; + +import { + contactFields, + contactOperations, +} from './ContactDescription'; + +import * as moment from 'moment-timezone'; +import { response } from 'express'; + enum Status { Open = 2, Pending = 3, @@ -77,7 +91,7 @@ export class Freshdesk implements INodeType { description: 'Consume Freshdesk API', defaults: { name: 'Freshdesk', - color: '#c02428', + color: '#25c10b', }, inputs: ['main'], outputs: ['main'], @@ -94,6 +108,10 @@ export class Freshdesk implements INodeType { type: 'options', required: true, options: [ + { + name: 'Contact', + value: 'contact', + }, { name: 'Ticket', value: 'ticket', @@ -1046,6 +1064,9 @@ export class Freshdesk implements INodeType { default: '', description: 'Ticket ID', }, + // CONTACTS + ...contactOperations, + ...contactFields, ] }; @@ -1342,14 +1363,67 @@ export class Freshdesk implements INodeType { const ticketId = this.getNodeParameter('ticketId', i) as string; responseData = await freshdeskApiRequest.call(this, 'DELETE', `/tickets/${ticketId}`); } + } else if (resource === 'contact') { + //https://developers.freshdesk.com/api/#create_contact + if (operation === 'create') { + const name = this.getNodeParameter('name', i) as string; + const email = this.getNodeParameter('email', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i, {}) as IDataObject; + + if (additionalFields.customFields) { + const metadata = (additionalFields.customFields as IDataObject).customField as IDataObject[]; + additionalFields.custom_fields = {}; + for (const data of metadata) { + //@ts-ignore + additionalFields.custom_fields[data.name as string] = data.value; + } + delete additionalFields.customFields; + } + + const body: ICreateContactBody = additionalFields; + body.name = name; + if (email) { + body.email = email; + } + responseData = await freshdeskApiRequest.call(this, 'POST', '/contacts', body); + //https://developers.freshdesk.com/api/#delete_contact + } else if (operation === 'delete') { + const contactId = this.getNodeParameter('contactId', i) as string; + responseData = await freshdeskApiRequest.call(this, 'DELETE', `/contacts/${contactId}`, {}); + } else if (operation === 'get') { + const contactId = this.getNodeParameter('contactId', i) as string; + responseData = await freshdeskApiRequest.call(this, 'GET', `/contacts/${contactId}`, {}); + //https://developers.freshdesk.com/api/#list_all_contacts + } else if (operation === 'getAll') { + const qs = this.getNodeParameter('filters', i, {}) as IDataObject; + responseData = await freshdeskApiRequest.call(this, 'GET', '/contacts', {}, qs); + //https://developers.freshdesk.com/api/#update_contact + } else if (operation === 'update') { + const contactId = this.getNodeParameter('contactId', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i, {}) as IDataObject; + + if (additionalFields.customFields) { + const metadata = (additionalFields.customFields as IDataObject).customField as IDataObject[]; + additionalFields.custom_fields = {}; + for (const data of metadata) { + //@ts-ignore + additionalFields.custom_fields[data.name as string] = data.value; + } + delete additionalFields.customFields; + } + + const body: ICreateContactBody = additionalFields; + responseData = await freshdeskApiRequest.call(this, 'PUT', `/contacts/${contactId}`, body); + } } + if (Array.isArray(responseData)) { returnData.push.apply(returnData, responseData as IDataObject[]); } else { if (responseData === undefined) { - responseData = { json: { + responseData = { success: true, - } }; + }; } returnData.push(responseData as IDataObject); diff --git a/packages/nodes-base/nodes/Freshdesk/GenericFunctions.ts b/packages/nodes-base/nodes/Freshdesk/GenericFunctions.ts index 50089fdcc5..e52debfd64 100644 --- a/packages/nodes-base/nodes/Freshdesk/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Freshdesk/GenericFunctions.ts @@ -1,4 +1,6 @@ -import { OptionsWithUri } from 'request'; +import { + OptionsWithUri, +} from 'request'; import { IExecuteFunctions, @@ -6,7 +8,9 @@ import { BINARY_ENCODING } from 'n8n-core'; -import { IDataObject } from 'n8n-workflow'; +import { + IDataObject, +} from 'n8n-workflow'; export async function freshdeskApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any