From 247954c0fa9ff36d2a14ef1bcc0376bf0bd5b082 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Wed, 6 Nov 2019 19:58:18 -0500 Subject: [PATCH 01/15] freshdesk-node-setup --- .../credentials/FreshdeskApi.credentials.ts | 18 ++++++ .../nodes/Freshdesk/Freskdesk.node.ts | 56 ++++++++++++++++++ .../nodes/Freshdesk/GenericFunctions.ts | 52 ++++++++++++++++ .../nodes-base/nodes/Freshdesk/freshdesk.png | Bin 0 -> 4956 bytes packages/nodes-base/package.json | 6 +- 5 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 packages/nodes-base/credentials/FreshdeskApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Freshdesk/Freskdesk.node.ts create mode 100644 packages/nodes-base/nodes/Freshdesk/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Freshdesk/freshdesk.png diff --git a/packages/nodes-base/credentials/FreshdeskApi.credentials.ts b/packages/nodes-base/credentials/FreshdeskApi.credentials.ts new file mode 100644 index 0000000000..0f06b4e356 --- /dev/null +++ b/packages/nodes-base/credentials/FreshdeskApi.credentials.ts @@ -0,0 +1,18 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class FreshdeskApi implements ICredentialType { + name = 'freshdeskApi'; + displayName = 'Freshdesk API'; + properties = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Freshdesk/Freskdesk.node.ts b/packages/nodes-base/nodes/Freshdesk/Freskdesk.node.ts new file mode 100644 index 0000000000..d039914486 --- /dev/null +++ b/packages/nodes-base/nodes/Freshdesk/Freskdesk.node.ts @@ -0,0 +1,56 @@ +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 new file mode 100644 index 0000000000..a967f44b99 --- /dev/null +++ b/packages/nodes-base/nodes/Freshdesk/GenericFunctions.ts @@ -0,0 +1,52 @@ +import { OptionsWithUri } from 'request'; + +import { + IExecuteFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IExecuteSingleFunctions +} 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'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const data = Object.assign({}, body, { key: credentials.apiKey }); + + const endpoint = 'mandrillapp.com/api/1.0'; + + const options: OptionsWithUri = { + headers, + method, + uri: `https://${endpoint}${resource}${action}.json`, + body: data, + json: true + }; + + + try { + return await this.helpers.request!(options); + } catch (error) { + 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; + } + throw error.response.body; + } +} diff --git a/packages/nodes-base/nodes/Freshdesk/freshdesk.png b/packages/nodes-base/nodes/Freshdesk/freshdesk.png new file mode 100644 index 0000000000000000000000000000000000000000..36692057e456cb3763124a1d9ac216fd6c4b01a8 GIT binary patch literal 4956 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 Date: Thu, 7 Nov 2019 01:06:19 -0500 Subject: [PATCH 02/15] 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; From 2292cc018c1fe4090de437047ba3cd454a8ec0af Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Thu, 7 Nov 2019 13:07:29 -0500 Subject: [PATCH 03/15] UI finished --- .../credentials/FreshdeskApi.credentials.ts | 20 +- .../nodes/Freshdesk/Freshdesk.node.ts | 226 ++++++++++++++++-- .../nodes/Freshdesk/GenericFunctions.ts | 6 +- 3 files changed, 219 insertions(+), 33 deletions(-) diff --git a/packages/nodes-base/credentials/FreshdeskApi.credentials.ts b/packages/nodes-base/credentials/FreshdeskApi.credentials.ts index 26e0e4f316..3179abff1f 100644 --- a/packages/nodes-base/credentials/FreshdeskApi.credentials.ts +++ b/packages/nodes-base/credentials/FreshdeskApi.credentials.ts @@ -9,23 +9,17 @@ export class FreshdeskApi implements ICredentialType { displayName = 'Freshdesk API'; properties = [ { - displayName: 'Username', - name: 'username', + displayName: 'API Key', + name: 'apiKey', 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' - }, + placeholder: 'https://domain.freshdesk.com', + default: '' + } ]; } diff --git a/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts b/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts index 68e1559084..7dbeb88a69 100644 --- a/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts +++ b/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts @@ -13,9 +13,6 @@ import { freshdeskApiRequest } from './GenericFunctions'; -import moment = require('moment'); -import _ = require('lodash') - enum Status { Open = 1, Pending = 2, @@ -30,6 +27,16 @@ enum Priority { Urgent = 4 } +enum Source { + Email = 1, + Portal = 2, + Phone = 3, + Chat = 4, + Mobihelp = 5, + FeedbackWidget = 6, + OutboundEmail =7 +} + interface ICreateTicketBody { name?: string; requester_id?: number; @@ -37,13 +44,13 @@ interface ICreateTicketBody { 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, + 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; @@ -51,7 +58,7 @@ interface ICreateTicketBody { fr_due_by?: string; group_id?: number; product_id?: number; - source: number; + source: Source; tags: [string]; company_id: number; } @@ -255,6 +262,54 @@ export class Freshdesk implements INodeType { default: 'low', description: 'Priority', }, + { + displayName: 'Source', + name: 'source', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'create' + ] + }, + }, + options: [ + { + name: 'Email', + value: 'email', + }, + { + name: 'Portal', + value: 'portal', + }, + { + name: 'Phone', + value: 'phone', + }, + { + name: 'Chat', + value: 'chat', + }, + { + name: 'Mobihelp', + value: 'mobileHelp', + }, + { + name: 'Feedback Widget', + value: 'feedbackWidget', + }, + { + name: 'Outbound Email', + value: 'OutboundEmail', + } + ], + default: 'portal', + description: 'The channel through which the ticket was created.', + }, { displayName: 'JSON Parameters', name: 'jsonParameters', @@ -316,6 +371,7 @@ export class Freshdesk implements INodeType { displayName: 'Description', name: 'description', type: 'string', + required: false, default: '', typeOptions: { rows: 5, @@ -327,6 +383,7 @@ export class Freshdesk implements INodeType { displayName: 'Agent', name: 'agent', type: 'options', + required: false, default: '', typeOptions: { loadOptionsMethod: 'getAgents' @@ -336,10 +393,78 @@ export class Freshdesk implements INodeType { { displayName: 'CC Emails', name: 'ccEmails', + required: false, type: 'string', default: '', description: `separated by , email addresses added in the 'cc' field of the incoming ticket email`, }, + { + displayName: 'Tags', + name: 'tags', + required: false, + type: 'string', + default: '', + description: `separated by , tags that have been associated with the ticket`, + }, + { + displayName: 'Due By', + name: 'dueBy', + required: false, + type: 'dateTime', + default: '', + description: `Timestamp that denotes when the ticket is due to be resolved`, + }, + { + displayName: 'Email config Id', + name: 'emailConfigId', + type: 'number', + required: false, + default: '', + description: `ID of email config which is used for this ticket. (i.e., support@yourcompany.com/sales@yourcompany.com) + If product_id is given and email_config_id is not given, product's primary email_config_id will be set`, + }, + { + displayName: 'FR Due By', + name: 'frDueBy', + type: 'number', + required: false, + default: '', + description: `Timestamp that denotes when the first response is due`, + }, + { + displayName: 'Group', + name: 'group', + required: false, + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getGroups' + }, + description: `ID of the group to which the ticket has been assigned. The default value is the ID of the group that is associated with the given email_config_id`, + }, + { + displayName: 'Product', + name: 'product', + required: false, + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getProducts' + }, + description: `ID of the product to which the ticket is associated. + It will be ignored if the email_config_id attribute is set in the request.`, + }, + { + displayName: 'Company', + name: 'company', + required: false, + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getCompanies' + }, + description: `Company ID of the requester. This attribute can only be set if the Multiple Companies feature is enabled (Estate plan and above)`, + }, ] }, { @@ -347,6 +472,7 @@ export class Freshdesk implements INodeType { name: 'customFieldsUi', placeholder: 'Add Custom fields', type: 'fixedCollection', + required: false, default: '', typeOptions: { multipleValues: true, @@ -372,6 +498,7 @@ export class Freshdesk implements INodeType { values: [ { displayName: 'Key', + required: false, name: 'key', type: 'string', default: '', @@ -380,6 +507,7 @@ export class Freshdesk implements INodeType { displayName: 'Value', name: 'value', type: 'string', + required: false, default: '', }, ], @@ -407,6 +535,7 @@ export class Freshdesk implements INodeType { }, }, default: '', + required: false, placeholder: `{ "gadget":"Cold Welder" }`, @@ -425,7 +554,7 @@ export class Freshdesk implements INodeType { try { agents = await freshdeskApiRequest.call(this, '/agents', 'GET'); } catch (err) { - throw new Error(`Mandrill Error: ${err}`); + throw new Error(`Freshdesk Error: ${err}`); } for (const agent of agents) { const agentName = agent.contact.name; @@ -436,19 +565,82 @@ export class Freshdesk implements INodeType { value: agentId, }); } - return returnData; - } + }, + + // Get all the groups to display them to user so that he can + // select them easily + async getGroups(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let groups; + try { + groups = await freshdeskApiRequest.call(this, '/groups', 'GET'); + } catch (err) { + throw new Error(`Freshdesk Error: ${err}`); + } + for (const group of groups) { + const groupName = group.name; + const groupId = group.id; + + returnData.push({ + name: groupName, + value: groupId, + }); + } + return returnData; + }, + + // Get all the products to display them to user so that he can + // select them easily + async getProducts(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let products; + try { + products = await freshdeskApiRequest.call(this, '/products', 'GET'); + } catch (err) { + throw new Error(`Freshdesk Error: ${err}`); + } + for (const product of products) { + const productName = product.name; + const productId = product.id; + + returnData.push({ + name: productName, + value: productId, + }); + } + return returnData; + }, + + // Get all the companies to display them to user so that he can + // select them easily + async getCompanies(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let companies; + try { + companies = await freshdeskApiRequest.call(this, '/companies', 'GET'); + } catch (err) { + throw new Error(`Freshdesk Error: ${err}`); + } + for (const company of companies) { + const companyName = company.name; + const companyId = company.id; + + returnData.push({ + name: companyName, + value: companyId, + }); + } + return returnData; + }, }, }; - - async executeSingle(this: IExecuteSingleFunctions): Promise { return { json: {} - } + }; } } diff --git a/packages/nodes-base/nodes/Freshdesk/GenericFunctions.ts b/packages/nodes-base/nodes/Freshdesk/GenericFunctions.ts index 3a80586c72..acab34f810 100644 --- a/packages/nodes-base/nodes/Freshdesk/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Freshdesk/GenericFunctions.ts @@ -19,9 +19,9 @@ export async function freshdeskApiRequest(this: IHookFunctions | IExecuteFunctio throw new Error('No credentials got returned!'); } - const userpass = `${credentials.username}:${credentials.password}` + const apiKey = `${credentials.apiKey}:X`; - const headerWithAuthentication = Object.assign({}, headers, { Authorization: `Basic ${Buffer.from(userpass).toString(BINARY_ENCODING)}` }); + const headerWithAuthentication = Object.assign({}, headers, { Authorization: `${Buffer.from(apiKey).toString(BINARY_ENCODING)}` }); const endpoint = 'freshdesk.com/api/v2/'; @@ -34,7 +34,7 @@ export async function freshdeskApiRequest(this: IHookFunctions | IExecuteFunctio }; if (_.isEmpty(options.body)) { - delete options.body + delete options.body; } try { From 226e8467f437d87d7bd66a8f9fd65fc465b546be Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 7 Nov 2019 22:13:51 +0100 Subject: [PATCH 04/15] :bookmark: Release n8n-nodes-base@0.29.0 --- packages/nodes-base/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 6af0a99a5c..3d91f5ce62 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.28.0", + "version": "0.29.0", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 72341a425eef4d983c3b2addd5bbb310e6e0463c Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 7 Nov 2019 22:15:17 +0100 Subject: [PATCH 05/15] :bookmark: Release n8n-editor-ui@0.25.0 --- packages/editor-ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index 911257526e..193e6cf8d8 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -1,6 +1,6 @@ { "name": "n8n-editor-ui", - "version": "0.24.0", + "version": "0.25.0", "description": "Workflow Editor UI for n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From 72f0fcfa87a10263cb12aca21e4ccad0794851b6 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 7 Nov 2019 22:17:17 +0100 Subject: [PATCH 06/15] :arrow_up: Set n8n-editor-ui@0.25.0, n8n-nodes-base@0.29.0 on n8n --- packages/cli/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index ad4bde475b..59055984e0 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -90,8 +90,8 @@ "localtunnel": "^1.9.1", "mongodb": "^3.2.3", "n8n-core": "~0.14.0", - "n8n-editor-ui": "~0.24.0", - "n8n-nodes-base": "~0.28.0", + "n8n-editor-ui": "~0.25.0", + "n8n-nodes-base": "~0.29.0", "n8n-workflow": "~0.15.0", "open": "^6.1.0", "pg": "^7.11.0", From 1cf21a88588e89f72c12974cde840fd27a82a4c9 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 7 Nov 2019 22:18:27 +0100 Subject: [PATCH 07/15] :bookmark: Release n8n@0.34.0 --- packages/cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 59055984e0..a7798fab7a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.33.0", + "version": "0.34.0", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From c23b6ed90471f3593afb2ffec647794d6c8dc5c6 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 8 Nov 2019 07:18:01 +0100 Subject: [PATCH 08/15] :zap: Improve error reporting on expression error --- packages/core/src/NodeExecuteFunctions.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 58e1e320d8..344abf9f83 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -220,7 +220,13 @@ export function getNodeParameter(workflow: Workflow, runExecutionData: IRunExecu throw new Error(`Could not get parameter "${parameterName}"!`); } - const returnData = workflow.getParameterValue(value, runExecutionData, runIndex, itemIndex, node.name, connectionInputData); + let returnData; + try { + returnData = workflow.getParameterValue(value, runExecutionData, runIndex, itemIndex, node.name, connectionInputData); + } catch (e) { + e.message += ` [Error in parameter: "${parameterName}"]`; + throw e; + } return returnData; } From 021762c74e660969b975aeb297d02dfc39beaa8a Mon Sep 17 00:00:00 2001 From: mtahiue Date: Fri, 8 Nov 2019 12:41:47 +0100 Subject: [PATCH 09/15] Added due_date to gitlab create and edit issue --- .../nodes-base/nodes/Gitlab/Gitlab.node.ts | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts b/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts index bcf5712a84..b832062442 100644 --- a/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts +++ b/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts @@ -317,7 +317,31 @@ export class Gitlab implements INodeType { }, ], }, - + { + displayName: 'Due Date', + name: 'date_date', + type: 'string', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'issue', + ], + }, + }, + default: { 'due_date': '' }, + options: [ + { + displayName: 'Due Date', + name: 'date_date', + type: 'string', + default: 0, + description: 'Due Date for issue. Date time string in the format YEAR-MONTH-DAY, e.g. 2016-03-11', + }, + ], + }, // ---------------------------------- // issue:createComment // ---------------------------------- @@ -474,6 +498,13 @@ export class Gitlab implements INodeType { }, ], }, + { + displayName: 'Due Date', + name: 'due_date', + type: 'string', + default: '', + description: 'The Due Date of the issue. Date time string in the format YEAR-MONTH-DAY, e.g. 2016-03-11', + }, ], }, From bea9fd033b944fa2ad9e8a7b8f727b79af16c92f Mon Sep 17 00:00:00 2001 From: mtahiue Date: Fri, 8 Nov 2019 15:10:45 +0100 Subject: [PATCH 10/15] Fixed name of description field --- packages/nodes-base/nodes/Gitlab/Gitlab.node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts b/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts index b832062442..47ac63b320 100644 --- a/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts +++ b/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts @@ -241,7 +241,7 @@ export class Gitlab implements INodeType { }, { displayName: 'Body', - name: 'body', + name: 'description', type: 'string', typeOptions: { rows: 5, From 2a54958c0f01a3dfc49d56eeec6b0936c380564c Mon Sep 17 00:00:00 2001 From: mtahiue Date: Fri, 8 Nov 2019 15:13:48 +0100 Subject: [PATCH 11/15] Fixed name of description field for edit --- packages/nodes-base/nodes/Gitlab/Gitlab.node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts b/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts index 47ac63b320..979bef8f77 100644 --- a/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts +++ b/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts @@ -433,7 +433,7 @@ export class Gitlab implements INodeType { }, { displayName: 'Body', - name: 'body', + name: 'description', type: 'string', typeOptions: { rows: 5, From c478fd9ddb1abfc7f2ebf12fc75c8a13b0e92106 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Fri, 8 Nov 2019 10:41:10 -0500 Subject: [PATCH 12/15] done --- .../nodes/Freshdesk/Freshdesk.node.ts | 340 ++++++++++++------ .../nodes/Freshdesk/GenericFunctions.ts | 16 +- 2 files changed, 254 insertions(+), 102 deletions(-) diff --git a/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts b/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts index 7dbeb88a69..3fb530faa4 100644 --- a/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts +++ b/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts @@ -10,7 +10,9 @@ import { INodePropertyOptions, } from 'n8n-workflow'; import { - freshdeskApiRequest + freshdeskApiRequest, + validateJSON, + capitalize } from './GenericFunctions'; enum Status { @@ -34,7 +36,7 @@ enum Source { Chat = 4, Mobihelp = 5, FeedbackWidget = 6, - OutboundEmail =7 + OutboundEmail = 7 } interface ICreateTicketBody { @@ -45,7 +47,7 @@ interface ICreateTicketBody { phone?: string; twitter_id?: string; unique_external_id?: string; - subject?: string; + subject?: string | null; type?: string; status: Status; priority: Priority; @@ -59,8 +61,8 @@ interface ICreateTicketBody { group_id?: number; product_id?: number; source: Source; - tags: [string]; - company_id: number; + tags?: [string]; + company_id?: number; } export class Freshdesk implements INodeType { @@ -310,23 +312,23 @@ export class Freshdesk implements INodeType { default: 'portal', description: 'The channel through which the ticket was created.', }, - { - displayName: 'JSON Parameters', - name: 'jsonParameters', - type: 'boolean', - default: false, - description: '', - displayOptions: { - show: { - resource: [ - 'ticket' - ], - operation: [ - 'create', - ] - }, - }, - }, + // { + // displayName: 'JSON Parameters', + // name: 'jsonParameters', + // type: 'boolean', + // default: false, + // description: '', + // displayOptions: { + // show: { + // resource: [ + // 'ticket' + // ], + // operation: [ + // 'create', + // ] + // }, + // }, + // }, { displayName: 'Options', name: 'options', @@ -363,9 +365,31 @@ export class Freshdesk implements INodeType { { displayName: 'Type', name: 'type', - type: 'string', - default: '', + type: 'options', + default: 'Question', description: 'Helps categorize the ticket according to the different kinds of issues your support team deals with.', + options: [ + { + name: 'Question', + value: 'Question' + }, + { + name: 'Incident', + value: 'Incident' + }, + { + name: 'Problem', + value: 'Problem' + }, + { + name: 'Feature Request', + value: 'Feature Request' + }, + { + name: 'Refund', + value: 'Refund' + }, + ] }, { displayName: 'Description', @@ -426,7 +450,7 @@ export class Freshdesk implements INodeType { { displayName: 'FR Due By', name: 'frDueBy', - type: 'number', + type: 'dateTime', required: false, default: '', description: `Timestamp that denotes when the first response is due`, @@ -467,80 +491,80 @@ export class Freshdesk implements INodeType { }, ] }, - { - displayName: 'Custom Fields', - name: 'customFieldsUi', - placeholder: 'Add Custom fields', - type: 'fixedCollection', - required: false, - 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', - required: false, - name: 'key', - type: 'string', - default: '', - }, - { - displayName: 'Value', - name: 'value', - type: 'string', - required: false, - default: '', - }, - ], - }, - ], - }, - { - displayName: 'Custom Fields', - name: 'customFieldsJson', - type: 'json', - typeOptions: { - alwaysOpenEditWindow: true, - }, - displayOptions: { - show: { - resource: [ - 'ticket' - ], - operation: [ - 'create' - ], - jsonParameters: [ - true, - ], - }, - }, - default: '', - required: false, - placeholder: `{ - "gadget":"Cold Welder" - }`, - description: 'Key value pairs containing the names and values of custom fields.', - }, + // { + // displayName: 'Custom Fields', + // name: 'customFieldsUi', + // placeholder: 'Add Custom fields', + // type: 'fixedCollection', + // required: false, + // 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', + // required: false, + // name: 'key', + // type: 'string', + // default: '', + // }, + // { + // displayName: 'Value', + // name: 'value', + // type: 'string', + // required: false, + // default: '', + // }, + // ], + // }, + // ], + // }, + // { + // displayName: 'Custom Fields', + // name: 'customFieldsJson', + // type: 'json', + // typeOptions: { + // alwaysOpenEditWindow: true, + // }, + // displayOptions: { + // show: { + // resource: [ + // 'ticket' + // ], + // operation: [ + // 'create' + // ], + // jsonParameters: [ + // true, + // ], + // }, + // }, + // default: '', + // required: false, + // placeholder: `{ + // "gadget":"Cold Welder" + // }`, + // description: 'Key value pairs containing the names and values of custom fields.', + // }, ] }; @@ -637,10 +661,124 @@ export class Freshdesk implements INodeType { }; async executeSingle(this: IExecuteSingleFunctions): Promise { + const resource = this.getNodeParameter('resource') as string; + const opeation = this.getNodeParameter('operation') as string; + let response; + + if (resource === 'ticket') { + if (opeation === 'create') { + const requester = this.getNodeParameter('requester') as string; + const value = this.getNodeParameter('requesterIdentificationValue') as string; + const status = this.getNodeParameter('status') as string; + const priority = this.getNodeParameter('priority') as string; + const source = this.getNodeParameter('source') as string; + const options = this.getNodeParameter('options') as IDataObject; + //const jsonActive = this.getNodeParameter('jsonParameters') as boolean; + + const body: ICreateTicketBody = { + // @ts-ignore + status: Status[capitalize(status)], + // @ts-ignore + priority: Priority[capitalize(priority)], + // @ts-ignore + source: Source[capitalize(source)] + }; + + if (requester === 'requesterId') { + // @ts-ignore + if (isNaN(value)) { + throw new Error('Requester Id must be a number'); + } + body.requester_id = parseInt(value, 10); + } else if (requester === 'email'){ + body.email = value; + } else if (requester === 'facebookId'){ + body.facebook_id = value; + } else if (requester === 'phone'){ + body.phone = value; + } else if (requester === 'twitterId'){ + body.twitter_id = value; + } else if (requester === 'uniqueExternalId'){ + body.unique_external_id = value; + } + + // if (!jsonActive) { + // const customFieldsUi = this.getNodeParameter('customFieldsUi') as IDataObject; + // if (Object.keys(customFieldsUi).length > 0) { + // const aux: IDataObject = {}; + // // @ts-ignore + // customFieldsUi.customFieldsValues.forEach( o => { + // aux[`${o.key}`] = o.value; + // return aux; + // }); + // body.custom_fields = aux; + // } else { + // body.custom_fields = validateJSON(this.getNodeParameter('customFielsJson') as string); + // } + + if (options.name) { + body.name = options.name as string; + } + + if (options.subject) { + body.subject = options.subject as string; + } else { + body.subject = 'null'; + } + + if (options.type) { + body.type = options.type as string; + } + + if (options.description) { + body.description = options.description as string; + } else { + body.description = 'null'; + } + + if (options.agent) { + options.responder_id = options.agent as number; + } + + if (options.company) { + options.company_id = options.company as number; + } + + if (options.product) { + options.product_id = options.product as number; + } + + if (options.group) { + options.group_id = options.group as number; + } + + if (options.frDueBy) { + options.fr_due_by = options.frDueBy as string; + } + + if (options.emailConfigId) { + options.email_config_id = options.emailConfigId as number; + } + + if (options.dueBy) { + options.due_by = options.dueBy as string; + } + + if (options.ccEmails) { + // @ts-ignore + options.cc_emails = options.ccEmails.split(',') as [string]; + } + + try { + response = await freshdeskApiRequest.call(this, '/tickets', 'POST', body); + } catch (err) { + throw new Error(`Freskdesk Error: ${JSON.stringify(err)}`); + } + } + } return { - json: {} + json: response }; - } } diff --git a/packages/nodes-base/nodes/Freshdesk/GenericFunctions.ts b/packages/nodes-base/nodes/Freshdesk/GenericFunctions.ts index acab34f810..ea25ccadc8 100644 --- a/packages/nodes-base/nodes/Freshdesk/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Freshdesk/GenericFunctions.ts @@ -9,7 +9,6 @@ import { } 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, body: any = {}, headers?: object): Promise { // tslint:disable-line:no-any @@ -50,3 +49,18 @@ export async function freshdeskApiRequest(this: IHookFunctions | IExecuteFunctio throw error.response.body; } } + +export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any + let result; + try { + result = JSON.parse(json!); + } catch (exception) { + result = []; + } + return result; +} + +export function capitalize (s: string) : string { + if (typeof s !== 'string') return ''; + return s.charAt(0).toUpperCase() + s.slice(1); +} From 3b2e8867a49fb03be631b0a600d1913705fd2d58 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 9 Nov 2019 13:14:30 +0100 Subject: [PATCH 13/15] :books: Add documentation for some more environment variables --- docs/configuration.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index 8b5e2160c5..51473c0ef1 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -6,6 +6,32 @@ It is possible to change some of the n8n defaults via special environment variab The ones that currently exist are: +## Publish + +Sets how n8n should be made available. + +```bash +# The port n8n should be made available on +N8N_PORT=5678 + +# This ones are currently only important for the webhook URL creation. +# So if "WEBHOOK_TUNNEL_URL" got set they do get ignored. It is however +# encouraged to set them correctly anyway in case they will become +# important in the future. +N8N_PROTOCOL=https +N8N_HOST=n8n.example.com +``` + + +## Base URL + +Tells the frontend how to reach the REST API of the backend. + +```bash +export VUE_APP_URL_BASE_API="https://n8n.example.com/" +``` + + ## Execution Data Manual Runs n8n creates a random encryption key automatically on the first launch and saves @@ -99,3 +125,18 @@ user-folder via an environment variable. ```bash export N8N_USER_FOLDER="/home/jim/n8n" ``` + + +## Webhook URL + +The webhook URL will normally be created automatically by combining +`N8N_PROTOCOL`, `N8N_HOST` and `N8N_PORT`. If n8n runs, however, behind a +reverse proxy that would not work. Because n8n does for example run internally +on port 5678 but is exposed to the web via the reverse proxy on port 443. In +that case, it is important to set the webhook URL manually that it can be +displayed correctly in the Editor UI and even more important that the correct +webhook URLs get registred with external services. + +```bash +export WEBHOOK_TUNNEL_URL="https://n8n.example.com/" +``` From ae0ee5ca0127d9c8f0ca3ade3cfe243f4959450d Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 10 Nov 2019 10:17:56 +0100 Subject: [PATCH 14/15] :zap: Fix due_date on Gitlab-Node --- .../nodes-base/nodes/Gitlab/Gitlab.node.ts | 51 +++++++++---------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts b/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts index c37cfd8b82..1f1d0b5199 100644 --- a/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts +++ b/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts @@ -241,7 +241,7 @@ export class Gitlab implements INodeType { }, { displayName: 'Body', - name: 'description', + name: 'body', type: 'string', typeOptions: { rows: 5, @@ -259,6 +259,23 @@ export class Gitlab implements INodeType { }, description: 'The body of the issue.', }, + { + displayName: 'Due Date', + name: 'due_date', + type: 'dateTime', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'issue', + ], + }, + }, + default: '', + description: 'Due Date for issue.', + }, { displayName: 'Labels', name: 'labels', @@ -317,31 +334,8 @@ export class Gitlab implements INodeType { }, ], }, - { - displayName: 'Due Date', - name: 'date_date', - type: 'string', - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'issue', - ], - }, - }, - default: { 'due_date': '' }, - options: [ - { - displayName: 'Due Date', - name: 'date_date', - type: 'string', - default: 0, - description: 'Due Date for issue. Date time string in the format YEAR-MONTH-DAY, e.g. 2016-03-11', - }, - ], - }, + + // ---------------------------------- // issue:createComment // ---------------------------------- @@ -501,9 +495,9 @@ export class Gitlab implements INodeType { { displayName: 'Due Date', name: 'due_date', - type: 'string', + type: 'dateTime', default: '', - description: 'The Due Date of the issue. Date time string in the format YEAR-MONTH-DAY, e.g. 2016-03-11', + description: 'Due Date for issue.', }, ], }, @@ -860,6 +854,7 @@ export class Gitlab implements INodeType { body.title = this.getNodeParameter('title', i) as string; body.description = this.getNodeParameter('body', i) as string; + body.due_date = this.getNodeParameter('due_date', i) as string; const labels = this.getNodeParameter('labels', i) as IDataObject[]; const assigneeIds = this.getNodeParameter('assignee_ids', i) as IDataObject[]; From c6cf496f64f528edf94e6cb04a07d2a2dfba89ed Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 10 Nov 2019 12:33:23 +0100 Subject: [PATCH 15/15] :zap: Fix status issue on Freshdesk-Node and package.json file --- .../nodes/Freshdesk/Freshdesk.node.ts | 35 ++++++++++--------- packages/nodes-base/package.json | 16 ++++----- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts b/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts index 3fb530faa4..a6594ec768 100644 --- a/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts +++ b/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts @@ -16,10 +16,10 @@ import { } from './GenericFunctions'; enum Status { - Open = 1, - Pending = 2, - Resolved = 3, - Closed = 4 + Open = 2, + Pending = 3, + Resolved = 4, + Closed = 5, } enum Priority { @@ -36,7 +36,7 @@ enum Source { Chat = 4, Mobihelp = 5, FeedbackWidget = 6, - OutboundEmail = 7 + OutboundEmail = 7 } interface ICreateTicketBody { @@ -683,7 +683,7 @@ export class Freshdesk implements INodeType { // @ts-ignore source: Source[capitalize(source)] }; - + if (requester === 'requesterId') { // @ts-ignore if (isNaN(value)) { @@ -737,36 +737,39 @@ export class Freshdesk implements INodeType { } if (options.agent) { - options.responder_id = options.agent as number; + body.responder_id = options.agent as number; } if (options.company) { - options.company_id = options.company as number; + body.company_id = options.company as number; } if (options.product) { - options.product_id = options.product as number; + body.product_id = options.product as number; } - + if (options.group) { - options.group_id = options.group as number; + body.group_id = options.group as number; } if (options.frDueBy) { - options.fr_due_by = options.frDueBy as string; + body.fr_due_by = options.frDueBy as string; } if (options.emailConfigId) { - options.email_config_id = options.emailConfigId as number; + body.email_config_id = options.emailConfigId as number; } if (options.dueBy) { - options.due_by = options.dueBy as string; + body.due_by = options.dueBy as string; + } + + if (options.tags) { + body.tags = (options.tags as string).split(',') as [string]; } if (options.ccEmails) { - // @ts-ignore - options.cc_emails = options.ccEmails.split(',') as [string]; + body.cc_emails = (options.ccEmails as string).split(',') as [string]; } try { diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index b8054bba58..0343726121 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -33,6 +33,7 @@ "dist/credentials/Aws.credentials.js", "dist/credentials/ChargebeeApi.credentials.js", "dist/credentials/DropboxApi.credentials.js", + "dist/credentials/FreshdeskApi.credentials.js", "dist/credentials/GithubApi.credentials.js", "dist/credentials/GitlabApi.credentials.js", "dist/credentials/GoogleApi.credentials.js", @@ -42,6 +43,7 @@ "dist/credentials/Imap.credentials.js", "dist/credentials/LinkFishApi.credentials.js", "dist/credentials/MailgunApi.credentials.js", + "dist/credentials/MandrillApi.credentials.js", "dist/credentials/MattermostApi.credentials.js", "dist/credentials/MongoDb.credentials.js", "dist/credentials/NextCloudApi.credentials.js", @@ -53,12 +55,10 @@ "dist/credentials/Smtp.credentials.js", "dist/credentials/StripeApi.credentials.js", "dist/credentials/TelegramApi.credentials.js", + "dist/credentials/TodoistApi.credentials.js", "dist/credentials/TrelloApi.credentials.js", "dist/credentials/TwilioApi.credentials.js", - "dist/credentials/TypeformApi.credentials.js", - "dist/credentials/MandrillApi.credentials.js", - "dist/credentials/FreshdeskApi.credentials.js" - "dist/credentials/TodoistApi.credentials.js" + "dist/credentials/TypeformApi.credentials.js" ], "nodes": [ "dist/nodes/ActiveCampaign/ActiveCampaign.node.js", @@ -79,6 +79,7 @@ "dist/nodes/EmailSend.node.js", "dist/nodes/ErrorTrigger.node.js", "dist/nodes/ExecuteCommand.node.js", + "dist/nodes/Freshdesk/Freshdesk.node.js", "dist/nodes/Function.node.js", "dist/nodes/FunctionItem.node.js", "dist/nodes/Github/Github.node.js", @@ -93,6 +94,7 @@ "dist/nodes/Interval.node.js", "dist/nodes/LinkFish/LinkFish.node.js", "dist/nodes/Mailgun/Mailgun.node.js", + "dist/nodes/Mandrill/Mandrill.node.js", "dist/nodes/Mattermost/Mattermost.node.js", "dist/nodes/Merge.node.js", "dist/nodes/MoveBinaryData.node.js", @@ -117,16 +119,14 @@ "dist/nodes/Stripe/StripeTrigger.node.js", "dist/nodes/Telegram/Telegram.node.js", "dist/nodes/Telegram/TelegramTrigger.node.js", + "dist/nodes/Todoist/Todoist.node.js", "dist/nodes/Trello/Trello.node.js", "dist/nodes/Trello/TrelloTrigger.node.js", "dist/nodes/Twilio/Twilio.node.js", "dist/nodes/Typeform/TypeformTrigger.node.js", "dist/nodes/WriteBinaryFile.node.js", "dist/nodes/Webhook.node.js", - "dist/nodes/Xml.node.js", - "dist/nodes/Mandrill/Mandrill.node.js", - "dist/nodes/Freshdesk/Freshdesk.node.js" - "dist/nodes/Todoist/Todoist.node.js" + "dist/nodes/Xml.node.js" ] }, "devDependencies": {