diff --git a/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts b/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts index f098f97358..ffe371e160 100644 --- a/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts @@ -62,6 +62,9 @@ export async function zendeskApiRequestAllItems(this: IHookFunctions | IExecuteF responseData = await zendeskApiRequest.call(this, method, resource, body, query, uri); uri = responseData.next_page; returnData.push.apply(returnData, responseData[propertyName]); + if (query.limit && query.limit <= returnData.length) { + return returnData; + } } while ( responseData.next_page !== undefined && responseData.next_page !== null @@ -69,3 +72,13 @@ export async function zendeskApiRequestAllItems(this: IHookFunctions | IExecuteF return returnData; } + +export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any + let result; + try { + result = JSON.parse(json!); + } catch (exception) { + result = undefined; + } + return result; +} diff --git a/packages/nodes-base/nodes/Zendesk/TicketDescription.ts b/packages/nodes-base/nodes/Zendesk/TicketDescription.ts index 7642248e7e..f5daab6535 100644 --- a/packages/nodes-base/nodes/Zendesk/TicketDescription.ts +++ b/packages/nodes-base/nodes/Zendesk/TicketDescription.ts @@ -21,9 +21,9 @@ export const ticketOperations = [ description: 'Create a ticket', }, { - name: 'Update', - value: 'update', - description: 'Update a ticket', + name: 'Delete', + value: 'delete', + description: 'Delete a ticket', }, { name: 'Get', @@ -36,9 +36,9 @@ export const ticketOperations = [ description: 'Get all tickets', }, { - name: 'Delete', - value: 'delete', - description: 'Delete a ticket', + name: 'Update', + value: 'update', + description: 'Update a ticket', }, ], default: 'create', @@ -81,7 +81,7 @@ export const ticketFields = [ displayOptions: { show: { resource: [ - 'ticket' + 'ticket', ], operation: [ 'create', @@ -103,6 +103,9 @@ export const ticketFields = [ operation: [ 'create', ], + jsonParameters: [ + false, + ], }, }, options: [ @@ -114,11 +117,14 @@ export const ticketFields = [ description: 'An id you can use to link Zendesk Support tickets to local records', }, { - displayName: 'Subject', - name: 'subject', - type: 'string', + displayName: 'Group', + name: 'group', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getGroups', + }, default: '', - description: 'The value of the subject field for this ticket', + description: 'The group this ticket is assigned to', }, { displayName: 'Recipient', @@ -128,14 +134,40 @@ export const ticketFields = [ description: 'The original recipient e-mail address of the ticket', }, { - displayName: 'Group', - name: 'group', + displayName: 'Status', + name: 'status', type: 'options', - typeOptions: { - loadOptionsMethod: 'getGroups', - }, + options: [ + { + name: 'Open', + value: 'open', + }, + { + name: 'New', + value: 'new', + }, + { + name: 'Pending', + value: 'pending', + }, + { + name: 'Solved', + value: 'solved', + }, + { + name: 'Closed', + value: 'closed', + }, + ], default: '', - description: 'The group this ticket is assigned to', + description: 'The state of the ticket', + }, + { + displayName: 'Subject', + name: 'subject', + type: 'string', + default: '', + description: 'The value of the subject field for this ticket', }, { displayName: 'Tags', @@ -172,37 +204,78 @@ export const ticketFields = [ default: '', description: 'The type of this ticket', }, + ], + }, + { + displayName: 'Custom Fields', + name: 'customFieldsUi', + placeholder: 'Add Custom Field', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'create', + ], + jsonParameters: [ + false, + ], + }, + }, + default: {}, + options: [ { - displayName: 'Status', - name: 'status', - type: 'options', - options: [ + displayName: 'Custom Field', + name: 'customFieldsValues', + values: [ { - name: 'Open', - value: 'open', + displayName: 'ID', + name: 'id', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCustomFields', + }, + default: '', + description: 'Custom field ID', }, { - name: 'New', - value: 'new', - }, - { - name: 'Pending', - value: 'pending', - }, - { - name: 'Solved', - value: 'solved', - }, - { - name: 'Closed', - value: 'closed', + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Custom field Value.', }, ], - default: '', - description: 'The state of the ticket', - } + }, ], }, + { + displayName: ' Additional Fields', + name: 'additionalFieldsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'create', + ], + jsonParameters: [ + true, + ], + }, + }, + }, { displayName: ' Custom Fields', name: 'customFieldsJson', @@ -224,14 +297,13 @@ export const ticketFields = [ ], }, }, - required: true, description: `Array of customs fields Details`, }, /* -------------------------------------------------------------------------- */ /* ticket:update */ /* -------------------------------------------------------------------------- */ { - displayName: 'ID', + displayName: 'Ticket ID', name: 'id', type: 'string', default: '', @@ -279,6 +351,9 @@ export const ticketFields = [ operation: [ 'update', ], + jsonParameters: [ + false, + ], }, }, options: [ @@ -290,11 +365,14 @@ export const ticketFields = [ description: 'An id you can use to link Zendesk Support tickets to local records', }, { - displayName: 'Subject', - name: 'subject', - type: 'string', + displayName: 'Group', + name: 'group', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getGroups', + }, default: '', - description: 'The value of the subject field for this ticket', + description: 'The group this ticket is assigned to', }, { displayName: 'Recipient', @@ -304,14 +382,40 @@ export const ticketFields = [ description: 'The original recipient e-mail address of the ticket', }, { - displayName: 'Group', - name: 'group', + displayName: 'Status', + name: 'status', type: 'options', - typeOptions: { - loadOptionsMethod: 'getGroups', - }, + options: [ + { + name: 'Open', + value: 'open', + }, + { + name: 'New', + value: 'new', + }, + { + name: 'Pending', + value: 'pending', + }, + { + name: 'Solved', + value: 'solved', + }, + { + name: 'Closed', + value: 'closed', + }, + ], default: '', - description: 'The group this ticket is assigned to', + description: 'The state of the ticket', + }, + { + displayName: 'Subject', + name: 'subject', + type: 'string', + default: '', + description: 'The value of the subject field for this ticket', }, { displayName: 'Tags', @@ -348,37 +452,78 @@ export const ticketFields = [ default: '', description: 'The type of this ticket', }, + ], + }, + { + displayName: 'Custom Fields', + name: 'customFieldsUi', + placeholder: 'Add Custom Field', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'update', + ], + jsonParameters: [ + false, + ], + }, + }, + default: {}, + options: [ { - displayName: 'Status', - name: 'status', - type: 'options', - options: [ + displayName: 'Custom Field', + name: 'customFieldsValues', + values: [ { - name: 'Open', - value: 'open', + displayName: 'ID', + name: 'id', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCustomFields', + }, + default: '', + description: 'Custom field ID', }, { - name: 'New', - value: 'new', - }, - { - name: 'Pending', - value: 'pending', - }, - { - name: 'Solved', - value: 'solved', - }, - { - name: 'Closed', - value: 'closed', + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Custom field Value.', }, ], - default: '', - description: 'The state of the ticket', - } + }, ], }, + { + displayName: ' Update Fields', + name: 'updateFieldsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'update', + ], + jsonParameters: [ + true, + ], + }, + }, + }, { displayName: ' Custom Fields', name: 'customFieldsJson', @@ -407,7 +552,7 @@ export const ticketFields = [ /* ticket:get */ /* -------------------------------------------------------------------------- */ { - displayName: 'ID', + displayName: 'Ticket ID', name: 'id', type: 'string', default: '', @@ -485,35 +630,6 @@ export const ticketFields = [ }, }, options: [ - { - displayName: 'Status', - name: 'status', - type: 'options', - options: [ - { - name: 'Open', - value: 'open', - }, - { - name: 'New', - value: 'new', - }, - { - name: 'Pending', - value: 'pending', - }, - { - name: 'Solved', - value: 'solved', - }, - { - name: 'Closed', - value: 'closed', - }, - ], - default: '', - description: 'The state of the ticket', - }, { displayName: 'Sort By', name: 'sortBy', @@ -559,7 +675,36 @@ export const ticketFields = [ ], default: 'desc', description: 'Sort order', - } + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'Open', + value: 'open', + }, + { + name: 'New', + value: 'new', + }, + { + name: 'Pending', + value: 'pending', + }, + { + name: 'Solved', + value: 'solved', + }, + { + name: 'Closed', + value: 'closed', + }, + ], + default: '', + description: 'The state of the ticket', + }, ], }, @@ -567,7 +712,7 @@ export const ticketFields = [ /* ticket:delete */ /* -------------------------------------------------------------------------- */ { - displayName: 'ID', + displayName: 'Ticket ID', name: 'id', type: 'string', default: '', diff --git a/packages/nodes-base/nodes/Zendesk/TicketFieldDescription.ts b/packages/nodes-base/nodes/Zendesk/TicketFieldDescription.ts index 8e061db402..9ba754eef4 100644 --- a/packages/nodes-base/nodes/Zendesk/TicketFieldDescription.ts +++ b/packages/nodes-base/nodes/Zendesk/TicketFieldDescription.ts @@ -54,4 +54,49 @@ export const ticketFieldFields = [ }, description: 'ticketField ID', }, + +/* -------------------------------------------------------------------------- */ +/* ticketField:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'ticketField', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'ticketField', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 100, + }, + default: 100, + description: 'How many results to return.', + }, ] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts b/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts index b66481bcc1..a64feb9ca5 100644 --- a/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts +++ b/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts @@ -12,6 +12,7 @@ import { } from 'n8n-workflow'; import { + validateJSON, zendeskApiRequest, zendeskApiRequestAllItems, } from './GenericFunctions'; @@ -30,6 +31,7 @@ import { ITicket, IComment, } from './TicketInterface'; +import { response } from 'express'; export class Zendesk implements INodeType { description: INodeTypeDescription = { @@ -83,6 +85,33 @@ export class Zendesk implements INodeType { methods = { loadOptions: { + // Get all the custom fields to display them to user so that he can + // select them easily + async getCustomFields(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const customFields = [ + 'text', + 'textarea', + 'date', + 'integer', + 'decimal', + 'regexp', + 'multiselect', + 'tagger', + ]; + const fields = await zendeskApiRequestAllItems.call(this, 'ticket_fields', 'GET', '/ticket_fields'); + for (const field of fields) { + if (customFields.includes(field.type)) { + const fieldName = field.title; + const fieldId = field.id; + returnData.push({ + name: fieldName, + value: fieldId, + }); + } + } + return returnData; + }, // Get all the groups to display them to user so that he can // select them easily async getGroups(this: ILoadOptionsFunctions): Promise { @@ -131,42 +160,71 @@ export class Zendesk implements INodeType { 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, }; const body: ITicket = { comment, }; - if (additionalFields.type) { - body.type = additionalFields.type as string; - } - if (additionalFields.externalId) { - body.external_id = additionalFields.externalId as string; - } - if (additionalFields.subject) { - body.subject = additionalFields.subject as string; - } - if (additionalFields.status) { - body.status = additionalFields.status as string; - } - if (additionalFields.recipient) { - body.recipient = additionalFields.recipient as string; - } - if (additionalFields.group) { - body.group = additionalFields.group as string; - } - if (additionalFields.tags) { - body.tags = additionalFields.tags as string[]; - } 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'); + + if (customFieldsJson !== '' ) { + + if (validateJSON(customFieldsJson) !== undefined) { + + body.custom_fields = JSON.parse(customFieldsJson); + + } else { + throw new Error('Custom fields must be a valid JSON'); + } + } + + const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string; + + if (additionalFieldsJson !== '' ) { + + if (validateJSON(additionalFieldsJson) !== undefined) { + + Object.assign(body, JSON.parse(additionalFieldsJson)); + + } else { + throw new Error('Additional fields must be a valid JSON'); + } + } + + + } else { + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + const customFields = (this.getNodeParameter('customFieldsUi', i) as IDataObject).customFieldsValues as IDataObject[]; + + if (additionalFields.type) { + body.type = additionalFields.type as string; + } + if (additionalFields.externalId) { + body.external_id = additionalFields.externalId as string; + } + if (additionalFields.subject) { + body.subject = additionalFields.subject as string; + } + if (additionalFields.status) { + body.status = additionalFields.status as string; + } + if (additionalFields.recipient) { + body.recipient = additionalFields.recipient as string; + } + if (additionalFields.group) { + body.group = additionalFields.group as string; + } + if (additionalFields.tags) { + body.tags = additionalFields.tags as string[]; + } + if (customFields) { + body.custom_fields = customFields; } - body.custom_fields = JSON.parse(customFieldsJson); } responseData = await zendeskApiRequest.call(this, 'POST', '/tickets', { ticket: body }); responseData = responseData.ticket; @@ -175,37 +233,66 @@ export class Zendesk implements INodeType { 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) { - body.type = updateFields.type as string; - } - if (updateFields.externalId) { - body.external_id = updateFields.externalId as string; - } - if (updateFields.subject) { - body.subject = updateFields.subject as string; - } - if (updateFields.status) { - body.status = updateFields.status as string; - } - if (updateFields.recipient) { - body.recipient = updateFields.recipient as string; - } - if (updateFields.group) { - body.group = updateFields.group as string; - } - if (updateFields.tags) { - body.tags = updateFields.tags as string[]; - } + 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'); + + if (customFieldsJson !== '' ) { + + if (validateJSON(customFieldsJson) !== undefined) { + + body.custom_fields = JSON.parse(customFieldsJson); + + } else { + throw new Error('Custom fields must be a valid JSON'); + } + } + + const updateFieldsJson = this.getNodeParameter('updateFieldsJson', i) as string; + + if (updateFieldsJson !== '' ) { + + if (validateJSON(updateFieldsJson) !== undefined) { + + Object.assign(body, JSON.parse(updateFieldsJson)); + + } else { + throw new Error('Additional fields must be a valid JSON'); + } + } + + } else { + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + const customFields = (this.getNodeParameter('customFieldsUi', i) as IDataObject).customFieldsValues as IDataObject[]; + + if (updateFields.type) { + body.type = updateFields.type as string; + } + if (updateFields.externalId) { + body.external_id = updateFields.externalId as string; + } + if (updateFields.subject) { + body.subject = updateFields.subject as string; + } + if (updateFields.status) { + body.status = updateFields.status as string; + } + if (updateFields.recipient) { + body.recipient = updateFields.recipient as string; + } + if (updateFields.group) { + body.group = updateFields.group as string; + } + if (updateFields.tags) { + body.tags = updateFields.tags as string[]; + } + if (customFields) { + body.custom_fields = customFields; } - body.custom_fields = JSON.parse(customFieldsJson); } responseData = await zendeskApiRequest.call(this, 'PUT', `/tickets/${ticketId}`, { ticket: body }); responseData = responseData.ticket; @@ -259,8 +346,15 @@ export class Zendesk implements INodeType { } //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; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + if (returnAll) { + responseData = await zendeskApiRequestAllItems.call(this, 'ticket_fields', 'GET', '/ticket_fields', {}, qs); + } else { + const limit = this.getNodeParameter('limit', i) as number; + qs.limit = limit; + responseData = await zendeskApiRequestAllItems.call(this, 'ticket_fields', 'GET', '/ticket_fields', {}, qs); + responseData = responseData.slice(0, limit); + } } } if (Array.isArray(responseData)) {