🔀 Merge branch 'master' into oauth-support

This commit is contained in:
Jan Oberhauser
2020-04-14 20:54:19 +02:00
35 changed files with 1201 additions and 449 deletions

View File

@@ -21,6 +21,7 @@ import {
formOperations
} from './FormDescription';
import { submitForm } from './FormFunctions';
import { createDataFromParameters } from './GenericFunctions';
import {
singletonFields,
singletonOperations,
@@ -56,7 +57,7 @@ export class Cockpit implements INodeType {
displayName: 'Resource',
name: 'resource',
type: 'options',
default: 'collections',
default: 'collection',
description: 'Resource to consume.',
options: [
{
@@ -74,7 +75,6 @@ export class Cockpit implements INodeType {
],
},
...collectionOperations,
...collectionFields,
...formOperations,
@@ -84,7 +84,6 @@ export class Cockpit implements INodeType {
],
};
methods = {
loadOptions: {
async getCollections(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
@@ -123,34 +122,37 @@ export class Cockpit implements INodeType {
for (let i = 0; i < length; i++) {
if (resource === 'collection') {
const collectionName = this.getNodeParameter('collection', i) as string;
if (operation === 'create') {
const data = this.getNodeParameter('data', i) as IDataObject;
const data = createDataFromParameters.call(this, i);
responseData = await createCollectionEntry.call(this, collectionName, data);
} else if (operation === 'getAll') {
const options = this.getNodeParameter('options', i) as IDataObject;
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
if (returnAll !== true) {
if (!returnAll) {
options.limit = this.getNodeParameter('limit', i) as number;
}
responseData = await getAllCollectionEntries.call(this, collectionName, options);
} else if (operation === 'update') {
const id = this.getNodeParameter('id', i) as string;
const data = this.getNodeParameter('data', i) as IDataObject;
const data = createDataFromParameters.call(this, i);
responseData = await createCollectionEntry.call(this, collectionName, data, id);
}
} else if (resource === 'form') {
const formName = this.getNodeParameter('form', i) as string;
if (operation === 'submit') {
const form = this.getNodeParameter('form', i) as IDataObject;
const form = createDataFromParameters.call(this, i);
responseData = await submitForm.call(this, formName, form);
}
} else if (resource === 'singleton') {
const singletonName = this.getNodeParameter('singleton', i) as string;
if (operation === 'get') {
responseData = await getSingleton.call(this, singletonName);
}

View File

@@ -14,17 +14,17 @@ export const collectionOperations = [
},
options: [
{
name: 'Create an entry',
name: 'Create an Entry',
value: 'create',
description: 'Create a collection entry',
},
{
name: 'Get all entries',
name: 'Get all Entries',
value: 'getAll',
description: 'Get all collection entries',
},
{
name: 'Update an entry',
name: 'Update an Entry',
value: 'update',
description: 'Update a collection entries',
},
@@ -54,29 +54,6 @@ export const collectionFields = [
description: 'Name of the collection to operate on.'
},
// Collection:entry:create
{
displayName: 'Data',
name: 'data',
type: 'json',
required: true,
default: '',
typeOptions: {
alwaysOpenEditWindow: true,
},
displayOptions: {
show: {
resource: [
'collection',
],
operation: [
'create',
]
},
},
description: 'The data to create.',
},
// Collection:entry:getAll
{
displayName: 'Return All',
@@ -139,22 +116,24 @@ export const collectionFields = [
{
displayName: 'Fields',
name: 'fields',
type: 'json',
type: 'string',
default: '',
typeOptions: {
alwaysOpenEditWindow: true,
},
description: 'Fields to get.',
placeholder: '_id,name',
description: 'Comma separated list of fields to get.',
},
{
displayName: 'Filter',
displayName: 'Filter Query',
name: 'filter',
type: 'json',
default: '',
typeOptions: {
alwaysOpenEditWindow: true,
},
description: 'Filter result by fields.',
placeholder: '{"name": "Jim"}',
description: 'Filter query in <a href="https://jeroen.github.io/mongolite/query-data.html" target="_blank">Mongolite format</a>.',
},
{
displayName: 'Language',
@@ -186,11 +165,12 @@ export const collectionFields = [
description: 'Skip number of entries.',
},
{
displayName: 'Sort',
displayName: 'Sort Query',
name: 'sort',
type: 'json',
default: '',
description: 'Sort result by fields.',
placeholder: '{"price": -1}',
description: 'Sort query in <a href="https://jeroen.github.io/mongolite/query-data.html" target="_blank">Mongolite format</a>.',
},
],
},
@@ -214,25 +194,95 @@ export const collectionFields = [
},
description: 'The entry ID.',
},
// Collection:entry:create
// Collection:entry:update
{
displayName: 'Data',
name: 'data',
type: 'json',
required: true,
default: '',
typeOptions: {
alwaysOpenEditWindow: true,
},
displayName: 'JSON Data fields',
name: 'jsonDataFields',
type: 'boolean',
default: false,
displayOptions: {
show: {
resource: [
'collection',
],
operation: [
'create',
'update',
]
},
},
description: 'The data to update.',
description: 'If new entry fields should be set via the value-key pair UI or JSON.',
},
{
displayName: 'Entry Data',
name: 'dataFieldsJson',
type: 'json',
default: '',
typeOptions: {
alwaysOpenEditWindow: true,
},
displayOptions: {
show: {
jsonDataFields: [
true,
],
resource: [
'collection',
],
operation: [
'create',
'update',
]
},
},
description: 'Entry data to send as JSON.',
},
{
displayName: 'Entry Data',
name: 'dataFieldsUi',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
default: {},
displayOptions: {
show: {
jsonDataFields: [
false,
],
resource: [
'collection',
],
operation: [
'create',
'update',
]
},
},
options: [
{
displayName: 'Field',
name: 'field',
values: [
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
description: 'Name of the field.',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
description: 'Value of the field.',
},
],
},
],
description: 'Entry data to send.',
},
] as INodeProperties[];

View File

@@ -9,7 +9,7 @@ import { cockpitApiRequest } from './GenericFunctions';
export async function createCollectionEntry(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resourceName: string, data: IDataObject, id?: string): Promise<any> { // tslint:disable-line:no-any
const body: ICollection = {
data: JSON.parse(data.toString())
data,
};
if (id) {
@@ -27,7 +27,16 @@ export async function getAllCollectionEntries(this: IExecuteFunctions | IExecute
const body: ICollection = {};
if (options.fields) {
body.fields = JSON.parse(options.fields.toString());
const fields = (options.fields as string).split(',').map(field => field.trim() );
const bodyFields = {
_id: false,
} as IDataObject;
for (const field of fields) {
bodyFields[field] = true;
}
body.fields = bodyFields;
}
if (options.filter) {

View File

@@ -14,7 +14,7 @@ export const formOperations = [
},
options: [
{
name: 'Submit a form',
name: 'Submit a Form',
value: 'submit',
description: 'Store submission of a form',
},
@@ -44,21 +44,88 @@ export const formFields = [
// Form:submit
{
displayName: 'Form data',
name: 'form',
displayName: 'JSON Data fields',
name: 'jsonDataFields',
type: 'boolean',
default: false,
displayOptions: {
show: {
resource: [
'form',
],
operation: [
'submit',
]
},
},
description: 'If form fields should be set via the value-key pair UI or JSON.',
},
{
displayName: 'Form Data',
name: 'dataFieldsJson',
type: 'json',
required: true,
default: '',
typeOptions: {
alwaysOpenEditWindow: true,
},
displayOptions: {
show: {
jsonDataFields: [
true,
],
resource: [
'form',
],
operation: [
'submit',
]
},
},
description: 'The data to save.',
description: 'Form data to send as JSON.',
},
{
displayName: 'Form Data',
name: 'dataFieldsUi',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
default: {},
displayOptions: {
show: {
jsonDataFields: [
false,
],
resource: [
'form',
],
operation: [
'submit',
]
},
},
options: [
{
displayName: 'Field',
name: 'field',
values: [
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
description: 'Name of the field.',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
description: 'Value of the field.',
},
],
},
],
description: 'Form data to send.',
},
] as INodeProperties[];

View File

@@ -9,7 +9,7 @@ import { cockpitApiRequest } from './GenericFunctions';
export async function submitForm(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resourceName: string, form: IDataObject) {
const body: IForm = {
form: JSON.parse(form.toString())
form
};
return cockpitApiRequest.call(this, 'post', `/forms/submit/${resourceName}`, body);

View File

@@ -44,3 +44,26 @@ export async function cockpitApiRequest(this: IExecuteFunctions | IExecuteSingle
throw new Error(`Cockpit error [${error.statusCode}]: ` + errorMessage);
}
}
export function createDataFromParameters(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, itemIndex: number): IDataObject {
const dataFieldsAreJson = this.getNodeParameter('jsonDataFields', itemIndex) as boolean;
if (dataFieldsAreJson) {
// Parameters are defined as JSON
return JSON.parse(this.getNodeParameter('dataFieldsJson', itemIndex, {}) as string);
}
// Parameters are defined in UI
const uiDataFields = this.getNodeParameter('dataFieldsUi', itemIndex, {}) as IDataObject;
const unpacked: IDataObject = {};
if (uiDataFields.field === undefined) {
return unpacked;
}
for (const field of uiDataFields!.field as IDataObject[]) {
unpacked[field!.name as string] = field!.value;
}
return unpacked;
}

View File

@@ -16,7 +16,7 @@ export const singletonOperations = [
{
name: 'Get',
value: 'get',
description: 'Gets a singleton',
description: 'Gets a Singleton',
},
],
default: 'get',

View File

@@ -47,9 +47,6 @@ export class Function implements INodeType {
// Define the global objects for the custom function
const sandbox = {
evaluateExpression: (expression: string, itemIndex = 0) => {
return this.evaluateExpression(expression, itemIndex);
},
getNodeParameter: this.getNodeParameter,
getWorkflowStaticData: this.getWorkflowStaticData,
helpers: this.helpers,

View File

@@ -48,9 +48,6 @@ export class FunctionItem implements INodeType {
// Define the global objects for the custom function
const sandbox = {
evaluateExpression: (expression: string, itemIndex: number | undefined) => {
return this.evaluateExpression(expression, itemIndex);
},
getBinaryData: (): IBinaryKeyData | undefined => {
return item.binary;
},

View File

@@ -713,12 +713,8 @@ export class HttpRequest implements INodeType {
}
}
try {
// @ts-ignore
requestOptions[optionData.name] = JSON.parse(tempValue as string);
} catch (error) {
throw new Error(`${optionData.name} must be a valid JSON`);
}
// @ts-ignore
requestOptions[optionData.name] = tempValue;
// @ts-ignore
if (typeof requestOptions[optionData.name] !== 'object' && options.bodyContentType !== 'raw') {

View File

@@ -7,14 +7,15 @@ import {
import { OptionsWithUri } from 'request';
import { IDataObject } from 'n8n-workflow';
export interface IAttachment {
export interface IAttachment {
fields: {
item?: object[];
};
actions: {
item?: object[];
};
}
/**
* Make an API request to Telegram
*

View File

@@ -448,78 +448,181 @@ export class Mattermost implements INodeType {
placeholder: 'Add attachment item',
options: [
{
displayName: 'Fallback Text',
name: 'fallback',
type: 'string',
displayName: 'Actions',
name: 'actions',
placeholder: 'Add Actions',
description: 'Actions to add to message. More information can be found <a href="https://docs.mattermost.com/developer/interactive-messages.html" target="_blank">here</a>',
type: 'fixedCollection',
typeOptions: {
alwaysOpenEditWindow: true,
multipleValues: true,
},
default: '',
description: 'Required plain-text summary of the attachment.',
},
{
displayName: 'Text',
name: 'text',
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
},
default: '',
description: 'Text to send.',
},
{
displayName: 'Title',
name: 'title',
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
},
default: '',
description: 'Title of the message.',
},
{
displayName: 'Title Link',
name: 'title_link',
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
},
default: '',
description: 'Link of the title.',
},
{
displayName: 'Color',
name: 'color',
type: 'color',
default: '#ff0000',
description: 'Color of the line left of text.',
},
{
displayName: 'Pretext',
name: 'pretext',
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
},
default: '',
description: 'Text which appears before the message block.',
},
{
displayName: 'Author Name',
name: 'author_name',
type: 'string',
default: '',
description: 'Name that should appear.',
},
{
displayName: 'Author Link',
name: 'author_link',
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
},
default: '',
description: 'Link for the author.',
default: {},
options: [
{
displayName: 'Item',
name: 'item',
values: [
{
displayName: 'Type',
name: 'type',
type: 'options',
options: [
{
name: 'Button',
value: 'button',
},
{
name: 'Select',
value: 'select',
},
],
default: 'button',
description: 'The type of the action.',
},
{
displayName: 'Data Source',
name: 'data_source',
type: 'options',
displayOptions: {
show: {
type: [
'select'
],
},
},
options: [
{
name: 'Channels',
value: 'channels',
},
{
name: 'Custom',
value: 'custom',
},
{
name: 'Users',
value: 'users',
},
],
default: 'custom',
description: 'The type of the action.',
},
{
displayName: 'Options',
name: 'options',
placeholder: 'Add Option',
description: 'Adds a new option to select field.',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
displayOptions: {
show: {
data_source: [
'custom'
],
type: [
'select'
],
},
},
default: {},
options: [
{
name: 'option',
displayName: 'Option',
default: {},
values: [
{
displayName: 'Option Text',
name: 'text',
type: 'string',
default: '',
description: 'Text of the option.',
},
{
displayName: 'Option Value',
name: 'value',
type: 'string',
default: '',
description: 'Value of the option.',
},
]
},
],
},
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
description: 'Name of the Action.',
},
{
displayName: 'Integration',
name: 'integration',
placeholder: 'Add Integration',
description: 'Integration to add to message.',
type: 'fixedCollection',
typeOptions: {
multipleValues: false,
},
default: {},
options: [
{
displayName: 'Item',
name: 'item',
default: {},
values: [
{
displayName: 'URL',
name: 'url',
type: 'string',
default: '',
description: 'URL of the Integration.',
},
{
displayName: 'Context',
name: 'context',
placeholder: 'Add Context to Integration',
description: 'Adds a Context values set.',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
default: {},
options: [
{
name: 'property',
displayName: 'Property',
default: {},
values: [
{
displayName: 'Property Name',
name: 'name',
type: 'string',
default: '',
description: 'Name of the property to set.',
},
{
displayName: 'Property Value',
name: 'value',
type: 'string',
default: '',
description: 'Value of the property to set.',
},
]
},
],
},
]
},
],
},
]
},
],
},
{
displayName: 'Author Icon',
@@ -532,44 +635,38 @@ export class Mattermost implements INodeType {
description: 'Icon which should appear for the user.',
},
{
displayName: 'Image URL',
name: 'image_url',
displayName: 'Author Link',
name: 'author_link',
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
},
default: '',
description: 'URL of image.',
description: 'Link for the author.',
},
{
displayName: 'Thumbnail URL',
name: 'thumb_url',
displayName: 'Author Name',
name: 'author_name',
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
},
default: '',
description: 'URL of thumbnail.',
description: 'Name that should appear.',
},
{
displayName: 'Footer',
name: 'footer',
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
},
default: '',
description: 'Text of footer to add.',
displayName: 'Color',
name: 'color',
type: 'color',
default: '#ff0000',
description: 'Color of the line left of text.',
},
{
displayName: 'Footer Icon',
name: 'footer_icon',
displayName: 'Fallback Text',
name: 'fallback',
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
},
default: '',
description: 'Icon which should appear next to footer.',
description: 'Required plain-text summary of the attachment.',
},
{
displayName: 'Fields',
@@ -610,7 +707,87 @@ export class Mattermost implements INodeType {
]
},
],
}
},
{
displayName: 'Footer',
name: 'footer',
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
},
default: '',
description: 'Text of footer to add.',
},
{
displayName: 'Footer Icon',
name: 'footer_icon',
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
},
default: '',
description: 'Icon which should appear next to footer.',
},
{
displayName: 'Image URL',
name: 'image_url',
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
},
default: '',
description: 'URL of image.',
},
{
displayName: 'Pretext',
name: 'pretext',
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
},
default: '',
description: 'Text which appears before the message block.',
},
{
displayName: 'Text',
name: 'text',
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
},
default: '',
description: 'Text to send.',
},
{
displayName: 'Thumbnail URL',
name: 'thumb_url',
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
},
default: '',
description: 'URL of thumbnail.',
},
{
displayName: 'Title',
name: 'title',
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
},
default: '',
description: 'Title of the message.',
},
{
displayName: 'Title Link',
name: 'title_link',
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
},
default: '',
description: 'Link of the title.',
},
],
},
{
@@ -890,6 +1067,47 @@ export class Mattermost implements INodeType {
}
}
}
for (const attachment of attachments) {
if (attachment.actions !== undefined) {
if (attachment.actions.item !== undefined) {
// Move the field-content up
// @ts-ignore
attachment.actions = attachment.actions.item;
} else {
// If it does not have any items set remove it
delete attachment.actions;
}
}
}
for (const attachment of attachments) {
if (Array.isArray(attachment.actions)) {
for (const attaction of attachment.actions) {
if (attaction.type === 'button') {
delete attaction.type;
}
if (attaction.data_source === 'custom') {
delete attaction.data_source;
}
if (attaction.options) {
attaction.options = attaction.options.option;
}
if (attaction.integration.item !== undefined) {
attaction.integration = attaction.integration.item;
if (Array.isArray(attaction.integration.context.property)) {
const tmpcontex = {};
for (const attactionintegprop of attaction.integration.context.property) {
Object.assign(tmpcontex, { [attactionintegprop.name]: attactionintegprop.value });
}
delete attaction.integration.context;
attaction.integration.context = tmpcontex;
}
}
}
}
}
body.props = {
attachments,

View File

@@ -28,7 +28,7 @@ import {
function flattenObject (data: IDataObject) {
const returnData: IDataObject = {};
for (const key1 of Object.keys(data)) {
if ((typeof data[key1]) === 'object') {
if (data[key1] !== null && (typeof data[key1]) === 'object') {
const flatObject = flattenObject(data[key1] as IDataObject);
for (const key2 in flatObject) {
if (flatObject[key2] === undefined) {
@@ -133,6 +133,11 @@ export class SpreadsheetFile implements INodeType {
value: 'xls',
description: 'Excel',
},
{
name: 'XLSX',
value: 'xlsx',
description: 'Excel',
},
],
default: 'xls',
displayOptions: {
@@ -236,6 +241,7 @@ export class SpreadsheetFile implements INodeType {
'/fileFormat': [
'ods',
'xls',
'xlsx',
],
},
},
@@ -337,7 +343,9 @@ export class SpreadsheetFile implements INodeType {
} else if (fileFormat === 'ods') {
wopts.bookType = 'ods';
} else if (fileFormat === 'xls') {
wopts.bookType = 'xlml';
wopts.bookType = 'xls';
} else if (fileFormat === 'xlsx') {
wopts.bookType = 'xlsx';
}
// Convert the data in the correct format

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,9 +103,47 @@ export const ticketFields = [
operation: [
'create',
],
jsonParameters: [
false,
],
},
},
options: [
{
displayName: 'Custom Fields',
name: 'customFieldsUi',
placeholder: 'Add Custom Field',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
default: {},
options: [
{
displayName: 'Custom Field',
name: 'customFieldsValues',
values: [
{
displayName: 'ID',
name: 'id',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getCustomFields',
},
default: '',
description: 'Custom field ID',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
description: 'Custom field Value.',
},
],
},
],
},
{
displayName: 'External ID',
name: 'externalId',
@@ -113,20 +151,6 @@ export const ticketFields = [
default: '',
description: 'An id you can use to link Zendesk Support tickets to local records',
},
{
displayName: 'Subject',
name: 'subject',
type: 'string',
default: '',
description: 'The value of the subject field for this ticket',
},
{
displayName: 'Recipient',
name: 'recipient',
type: 'string',
default: '',
description: 'The original recipient e-mail address of the ticket',
},
{
displayName: 'Group',
name: 'group',
@@ -137,6 +161,49 @@ export const ticketFields = [
default: '',
description: 'The group this ticket is assigned to',
},
{
displayName: 'Recipient',
name: 'recipient',
type: 'string',
default: '',
description: 'The original recipient e-mail address of the ticket',
},
{
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: 'Subject',
name: 'subject',
type: 'string',
default: '',
description: 'The value of the subject field for this ticket',
},
{
displayName: 'Tags',
name: 'tags',
@@ -172,40 +239,11 @@ export const ticketFields = [
default: '',
description: 'The type of this ticket',
},
{
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: ' Custom Fields',
name: 'customFieldsJson',
displayName: ' Additional Fields',
name: 'additionalFieldsJson',
type: 'json',
typeOptions: {
alwaysOpenEditWindow: true,
@@ -224,14 +262,14 @@ 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: `Object of values to set as described <a href="https://developer.zendesk.com/rest_api/docs/support/tickets" target="_blank">here</a>.`,
},
/* -------------------------------------------------------------------------- */
/* ticket:update */
/* -------------------------------------------------------------------------- */
{
displayName: 'ID',
displayName: 'Ticket ID',
name: 'id',
type: 'string',
default: '',
@@ -279,9 +317,47 @@ export const ticketFields = [
operation: [
'update',
],
jsonParameters: [
false,
],
},
},
options: [
{
displayName: 'Custom Fields',
name: 'customFieldsUi',
placeholder: 'Add Custom Field',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
default: {},
options: [
{
displayName: 'Custom Field',
name: 'customFieldsValues',
values: [
{
displayName: 'ID',
name: 'id',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getCustomFields',
},
default: '',
description: 'Custom field ID',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
description: 'Custom field Value.',
},
],
},
],
},
{
displayName: 'External ID',
name: 'externalId',
@@ -289,20 +365,6 @@ export const ticketFields = [
default: '',
description: 'An id you can use to link Zendesk Support tickets to local records',
},
{
displayName: 'Subject',
name: 'subject',
type: 'string',
default: '',
description: 'The value of the subject field for this ticket',
},
{
displayName: 'Recipient',
name: 'recipient',
type: 'string',
default: '',
description: 'The original recipient e-mail address of the ticket',
},
{
displayName: 'Group',
name: 'group',
@@ -313,6 +375,49 @@ export const ticketFields = [
default: '',
description: 'The group this ticket is assigned to',
},
{
displayName: 'Recipient',
name: 'recipient',
type: 'string',
default: '',
description: 'The original recipient e-mail address of the ticket',
},
{
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: 'Subject',
name: 'subject',
type: 'string',
default: '',
description: 'The value of the subject field for this ticket',
},
{
displayName: 'Tags',
name: 'tags',
@@ -348,40 +453,11 @@ export const ticketFields = [
default: '',
description: 'The type of this ticket',
},
{
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: ' Custom Fields',
name: 'customFieldsJson',
displayName: ' Update Fields',
name: 'updateFieldsJson',
type: 'json',
typeOptions: {
alwaysOpenEditWindow: true,
@@ -400,14 +476,14 @@ 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'>Details</a>`,
description: `Object of values to update as described <a href="https://developer.zendesk.com/rest_api/docs/support/tickets" target="_blank">here</a>.`,
},
/* -------------------------------------------------------------------------- */
/* ticket:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'ID',
displayName: 'Ticket ID',
name: 'id',
type: 'string',
default: '',
@@ -485,35 +561,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 +606,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 +643,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,42 +160,54 @@ 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');
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;
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 (additionalFields.customFieldsUi) {
body.custom_fields = (additionalFields.customFieldsUi as IDataObject).customFieldsValues as IDataObject[];
}
body.custom_fields = JSON.parse(customFieldsJson);
}
responseData = await zendeskApiRequest.call(this, 'POST', '/tickets', { ticket: body });
responseData = responseData.ticket;
@@ -175,37 +216,50 @@ 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');
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;
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 (updateFields.customFieldsUi) {
body.custom_fields = (updateFields.customFieldsUi as IDataObject).customFieldsValues as IDataObject[];
}
body.custom_fields = JSON.parse(customFieldsJson);
}
responseData = await zendeskApiRequest.call(this, 'PUT', `/tickets/${ticketId}`, { ticket: body });
responseData = responseData.ticket;
@@ -259,8 +313,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)) {

View File

@@ -1,6 +1,6 @@
{
"name": "n8n-nodes-base",
"version": "0.55.0",
"version": "0.57.1",
"description": "Base nodes of n8n",
"license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io",
@@ -287,7 +287,7 @@
"@types/xml2js": "^0.4.3",
"gulp": "^4.0.0",
"jest": "^24.9.0",
"n8n-workflow": "~0.26.0",
"n8n-workflow": "~0.28.0",
"ts-jest": "^24.0.2",
"tslint": "^5.17.0",
"typescript": "~3.7.4"
@@ -310,7 +310,7 @@
"moment-timezone": "0.5.28",
"mongodb": "^3.3.2",
"mysql2": "^2.0.1",
"n8n-core": "~0.29.0",
"n8n-core": "~0.31.0",
"nodemailer": "^5.1.1",
"pdf-parse": "^1.1.1",
"pg-promise": "^9.0.3",