🔀 Merge branch 'feature/zendesk-improvements' of https://github.com/RicardoE105/n8n into RicardoE105-feature/zendesk-improvements

This commit is contained in:
Jan Oberhauser 2020-04-11 11:08:25 +02:00
commit e7f1fce1dd
4 changed files with 460 additions and 163 deletions

View file

@ -62,6 +62,9 @@ export async function zendeskApiRequestAllItems(this: IHookFunctions | IExecuteF
responseData = await zendeskApiRequest.call(this, method, resource, body, query, uri); responseData = await zendeskApiRequest.call(this, method, resource, body, query, uri);
uri = responseData.next_page; uri = responseData.next_page;
returnData.push.apply(returnData, responseData[propertyName]); returnData.push.apply(returnData, responseData[propertyName]);
if (query.limit && query.limit <= returnData.length) {
return returnData;
}
} while ( } while (
responseData.next_page !== undefined && responseData.next_page !== undefined &&
responseData.next_page !== null responseData.next_page !== null
@ -69,3 +72,13 @@ export async function zendeskApiRequestAllItems(this: IHookFunctions | IExecuteF
return returnData; 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;
}

View file

@ -21,9 +21,9 @@ export const ticketOperations = [
description: 'Create a ticket', description: 'Create a ticket',
}, },
{ {
name: 'Update', name: 'Delete',
value: 'update', value: 'delete',
description: 'Update a ticket', description: 'Delete a ticket',
}, },
{ {
name: 'Get', name: 'Get',
@ -36,9 +36,9 @@ export const ticketOperations = [
description: 'Get all tickets', description: 'Get all tickets',
}, },
{ {
name: 'Delete', name: 'Update',
value: 'delete', value: 'update',
description: 'Delete a ticket', description: 'Update a ticket',
}, },
], ],
default: 'create', default: 'create',
@ -81,7 +81,7 @@ export const ticketFields = [
displayOptions: { displayOptions: {
show: { show: {
resource: [ resource: [
'ticket' 'ticket',
], ],
operation: [ operation: [
'create', 'create',
@ -103,6 +103,9 @@ export const ticketFields = [
operation: [ operation: [
'create', 'create',
], ],
jsonParameters: [
false,
],
}, },
}, },
options: [ options: [
@ -114,11 +117,14 @@ export const ticketFields = [
description: 'An id you can use to link Zendesk Support tickets to local records', description: 'An id you can use to link Zendesk Support tickets to local records',
}, },
{ {
displayName: 'Subject', displayName: 'Group',
name: 'subject', name: 'group',
type: 'string', type: 'options',
typeOptions: {
loadOptionsMethod: 'getGroups',
},
default: '', default: '',
description: 'The value of the subject field for this ticket', description: 'The group this ticket is assigned to',
}, },
{ {
displayName: 'Recipient', displayName: 'Recipient',
@ -128,14 +134,40 @@ export const ticketFields = [
description: 'The original recipient e-mail address of the ticket', description: 'The original recipient e-mail address of the ticket',
}, },
{ {
displayName: 'Group', displayName: 'Status',
name: 'group', name: 'status',
type: 'options', type: 'options',
typeOptions: { options: [
loadOptionsMethod: 'getGroups', {
}, name: 'Open',
value: 'open',
},
{
name: 'New',
value: 'new',
},
{
name: 'Pending',
value: 'pending',
},
{
name: 'Solved',
value: 'solved',
},
{
name: 'Closed',
value: 'closed',
},
],
default: '', 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', displayName: 'Tags',
@ -172,37 +204,78 @@ export const ticketFields = [
default: '', default: '',
description: 'The type of this ticket', 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', displayName: 'Custom Field',
name: 'status', name: 'customFieldsValues',
type: 'options', values: [
options: [
{ {
name: 'Open', displayName: 'ID',
value: 'open', name: 'id',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getCustomFields',
},
default: '',
description: 'Custom field ID',
}, },
{ {
name: 'New', displayName: 'Value',
value: 'new', name: 'value',
}, type: 'string',
{ default: '',
name: 'Pending', description: 'Custom field Value.',
value: 'pending',
},
{
name: 'Solved',
value: 'solved',
},
{
name: 'Closed',
value: 'closed',
}, },
], ],
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', displayName: ' Custom Fields',
name: 'customFieldsJson', name: 'customFieldsJson',
@ -224,14 +297,13 @@ export const ticketFields = [
], ],
}, },
}, },
required: true,
description: `Array of customs fields <a href="https://developer.zendesk.com/rest_api/docs/support/tickets#setting-custom-field-values" target="_blank">Details</a>`, description: `Array of customs fields <a href="https://developer.zendesk.com/rest_api/docs/support/tickets#setting-custom-field-values" target="_blank">Details</a>`,
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* ticket:update */ /* ticket:update */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'ID', displayName: 'Ticket ID',
name: 'id', name: 'id',
type: 'string', type: 'string',
default: '', default: '',
@ -279,6 +351,9 @@ export const ticketFields = [
operation: [ operation: [
'update', 'update',
], ],
jsonParameters: [
false,
],
}, },
}, },
options: [ options: [
@ -290,11 +365,14 @@ export const ticketFields = [
description: 'An id you can use to link Zendesk Support tickets to local records', description: 'An id you can use to link Zendesk Support tickets to local records',
}, },
{ {
displayName: 'Subject', displayName: 'Group',
name: 'subject', name: 'group',
type: 'string', type: 'options',
typeOptions: {
loadOptionsMethod: 'getGroups',
},
default: '', default: '',
description: 'The value of the subject field for this ticket', description: 'The group this ticket is assigned to',
}, },
{ {
displayName: 'Recipient', displayName: 'Recipient',
@ -304,14 +382,40 @@ export const ticketFields = [
description: 'The original recipient e-mail address of the ticket', description: 'The original recipient e-mail address of the ticket',
}, },
{ {
displayName: 'Group', displayName: 'Status',
name: 'group', name: 'status',
type: 'options', type: 'options',
typeOptions: { options: [
loadOptionsMethod: 'getGroups', {
}, name: 'Open',
value: 'open',
},
{
name: 'New',
value: 'new',
},
{
name: 'Pending',
value: 'pending',
},
{
name: 'Solved',
value: 'solved',
},
{
name: 'Closed',
value: 'closed',
},
],
default: '', 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', displayName: 'Tags',
@ -348,37 +452,78 @@ export const ticketFields = [
default: '', default: '',
description: 'The type of this ticket', 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', displayName: 'Custom Field',
name: 'status', name: 'customFieldsValues',
type: 'options', values: [
options: [
{ {
name: 'Open', displayName: 'ID',
value: 'open', name: 'id',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getCustomFields',
},
default: '',
description: 'Custom field ID',
}, },
{ {
name: 'New', displayName: 'Value',
value: 'new', name: 'value',
}, type: 'string',
{ default: '',
name: 'Pending', description: 'Custom field Value.',
value: 'pending',
},
{
name: 'Solved',
value: 'solved',
},
{
name: 'Closed',
value: 'closed',
}, },
], ],
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', displayName: ' Custom Fields',
name: 'customFieldsJson', name: 'customFieldsJson',
@ -407,7 +552,7 @@ export const ticketFields = [
/* ticket:get */ /* ticket:get */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'ID', displayName: 'Ticket ID',
name: 'id', name: 'id',
type: 'string', type: 'string',
default: '', default: '',
@ -485,35 +630,6 @@ export const ticketFields = [
}, },
}, },
options: [ 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', displayName: 'Sort By',
name: 'sortBy', name: 'sortBy',
@ -559,7 +675,36 @@ export const ticketFields = [
], ],
default: 'desc', default: 'desc',
description: 'Sort order', 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 */ /* ticket:delete */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'ID', displayName: 'Ticket ID',
name: 'id', name: 'id',
type: 'string', type: 'string',
default: '', default: '',

View file

@ -54,4 +54,49 @@ export const ticketFieldFields = [
}, },
description: 'ticketField ID', 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[]; ] as INodeProperties[];

