diff --git a/packages/nodes-base/nodes/Clockify/Clockify.node.ts b/packages/nodes-base/nodes/Clockify/Clockify.node.ts index 321c9aefce..b006148f30 100644 --- a/packages/nodes-base/nodes/Clockify/Clockify.node.ts +++ b/packages/nodes-base/nodes/Clockify/Clockify.node.ts @@ -39,6 +39,11 @@ import { tagOperations, } from './TagDescription'; +import { + taskFields, + taskOperations, +} from './TaskDescription'; + import { timeEntryFields, timeEntryOperations, @@ -81,6 +86,10 @@ export class Clockify implements INodeType { name: 'Tag', value: 'tag', }, + { + name: 'Task', + value: 'task', + }, { name: 'Time Entry', value: 'timeEntry', @@ -91,6 +100,7 @@ export class Clockify implements INodeType { }, ...projectOperations, ...tagOperations, + ...taskOperations, ...timeEntryOperations, { displayName: 'Workspace ID', @@ -104,6 +114,7 @@ export class Clockify implements INodeType { }, ...projectFields, ...tagFields, + ...taskFields, ...timeEntryFields, ], }; @@ -457,6 +468,152 @@ export class Clockify implements INodeType { } } + if (resource === 'task') { + if (operation === 'create') { + const workspaceId = this.getNodeParameter( + 'workspaceId', + i, + ) as string; + + const projectId = this.getNodeParameter('projectId', i) as string; + + const name = this.getNodeParameter('name', i) as string; + + const additionalFields = this.getNodeParameter( + 'additionalFields', + i, + ) as IDataObject; + + const body: IDataObject = { + name, + }; + + Object.assign(body, additionalFields); + + if (body.estimate) { + const [hour, minute] = (body.estimate as string).split(':'); + body.estimate = `PT${hour}H${minute}M`; + } + + responseData = await clockifyApiRequest.call( + this, + 'POST', + `/workspaces/${workspaceId}/projects/${projectId}/tasks`, + body, + qs, + ); + } + + if (operation === 'delete') { + const workspaceId = this.getNodeParameter( + 'workspaceId', + i, + ) as string; + + const projectId = this.getNodeParameter('projectId', i) as string; + + const taskId = this.getNodeParameter('taskId', i) as string; + + responseData = await clockifyApiRequest.call( + this, + 'DELETE', + `/workspaces/${workspaceId}/projects/${projectId}/tasks/${taskId}`, + {}, + qs, + ); + } + + if (operation === 'get') { + const workspaceId = this.getNodeParameter( + 'workspaceId', + i, + ) as string; + + const projectId = this.getNodeParameter('projectId', i) as string; + + const taskId = this.getNodeParameter('taskId', i) as string; + + responseData = await clockifyApiRequest.call( + this, + 'GET', + `/workspaces/${workspaceId}/projects/${projectId}/tasks/${taskId}`, + {}, + qs, + ); + } + + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + + const workspaceId = this.getNodeParameter( + 'workspaceId', + i, + ) as string; + + const projectId = this.getNodeParameter('projectId', i) as string; + + const filters = this.getNodeParameter( + 'filters', + i, + ) as IDataObject; + + Object.assign(qs, filters); + + if (returnAll) { + responseData = await clockifyApiRequestAllItems.call( + this, + 'GET', + `/workspaces/${workspaceId}/projects/${projectId}/tasks`, + {}, + qs, + ); + } else { + qs['page-size'] = this.getNodeParameter('limit', i) as number; + + responseData = await clockifyApiRequest.call( + this, + 'GET', + `/workspaces/${workspaceId}/projects/${projectId}/tasks`, + {}, + qs, + ); + } + } + + if (operation === 'update') { + const workspaceId = this.getNodeParameter( + 'workspaceId', + i, + ) as string; + + const projectId = this.getNodeParameter('projectId', i) as string; + + const taskId = this.getNodeParameter('taskId', i) as string; + + const updateFields = this.getNodeParameter( + 'updateFields', + i, + ) as IDataObject; + + const body: IDataObject = {}; + + Object.assign(body, updateFields); + + if (body.estimate) { + const [hour, minute] = (body.estimate as string).split(':'); + body.estimate = `PT${hour}H${minute}M`; + } + + responseData = await clockifyApiRequest.call( + this, + 'PUT', + `/workspaces/${workspaceId}/projects/${projectId}/tasks/${taskId}`, + body, + qs, + ); + } + } + if (resource === 'timeEntry') { if (operation === 'create') { diff --git a/packages/nodes-base/nodes/Clockify/ClockifyTrigger.node.ts b/packages/nodes-base/nodes/Clockify/ClockifyTrigger.node.ts index 047a1b5ef0..8a8341b42a 100644 --- a/packages/nodes-base/nodes/Clockify/ClockifyTrigger.node.ts +++ b/packages/nodes-base/nodes/Clockify/ClockifyTrigger.node.ts @@ -24,7 +24,7 @@ export class ClockifyTrigger implements INodeType { displayName: 'Clockify Trigger', icon: 'file:clockify.svg', name: 'clockifyTrigger', - group: [ 'trigger' ], + group: ['trigger'], version: 1, description: 'Listens to Clockify events', defaults: { @@ -32,7 +32,7 @@ export class ClockifyTrigger implements INodeType { color: '#000000', }, inputs: [], - outputs: [ 'main' ], + outputs: ['main'], credentials: [ { name: 'clockifyApi', @@ -109,7 +109,7 @@ export class ClockifyTrigger implements INodeType { qs.start = webhookData.lastTimeChecked; qs.end = moment().tz(workflowTimezone).format('YYYY-MM-DDTHH:mm:ss') + 'Z'; qs.hydrated = true; - qs[ 'in-progress' ] = false; + qs['in-progress'] = false; break; } @@ -117,7 +117,7 @@ export class ClockifyTrigger implements INodeType { webhookData.lastTimeChecked = qs.end; if (Array.isArray(result) && result.length !== 0) { - return [ this.helpers.returnJsonArray(result) ]; + return [this.helpers.returnJsonArray(result)]; } return null; } diff --git a/packages/nodes-base/nodes/Clockify/CommonDtos.ts b/packages/nodes-base/nodes/Clockify/CommonDtos.ts index c85c892345..da8530119a 100644 --- a/packages/nodes-base/nodes/Clockify/CommonDtos.ts +++ b/packages/nodes-base/nodes/Clockify/CommonDtos.ts @@ -10,6 +10,11 @@ enum MembershipStatusEnum { INACTIVE = 'INACTIVE', } +enum TaskStatusEnum { + ACTIVE = 'ACTIVE', + DONE = 'DONE', +} + export interface IMembershipDto { hourlyRate: IHourlyRateDto; membershipStatus: MembershipStatusEnum; @@ -25,6 +30,17 @@ export interface ITagDto { archived: boolean; } +export interface ITaskDto { + assigneeIds: object; + estimate: string; + id: string; + name: any; // tslint:disable-line:no-any + workspaceId: string; + projectId: string; + 'is-active': boolean; + status: TaskStatusEnum; +} + export interface ITimeIntervalDto { duration: string; end: string; diff --git a/packages/nodes-base/nodes/Clockify/ProjectInterfaces.ts b/packages/nodes-base/nodes/Clockify/ProjectInterfaces.ts index 689fa66e07..4b07c0662c 100644 --- a/packages/nodes-base/nodes/Clockify/ProjectInterfaces.ts +++ b/packages/nodes-base/nodes/Clockify/ProjectInterfaces.ts @@ -1,4 +1,7 @@ -import { IHourlyRateDto, IMembershipDto } from './CommonDtos'; +import { + IHourlyRateDto, + IMembershipDto, +} from './CommonDtos'; enum EstimateEnum { AUTO = 'AUTO', @@ -52,4 +55,5 @@ export interface ITaskDto { name: string; projectId: string; status: TaskStatusEnum; + 'is-active': boolean; } diff --git a/packages/nodes-base/nodes/Clockify/TaskDescription.ts b/packages/nodes-base/nodes/Clockify/TaskDescription.ts new file mode 100644 index 0000000000..f0f251bfec --- /dev/null +++ b/packages/nodes-base/nodes/Clockify/TaskDescription.ts @@ -0,0 +1,356 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const taskOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'task', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a task', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a task', + }, + { + name: 'Get', + value: 'get', + description: 'Get a task', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all tasks', + }, + { + name: 'Update', + value: 'update', + description: 'Update a task', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const taskFields = [ + { + displayName: 'Project ID', + name: 'projectId', + type: 'options', + typeOptions: { + loadOptionsDependsOn: [ + 'workspaceId', + ], + loadOptionsMethod: 'loadProjectsForWorkspace', + }, + displayOptions: { + show: { + resource: [ + 'task', + ], + }, + }, + required: true, + default: '', + }, + /* -------------------------------------------------------------------------- */ + /* task:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Task Name', + name: 'name', + type: 'string', + required: true, + default: '', + description: 'Name of task to create', + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'task', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Assignee IDs', + name: 'assigneeIds', + type: 'multiOptions', + default: [], + typeOptions: { + loadOptionsMethod: 'loadUsersForWorkspace', + }, + }, + { + displayName: 'Estimate', + name: 'estimate', + type: 'string', + default: '', + placeholder: '2:30', + description: 'Estimate time the task will take, e.x: 2:30 (2 hours and 30 minutes)', + }, + ], + }, + + /* -------------------------------------------------------------------------- */ + /* task:delete */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Task ID', + name: 'taskId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'delete', + ], + }, + }, + description: 'ID of task to delete', + }, + + /* -------------------------------------------------------------------------- */ + /* task:get */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Task ID', + name: 'taskId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'get', + ], + }, + }, + description: 'ID of task to get', + }, + + /* -------------------------------------------------------------------------- */ + /* 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', + placeholder: 'Add Filter', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'task', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Is Active', + name: 'is-active', + type: 'boolean', + default: false, + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'Text to match in the task name', + }, + { + displayName: 'Sort Column', + name: 'sort-column', + type: 'options', + options: [ + { + name: 'Name', + value: 'NAME', + }, + ], + default: 'NAME', + }, + { + displayName: 'Sort Order', + name: 'sort-order', + type: 'options', + options: [ + { + name: 'Ascending', + value: 'ASCENDING', + }, + { + name: 'Descending', + value: 'DESCENDING', + }, + ], + default: 'ASCENDING', + }, + ], + }, + + /* -------------------------------------------------------------------------- */ + /* task:update */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Task ID', + name: 'taskId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'update', + ], + }, + }, + description: 'ID of task to update', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'task', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Assignee IDs', + name: 'assigneeIds', + type: 'multiOptions', + default: [], + typeOptions: { + loadOptionsMethod: 'loadUsersForWorkspace', + }, + }, + { + displayName: 'Estimate', + name: 'estimate', + type: 'string', + default: '', + placeholder: '2:30', + description: 'Estimate time the task will take, e.x: 2:30 (2 hours and 30 minutes)', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'Active', + value: 'ACTIVE', + }, + { + name: 'Done', + value: 'DONE', + }, + ], + default: 'ACTIVE', + }, + ], + }, +] as INodeProperties[];