diff --git a/packages/nodes-base/nodes/Asana/Asana.node.ts b/packages/nodes-base/nodes/Asana/Asana.node.ts index f308cba65..aa0ac83d5 100644 --- a/packages/nodes-base/nodes/Asana/Asana.node.ts +++ b/packages/nodes-base/nodes/Asana/Asana.node.ts @@ -17,6 +17,8 @@ import { getWorkspaces, } from './GenericFunctions'; +import * as moment from 'moment-timezone'; + export class Asana implements INodeType { description: INodeTypeDescription = { displayName: 'Asana', @@ -83,6 +85,10 @@ export class Asana implements INodeType { name: 'Project', value: 'project', }, + { + name: 'Subtask', + value: 'subtask', + }, { name: 'Task', value: 'task', @@ -103,6 +109,166 @@ export class Asana implements INodeType { default: 'task', description: 'The resource to operate on.', }, + // ---------------------------------- + // subtask + // ---------------------------------- + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'subtask', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a subtask', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, + + // ---------------------------------- + // subtask:create + // ---------------------------------- + { + displayName: 'Parent Task ID', + name: 'taskId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'subtask', + ], + }, + }, + description: 'The task to operate on.', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'subtask', + ], + }, + }, + description: 'The name of the subtask to create', + }, + { + displayName: 'Additional Fields', + name: 'otherProperties', + type: 'collection', + displayOptions: { + show: { + resource: [ + 'subtask', + ], + operation: [ + 'create', + ], + }, + }, + default: {}, + placeholder: 'Add Field', + options: [ + { + displayName: 'Assignee', + name: 'assignee', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + default: '', + description: 'Set Assignee on the subtask', + }, + { + displayName: 'Assignee Status', + name: 'assignee_status', + type: 'options', + options: [ + { + name: 'Inbox', + value: 'inbox', + }, + { + name: 'Today', + value: 'today', + }, + { + name: 'Upcoming', + value: 'upcoming', + }, + { + name: 'Later', + value: 'later', + }, + ], + default: 'inbox', + description: 'Set Assignee status on the subtask (requires Assignee)', + }, + { + displayName: 'Completed', + name: 'completed', + type: 'boolean', + default: false, + description: 'If the subtask should be marked completed.', + }, + { + displayName: 'Due On', + name: 'due_on', + type: 'dateTime', + default: '', + description: 'Date on which the time is due.', + }, + { + displayName: 'Liked', + name: 'liked', + type: 'boolean', + default: false, + description: 'If the task is liked by the authorized user.', + }, + { + displayName: 'Notes', + name: 'notes', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + rows: 5, + }, + default: '', + description: 'The task notes', + }, + { + displayName: 'Workspace', + name: 'workspace', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getWorkspaces', + }, + default: '', + description: 'The workspace to create the subtask in', + }, + ], + }, // ---------------------------------- // task @@ -134,6 +300,11 @@ export class Asana implements INodeType { value: 'get', description: 'Get a task', }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all tasks', + }, { name: 'Move', value: 'move', @@ -241,6 +412,138 @@ export class Asana implements INodeType { }, description: 'The ID of the task to get the data of.', }, + // ---------------------------------- + // task:getAll + // ---------------------------------- + { + 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', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'task', + ], + }, + }, + default: {}, + description: 'Properties to search for', + placeholder: 'Add Filter', + options: [ + { + displayName: 'Assignee', + name: 'assignee', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + default: '', + description: 'The assignee to filter tasks on. Note: If you specify assignee, you must also specify the workspace to filter on.', + }, + { + displayName: 'Fields', + name: 'opt_fields', + type: 'string', + default: '', + description: 'Defines fields to return. Multiple can be set separated by comma.', + }, + { + displayName: 'Pretty', + name: 'opt_pretty', + type: 'boolean', + default: false, + description: 'Provides “pretty” output.', + }, + { + displayName: 'Project', + name: 'project', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getProjects', + }, + default: '', + description: 'The project to filter tasks on.', + }, + { + displayName: 'Section', + name: 'section', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getSections', + }, + default: '', + description: 'The section to filter tasks on.', + }, + { + displayName: 'workspace', + name: 'workspace', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getWorkspaces', + }, + default: '', + description: 'The workspace to filter tasks on. Note: If you specify workspace, you must also specify the assignee to filter on.', + }, + { + displayName: 'Completed Since', + name: 'completed_since', + type: 'dateTime', + default: '', + description: 'Only return tasks that are either incomplete or that have been completed since this time.', + }, + { + displayName: 'Modified Since', + name: 'modified_since', + type: 'dateTime', + default: '', + description: 'Only return tasks that have been modified since the given time.', + }, + ], + }, // ---------------------------------- // task:move @@ -1225,6 +1528,7 @@ export class Asana implements INodeType { async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); const returnData: IDataObject[] = []; + const timezone = this.getTimezone(); const resource = this.getNodeParameter('resource', 0) as string; const operation = this.getNodeParameter('operation', 0) as string; @@ -1240,6 +1544,27 @@ export class Asana implements INodeType { body = {}; qs = {}; + if (resource === 'subtask') { + if (operation === 'create') { + // ---------------------------------- + // subtask:create + // ---------------------------------- + + const taskId = this.getNodeParameter('taskId', i) as string; + + requestMethod = 'POST'; + endpoint = `/tasks/${taskId}/subtasks`; + + body.name = this.getNodeParameter('name', i) as string; + + const otherProperties = this.getNodeParameter('otherProperties', i) as IDataObject; + Object.assign(body, otherProperties); + + responseData = await asanaApiRequest.call(this, requestMethod, endpoint, body, qs); + + responseData = responseData.data; + } + } if (resource === 'task') { if (operation === 'create') { // ---------------------------------- @@ -1285,7 +1610,45 @@ export class Asana implements INodeType { responseData = await asanaApiRequest.call(this, requestMethod, endpoint, body, qs); responseData = responseData.data; + + } else if (operation === 'getAll') { + // ---------------------------------- + // task:getAll + // ---------------------------------- + const filters = this.getNodeParameter('filters', i) as IDataObject; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + + requestMethod = 'GET'; + endpoint = `/tasks`; + + Object.assign(qs, filters); + + if (qs.modified_since) { + qs.modified_since = moment.tz(qs.modified_since as string, timezone).format(); + } + + if (qs.completed_since) { + qs.completed_since = moment.tz(qs.completed_since as string, timezone).format(); + } + + if (qs.fields) { + qs.fields = (qs.fields as string).split(','); + } + + if (returnAll) { + + responseData = await asanaApiRequestAllItems.call(this, requestMethod, endpoint, body, qs); + + } else { + + qs.limit = this.getNodeParameter('limit', i) as boolean; + + responseData = await asanaApiRequest.call(this, requestMethod, endpoint, body, qs); + + responseData = responseData.data; + } + } else if (operation === 'move') { // ---------------------------------- // task:move