View file

@ -12,6 +12,7 @@ import {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
validateJSON,
zendeskApiRequest, zendeskApiRequest,
zendeskApiRequestAllItems, zendeskApiRequestAllItems,
} from './GenericFunctions'; } from './GenericFunctions';
@ -30,6 +31,7 @@ import {
ITicket, ITicket,
IComment, IComment,
} from './TicketInterface'; } from './TicketInterface';
import { response } from 'express';
export class Zendesk implements INodeType { export class Zendesk implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {
@ -83,6 +85,33 @@ export class Zendesk implements INodeType {
methods = { methods = {
loadOptions: { loadOptions: {
// Get all the custom fields to display them to user so that he can
// select them easily
async getCustomFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
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 // Get all the groups to display them to user so that he can
// select them easily // select them easily
async getGroups(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> { async getGroups(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
@ -131,42 +160,71 @@ export class Zendesk implements INodeType {
if (operation === 'create') { if (operation === 'create') {
const description = this.getNodeParameter('description', i) as string; const description = this.getNodeParameter('description', i) as string;
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean; const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
const comment: IComment = { const comment: IComment = {
body: description, body: description,
}; };
const body: ITicket = { const body: ITicket = {
comment, 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) { if (jsonParameters) {
const customFieldsJson = this.getNodeParameter('customFieldsJson', i) as string; const customFieldsJson = this.getNodeParameter('customFieldsJson', i) as string;
try {
JSON.parse(customFieldsJson); if (customFieldsJson !== '' ) {
} catch(err) {
throw new Error('Custom fields must be a valid JSON'); 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 = await zendeskApiRequest.call(this, 'POST', '/tickets', { ticket: body });
responseData = responseData.ticket; responseData = responseData.ticket;
@ -175,37 +233,66 @@ export class Zendesk implements INodeType {
if (operation === 'update') { if (operation === 'update') {
const ticketId = this.getNodeParameter('id', i) as string; const ticketId = this.getNodeParameter('id', i) as string;
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean; const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
const body: ITicket = {}; 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) { if (jsonParameters) {
const customFieldsJson = this.getNodeParameter('customFieldsJson', i) as string; const customFieldsJson = this.getNodeParameter('customFieldsJson', i) as string;
try {
JSON.parse(customFieldsJson); if (customFieldsJson !== '' ) {
} catch(err) {
throw new Error('Custom fields must be a valid JSON'); 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 = await zendeskApiRequest.call(this, 'PUT', `/tickets/${ticketId}`, { ticket: body });
responseData = responseData.ticket; 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 //https://developer.zendesk.com/rest_api/docs/support/ticket_fields#list-ticket-fields
if (operation === 'getAll') { if (operation === 'getAll') {
responseData = await zendeskApiRequest.call(this, 'GET', '/ticket_fields', {}, qs); const returnAll = this.getNodeParameter('returnAll', i) as boolean;
responseData = responseData.ticket_fields; 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)) { if (Array.isArray(responseData)) {