From 624da2f727980fb304b22681422099d29fd8d762 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Mon, 3 Feb 2020 12:51:22 -0500 Subject: [PATCH 1/2] :sparkles: Add custom fields support done --- .../nodes-base/nodes/ClickUp/ClickUp.node.ts | 39 +++- .../nodes/ClickUp/GenericFunctions.ts | 10 ++ .../nodes/ClickUp/ListDescription.ts | 170 ++++++++++++++++++ .../nodes/ClickUp/TaskDescription.ts | 100 +++++++++++ .../nodes-base/nodes/ClickUp/TaskInterface.ts | 3 + 5 files changed, 321 insertions(+), 1 deletion(-) create mode 100644 packages/nodes-base/nodes/ClickUp/ListDescription.ts diff --git a/packages/nodes-base/nodes/ClickUp/ClickUp.node.ts b/packages/nodes-base/nodes/ClickUp/ClickUp.node.ts index 1578243214..1a4eb5f7c2 100644 --- a/packages/nodes-base/nodes/ClickUp/ClickUp.node.ts +++ b/packages/nodes-base/nodes/ClickUp/ClickUp.node.ts @@ -12,11 +12,16 @@ import { import { clickupApiRequest, clickupApiRequestAllItems, + validateJSON, } from './GenericFunctions'; import { taskFields, taskOperations, } from './TaskDescription'; +import { + listFields, + listOperations, +} from './ListDescription'; import { ITask, } from './TaskInterface'; @@ -52,12 +57,18 @@ export class ClickUp implements INodeType { name: 'Task', value: 'task', }, + { + name: 'List', + value: 'list', + }, ], default: 'task', description: 'Resource to consume.', }, ...taskOperations, ...taskFields, + ...listOperations, + ...listFields, ], }; @@ -208,6 +219,7 @@ export class ClickUp implements INodeType { if (operation === 'create') { const listId = this.getNodeParameter('list', i) as string; const name = this.getNodeParameter('name', i) as string; + const jsonActive = this.getNodeParameter('jsonParameters', i) as boolean; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; const body: ITask = { name, @@ -252,7 +264,13 @@ export class ClickUp implements INodeType { delete body.content; body.markdown_content = additionalFields.content as string; } - + if (jsonActive) { + const customFields = validateJSON(this.getNodeParameter('customFieldsJson', i) as string); + if (customFields === undefined) { + throw new Error('Custom Fields: Invalid JSON'); + } + body.custom_fields = customFields; + } responseData = await clickupApiRequest.call(this, 'POST', `/list/${listId}/task`, body); } if (operation === 'update') { @@ -351,11 +369,30 @@ export class ClickUp implements INodeType { // responseData = responseData.tasks; } } + if (operation === 'setCustomField') { + const taskId = this.getNodeParameter('task', i) as string; + const fieldId = this.getNodeParameter('field', i) as string; + const value = this.getNodeParameter('value', i) as string; + const body: IDataObject = {}; + body.value = value; + //@ts-ignore + if (!isNaN(value)) { + body.value = parseInt(value, 10); + } + responseData = await clickupApiRequest.call(this, 'POST', `/task/${taskId}/field/${fieldId}`, body); + } if (operation === 'delete') { const taskId = this.getNodeParameter('id', i) as string; responseData = await clickupApiRequest.call(this, 'DELETE', `/task/${taskId}`, {}); } } + if (resource === 'list') { + if (operation === 'customFields') { + const listId = this.getNodeParameter('list', i) as string; + responseData = await clickupApiRequest.call(this, 'GET', `/list/${listId}/field`); + responseData = responseData.fields; + } + } if (Array.isArray(responseData)) { returnData.push.apply(returnData, responseData as IDataObject[]); } else { diff --git a/packages/nodes-base/nodes/ClickUp/GenericFunctions.ts b/packages/nodes-base/nodes/ClickUp/GenericFunctions.ts index d58ee1189a..5ef1238b84 100644 --- a/packages/nodes-base/nodes/ClickUp/GenericFunctions.ts +++ b/packages/nodes-base/nodes/ClickUp/GenericFunctions.ts @@ -54,3 +54,13 @@ export async function clickupApiRequestAllItems(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/ClickUp/ListDescription.ts b/packages/nodes-base/nodes/ClickUp/ListDescription.ts new file mode 100644 index 0000000000..517d8497f2 --- /dev/null +++ b/packages/nodes-base/nodes/ClickUp/ListDescription.ts @@ -0,0 +1,170 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const listOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'list', + ], + }, + }, + options: [ + { + name: 'Custom Fields', + value: 'customFields', + description: `Retrieve list's custom fields`, + }, + ], + default: 'customFields', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const listFields = [ + +/* -------------------------------------------------------------------------- */ +/* list:customFields */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Team', + name: 'team', + type: 'options', + default: '', + displayOptions: { + show: { + resource: [ + 'list', + ], + operation: [ + 'customFields', + ], + }, + }, + typeOptions: { + loadOptionsMethod: 'getTeams', + }, + required: true, + }, + { + displayName: 'Space', + name: 'space', + type: 'options', + default: '', + displayOptions: { + show: { + resource: [ + 'list', + ], + operation: [ + 'customFields', + ], + }, + }, + typeOptions: { + loadOptionsMethod: 'getSpaces', + loadOptionsDependsOn: [ + 'team', + ] + }, + required: true, + }, + { + displayName: 'Folderless List', + name: 'folderless', + type: 'boolean', + default: false, + displayOptions: { + show: { + resource: [ + 'list', + ], + operation: [ + 'customFields', + ], + }, + }, + required: true, + }, + { + displayName: 'Folder', + name: 'folder', + type: 'options', + default: '', + displayOptions: { + show: { + resource: [ + 'list', + ], + operation: [ + 'customFields', + ], + folderless: [ + false, + ], + }, + }, + typeOptions: { + loadOptionsMethod: 'getFolders', + loadOptionsDependsOn: [ + 'space', + ], + }, + required: true, + }, + { + displayName: 'List', + name: 'list', + type: 'options', + default: '', + displayOptions: { + show: { + resource: [ + 'list', + ], + operation: [ + 'customFields', + ], + folderless: [ + true, + ], + }, + }, + typeOptions: { + loadOptionsMethod: 'getFolderlessLists', + loadOptionsDependsOn: [ + 'space', + ], + }, + required: true, + }, + { + displayName: 'List', + name: 'list', + type: 'options', + default: '', + displayOptions: { + show: { + resource: [ + 'list', + ], + operation: [ + 'customFields', + ], + folderless: [ + false, + ], + }, + }, + typeOptions: { + loadOptionsMethod: 'getLists', + loadOptionsDependsOn: [ + 'folder', + ] + }, + required: true, + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/ClickUp/TaskDescription.ts b/packages/nodes-base/nodes/ClickUp/TaskDescription.ts index 4f1bcb53ef..56c60d6624 100644 --- a/packages/nodes-base/nodes/ClickUp/TaskDescription.ts +++ b/packages/nodes-base/nodes/ClickUp/TaskDescription.ts @@ -33,6 +33,11 @@ export const taskOperations = [ value: 'getAll', description: 'Get all tasks', }, + { + name: 'Set custom field', + value: 'setCustomField', + description: 'Set a custom field', + }, { name: 'Update', value: 'update', @@ -205,6 +210,22 @@ export const taskFields = [ required: true, description: 'The first name on the task', }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'create', + ] + }, + }, + default: false, + }, { displayName: 'Additional Fields', name: 'additionalFields', @@ -325,6 +346,28 @@ export const taskFields = [ }, ], }, + { + displayName: 'Custom Fields', + name: 'customFieldsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'create', + ], + jsonParameters: [ + true, + ], + }, + }, + default: '', + }, /* -------------------------------------------------------------------------- */ /* task:update */ /* -------------------------------------------------------------------------- */ @@ -802,4 +845,61 @@ export const taskFields = [ }, description: 'task ID', }, +/* -------------------------------------------------------------------------- */ +/* task:setCustomField */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Task ID', + name: 'task', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'setCustomField', + ], + }, + }, + description: 'Task ID', + }, + { + displayName: 'Field ID', + name: 'field', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'setCustomField', + ], + }, + }, + description: 'Task ID', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'setCustomField', + ], + }, + }, + description: 'Value', + }, ] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/ClickUp/TaskInterface.ts b/packages/nodes-base/nodes/ClickUp/TaskInterface.ts index 70a3899e15..44a61805f5 100644 --- a/packages/nodes-base/nodes/ClickUp/TaskInterface.ts +++ b/packages/nodes-base/nodes/ClickUp/TaskInterface.ts @@ -1,3 +1,5 @@ +import { IDataObject } from "n8n-workflow"; + export interface ITask { name?: string; content?: string; @@ -13,4 +15,5 @@ export interface ITask { markdown_content?: string; notify_all?: boolean; parent?: string; + custom_fields?: IDataObject[]; } From 59a81b81a0c5c159df431d45f08af2a46109897e Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 6 Feb 2020 18:41:00 -0800 Subject: [PATCH 2/2] :zap: Minor improvements to ClickUp-Node --- .../nodes-base/nodes/ClickUp/ClickUp.node.ts | 38 ++++++---- .../nodes/ClickUp/TaskDescription.ts | 75 ++++++++----------- 2 files changed, 56 insertions(+), 57 deletions(-) diff --git a/packages/nodes-base/nodes/ClickUp/ClickUp.node.ts b/packages/nodes-base/nodes/ClickUp/ClickUp.node.ts index 1a4eb5f7c2..887f9033ae 100644 --- a/packages/nodes-base/nodes/ClickUp/ClickUp.node.ts +++ b/packages/nodes-base/nodes/ClickUp/ClickUp.node.ts @@ -53,14 +53,14 @@ export class ClickUp implements INodeType { name: 'resource', type: 'options', options: [ - { - name: 'Task', - value: 'task', - }, { name: 'List', value: 'list', }, + { + name: 'Task', + value: 'task', + }, ], default: 'task', description: 'Resource to consume.', @@ -219,11 +219,17 @@ export class ClickUp implements INodeType { if (operation === 'create') { const listId = this.getNodeParameter('list', i) as string; const name = this.getNodeParameter('name', i) as string; - const jsonActive = this.getNodeParameter('jsonParameters', i) as boolean; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; const body: ITask = { name, }; + if (additionalFields.customFieldsJson) { + const customFields = validateJSON(additionalFields.customFieldsJson as string); + if (customFields === undefined) { + throw new Error('Custom Fields: Invalid JSON'); + } + body.custom_fields = customFields; + } if (additionalFields.content) { body.content = additionalFields.content as string; } @@ -264,13 +270,6 @@ export class ClickUp implements INodeType { delete body.content; body.markdown_content = additionalFields.content as string; } - if (jsonActive) { - const customFields = validateJSON(this.getNodeParameter('customFieldsJson', i) as string); - if (customFields === undefined) { - throw new Error('Custom Fields: Invalid JSON'); - } - body.custom_fields = customFields; - } responseData = await clickupApiRequest.call(this, 'POST', `/list/${listId}/task`, body); } if (operation === 'update') { @@ -373,11 +372,20 @@ export class ClickUp implements INodeType { const taskId = this.getNodeParameter('task', i) as string; const fieldId = this.getNodeParameter('field', i) as string; const value = this.getNodeParameter('value', i) as string; + const jsonParse = this.getNodeParameter('jsonParse', i) as boolean; + const body: IDataObject = {}; body.value = value; - //@ts-ignore - if (!isNaN(value)) { - body.value = parseInt(value, 10); + if (jsonParse === true) { + body.value = validateJSON(body.value); + if (body.value === undefined) { + throw new Error('Value is invalid JSON!'); + } + } else { + //@ts-ignore + if (!isNaN(body.value)) { + body.value = parseInt(body.value, 10); + } } responseData = await clickupApiRequest.call(this, 'POST', `/task/${taskId}/field/${fieldId}`, body); } diff --git a/packages/nodes-base/nodes/ClickUp/TaskDescription.ts b/packages/nodes-base/nodes/ClickUp/TaskDescription.ts index 56c60d6624..77a12ed07a 100644 --- a/packages/nodes-base/nodes/ClickUp/TaskDescription.ts +++ b/packages/nodes-base/nodes/ClickUp/TaskDescription.ts @@ -210,22 +210,6 @@ export const taskFields = [ required: true, description: 'The first name on the task', }, - { - displayName: 'JSON Parameters', - name: 'jsonParameters', - type: 'boolean', - displayOptions: { - show: { - resource: [ - 'task', - ], - operation: [ - 'create', - ] - }, - }, - default: false, - }, { displayName: 'Additional Fields', name: 'additionalFields', @@ -256,6 +240,16 @@ export const taskFields = [ default: [], }, + { + displayName: 'Custom Fields JSON', + name: 'customFieldsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: 'Custom fields to set as JSON in the format:
[{"id": "", "value": ""}]', + }, { displayName: 'Content', name: 'content', @@ -346,28 +340,6 @@ export const taskFields = [ }, ], }, - { - displayName: 'Custom Fields', - name: 'customFieldsJson', - type: 'json', - typeOptions: { - alwaysOpenEditWindow: true, - }, - displayOptions: { - show: { - resource: [ - 'task', - ], - operation: [ - 'create', - ], - jsonParameters: [ - true, - ], - }, - }, - default: '', - }, /* -------------------------------------------------------------------------- */ /* task:update */ /* -------------------------------------------------------------------------- */ @@ -764,7 +736,7 @@ export const taskFields = [ name: 'includeClosed', type: 'boolean', default: false, - description: 'the api does not include closed tasks. Set this to true and dont send a status filter to include closed tasks', + description: 'The response does by default not include closed tasks. Set this to true and dont send a status filter to include closed tasks.', }, { displayName: 'Order By', @@ -864,7 +836,7 @@ export const taskFields = [ ], }, }, - description: 'Task ID', + description: 'The ID of the task to add custom field to.', }, { displayName: 'Field ID', @@ -882,7 +854,26 @@ export const taskFields = [ ], }, }, - description: 'Task ID', + description: 'The ID of the field to add custom field to.', + }, + { + displayName: 'Value is JSON', + name: 'jsonParse', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'setCustomField', + ] + }, + }, + default: false, + description: `The value is JSON and will be parsed as such. Is needed
+ if for example needed for labels which expects the value
+ to be an array.`, }, { displayName: 'Value', @@ -900,6 +891,6 @@ export const taskFields = [ ], }, }, - description: 'Value', + description: 'The value to set on custom field.', }, ] as INodeProperties[];