diff --git a/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts b/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts index f3320f5184..379a99fe28 100644 --- a/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts @@ -42,7 +42,7 @@ export async function zendeskApiRequest(this: IHookFunctions | IExecuteFunctions } const base64Key = Buffer.from(`${credentials.email}/token:${credentials.apiToken}`).toString('base64'); - options.uri = `https://${credentials.subdomain}.zendesk.com/api/v2${resource}.json`; + options.uri = uri || `https://${credentials.subdomain}.zendesk.com/api/v2${resource}.json`; options.headers!['Authorization'] = `Basic ${base64Key}`; return await this.helpers.request!(options); } else { @@ -52,7 +52,7 @@ export async function zendeskApiRequest(this: IHookFunctions | IExecuteFunctions throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } - options.uri = `https://${credentials.subdomain}.zendesk.com/api/v2${resource}.json`; + options.uri = uri || `https://${credentials.subdomain}.zendesk.com/api/v2${resource}.json`; return await this.helpers.requestOAuth2!.call(this, 'zendeskOAuth2Api', options); } diff --git a/packages/nodes-base/nodes/Zendesk/TicketDescription.ts b/packages/nodes-base/nodes/Zendesk/TicketDescription.ts index e3c64f7bbd..6008351dc8 100644 --- a/packages/nodes-base/nodes/Zendesk/TicketDescription.ts +++ b/packages/nodes-base/nodes/Zendesk/TicketDescription.ts @@ -1,6 +1,6 @@ import { INodeProperties, - } from 'n8n-workflow'; +} from 'n8n-workflow'; export const ticketOperations = [ { @@ -35,6 +35,11 @@ export const ticketOperations = [ value: 'getAll', description: 'Get all tickets', }, + { + name: 'Recover', + value: 'recover', + description: 'Recover a suspended ticket', + }, { name: 'Update', value: 'update', @@ -48,9 +53,9 @@ export const ticketOperations = [ export const ticketFields = [ -/* -------------------------------------------------------------------------- */ -/* ticket:create */ -/* -------------------------------------------------------------------------- */ + /* -------------------------------------------------------------------------- */ + /* ticket:create */ + /* -------------------------------------------------------------------------- */ { displayName: 'Description', name: 'description', @@ -265,9 +270,9 @@ export const ticketFields = [ description: `Object of values to set as described here.`, }, -/* -------------------------------------------------------------------------- */ -/* ticket:update */ -/* -------------------------------------------------------------------------- */ + /* -------------------------------------------------------------------------- */ + /* ticket:update */ + /* -------------------------------------------------------------------------- */ { displayName: 'Ticket ID', name: 'id', @@ -323,6 +328,13 @@ export const ticketFields = [ }, }, options: [ + { + displayName: 'Assignee Email', + name: 'assigneeEmail', + type: 'string', + default: '', + description: 'The e-mail address of the assignee', + }, { displayName: 'Custom Fields', name: 'customFieldsUi', @@ -375,6 +387,20 @@ export const ticketFields = [ default: '', description: 'The group this ticket is assigned to', }, + { + displayName: 'Internal Note', + name: 'internalNote', + type: 'string', + default: '', + description: 'Internal Ticket Note (Accepts HTML)', + }, + { + displayName: 'Public Reply', + name: 'publicReply', + type: 'string', + default: '', + description: 'Public ticket reply', + }, { displayName: 'Recipient', name: 'recipient', @@ -478,10 +504,38 @@ export const ticketFields = [ }, description: `Object of values to update as described here.`, }, - -/* -------------------------------------------------------------------------- */ -/* ticket:get */ -/* -------------------------------------------------------------------------- */ + { + displayName: 'Ticket Type', + name: 'ticketType', + type: 'options', + options: [ + { + name: 'Regular', + value: 'regular', + }, + { + name: 'Suspended', + value: 'suspended', + }, + ], + default: 'regular', + required: true, + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'get', + 'delete', + 'getAll', + ], + }, + }, + }, + /* -------------------------------------------------------------------------- */ + /* ticket:get */ + /* -------------------------------------------------------------------------- */ { displayName: 'Ticket ID', name: 'id', @@ -496,13 +550,37 @@ export const ticketFields = [ operation: [ 'get', ], + ticketType: [ + 'regular', + ], }, }, description: 'Ticket ID', }, -/* -------------------------------------------------------------------------- */ -/* ticket:getAll */ -/* -------------------------------------------------------------------------- */ + { + displayName: 'Suspended Ticket ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'get', + ], + ticketType: [ + 'suspended', + ], + }, + }, + description: 'Ticket ID', + }, + /* -------------------------------------------------------------------------- */ + /* ticket:getAll */ + /* -------------------------------------------------------------------------- */ { displayName: 'Return All', name: 'returnAll', @@ -561,6 +639,37 @@ export const ticketFields = [ }, }, options: [ + { + displayName: 'Group', + name: 'group', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getGroups', + }, + displayOptions: { + show: { + '/ticketType': [ + 'regular', + ], + }, + }, + default: '', + description: 'The group to search', + }, + { + displayName: 'Query', + name: 'query', + type: 'string', + displayOptions: { + show: { + '/ticketType': [ + 'regular', + ], + }, + }, + default: '', + description: 'Query syntax to search tickets', + }, { displayName: 'Sort By', name: 'sortBy', @@ -596,21 +705,28 @@ export const ticketFields = [ type: 'options', options: [ { - name: 'Asc', + name: 'Ascending', value: 'asc', }, { - name: 'Desc', + name: 'Descending', value: 'desc', }, ], - default: 'desc', + default: 'asc', description: 'Sort order', }, { displayName: 'Status', name: 'status', type: 'options', + displayOptions: { + show: { + '/ticketType': [ + 'regular', + ], + }, + }, options: [ { name: 'Open', @@ -639,9 +755,9 @@ export const ticketFields = [ ], }, -/* -------------------------------------------------------------------------- */ -/* ticket:delete */ -/* -------------------------------------------------------------------------- */ + /* -------------------------------------------------------------------------- */ + /* ticket:delete */ + /* -------------------------------------------------------------------------- */ { displayName: 'Ticket ID', name: 'id', @@ -656,8 +772,52 @@ export const ticketFields = [ operation: [ 'delete', ], + ticketType: [ + 'regular', + ], }, }, description: 'Ticket ID', }, + { + displayName: 'Suspended Ticket ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'delete', + ], + ticketType: [ + 'suspended', + ], + }, + }, + description: 'Ticket ID', + }, + /* -------------------------------------------------------------------------- */ + /* ticket:recover */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Suspended Ticket ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'ticket', + ], + operation: [ + 'recover', + ], + }, + }, + }, ] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Zendesk/TicketInterface.ts b/packages/nodes-base/nodes/Zendesk/TicketInterface.ts index f3338ca260..da79435aba 100644 --- a/packages/nodes-base/nodes/Zendesk/TicketInterface.ts +++ b/packages/nodes-base/nodes/Zendesk/TicketInterface.ts @@ -4,6 +4,8 @@ import { export interface IComment { body?: string; + html_body?: string; + public?: boolean; } export interface ITicket { @@ -16,4 +18,5 @@ export interface ITicket { status?: string; recipient?: string; custom_fields?: IDataObject[]; + assignee_email?: string; } diff --git a/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts b/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts index b2af1df0bd..22718413d1 100644 --- a/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts +++ b/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts @@ -42,7 +42,7 @@ import { import { IComment, ITicket, - } from './TicketInterface'; +} from './TicketInterface'; export class Zendesk implements INodeType { description: INodeTypeDescription = { @@ -99,7 +99,7 @@ export class Zendesk implements INodeType { }, ], default: 'apiToken', - description: 'The resource to operate on.', + description: 'The resource to operate on', }, { displayName: 'Resource', @@ -109,7 +109,7 @@ export class Zendesk implements INodeType { { name: 'Ticket', value: 'ticket', - description: 'Tickets are the means through which your end users (customers) communicate with agents in Zendesk Support.', + description: 'Tickets are the means through which your end users (customers) communicate with agents in Zendesk Support', }, { name: 'Ticket Field', @@ -128,7 +128,7 @@ export class Zendesk implements INodeType { }, ], default: 'ticket', - description: 'Resource to consume.', + description: 'Resource to consume', }, // TICKET ...ticketOperations, @@ -286,12 +286,12 @@ export class Zendesk implements INodeType { body: description, }; const body: ITicket = { - comment, + comment, }; if (jsonParameters) { const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string; - if (additionalFieldsJson !== '' ) { + if (additionalFieldsJson !== '') { if (validateJSON(additionalFieldsJson) !== undefined) { @@ -343,7 +343,7 @@ export class Zendesk implements INodeType { if (jsonParameters) { const updateFieldsJson = this.getNodeParameter('updateFieldsJson', i) as string; - if (updateFieldsJson !== '' ) { + if (updateFieldsJson !== '') { if (validateJSON(updateFieldsJson) !== undefined) { @@ -382,44 +382,87 @@ export class Zendesk implements INodeType { if (updateFields.customFieldsUi) { body.custom_fields = (updateFields.customFieldsUi as IDataObject).customFieldsValues as IDataObject[]; } + if (updateFields.assigneeEmail) { + body.assignee_email = updateFields.assigneeEmail as string; + } + if (updateFields.internalNote) { + const comment: IComment = { + html_body: updateFields.internalNote as string, + public: false, + }; + body.comment = comment; + } + + if (updateFields.publicReply) { + const comment: IComment = { + body: updateFields.publicReply as string, + public: true, + }; + body.comment = comment; + } + } responseData = await zendeskApiRequest.call(this, 'PUT', `/tickets/${ticketId}`, { ticket: body }); responseData = responseData.ticket; } //https://developer.zendesk.com/rest_api/docs/support/tickets#show-ticket + //https://developer.zendesk.com/api-reference/ticketing/tickets/suspended_tickets/#show-suspended-ticket if (operation === 'get') { + const ticketType = this.getNodeParameter('ticketType', i) as string; const ticketId = this.getNodeParameter('id', i) as string; - responseData = await zendeskApiRequest.call(this, 'GET', `/tickets/${ticketId}`, {}); - responseData = responseData.ticket; + const endpoint = (ticketType === 'regular') ? `/tickets/${ticketId}` : `/suspended_tickets/${ticketId}`; + responseData = await zendeskApiRequest.call(this, 'GET', endpoint, {}); + responseData = responseData.ticket || responseData.suspended_ticket; } //https://developer.zendesk.com/rest_api/docs/support/search#list-search-results + //https://developer.zendesk.com/api-reference/ticketing/tickets/suspended_tickets/#list-suspended-tickets if (operation === 'getAll') { + const ticketType = this.getNodeParameter('ticketType', i) as string; const returnAll = this.getNodeParameter('returnAll', i) as boolean; const options = this.getNodeParameter('options', i) as IDataObject; qs.query = 'type:ticket'; + if (options.query) { + qs.query += ` ${options.query}`; + } if (options.status) { qs.query += ` status:${options.status}`; } + if (options.group) { + qs.query += ` group:${options.group}`; + } + if (options.sortBy) { qs.sort_by = options.sortBy; } if (options.sortOrder) { qs.sort_order = options.sortOrder; } + const endpoint = (ticketType === 'regular') ? `/search` : `/suspended_tickets`; + const property = (ticketType === 'regular') ? 'results' : 'suspended_tickets'; if (returnAll) { - responseData = await zendeskApiRequestAllItems.call(this, 'results', 'GET', `/search`, {}, qs); + responseData = await zendeskApiRequestAllItems.call(this, property, 'GET', endpoint, {}, 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; + responseData = await zendeskApiRequest.call(this, 'GET', endpoint, {}, qs); + responseData = responseData.results || responseData.suspended_tickets; } } //https://developer.zendesk.com/rest_api/docs/support/tickets#delete-ticket + //https://developer.zendesk.com/api-reference/ticketing/tickets/suspended_tickets/#delete-suspended-ticket if (operation === 'delete') { + const ticketType = this.getNodeParameter('ticketType', i) as string; + const ticketId = this.getNodeParameter('id', i) as string; + const endpoint = (ticketType === 'regular') ? `/tickets/${ticketId}` : `/suspended_tickets/${ticketId}`; + responseData = await zendeskApiRequest.call(this, 'DELETE', endpoint, {}); + responseData = { success: true }; + } + //https://developer.zendesk.com/api-reference/ticketing/tickets/suspended_tickets/#recover-suspended-ticket + if (operation === 'recover') { const ticketId = this.getNodeParameter('id', i) as string; try { - responseData = await zendeskApiRequest.call(this, 'DELETE', `/tickets/${ticketId}`, {}); + responseData = await zendeskApiRequest.call(this, 'PUT', `/suspended_tickets/${ticketId}/recover`, {}); + responseData = responseData.ticket; } catch (error) { throw new NodeApiError(this.getNode(), error); }