Add task resource to Microsoft Team Node (#1403)

*  Add task resource to Microsoft Team Node

*  Minor improvement on Microsoft Team Node

Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
Ricardo Espinoza 2021-02-07 15:39:39 -05:00 committed by GitHub
parent 4150ae00c7
commit 7c2755c970
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 638 additions and 1 deletions

View file

@ -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<INodePropertyOptions[]> {
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<INodePropertyOptions[]> {
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<INodePropertyOptions[]> {
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<INodePropertyOptions[]> {
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<INodePropertyOptions[]> {
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 {

View file

@ -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[];

Binary file not shown.

Before

Width:  |  Height:  |  Size: 725 B

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 2381.4 2354.5" style="enable-background:new 0 0 2381.4 2354.5;" xml:space="preserve">
<style type="text/css">
.st0{fill:#5558AF;}
.st1{fill:#FFFFFF;}
</style>
<g>
<path class="st0" d="M2015.6,899.2c19.5,19.5,42.5,35,67.9,45.8c53,22.2,112.7,22.2,165.8,0c51.2-21.8,92-62.5,113.7-113.7
c22.2-53,22.2-112.7,0-165.8c-21.8-51.2-62.5-92-113.7-113.7c-53-22.2-112.7-22.2-165.8,0c-51.2,21.8-92,62.5-113.7,113.7
c-22.2,53-22.2,112.7,0,165.8C1980.6,856.6,1996.2,879.7,2015.6,899.2L2015.6,899.2z M1953.2,1097v642.1h107
c36.8-0.2,73.4-3.6,109.5-10.4c36.3-6.4,71.3-18.6,103.7-36.2c30.6-16.6,57-40,77.3-68.2c21.3-31.3,32-68.6,30.5-106.5V1097H1953.2
z M1606.4,827.8c28.4,0.2,56.6-5.5,82.8-16.7c51.2-21.8,91.9-62.5,113.6-113.7c22.2-53,22.2-112.7-0.1-165.8
c-21.8-51.2-62.5-92-113.7-113.7c-26.2-11.2-54.4-16.9-82.9-16.7c-28.3-0.2-56.3,5.5-82.3,16.7c-19.4,8.3-25.5,19.1-52.2,32.1v329
c26.8,13.1,32.8,23.8,52.2,32.1C1549.9,822.4,1578,828,1606.4,827.8L1606.4,827.8z M1471.6,1908.9c26.8,5.8,36.4,10.3,55.4,12.9
c20.8,3,41.8,4.5,62.8,4.6c32.4-0.2,64.8-3.6,96.5-10.4c32.3-6.5,63.3-18.6,91.5-35.7c27.7-17,51-40.2,68.2-67.7
c19-32.1,28.3-69.1,26.9-106.4v-743h-401.3V1908.9z M0,2113.7l1391.3,240.8V0L0,240.8V2113.7z"/>
</g>
<path class="st1" d="M1016.7,722.4l-642.1,39.1v148.1l240.8-9.7v686.7l160.5,9.4V893.6l240.8-10.7V722.4z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB