From c10df92b48e3fe931e577edbe9d424140c2ea03e Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Fri, 28 Feb 2020 19:55:14 -0500 Subject: [PATCH] :sparkles: Drift node --- .../credentials/DriftApi.credentials.ts | 18 ++ .../nodes/Drift/ContactDescription.ts | 206 ++++++++++++++++++ .../nodes/Drift/ContactInterface.ts | 6 + packages/nodes-base/nodes/Drift/Drift.node.ts | 130 +++++++++++ .../nodes/Drift/GenericFunctions.ts | 50 +++++ packages/nodes-base/nodes/Drift/drift.png | Bin 0 -> 3189 bytes packages/nodes-base/package.json | 2 + 7 files changed, 412 insertions(+) create mode 100644 packages/nodes-base/credentials/DriftApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Drift/ContactDescription.ts create mode 100644 packages/nodes-base/nodes/Drift/ContactInterface.ts create mode 100644 packages/nodes-base/nodes/Drift/Drift.node.ts create mode 100644 packages/nodes-base/nodes/Drift/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Drift/drift.png diff --git a/packages/nodes-base/credentials/DriftApi.credentials.ts b/packages/nodes-base/credentials/DriftApi.credentials.ts new file mode 100644 index 000000000..83f6374c7 --- /dev/null +++ b/packages/nodes-base/credentials/DriftApi.credentials.ts @@ -0,0 +1,18 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class DriftApi implements ICredentialType { + name = 'driftApi'; + displayName = 'Drift API'; + properties = [ + { + displayName: 'Personal Access Token', + name: 'accessToken', + type: 'string' as NodePropertyTypes, + default: '', + description: 'Visit your account details page, and grab the Access Token. See Drift auth.' + }, + ]; +} diff --git a/packages/nodes-base/nodes/Drift/ContactDescription.ts b/packages/nodes-base/nodes/Drift/ContactDescription.ts new file mode 100644 index 000000000..ad5bc14d5 --- /dev/null +++ b/packages/nodes-base/nodes/Drift/ContactDescription.ts @@ -0,0 +1,206 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const contactOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'contact', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a contact', + }, + { + name: 'Custom Attributes', + value: 'getCustomAttributes', + description: 'Get custom attributes', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a contact', + }, + { + name: 'Get', + value: 'get', + description: 'Get a contact', + }, + { + name: 'Update', + value: 'update', + description: 'Update a contact', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const contactFields = [ + +/* -------------------------------------------------------------------------- */ +/* contact:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Email', + name: 'email', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'create', + ], + }, + }, + description: 'The email of the Contact', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'The name of the Contact', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + description: 'The phone number associated with the Contact', + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* contact:update */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Contact ID', + name: 'contactId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'update', + ] + }, + }, + description: 'Unique identifier for the contact.', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + description: 'The email of the Contact', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'The name of the Contact', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + description: 'The phone number associated with the Contact', + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* contact:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Contact ID', + name: 'contactId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'get', + ] + }, + }, + description: 'Unique identifier for the contact.', + }, +/* -------------------------------------------------------------------------- */ +/* contact:delete */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Contact ID', + name: 'contactId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'delete', + ] + }, + }, + description: 'Unique identifier for the contact.', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Drift/ContactInterface.ts b/packages/nodes-base/nodes/Drift/ContactInterface.ts new file mode 100644 index 000000000..d228a1c88 --- /dev/null +++ b/packages/nodes-base/nodes/Drift/ContactInterface.ts @@ -0,0 +1,6 @@ +export interface IContact { + email?: string; + name?: string; + phone?: string; + tags?: string[]; +} diff --git a/packages/nodes-base/nodes/Drift/Drift.node.ts b/packages/nodes-base/nodes/Drift/Drift.node.ts new file mode 100644 index 000000000..95566dfb6 --- /dev/null +++ b/packages/nodes-base/nodes/Drift/Drift.node.ts @@ -0,0 +1,130 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; +import { + INodeTypeDescription, + INodeType, + INodeExecutionData, + IDataObject, +} from 'n8n-workflow'; +import { + driftApiRequest, +} from './GenericFunctions'; +import { + contactFields, + contactOperations, +} from './ContactDescription'; +import { + IContact, +} from './ContactInterface'; + +export class Drift implements INodeType { + description: INodeTypeDescription = { + displayName: 'Drift', + name: 'drift', + icon: 'file:drift.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Drift API', + defaults: { + name: 'Drift ', + color: '#404040', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'driftApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Contact', + value: 'contact', + }, + ], + default: 'contact', + description: 'Resource to consume.', + }, + ...contactOperations, + ...contactFields, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + let responseData; + const qs: IDataObject = {}; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + for (let i = 0; i < length; i++) { + if (resource === 'contact') { + //https://devdocs.drift.com/docs/creating-a-contact + if (operation === 'create') { + const email = this.getNodeParameter('email', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const body: IContact = { + email, + }; + if (additionalFields.name) { + body.name = additionalFields.name as string; + } + if (additionalFields.phone) { + body.phone = additionalFields.phone as string; + } + responseData = await driftApiRequest.call(this, 'POST', '/contacts', { attributes: body }); + responseData = responseData.data + } + //https://devdocs.drift.com/docs/updating-a-contact + if (operation === 'update') { + const contactId = this.getNodeParameter('contactId', i) as string; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + const body: IContact = {}; + if (updateFields.name) { + body.name = updateFields.name as string; + } + if (updateFields.phone) { + body.phone = updateFields.phone as string; + } + if (updateFields.email) { + body.email = updateFields.email as string; + } + responseData = await driftApiRequest.call(this, 'PATCH', `/contacts/${contactId}`, { attributes: body }); + responseData = responseData.data + } + //https://devdocs.drift.com/docs/retrieving-contact + if (operation === 'get') { + const contactId = this.getNodeParameter('contactId', i) as string; + responseData = await driftApiRequest.call(this, 'GET', `/contacts/${contactId}`); + responseData = responseData.data + } + //https://devdocs.drift.com/docs/listing-custom-attributes + if (operation === 'getCustomAttributes') { + responseData = await driftApiRequest.call(this, 'GET', '/contacts/attributes'); + responseData = responseData.data.properties; + } + //https://devdocs.drift.com/docs/removing-a-contact + if (operation === 'delete') { + const contactId = this.getNodeParameter('contactId', i) as string; + responseData = await driftApiRequest.call(this, 'DELETE', `/contacts/${contactId}`); + responseData = { success: true }; + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as IDataObject); + } + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Drift/GenericFunctions.ts b/packages/nodes-base/nodes/Drift/GenericFunctions.ts new file mode 100644 index 000000000..3f798398c --- /dev/null +++ b/packages/nodes-base/nodes/Drift/GenericFunctions.ts @@ -0,0 +1,50 @@ +import { OptionsWithUri } from 'request'; + +import { + IExecuteFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + IDataObject, + IHookFunctions, + IWebhookFunctions +} from 'n8n-workflow'; + +export async function driftApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const credentials = this.getCredentials('driftApi'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const endpoint = 'https://driftapi.com'; + + let options: OptionsWithUri = { + headers: { + Authorization: `Bearer ${credentials.accessToken}`, + }, + method, + body, + qs: query, + uri: uri || `${endpoint}${resource}`, + json: true + }; + if (!Object.keys(body).length) { + delete options.form; + } + if (!Object.keys(query).length) { + delete options.qs; + } + options = Object.assign({}, options, option); + try { + return await this.helpers.request!(options); + } catch (error) { + if (error.response) { + const errorMessage = error.message || (error.response.body && error.response.body.message ) + throw new Error(`Drift error response [${error.statusCode}]: ${errorMessage}`); + } + throw error; + } +} diff --git a/packages/nodes-base/nodes/Drift/drift.png b/packages/nodes-base/nodes/Drift/drift.png new file mode 100644 index 0000000000000000000000000000000000000000..82050d2ca84ac63fe0cbed72b2d68617e6b6c92b GIT binary patch literal 3189 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 Q9R3B&&RUw3o^ieNZ#uR4zW@LL literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 876d16ff6..a99c9db2e 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -42,6 +42,7 @@ "dist/credentials/CopperApi.credentials.js", "dist/credentials/DisqusApi.credentials.js", "dist/credentials/DropboxApi.credentials.js", + "dist/credentials/DriftApi.credentials.js", "dist/credentials/EventbriteApi.credentials.js", "dist/credentials/FreshdeskApi.credentials.js", "dist/credentials/FileMaker.credentials.js", @@ -129,6 +130,7 @@ "dist/nodes/Disqus/Disqus.node.js", "dist/nodes/Dropbox/Dropbox.node.js", "dist/nodes/DateTime.node.js", + "dist/nodes/Drift/Drift.node.js", "dist/nodes/EditImage.node.js", "dist/nodes/EmailReadImap.node.js", "dist/nodes/EmailSend.node.js",