From e014b6374803e2ecdbf43b7996483798ff89f5b8 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Thu, 7 Nov 2019 01:06:19 -0500 Subject: [PATCH] UI changes --- .../credentials/FreshdeskApi.credentials.ts | 17 +- .../nodes/Freshdesk/Freshdesk.node.ts | 454 ++++++++++++++++++ .../nodes/Freshdesk/Freskdesk.node.ts | 56 --- .../nodes/Freshdesk/GenericFunctions.ts | 30 +- 4 files changed, 484 insertions(+), 73 deletions(-) create mode 100644 packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts delete mode 100644 packages/nodes-base/nodes/Freshdesk/Freskdesk.node.ts diff --git a/packages/nodes-base/credentials/FreshdeskApi.credentials.ts b/packages/nodes-base/credentials/FreshdeskApi.credentials.ts index 0f06b4e356..26e0e4f316 100644 --- a/packages/nodes-base/credentials/FreshdeskApi.credentials.ts +++ b/packages/nodes-base/credentials/FreshdeskApi.credentials.ts @@ -9,10 +9,23 @@ export class FreshdeskApi implements ICredentialType { displayName = 'Freshdesk API'; properties = [ { - displayName: 'API Key', - name: 'apiKey', + displayName: 'Username', + name: 'username', type: 'string' as NodePropertyTypes, default: '', + }, + { + displayName: 'Password', + name: 'password', + type: 'password' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string' as NodePropertyTypes, + default: '', + placeholder: 'https://domain.freshdesk.com' }, ]; } diff --git a/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts b/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts new file mode 100644 index 0000000000..68e1559084 --- /dev/null +++ b/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts @@ -0,0 +1,454 @@ +import { + IExecuteSingleFunctions, +} from 'n8n-core'; +import { + IDataObject, + INodeTypeDescription, + INodeExecutionData, + INodeType, + ILoadOptionsFunctions, + INodePropertyOptions, +} from 'n8n-workflow'; +import { + freshdeskApiRequest +} from './GenericFunctions'; + +import moment = require('moment'); +import _ = require('lodash') + +enum Status { + Open = 1, + Pending = 2, + Resolved = 3, + Closed = 4 +} + +enum Priority { + Low = 1, + Medium = 2, + High = 3, + Urgent = 4 +} + +interface ICreateTicketBody { + name?: string; + requester_id?: number; + email?: string; + facebook_id?: string; + phone?: string; + twitter_id?: string; + unique_external_id?: string, + subject?: string, + type?: string, + status: Status, + priority: Priority, + description?: string, + responder_id?: number, + cc_emails?: [string]; + custom_fields?: IDataObject; + due_by?: string; + email_config_id?: number; + fr_due_by?: string; + group_id?: number; + product_id?: number; + source: number; + tags: [string]; + company_id: number; +} + +export class Freshdesk implements INodeType { + + description: INodeTypeDescription = { + displayName: 'Freshdesk', + name: 'freshdesk', + icon: 'file:freshdesk.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Freshdesk API', + defaults: { + name: 'Freshdesk', + color: '#c02428', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'freshdeskApi', + required: true, + } + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + required: true, + options: [ + { + name: 'Ticket', + value: 'ticket', + }, + ], + default: 'ticket', + description: 'The resource to operate on.', + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'ticket', + ] + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new ticket', + } + ], + default: 'create', + description: 'The operation to perform.', + }, + { + displayName: 'Requester Identification', + name: 'requester', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'create' + ] + }, + }, + options: [ + { + name: 'Requester Id', + value: 'requesterId', + description: `User ID of the requester. For existing contacts, the requester_id can be passed instead of the requester's email.`, + }, + { + name: 'Email', + value: 'email', + description: `Email address of the requester. If no contact exists with this email address in Freshdesk, it will be added as a new contact.`, + }, + { + name: 'Facebook Id', + value: 'facebookId', + description: `Facebook ID of the requester. If no contact exists with this facebook_id, then a new contact will be created.`, + }, + { + name: 'Phone', + value: 'phone', + description: `Phone number of the requester. If no contact exists with this phone number in Freshdesk, it will be added as a new contact. If the phone number is set and the email address is not, then the name attribute is mandatory.`, + }, + { + name: 'Twitter Id', + value: 'twitterId', + description: `Twitter handle of the requester. If no contact exists with this handle in Freshdesk, it will be added as a new contact.`, + }, + { + name: 'Unique External Id', + value: 'uniqueExternalId', + description: `External ID of the requester. If no contact exists with this external ID in Freshdesk, they will be added as a new contact.`, + }, + ], + default: 'requesterId', + description: 'Requester Identification', + }, + { + displayName: 'Value', + name: 'requesterIdentificationValue', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'create' + ] + }, + }, + default: '', + description: `Value of the identification selected `, + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'create' + ] + }, + }, + options: [ + { + name: 'Open', + value: 'open', + }, + { + name: 'Pending', + value: 'pending', + }, + { + name: 'Resolved', + value: 'resolved', + }, + { + name: 'Closed', + value: 'closed', + } + ], + default: 'pending', + description: 'Status', + }, + { + displayName: 'Priority', + name: 'priority', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'create' + ] + }, + }, + options: [ + { + name: 'Low', + value: 'low', + }, + { + name: 'Medium', + value: 'medium', + }, + { + name: 'High', + value: 'high', + }, + { + name: 'Urgent', + value: 'urgent', + } + ], + default: 'low', + description: 'Priority', + }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + resource: [ + 'ticket' + ], + operation: [ + 'create', + ] + }, + }, + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'ticket' + ], + operation: [ + 'create' + ], + }, + }, + options: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + placeholder: '', + description: 'Name of the requester', + }, + { + displayName: 'Subject', + name: 'subject', + type: 'string', + default: '', + placeholder: '', + description: 'Subject of the ticket.', + }, + { + displayName: 'Type', + name: 'type', + type: 'string', + default: '', + description: 'Helps categorize the ticket according to the different kinds of issues your support team deals with.', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + typeOptions: { + rows: 5, + alwaysOpenEditWindow: true, + }, + description: 'HTML content of the ticket.', + }, + { + displayName: 'Agent', + name: 'agent', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getAgents' + }, + description: 'ID of the agent to whom the ticket has been assigned', + }, + { + displayName: 'CC Emails', + name: 'ccEmails', + type: 'string', + default: '', + description: `separated by , email addresses added in the 'cc' field of the incoming ticket email`, + }, + ] + }, + { + displayName: 'Custom Fields', + name: 'customFieldsUi', + placeholder: 'Add Custom fields', + type: 'fixedCollection', + default: '', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + resource: [ + 'ticket' + ], + operation: [ + 'create' + ], + jsonParameters: [ + false, + ], + }, + }, + description: 'Key value pairs containing the names and values of custom fields.', + options: [ + { + name: 'customFieldsValues', + displayName: 'Custom fields', + values: [ + { + displayName: 'Key', + name: 'key', + type: 'string', + default: '', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Custom Fields', + name: 'customFieldsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + displayOptions: { + show: { + resource: [ + 'ticket' + ], + operation: [ + 'create' + ], + jsonParameters: [ + true, + ], + }, + }, + default: '', + placeholder: `{ + "gadget":"Cold Welder" + }`, + description: 'Key value pairs containing the names and values of custom fields.', + }, + ] + }; + + methods = { + loadOptions: { + // Get all the agents to display them to user so that he can + // select them easily + async getAgents(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let agents; + try { + agents = await freshdeskApiRequest.call(this, '/agents', 'GET'); + } catch (err) { + throw new Error(`Mandrill Error: ${err}`); + } + for (const agent of agents) { + const agentName = agent.contact.name; + const agentId = agent.id; + + returnData.push({ + name: agentName, + value: agentId, + }); + } + + return returnData; + } + }, + }; + + + + async executeSingle(this: IExecuteSingleFunctions): Promise { + + return { + json: {} + } + + } +} diff --git a/packages/nodes-base/nodes/Freshdesk/Freskdesk.node.ts b/packages/nodes-base/nodes/Freshdesk/Freskdesk.node.ts deleted file mode 100644 index d039914486..0000000000 --- a/packages/nodes-base/nodes/Freshdesk/Freskdesk.node.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - IExecuteSingleFunctions, -} from 'n8n-core'; -import { - IDataObject, - INodeTypeDescription, - INodeExecutionData, - INodeType, - ILoadOptionsFunctions, - INodePropertyOptions, -} from 'n8n-workflow'; -import { - freshdeskApiRequest -} from './GenericFunctions'; - -import moment = require('moment'); -import _ = require('lodash') - -export class Freshdesk implements INodeType { - - description: INodeTypeDescription = { - displayName: 'Freshdesk', - name: 'freshdesk', - icon: 'file:freshdesk.png', - group: ['output'], - version: 1, - subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', - description: 'Consume Freshdesk API', - defaults: { - name: 'Freshdesk', - color: '#c02428', - }, - inputs: ['main'], - outputs: ['main'], - credentials: [ - { - name: 'freshdeskApi', - required: true, - } - ], - properties: [ - ] - }; - - - methods = { - loadOptions: { - } - }; - - // async executeSingle(this: IExecuteSingleFunctions): Promise { - - - - // } -} diff --git a/packages/nodes-base/nodes/Freshdesk/GenericFunctions.ts b/packages/nodes-base/nodes/Freshdesk/GenericFunctions.ts index a967f44b99..3a80586c72 100644 --- a/packages/nodes-base/nodes/Freshdesk/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Freshdesk/GenericFunctions.ts @@ -4,31 +4,38 @@ import { IExecuteFunctions, IHookFunctions, ILoadOptionsFunctions, - IExecuteSingleFunctions + IExecuteSingleFunctions, + BINARY_ENCODING } from 'n8n-core'; import * as _ from 'lodash'; import { IDataObject } from 'n8n-workflow'; -export async function freshdeskApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resource: string, method: string, action: string, body: any = {}, headers?: object): Promise { // tslint:disable-line:no-any - const credentials = this.getCredentials('mandrillApi'); +export async function freshdeskApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resource: string, method: string, body: any = {}, headers?: object): Promise { // tslint:disable-line:no-any + + const credentials = this.getCredentials('freshdeskApi'); if (credentials === undefined) { throw new Error('No credentials got returned!'); } - const data = Object.assign({}, body, { key: credentials.apiKey }); + const userpass = `${credentials.username}:${credentials.password}` - const endpoint = 'mandrillapp.com/api/1.0'; + const headerWithAuthentication = Object.assign({}, headers, { Authorization: `Basic ${Buffer.from(userpass).toString(BINARY_ENCODING)}` }); + + const endpoint = 'freshdesk.com/api/v2/'; const options: OptionsWithUri = { - headers, + headers: headerWithAuthentication, method, - uri: `https://${endpoint}${resource}${action}.json`, - body: data, + body, + uri: `https://${credentials.domain}.${endpoint}${resource}`, json: true }; + if (_.isEmpty(options.body)) { + delete options.body + } try { return await this.helpers.request!(options); @@ -36,13 +43,6 @@ export async function freshdeskApiRequest(this: IHookFunctions | IExecuteFunctio console.error(error); const errorMessage = error.response.body.message || error.response.body.Message; - if (error.name === 'Invalid_Key') { - throw new Error('The provided API key is not a valid Mandrill API key'); - } else if (error.name === 'ValidationError') { - throw new Error('The parameters passed to the API call are invalid or not provided when required'); - } else if (error.name === 'GeneralError') { - throw new Error('An unexpected error occurred processing the request. Mandrill developers will be notified.'); - } if (errorMessage !== undefined) { throw errorMessage;