diff --git a/packages/nodes-base/nodes/Todoist/GenericFunctions.ts b/packages/nodes-base/nodes/Todoist/GenericFunctions.ts index f98b54f1c7..3362a8581c 100644 --- a/packages/nodes-base/nodes/Todoist/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Todoist/GenericFunctions.ts @@ -10,39 +10,35 @@ import { import * as _ from 'lodash'; import { IDataObject } from 'n8n-workflow'; -export async function mandrillApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resource: string, method: string, action: string, body: any = {}, headers?: object): Promise { // tslint:disable-line:no-any - const credentials = this.getCredentials('mandrillApi'); +export async function todoistApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resource: string, method: 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 data = Object.assign({}, body, { key: credentials.apiKey }); + const headerWithAuthentication = Object.assign({}, headers, { Authorization: `Bearer ${credentials.apiKey}` }); - const endpoint = 'mandrillapp.com/api/1.0'; + const endpoint = 'api.todoist.com/rest/v1'; const options: OptionsWithUri = { - headers, + headers: headerWithAuthentication, method, - uri: `https://${endpoint}${resource}${action}.json`, - body: data, + body, + uri: `https://${endpoint}${resource}`, json: true }; + if (_.isEmpty(options.body)) { + delete options.body + } try { return await this.helpers.request!(options); } catch (error) { - console.error(error); + //console.error(error); const errorMessage = error.response.body.message || error.response.body.Message; - if (error.name === 'Invalid_Key') { - throw new Error('The provided API key is not a valid Mandrill API key'); - } else if (error.name === 'ValidationError') { - throw new Error('The parameters passed to the API call are invalid or not provided when required'); - } else if (error.name === 'GeneralError') { - throw new Error('An unexpected error occurred processing the request. Mandrill developers will be notified.'); - } if (errorMessage !== undefined) { throw errorMessage; @@ -50,52 +46,3 @@ export async function mandrillApiRequest(this: IHookFunctions | IExecuteFunction throw error.response.body; } } - -export function getToEmailArray(toEmail: string): any { // tslint:disable-line:no-any - let toEmailArray; - if (toEmail.split(',').length > 0) { - const array = toEmail.split(','); - toEmailArray = _.map(array, (email) => { - return { - email, - type: 'to' - }; - }); - } else { - toEmailArray = [{ - email: toEmail, - type: 'to' - }]; - } - return toEmailArray; -} - -export function getGoogleAnalyticsDomainsArray(s: string): string[] { - let array: string[] = []; - if (s.split(',').length > 0) { - array = s.split(','); - } else { - array = [s]; - } - return array; -} - -export function getTags(s: string): any[] { // tslint:disable-line:no-any - let array = []; - if (s.split(',').length > 0) { - array = s.split(','); - } else { - array = [s]; - } - return array; -} - -export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any - let result; - try { - result = JSON.parse(json!); - } catch (exception) { - result = []; - } - return result; -} diff --git a/packages/nodes-base/nodes/Todoist/Todoist.node.ts b/packages/nodes-base/nodes/Todoist/Todoist.node.ts index b2f6b7dd22..5484b987bc 100644 --- a/packages/nodes-base/nodes/Todoist/Todoist.node.ts +++ b/packages/nodes-base/nodes/Todoist/Todoist.node.ts @@ -10,16 +10,12 @@ import { INodePropertyOptions, } from 'n8n-workflow'; import { - mandrillApiRequest, - getToEmailArray, - getGoogleAnalyticsDomainsArray, - getTags, - validateJSON + todoistApiRequest } from './GenericFunctions'; -export class Todoist implements INodeType { +import moment = require('moment'); - //https://mandrillapp.com/api/docs/messages.JSON.html#method=send-template +export class Todoist implements INodeType { description: INodeTypeDescription = { displayName: 'Todoist', @@ -41,50 +37,213 @@ export class Todoist implements INodeType { required: true, } ], - //multiOptions properties: [ - { - displayName: 'Testing', - name: 'testing', - placeholder: 'blabla', - type: 'fixedCollection', + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Task', + value: 'task', + description: 'Task resource.', + }, + ], default: '', - typeOptions: { - multipleValues: true, + required: true, + description: 'Resource to consume.', + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'task', + ], + }, }, options: [ { - name: 'label', - displayName: 'label', - values: [ - { - displayName: 'Name', - name: 'name', - type: 'options', - default: '', - options: [ - { - name: 'Message', - value: 'message', - description: 'Send a message.', - }, - ], - }, - { - displayName: 'Content', - name: 'content', - type: 'string', - default: '', - }, - ], + name: 'Create', + value: 'create', + description: 'Create a new task', }, ], - }, - ], - }; + default: 'create', + description: 'The operation to perform.', + }, + { + displayName: 'Project', + name: 'project', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getProjects', + }, + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'create' + ] + }, + }, + default: [], + description: 'The project you want to add the task to.', + }, + { + displayName: 'Content', + name: 'content', + type: 'string', + typeOptions: { + rows: 5, + }, + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'create' + ] + }, + }, + default: [], + required: true, + description: 'Task content', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'task', + ], + 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', + type: 'dateTime', + default: '', + description: 'Specific date and time in RFC3339 format in UTC.', + }, + { + displayName: 'Due String', + name: 'dueString', + type: 'string', + default: '', + description: 'Human defined task due date (ex.: “next Monday”, “Tomorrow”). Value is set using local (not UTC) time.', + }, + ] + } + ] + }; + + + 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}`); + } + for (const project of projects) { + const projectName = project.name; + const projectId = project.id; + + returnData.push({ + name: projectName, + value: projectId, + }); + } + + return returnData + } + }, + }; + async executeSingle(this: IExecuteSingleFunctions): Promise { - + const resource = this.getNodeParameter('resource') as string; + const opeation = this.getNodeParameter('operation') as string; + let response; + + if (resource === 'task' && opeation === 'create') { + + //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 options = this.getNodeParameter('options') as IDataObject; + + + interface IBodyCreateTask { + content: string; + project_id?: number; + parent?: number; + order?: number; + label_ids?: [number]; + priority?: number; + due_string?: string; + due_datetime?: string; + due_date?: string; + due_lang?: string; + } + + const body: IBodyCreateTask = { + content, + project_id: projectId, + priority: (options.priority!) ? parseInt(options.priority, 10) : 1, + } + + if (options.dueDateTime) { + body.due_datetime = moment(options.dueDateTime).utc().format() + } + + if (options.dueString) { + body.due_string = options.dueString + } + + try { + response = await todoistApiRequest.call(this, '/tasks', 'POST', body); + } catch (err) { + throw new Error(`Todoist Error: ${err}`); + } + } + + return { + json: response, + }; } } diff --git a/packages/nodes-base/nodes/Todoist/todoist.png b/packages/nodes-base/nodes/Todoist/todoist.png index 44691b2e49..5ce577f317 100644 Binary files a/packages/nodes-base/nodes/Todoist/todoist.png and b/packages/nodes-base/nodes/Todoist/todoist.png differ