🔀 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);
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;
}

View file

@ -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: 'Status',
name: 'status',
type: 'options',
displayName: 'Custom Fields',
name: 'customFieldsUi',
placeholder: 'Add Custom Field',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
displayOptions: {
show: {
resource: [
'ticket',
],
operation: [
'create',
],
jsonParameters: [
false,
],
},
},
default: {},
options: [
{
name: 'Open',
value: 'open',
},
displayName: 'Custom Field',
name: 'customFieldsValues',
values: [
{
name: 'New',
value: 'new',
displayName: 'ID',
name: 'id',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getCustomFields',
},
{
name: 'Pending',
value: 'pending',
},
{
name: 'Solved',
value: 'solved',
},
{
name: 'Closed',
value: 'closed',
},
],
default: '',
description: 'The state of the ticket',
}
description: 'Custom field ID',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
description: 'Custom field Value.',
},
],
},
],
},
{
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 <a href="https://developer.zendesk.com/rest_api/docs/support/tickets#setting-custom-field-values" target="_blank">Details</a>`,
},
/* -------------------------------------------------------------------------- */
/* 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: 'Status',
name: 'status',
type: 'options',
displayName: 'Custom Fields',
name: 'customFieldsUi',
placeholder: 'Add Custom Field',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
displayOptions: {
show: {
resource: [
'ticket',
],
operation: [
'update',
],
jsonParameters: [
false,
],
},
},
default: {},
options: [
{
name: 'Open',
value: 'open',
},
displayName: 'Custom Field',
name: 'customFieldsValues',
values: [
{
name: 'New',
value: 'new',
displayName: 'ID',
name: 'id',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getCustomFields',
},
{
name: 'Pending',
value: 'pending',
},
{
name: 'Solved',
value: 'solved',
},
{
name: 'Closed',
value: 'closed',
},
],
default: '',
description: 'The state of the ticket',
}
description: 'Custom field ID',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
description: 'Custom field Value.',
},
],
},
],
},
{
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: '',

View file

@ -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[];

View file

@ -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<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
// select them easily
async getGroups(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
@ -131,13 +160,47 @@ 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 (jsonParameters) {
const customFieldsJson = this.getNodeParameter('customFieldsJson', i) as string;
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;
}
@ -159,14 +222,9 @@ export class Zendesk implements INodeType {
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 (customFields) {
body.custom_fields = customFields;
}
body.custom_fields = JSON.parse(customFieldsJson);
}
responseData = await zendeskApiRequest.call(this, 'POST', '/tickets', { ticket: body });
responseData = responseData.ticket;
@ -175,8 +233,42 @@ 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 (jsonParameters) {
const customFieldsJson = this.getNodeParameter('customFieldsJson', i) as string;
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;
}
@ -198,14 +290,9 @@ export class Zendesk implements INodeType {
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 (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)) {