From adf3f62aabf5eef6f51a88fa2aced8b53106bea7 Mon Sep 17 00:00:00 2001 From: ricardo Date: Mon, 6 Apr 2020 19:11:50 -0400 Subject: [PATCH 1/2] :zap: Zendesk improvements --- .../nodes/Zendesk/ConditionDescription.ts | 4 +- .../nodes/Zendesk/GenericFunctions.ts | 13 ++- .../nodes/Zendesk/TicketDescription.ts | 86 +++++++++++++++++- .../nodes/Zendesk/TicketFieldDescription.ts | 57 ++++++++++++ .../nodes/Zendesk/TicketInterface.ts | 13 ++- .../nodes-base/nodes/Zendesk/Zendesk.node.ts | 90 +++++++++++++------ .../nodes/Zendesk/ZendeskTrigger.node.ts | 2 +- 7 files changed, 228 insertions(+), 37 deletions(-) create mode 100644 packages/nodes-base/nodes/Zendesk/TicketFieldDescription.ts diff --git a/packages/nodes-base/nodes/Zendesk/ConditionDescription.ts b/packages/nodes-base/nodes/Zendesk/ConditionDescription.ts index 99a1429b0c..1fb12cff8e 100644 --- a/packages/nodes-base/nodes/Zendesk/ConditionDescription.ts +++ b/packages/nodes-base/nodes/Zendesk/ConditionDescription.ts @@ -1,4 +1,6 @@ -import { INodeProperties } from 'n8n-workflow'; +import { + INodeProperties, + } from 'n8n-workflow'; export const conditionFields = [ { diff --git a/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts b/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts index 5126c69495..b78bfd81f4 100644 --- a/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts @@ -1,11 +1,17 @@ -import { OptionsWithUri } from 'request'; +import { + OptionsWithUri, + } from 'request'; + import { IExecuteFunctions, IExecuteSingleFunctions, IHookFunctions, ILoadOptionsFunctions, } from 'n8n-core'; -import { IDataObject } from 'n8n-workflow'; + +import { + IDataObject, + } from 'n8n-workflow'; export async function zendeskApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('zendeskApi'); @@ -28,7 +34,8 @@ export async function zendeskApiRequest(this: IHookFunctions | IExecuteFunctions try { return await this.helpers.request!(options); } catch (err) { - throw new Error(err); + const errorMessage = err.response.body.description || err.response.body; + throw new Error(`Zendesk error response [${err.statusCode}]: ${errorMessage}`); } } diff --git a/packages/nodes-base/nodes/Zendesk/TicketDescription.ts b/packages/nodes-base/nodes/Zendesk/TicketDescription.ts index 477a8074e4..177f9d715d 100644 --- a/packages/nodes-base/nodes/Zendesk/TicketDescription.ts +++ b/packages/nodes-base/nodes/Zendesk/TicketDescription.ts @@ -1,4 +1,6 @@ -import { INodeProperties } from 'n8n-workflow'; +import { + INodeProperties, + } from 'n8n-workflow'; export const ticketOperations = [ { @@ -70,6 +72,23 @@ export const ticketFields = [ required: true, description: 'The first comment on the ticket', }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + resource: [ + 'ticket' + ], + operation: [ + 'create', + ], + }, + }, + }, { displayName: 'Additional Fields', name: 'additionalFields', @@ -184,6 +203,30 @@ export const ticketFields = [ } ], }, + { + displayName: ' Custom Fields', + name: 'customFieldsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'create', + ], + jsonParameters: [ + true, + ], + }, + }, + required: true, + description: `Array of customs fields Details`, + }, /* -------------------------------------------------------------------------- */ /* ticket:update */ /* -------------------------------------------------------------------------- */ @@ -205,6 +248,23 @@ export const ticketFields = [ }, description: 'Ticket ID', }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + resource: [ + 'ticket' + ], + operation: [ + 'update', + ], + }, + }, + }, { displayName: 'Update Fields', name: 'updateFields', @@ -319,6 +379,30 @@ export const ticketFields = [ } ], }, + { + displayName: ' Custom Fields', + name: 'customFieldsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'update', + ], + jsonParameters: [ + true, + ], + }, + }, + required: true, + description: `Array of customs fields Details`, + }, /* -------------------------------------------------------------------------- */ /* ticket:get */ /* -------------------------------------------------------------------------- */ diff --git a/packages/nodes-base/nodes/Zendesk/TicketFieldDescription.ts b/packages/nodes-base/nodes/Zendesk/TicketFieldDescription.ts new file mode 100644 index 0000000000..8e061db402 --- /dev/null +++ b/packages/nodes-base/nodes/Zendesk/TicketFieldDescription.ts @@ -0,0 +1,57 @@ +import { + INodeProperties, + } from 'n8n-workflow'; + +export const ticketFieldOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'ticketField', + ], + }, + }, + options: [ + { + name: 'Get', + value: 'get', + description: 'Get a ticket field', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all system and custom ticket fields', + }, + ], + default: 'get', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const ticketFieldFields = [ + +/* -------------------------------------------------------------------------- */ +/* ticketField:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Ticket Field ID', + name: 'ticketFieldId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'ticketField', + ], + operation: [ + 'get', + ], + }, + }, + description: 'ticketField ID', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Zendesk/TicketInterface.ts b/packages/nodes-base/nodes/Zendesk/TicketInterface.ts index 5ef968381b..f3338ca260 100644 --- a/packages/nodes-base/nodes/Zendesk/TicketInterface.ts +++ b/packages/nodes-base/nodes/Zendesk/TicketInterface.ts @@ -1,3 +1,11 @@ +import { + IDataObject, + } from 'n8n-workflow'; + + export interface IComment { + body?: string; +} + export interface ITicket { subject?: string; comment?: IComment; @@ -7,8 +15,5 @@ export interface ITicket { tags?: string[]; status?: string; recipient?: string; -} - -export interface IComment { - body?: string; + custom_fields?: IDataObject[]; } diff --git a/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts b/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts index 91d055dae8..b66481bcc1 100644 --- a/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts +++ b/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts @@ -1,6 +1,7 @@ import { IExecuteFunctions, } from 'n8n-core'; + import { IDataObject, INodeTypeDescription, @@ -9,14 +10,22 @@ import { ILoadOptionsFunctions, INodePropertyOptions, } from 'n8n-workflow'; + import { zendeskApiRequest, zendeskApiRequestAllItems, } from './GenericFunctions'; + import { ticketFields, ticketOperations } from './TicketDescription'; + +import { + ticketFieldFields, + ticketFieldOperations +} from './TicketFieldDescription'; + import { ITicket, IComment, @@ -54,12 +63,21 @@ export class Zendesk implements INodeType { value: 'ticket', description: 'Tickets are the means through which your end users (customers) communicate with agents in Zendesk Support.', }, + { + name: 'Ticket Field', + value: 'ticketField', + description: 'Manage system and custom ticket fields', + }, ], default: 'ticket', description: 'Resource to consume.', }, + // TICKET ...ticketOperations, ...ticketFields, + // TICKET FIELDS + ...ticketFieldOperations, + ...ticketFieldFields, ], }; @@ -112,6 +130,7 @@ export class Zendesk implements INodeType { //https://developer.zendesk.com/rest_api/docs/support/tickets if (operation === 'create') { const description = this.getNodeParameter('description', i) as string; + const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; const comment: IComment = { body: description, @@ -140,16 +159,22 @@ export class Zendesk implements INodeType { if (additionalFields.tags) { body.tags = additionalFields.tags as string[]; } - try { - responseData = await zendeskApiRequest.call(this, 'POST', '/tickets', { ticket: body }); - responseData = responseData.ticket; - } catch (err) { - throw new Error(`Zendesk Error: ${err}`); + if (jsonParameters) { + const customFieldsJson = this.getNodeParameter('customFieldsJson', i) as string; + try { + JSON.parse(customFieldsJson); + } catch(err) { + throw new Error('Custom fields must be a valid JSON'); + } + body.custom_fields = JSON.parse(customFieldsJson); } + responseData = await zendeskApiRequest.call(this, 'POST', '/tickets', { ticket: body }); + responseData = responseData.ticket; } //https://developer.zendesk.com/rest_api/docs/support/tickets#update-ticket if (operation === 'update') { const ticketId = this.getNodeParameter('id', i) as string; + const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean; const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; const body: ITicket = {}; if (updateFields.type) { @@ -173,22 +198,23 @@ export class Zendesk implements INodeType { if (updateFields.tags) { body.tags = updateFields.tags as string[]; } - try { - responseData = await zendeskApiRequest.call(this, 'PUT', `/tickets/${ticketId}`, { ticket: body }); - responseData = responseData.ticket; - } catch (err) { - throw new Error(`Zendesk Error: ${err}`); + if (jsonParameters) { + const customFieldsJson = this.getNodeParameter('customFieldsJson', i) as string; + try { + JSON.parse(customFieldsJson); + } catch(err) { + throw new Error('Custom fields must be a valid JSON'); + } + body.custom_fields = JSON.parse(customFieldsJson); } + responseData = await zendeskApiRequest.call(this, 'PUT', `/tickets/${ticketId}`, { ticket: body }); + responseData = responseData.ticket; } //https://developer.zendesk.com/rest_api/docs/support/tickets#show-ticket if (operation === 'get') { const ticketId = this.getNodeParameter('id', i) as string; - try { - responseData = await zendeskApiRequest.call(this, 'GET', `/tickets/${ticketId}`, {}); - responseData = responseData.ticket; - } catch (err) { - throw new Error(`Zendesk Error: ${err}`); - } + responseData = await zendeskApiRequest.call(this, 'GET', `/tickets/${ticketId}`, {}); + responseData = responseData.ticket; } //https://developer.zendesk.com/rest_api/docs/support/search#list-search-results if (operation === 'getAll') { @@ -204,17 +230,13 @@ export class Zendesk implements INodeType { if (options.sortOrder) { qs.sort_order = options.sortOrder; } - try { - if (returnAll) { - responseData = await zendeskApiRequestAllItems.call(this, 'results', 'GET', `/search`, {}, qs); - } else { - const limit = this.getNodeParameter('limit', i) as number; - qs.per_page = limit; - responseData = await zendeskApiRequest.call(this, 'GET', `/search`, {}, qs); - responseData = responseData.results; - } - } catch (err) { - throw new Error(`Zendesk Error: ${err}`); + if (returnAll) { + responseData = await zendeskApiRequestAllItems.call(this, 'results', 'GET', `/search`, {}, qs); + } else { + const limit = this.getNodeParameter('limit', i) as number; + qs.per_page = limit; + responseData = await zendeskApiRequest.call(this, 'GET', `/search`, {}, qs); + responseData = responseData.results; } } //https://developer.zendesk.com/rest_api/docs/support/tickets#delete-ticket @@ -227,6 +249,20 @@ export class Zendesk implements INodeType { } } } + //https://developer.zendesk.com/rest_api/docs/support/ticket_fields + if (resource === 'ticketField') { + //https://developer.zendesk.com/rest_api/docs/support/tickets#show-ticket + if (operation === 'get') { + const ticketFieldId = this.getNodeParameter('ticketFieldId', i) as string; + responseData = await zendeskApiRequest.call(this, 'GET', `/ticket_fields/${ticketFieldId}`, {}); + responseData = responseData.ticket_field; + } + //https://developer.zendesk.com/rest_api/docs/support/ticket_fields#list-ticket-fields + if (operation === 'getAll') { + responseData = await zendeskApiRequest.call(this, 'GET', '/ticket_fields', {}, qs); + responseData = responseData.ticket_fields; + } + } if (Array.isArray(responseData)) { returnData.push.apply(returnData, responseData as IDataObject[]); } else { diff --git a/packages/nodes-base/nodes/Zendesk/ZendeskTrigger.node.ts b/packages/nodes-base/nodes/Zendesk/ZendeskTrigger.node.ts index e242141d92..cc4e26dbd4 100644 --- a/packages/nodes-base/nodes/Zendesk/ZendeskTrigger.node.ts +++ b/packages/nodes-base/nodes/Zendesk/ZendeskTrigger.node.ts @@ -21,7 +21,7 @@ import { zendeskApiRequestAllItems, } from './GenericFunctions'; import { - conditionFields + conditionFields, } from './ConditionDescription'; export class ZendeskTrigger implements INodeType { From d0c8e3f4e7322bae7c9db28e0363603aa2f29bef Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 9 Apr 2020 00:31:39 +0200 Subject: [PATCH 2/2] :zap: Small improvements to Zendesk-Node --- packages/nodes-base/nodes/Zendesk/GenericFunctions.ts | 9 ++++++++- packages/nodes-base/nodes/Zendesk/TicketDescription.ts | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts b/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts index b78bfd81f4..f098f97358 100644 --- a/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts @@ -34,7 +34,14 @@ export async function zendeskApiRequest(this: IHookFunctions | IExecuteFunctions try { return await this.helpers.request!(options); } catch (err) { - const errorMessage = err.response.body.description || err.response.body; + let errorMessage = err.message; + if (err.response && err.response.body && err.response.body.error) { + errorMessage = err.response.body.error; + if (typeof err.response.body.error !== 'string') { + errorMessage = JSON.stringify(errorMessage); + } + } + throw new Error(`Zendesk error response [${err.statusCode}]: ${errorMessage}`); } } diff --git a/packages/nodes-base/nodes/Zendesk/TicketDescription.ts b/packages/nodes-base/nodes/Zendesk/TicketDescription.ts index 177f9d715d..7642248e7e 100644 --- a/packages/nodes-base/nodes/Zendesk/TicketDescription.ts +++ b/packages/nodes-base/nodes/Zendesk/TicketDescription.ts @@ -225,7 +225,7 @@ export const ticketFields = [ }, }, required: true, - description: `Array of customs fields Details`, + description: `Array of customs fields Details`, }, /* -------------------------------------------------------------------------- */ /* ticket:update */