From c3277df25bf053948e64079b7f79d97c569fa8aa Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Wed, 26 Aug 2020 03:09:07 -0400 Subject: [PATCH] :zap: Todoist node enhancement (#823) * Todoist node enhancement * done * :zap: Improvements * :lipstick: Remove comment * :lipstick: remove unnecessary line * :books: Add breaking change message * :zap: Remove unnecessary line Co-authored-by: lukigarazus --- packages/cli/BREAKING-CHANGES.md | 20 + .../TodoistOAuth2Api.credentials.ts | 46 ++ .../nodes/Todoist/GenericFunctions.ts | 75 ++- .../nodes-base/nodes/Todoist/Todoist.node.ts | 430 ++++++++++++------ packages/nodes-base/nodes/Todoist/todoist.png | Bin 4124 -> 5648 bytes packages/nodes-base/package.json | 1 + 6 files changed, 395 insertions(+), 177 deletions(-) create mode 100644 packages/nodes-base/credentials/TodoistOAuth2Api.credentials.ts diff --git a/packages/cli/BREAKING-CHANGES.md b/packages/cli/BREAKING-CHANGES.md index 68be5ce947..c61a00fc5a 100644 --- a/packages/cli/BREAKING-CHANGES.md +++ b/packages/cli/BREAKING-CHANGES.md @@ -2,6 +2,26 @@ This list shows all the versions which include breaking changes and how to upgrade. +## 0.80.0 + +### What changed? + +We have renamed the operations on the Todoist Node to keep consistency with the codebase. Also, deleted the operations close_match and delete_match as these operations can be accomplished using the operations getAll, close, and delete. + +### When is action necessary? + +When one of the following operations is used. + +- close_by +- close_match +- delete_id +- delete_match + +### How to upgrade: + +After upgrading open all workflows, which contain the Todoist Node, set the corresponding operation, and then save the workflow. + +If the operations close_match or delete_match are used, recreate them using the operations getAll, delete and close. ## 0.69.0 diff --git a/packages/nodes-base/credentials/TodoistOAuth2Api.credentials.ts b/packages/nodes-base/credentials/TodoistOAuth2Api.credentials.ts new file mode 100644 index 0000000000..9341e69c65 --- /dev/null +++ b/packages/nodes-base/credentials/TodoistOAuth2Api.credentials.ts @@ -0,0 +1,46 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class TodoistOAuth2Api implements ICredentialType { + name = 'todoistOAuth2Api'; + extends = [ + 'oAuth2Api', + ]; + displayName = 'Todoist OAuth2 API'; + properties = [ + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://todoist.com/oauth/authorize', + required: true, + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://todoist.com/oauth/access_token', + required: true, + }, + { + displayName: 'Scope', + name: 'scope', + type: 'hidden' as NodePropertyTypes, + default: 'data:read_write,data:delete', + }, + { + displayName: 'Auth URI Query Parameters', + name: 'authQueryParameters', + type: 'hidden' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'hidden' as NodePropertyTypes, + default: 'body', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Todoist/GenericFunctions.ts b/packages/nodes-base/nodes/Todoist/GenericFunctions.ts index f4fdfad5e1..d8d7767680 100644 --- a/packages/nodes-base/nodes/Todoist/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Todoist/GenericFunctions.ts @@ -1,66 +1,37 @@ -import { OptionsWithUri } from 'request'; +import { + OptionsWithUri, +} from 'request'; import { IExecuteFunctions, IHookFunctions, ILoadOptionsFunctions, - IExecuteSingleFunctions } from 'n8n-core'; -import * as _ from 'lodash'; - -export const filterAndExecuteForEachTask = async function( - this: IExecuteSingleFunctions, - taskCallback: (t: any) => any -) { - const expression = this.getNodeParameter('expression') as string; - const projectId = this.getNodeParameter('project') as number; - // Enable regular expressions - const reg = new RegExp(expression); - const tasks = await todoistApiRequest.call(this, '/tasks', 'GET'); - const filteredTasks = tasks.filter( - // Make sure that project will match no matter what the type is. If project was not selected match all projects - (el: any) => (!projectId || el.project_id) && el.content.match(reg) - ); - return { - affectedTasks: ( - await Promise.all(filteredTasks.map((t: any) => taskCallback(t))) - ) - // This makes it more clear and informative. We pass the ID as a convention and content to give the user confirmation that his/her expression works as expected - .map( - (el, i) => - el || { id: filteredTasks[i].id, content: filteredTasks[i].content } - ) - }; -}; +import { + IDataObject, +} from 'n8n-workflow'; export async function todoistApiRequest( this: | IHookFunctions | IExecuteFunctions - | IExecuteSingleFunctions | ILoadOptionsFunctions, - resource: string, method: string, + resource: string, body: any = {}, - headers?: object -): Promise { - // tslint:disable-line:no-any - const credentials = this.getCredentials('todoistApi'); - - if (credentials === undefined) { - throw new Error('No credentials got returned!'); - } - - const headerWithAuthentication = Object.assign({}, headers, { Authorization: `Bearer ${credentials.apiKey}` }); + qs: IDataObject = {}, +): Promise { // tslint:disable-line:no-any + const authentication = this.getNodeParameter('authentication', 0, 'apiKey'); const endpoint = 'api.todoist.com/rest/v1'; const options: OptionsWithUri = { - headers: headerWithAuthentication, + headers: {}, method, + qs, uri: `https://${endpoint}${resource}`, - json: true + json: true, }; if (Object.keys(body).length !== 0) { @@ -68,13 +39,25 @@ export async function todoistApiRequest( } try { - return this.helpers.request!(options); + if (authentication === 'apiKey') { + const credentials = this.getCredentials('todoistApi') as IDataObject; + + //@ts-ignore + options.headers['Authorization'] = `Bearer ${credentials.apiKey}`; + + return this.helpers.request!(options); + } else { + //@ts-ignore + return await this.helpers.requestOAuth2.call(this, 'todoistOAuth2Api', options); + } + } catch (error) { - const errorMessage = error.response.body.message || error.response.body.Message; + const errorMessage = error.response.body; if (errorMessage !== undefined) { - throw errorMessage; + throw new Error(errorMessage); } - throw error.response.body; + + throw errorMessage; } } diff --git a/packages/nodes-base/nodes/Todoist/Todoist.node.ts b/packages/nodes-base/nodes/Todoist/Todoist.node.ts index fc70424c9f..66a8c1120a 100644 --- a/packages/nodes-base/nodes/Todoist/Todoist.node.ts +++ b/packages/nodes-base/nodes/Todoist/Todoist.node.ts @@ -1,6 +1,7 @@ -import { - IExecuteSingleFunctions, +import { + IExecuteFunctions, } from 'n8n-core'; + import { IDataObject, INodeTypeDescription, @@ -9,12 +10,11 @@ import { ILoadOptionsFunctions, INodePropertyOptions, } from 'n8n-workflow'; + import { todoistApiRequest, - filterAndExecuteForEachTask, } from './GenericFunctions'; - interface IBodyCreateTask { content: string; project_id?: number; @@ -48,9 +48,44 @@ export class Todoist implements INodeType { { name: 'todoistApi', required: true, - } + displayOptions: { + show: { + authentication: [ + 'apiKey', + ], + }, + }, + }, + { + name: 'todoistOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: [ + 'oAuth2', + ], + }, + }, + }, ], properties: [ + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + name: 'API Key', + value: 'apiKey', + }, + { + name: 'OAuth2', + value: 'oAuth2', + }, + ], + default: 'apiKey', + description: 'The resource to operate on.', + }, { displayName: 'Resource', name: 'resource', @@ -85,24 +120,29 @@ export class Todoist implements INodeType { description: 'Create a new task', }, { - name: 'Close by ID', - value: 'close_id', - description: 'Close a task by passing an ID', + name: 'Close', + value: 'close', + description: 'Close a task', }, { - name: 'Close matching', - value: 'close_match', - description: 'Close a task by passing a regular expression', + name: 'Delete', + value: 'delete', + description: 'Delete a task', }, { - name: 'Delete by ID', - value: 'delete_id', - description: 'Delete a task by passing an ID', + name: 'Get', + value: 'get', + description: 'Get a task', }, { - name: 'Delete matching', - value: 'delete_match', - description: 'Delete a task by passing a regular expression', + name: 'Get All', + value: 'getAll', + description: 'Get all tasks', + }, + { + name: 'Reopen', + value: 'reopen', + description: 'Reopen a task', }, ], default: 'create', @@ -122,9 +162,7 @@ export class Todoist implements INodeType { ], operation: [ 'create', - 'close_match', - 'delete_match', - ] + ], }, }, default: '', @@ -144,7 +182,7 @@ export class Todoist implements INodeType { ], operation: [ 'create', - ] + ], }, }, default: [], @@ -165,7 +203,7 @@ export class Todoist implements INodeType { ], operation: [ 'create', - ] + ], }, }, default: '', @@ -173,32 +211,27 @@ export class Todoist implements INodeType { description: 'Task content', }, { - displayName: 'ID', - name: 'id', + displayName: 'Task ID', + name: 'taskId', type: 'string', default: '', required: true, - typeOptions: { rows: 1 }, displayOptions: { - show: { resource: ['task'], operation: ['close_id', 'delete_id'] } + show: { + resource: [ + 'task', + ], + operation: [ + 'delete', + 'close', + 'get', + 'reopen', + ], + }, }, }, { - displayName: 'Expression to match', - name: 'expression', - type: 'string', - default: '', - required: true, - typeOptions: { rows: 1 }, - displayOptions: { - show: { - resource: ['task'], - operation: ['close_match', 'delete_match'] - } - } - }, - { - displayName: 'Options', + displayName: 'Additional Fields', name: 'options', type: 'collection', placeholder: 'Add Option', @@ -210,22 +243,10 @@ export class Todoist implements INodeType { ], operation: [ 'create', - ] + ], }, }, options: [ - { - displayName: 'Priority', - name: 'priority', - type: 'number', - typeOptions: { - numberStepSize: 1, - maxValue: 4, - minValue: 1, - }, - default: 1, - description: 'Task priority from 1 (normal) to 4 (urgent).', - }, { displayName: 'Due Date Time', name: 'dueDateTime', @@ -240,24 +261,131 @@ export class Todoist implements INodeType { default: '', description: 'Human defined task due date (ex.: “next Monday”, “Tomorrow”). Value is set using local (not UTC) time.', }, - ] - } - ] + { + displayName: 'Priority', + name: 'priority', + type: 'number', + typeOptions: { + numberStepSize: 1, + maxValue: 4, + minValue: 1, + }, + default: 1, + description: 'Task priority from 1 (normal) to 4 (urgent).', + }, + ], + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'task', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'task', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Filter', + name: 'filter', + type: 'string', + default: '', + description: 'Filter by any supported filter.', + }, + { + displayName: 'IDs', + name: 'ids', + type: 'string', + default: '', + description: 'A list of the task IDs to retrieve, this should be a comma separated list.', + }, + { + displayName: 'Label ID', + name: 'labelId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLabels', + }, + default: {}, + description: 'Filter tasks by label.', + }, + { + displayName: 'Lang', + name: 'lang', + type: 'string', + default: '', + description: 'IETF language tag defining what language filter is written in, if differs from default English', + }, + { + displayName: 'Project ID', + name: 'projectId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getProjects', + }, + default: '', + description: 'Filter tasks by project id.', + }, + ], + }, + ], }; - methods = { loadOptions: { // Get all the available projects to display them to user so that he can // select them easily async getProjects(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; - let projects; - try { - projects = await todoistApiRequest.call(this, '/projects', 'GET'); - } catch (err) { - throw new Error(`Todoist Error: ${err}`); - } + const projects = await todoistApiRequest.call(this, 'GET', '/projects'); for (const project of projects) { const projectName = project.name; const projectId = project.id; @@ -275,12 +403,8 @@ export class Todoist implements INodeType { // select them easily async getLabels(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; - let labels; - try { - labels = await todoistApiRequest.call(this, '/labels', 'GET'); - } catch (err) { - throw new Error(`Todoist Error: ${err}`); - } + const labels = await todoistApiRequest.call(this, 'GET', '/labels'); + for (const label of labels) { const labelName = label.name; const labelId = label.id; @@ -296,67 +420,111 @@ export class Todoist implements INodeType { } }; - async executeSingle(this: IExecuteSingleFunctions): Promise { - const resource = this.getNodeParameter('resource') as string; - const operation = this.getNodeParameter('operation') as string; - try { - return { - json: { result: await OPERATIONS[resource]?.[operation]?.bind(this)() } - }; - } catch (err) { - return { json: { error: `Todoist Error: ${err.message}` } }; + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + const qs: IDataObject = {}; + let responseData; + for (let i = 0; i < length; i++) { + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + if (resource === 'task') { + if (operation === 'create') { + //https://developer.todoist.com/rest/v1/#create-a-new-task + const content = this.getNodeParameter('content', i) as string; + const projectId = this.getNodeParameter('project', i) as number; + const labels = this.getNodeParameter('labels', i) as number[]; + const options = this.getNodeParameter('options', i) as IDataObject; + + const body: IBodyCreateTask = { + content, + project_id: projectId, + priority: (options.priority!) ? parseInt(options.priority as string, 10) : 1, + }; + + if (options.dueDateTime) { + body.due_datetime = options.dueDateTime as string; + } + + if (options.dueString) { + body.due_string = options.dueString as string; + } + + if (labels !== undefined && labels.length !== 0) { + body.label_ids = labels; + } + + responseData = await todoistApiRequest.call(this, 'POST', '/tasks', body); + } + if (operation === 'close') { + //https://developer.todoist.com/rest/v1/#close-a-task + const id = this.getNodeParameter('taskId', i) as string; + + responseData = await todoistApiRequest.call(this, 'POST', `/tasks/${id}/close`); + + responseData = { success: true }; + + } + if (operation === 'delete') { + //https://developer.todoist.com/rest/v1/#delete-a-task + const id = this.getNodeParameter('taskId', i) as string; + + responseData = await todoistApiRequest.call(this, 'DELETE', `/tasks/${id}`); + + responseData = { success: true }; + + } + if (operation === 'get') { + //https://developer.todoist.com/rest/v1/#get-an-active-task + const id = this.getNodeParameter('taskId', i) as string; + + responseData = await todoistApiRequest.call(this, 'GET', `/tasks/${id}`); + } + if (operation === 'getAll') { + //https://developer.todoist.com/rest/v1/#get-active-tasks + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const filters = this.getNodeParameter('filters', i) as IDataObject; + if (filters.projectId) { + qs.project_id = filters.projectId as string; + } + if (filters.labelId) { + qs.label_id = filters.labelId as string; + } + if (filters.filter) { + qs.filter = filters.filter as string; + } + if (filters.lang) { + qs.lang = filters.lang as string; + } + if (filters.ids) { + qs.ids = filters.ids as string; + } + + responseData = await todoistApiRequest.call(this, 'GET', '/tasks', {}, qs); + + if (!returnAll) { + const limit = this.getNodeParameter('limit', i) as number; + responseData = responseData.splice(0, limit); + } + } + if (operation === 'reopen') { + //https://developer.todoist.com/rest/v1/#get-an-active-task + const id = this.getNodeParameter('taskId', i) as string; + + responseData = await todoistApiRequest.call(this, 'POST', `/tasks/${id}/reopen`); + + responseData = { success: true }; + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as IDataObject); + } } + + return [this.helpers.returnJsonArray(returnData)]; } } - -const OPERATIONS: { - [key: string]: { [key: string]: (this: IExecuteSingleFunctions) => any }; -} = { - task: { - create(this: IExecuteSingleFunctions) { - //https://developer.todoist.com/rest/v1/#create-a-new-task - const content = this.getNodeParameter('content') as string; - const projectId = this.getNodeParameter('project') as number; - const labels = this.getNodeParameter('labels') as number[]; - const options = this.getNodeParameter('options') as IDataObject; - - const body: IBodyCreateTask = { - content, - project_id: projectId, - priority: (options.priority!) ? parseInt(options.priority as string, 10) : 1, - }; - - if (options.dueDateTime) { - body.due_datetime = options.dueDateTime as string; - } - - if (options.dueString) { - body.due_string = options.dueString as string; - } - - if (labels !== undefined && labels.length !== 0) { - body.label_ids = labels; - } - - return todoistApiRequest.call(this, '/tasks', 'POST', body); - }, - close_id(this: IExecuteSingleFunctions) { - const id = this.getNodeParameter('id') as string; - return todoistApiRequest.call(this, `/tasks/${id}/close`, 'POST'); - }, - delete_id(this: IExecuteSingleFunctions) { - const id = this.getNodeParameter('id') as string; - return todoistApiRequest.call(this, `/tasks/${id}`, 'DELETE'); - }, - close_match(this) { - return filterAndExecuteForEachTask.call(this, t => - todoistApiRequest.call(this, `/tasks/${t.id}/close`, 'POST') - ); - }, - delete_match(this) { - return filterAndExecuteForEachTask.call(this, t => - todoistApiRequest.call(this, `/tasks/${t.id}`, 'DELETE') - ); - } - } -}; diff --git a/packages/nodes-base/nodes/Todoist/todoist.png b/packages/nodes-base/nodes/Todoist/todoist.png index 00af868776dba6a2481f26d5d68022af41ec6b86..db8c55479ddee91544f786a198a6cce6393443e3 100644 GIT binary patch literal 5648 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 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index acbba35985..e3e597819c 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -151,6 +151,7 @@ "dist/credentials/SurveyMonkeyOAuth2Api.credentials.js", "dist/credentials/TelegramApi.credentials.js", "dist/credentials/TodoistApi.credentials.js", + "dist/credentials/TodoistOAuth2Api.credentials.js", "dist/credentials/TravisCiApi.credentials.js", "dist/credentials/TrelloApi.credentials.js", "dist/credentials/TwilioApi.credentials.js",