diff --git a/packages/nodes-base/nodes/Microsoft/Teams/MicrosoftTeams.node.ts b/packages/nodes-base/nodes/Microsoft/Teams/MicrosoftTeams.node.ts index 9b1a1a18cb..04d106c750 100644 --- a/packages/nodes-base/nodes/Microsoft/Teams/MicrosoftTeams.node.ts +++ b/packages/nodes-base/nodes/Microsoft/Teams/MicrosoftTeams.node.ts @@ -26,11 +26,16 @@ import { channelMessageOperations, } from './ChannelMessageDescription'; +import { + taskFields, + taskOperations, +} from './TaskDescription'; + export class MicrosoftTeams implements INodeType { description: INodeTypeDescription = { displayName: 'Microsoft Teams', name: 'microsoftTeams', - icon: 'file:teams.png', + icon: 'file:teams.svg', group: ['input'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', @@ -61,6 +66,10 @@ export class MicrosoftTeams implements INodeType { name: 'Channel Message (Beta)', value: 'channelMessage', }, + { + name: 'Task', + value: 'task', + }, ], default: 'channel', description: 'The resource to operate on.', @@ -71,6 +80,9 @@ export class MicrosoftTeams implements INodeType { /// MESSAGE ...channelMessageOperations, ...channelMessageFields, + ///TASK + ...taskOperations, + ...taskFields, ], }; @@ -107,6 +119,77 @@ export class MicrosoftTeams implements INodeType { } return returnData; }, + // Get all the groups to display them to user so that he can + // select them easily + async getGroups(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const { value } = await microsoftApiRequest.call(this, 'GET', '/v1.0/groups'); + for (const group of value) { + returnData.push({ + name: group.mail, + value: group.id, + }); + } + return returnData; + }, + // Get all the plans to display them to user so that he can + // select them easily + async getPlans(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const groupId = this.getCurrentNodeParameter('groupId') as string; + const { value } = await microsoftApiRequest.call(this, 'GET', `/v1.0/groups/${groupId}/planner/plans`); + for (const plan of value) { + returnData.push({ + name: plan.title, + value: plan.id, + }); + } + return returnData; + }, + // Get all the plans to display them to user so that he can + // select them easily + async getBuckets(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const planId = this.getCurrentNodeParameter('planId') as string; + const { value } = await microsoftApiRequest.call(this, 'GET', `/v1.0/planner/plans/${planId}/buckets`); + for (const bucket of value) { + returnData.push({ + name: bucket.name, + value: bucket.id, + }); + } + return returnData; + }, + // Get all the plans to display them to user so that he can + // select them easily + async getMembers(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const groupId = this.getCurrentNodeParameter('groupId') as string; + const { value } = await microsoftApiRequest.call(this, 'GET', `/v1.0/groups/${groupId}/members`); + for (const member of value) { + returnData.push({ + name: member.displayName, + value: member.id, + }); + } + return returnData; + }, + // Get all the labels to display them to user so that he can + // select them easily + async getLabels(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const planId = this.getCurrentNodeParameter('planId') as string; + const { categoryDescriptions } = await microsoftApiRequest.call(this, 'GET', `/v1.0/planner/plans/${planId}/details`); + for (const key of Object.keys(categoryDescriptions)) { + if (categoryDescriptions[key] !== null) { + returnData.push({ + name: categoryDescriptions[key], + value: key, + }); + } + } + return returnData; + }, }, }; @@ -206,6 +289,88 @@ export class MicrosoftTeams implements INodeType { } } } + if (resource === 'task') { + //https://docs.microsoft.com/en-us/graph/api/planner-post-tasks?view=graph-rest-1.0&tabs=http + if (operation === 'create') { + const planId = this.getNodeParameter('planId', i) as string; + const bucketId = this.getNodeParameter('bucketId', i) as string; + const title = this.getNodeParameter('title', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const body: IDataObject = { + planId, + bucketId, + title, + }; + Object.assign(body, additionalFields); + + if (body.assignedTo) { + body.assignments = { + [body.assignedTo as string]: { + '@odata.type': 'microsoft.graph.plannerAssignment', + 'orderHint': ' !', + }, + }; + delete body.assignedTo; + } + + if (Array.isArray(body.labels)) { + body.appliedCategories = (body.labels as string[]).map((label) => ({ [label]: true })); + } + + responseData = await microsoftApiRequest.call(this, 'POST', `/v1.0/planner/tasks`, body); + } + //https://docs.microsoft.com/en-us/graph/api/plannertask-delete?view=graph-rest-1.0&tabs=http + if (operation === 'delete') { + const taskId = this.getNodeParameter('taskId', i) as string; + const task = await microsoftApiRequest.call(this, 'GET', `/v1.0/planner/tasks/${taskId}`); + responseData = await microsoftApiRequest.call(this, 'DELETE', `/v1.0/planner/tasks/${taskId}`, {}, {}, undefined, { 'If-Match': task['@odata.etag'] }); + responseData = { success: true }; + } + //https://docs.microsoft.com/en-us/graph/api/plannertask-get?view=graph-rest-1.0&tabs=http + if (operation === 'get') { + const taskId = this.getNodeParameter('taskId', i) as string; + responseData = await microsoftApiRequest.call(this, 'GET', `/v1.0/planner/tasks/${taskId}`); + } + //https://docs.microsoft.com/en-us/graph/api/planneruser-list-tasks?view=graph-rest-1.0&tabs=http + if (operation === 'getAll') { + const memberId = this.getNodeParameter('memberId', i) as string; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + if (returnAll) { + responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/v1.0/users/${memberId}/planner/tasks`); + } else { + qs.limit = this.getNodeParameter('limit', i) as number; + responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/v1.0/users/${memberId}/planner/tasks`, {}); + responseData = responseData.splice(0, qs.limit); + } + } + //https://docs.microsoft.com/en-us/graph/api/plannertask-update?view=graph-rest-1.0&tabs=http + if (operation === 'update') { + 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.assignedTo) { + body.assignments = { + [body.assignedTo as string]: { + '@odata.type': 'microsoft.graph.plannerAssignment', + 'orderHint': ' !', + }, + }; + delete body.assignedTo; + } + + if (Array.isArray(body.labels)) { + body.appliedCategories = (body.labels as string[]).map((label) => ({ [label]: true })); + } + + const task = await microsoftApiRequest.call(this, 'GET', `/v1.0/planner/tasks/${taskId}`); + + responseData = await microsoftApiRequest.call(this, 'PATCH', `/v1.0/planner/tasks/${taskId}`, body, {}, undefined, { 'If-Match': task['@odata.etag'] }); + + responseData = { success: true }; + } + } if (Array.isArray(responseData)) { returnData.push.apply(returnData, responseData as IDataObject[]); } else { diff --git a/packages/nodes-base/nodes/Microsoft/Teams/TaskDescription.ts b/packages/nodes-base/nodes/Microsoft/Teams/TaskDescription.ts new file mode 100644 index 0000000000..6b555e8b80 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/Teams/TaskDescription.ts @@ -0,0 +1,451 @@ +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 = [ + + /* -------------------------------------------------------------------------- */ + /* task:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Group ID', + name: 'groupId', + required: true, + type: 'options', + typeOptions: { + loadOptionsMethod: 'getGroups', + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'task', + ], + }, + }, + default: '', + description: 'Group ID', + }, + { + displayName: 'Plan ID', + name: 'planId', + required: true, + type: 'options', + typeOptions: { + loadOptionsMethod: 'getPlans', + loadOptionsDependsOn: [ + 'groupId', + ], + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'task', + ], + }, + }, + default: '', + description: 'The ID of the Plan.', + }, + { + displayName: 'Bucket ID', + name: 'bucketId', + required: true, + type: 'options', + typeOptions: { + loadOptionsMethod: 'getBuckets', + loadOptionsDependsOn: [ + 'planId', + ], + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'task', + ], + }, + }, + default: '', + description: 'The ID of the Bucket.', + }, + { + displayName: 'Title', + name: 'title', + required: true, + type: 'string', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'task', + ], + }, + }, + default: '', + description: 'Title of the task.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'task', + ], + }, + }, + default: {}, + placeholder: 'Add Field', + options: [ + { + displayName: 'Assigned To', + name: 'assignedTo', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getMembers', + loadOptionsDependsOn: [ + 'groupId', + ], + }, + default: '', + description: `Date and time at which the task is due. The Timestamp type represents date and time information using ISO 8601 format and is always in UTC time.`, + }, + { + displayName: 'Due Date Time', + name: 'dueDateTime', + type: 'dateTime', + default: '', + description: `Date and time at which the task is due. The Timestamp type represents date and time information using ISO 8601 format and is always in UTC time.`, + }, + { + displayName: 'Labels', + name: 'labels', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getLabels', + loadOptionsDependsOn: [ + 'planId', + ], + }, + default: [], + description: `Percentage of task completion. When set to 100, the task is considered completed.`, + }, + { + displayName: 'Percent Complete', + name: 'percentComplete', + type: 'number', + typeOptions: { + minValue: 0, + maxValue: 100, + }, + default: 0, + description: `Percentage of task completion. When set to 100, the task is considered completed.`, + }, + ], + }, + + /* -------------------------------------------------------------------------- */ + /* task:delete */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Task ID', + name: 'taskId', + required: true, + type: 'string', + displayOptions: { + show: { + operation: [ + 'delete', + ], + resource: [ + 'task', + ], + }, + }, + default: '', + description: 'Task ID', + }, + + /* -------------------------------------------------------------------------- */ + /* task:get */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Task ID', + name: 'taskId', + required: true, + type: 'string', + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'task', + ], + }, + }, + default: '', + description: 'Task ID', + }, + + /* -------------------------------------------------------------------------- */ + /* task:getAll */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Group ID', + name: 'groupId', + required: true, + type: 'options', + typeOptions: { + loadOptionsMethod: 'getGroups', + }, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'task', + ], + }, + }, + default: '', + description: 'Group ID', + }, + { + displayName: 'Member ID', + name: 'memberId', + required: false, + type: 'options', + typeOptions: { + loadOptionsMethod: 'getMembers', + loadOptionsDependsOn: [ + 'groupId', + ], + }, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'task', + ], + }, + }, + default: '', + description: 'Member ID', + }, + { + 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.', + }, + + /* -------------------------------------------------------------------------- */ + /* task:update */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Task ID', + name: 'taskId', + required: true, + type: 'string', + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'task', + ], + }, + }, + default: '', + description: 'The ID of the Task.', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'task', + ], + }, + }, + default: {}, + placeholder: 'Add Field', + options: [ + { + displayName: 'Assigned To', + name: 'assignedTo', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getMembers', + loadOptionsDependsOn: [ + 'groupId', + ], + }, + default: '', + description: `Date and time at which the task is due. The Timestamp type represents date and time information using ISO 8601 format and is always in UTC time.`, + }, + { + displayName: 'Bucket ID', + name: 'bucketId', + type: 'string', + default: '', + description: 'Channel name as it will appear to the user in Microsoft Teams.', + }, + { + displayName: 'Due Date Time', + name: 'dueDateTime', + type: 'dateTime', + default: '', + description: `Date and time at which the task is due. The Timestamp type represents date and time information using ISO 8601 format and is always in UTC time.`, + }, + { + displayName: 'Group ID', + name: 'groupId', + type: 'string', + default: '', + description: 'Group ID', + }, + { + displayName: 'Labels', + name: 'labels', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getLabels', + loadOptionsDependsOn: [ + 'planId', + ], + }, + default: [], + description: `Percentage of task completion. When set to 100, the task is considered completed.`, + }, + { + displayName: 'Percent Complete', + name: 'percentComplete', + type: 'number', + typeOptions: { + minValue: 0, + maxValue: 100, + }, + default: 0, + description: `Percentage of task completion. When set to 100, the task is considered completed.`, + }, + { + displayName: 'Plan ID', + name: 'planId', + type: 'string', + default: '', + description: 'Channel name as it will appear to the user in Microsoft Teams.', + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + description: 'Title of the task.', + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Microsoft/Teams/teams.png b/packages/nodes-base/nodes/Microsoft/Teams/teams.png deleted file mode 100644 index 6ccb7ac5dd..0000000000 Binary files a/packages/nodes-base/nodes/Microsoft/Teams/teams.png and /dev/null differ diff --git a/packages/nodes-base/nodes/Microsoft/Teams/teams.svg b/packages/nodes-base/nodes/Microsoft/Teams/teams.svg new file mode 100644 index 0000000000..b05bd28750 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/Teams/teams.svg @@ -0,0 +1,21 @@ + + + + + + + + +