@blickwerk/n8n-nodes-microsoft-planner 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,7 +14,7 @@ Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes
14
14
 
15
15
  1. Go to **Settings > Community Nodes** in your n8n instance
16
16
  2. Select **Install**
17
- 3. Enter `n8n-nodes-microsoft-planner` in **Enter npm package name**
17
+ 3. Enter `@blickwerk/n8n-nodes-microsoft-planner` in **Enter npm package name**
18
18
  4. Agree to the [risks](https://docs.n8n.io/integrations/community-nodes/risks/) of using community nodes
19
19
  5. Select **Install**
20
20
 
@@ -22,10 +22,10 @@ After installing the node, you can use it like any other node in your workflows.
22
22
 
23
23
  ### Manual Installation
24
24
 
25
- To install manually from source:
25
+ To install manually:
26
26
 
27
27
  ```bash
28
- npm install n8n-nodes-microsoft-planner
28
+ npm install @blickwerk/n8n-nodes-microsoft-planner
29
29
  ```
30
30
 
31
31
  ## Prerequisites
@@ -36,6 +36,7 @@ You need to have:
36
36
  - Azure AD App Registration with the following API permissions:
37
37
  - `Tasks.ReadWrite` - Read and write tasks
38
38
  - `Group.ReadWrite.All` - Read and write all groups (required for Planner)
39
+ - `User.Read.All` - Read all users (required for user assignment by email)
39
40
 
40
41
  ## Setting up Azure AD App
41
42
 
@@ -54,10 +55,13 @@ You need to have:
54
55
  3. Select **Microsoft Graph**
55
56
  4. Select **Delegated permissions**
56
57
  5. Add the following permissions:
57
- - `Tasks.ReadWrite`
58
- - `Group.ReadWrite.All`
58
+ - `Tasks.ReadWrite` - Read and write tasks
59
+ - `Group.ReadWrite.All` - Read and write all groups (required for Planner)
60
+ - `User.Read.All` - Read all users' basic profiles (required for user assignment)
59
61
  6. Click **Grant admin consent**
60
62
 
63
+ **Note**: The `User.Read.All` permission is required if you want to assign tasks to users by email address.
64
+
61
65
  ### Create Client Secret
62
66
 
63
67
  1. Go to **Certificates & secrets**
@@ -80,6 +84,13 @@ Configure the Microsoft Planner OAuth2 API credentials in n8n:
80
84
  4. Click **Connect my account**
81
85
  5. Complete the OAuth flow
82
86
 
87
+ ## Features
88
+
89
+ - **Dynamic Dropdowns**: Automatically loads and displays your Plans, Buckets, and Tasks - no need to manually copy IDs!
90
+ - **User Assignment**: Assign tasks to users by email address
91
+ - **Priority Management**: Easy-to-use priority dropdown (Urgent, Important, Medium, Low)
92
+ - **Full CRUD Operations**: Create, Read, Update, and Delete tasks
93
+
83
94
  ## Operations
84
95
 
85
96
  ### Task
@@ -94,38 +105,55 @@ Configure the Microsoft Planner OAuth2 API credentials in n8n:
94
105
 
95
106
  ### Creating a Task
96
107
 
97
- To create a task, you need:
98
- - **Plan ID**: The ID of the plan
99
- - **Bucket ID**: The ID of the bucket within the plan
100
- - **Title**: The task title
108
+ To create a task:
109
+ 1. Select a **Plan** from the dropdown (automatically loaded from your Microsoft 365)
110
+ 2. Select a **Bucket** from the dropdown (loads buckets from the selected plan)
111
+ 3. Enter the **Title** of the task
101
112
 
102
113
  Optional fields:
103
- - Description
104
- - Priority (0-10, where 0 is most urgent)
105
- - Due Date Time
106
- - Start Date Time
107
- - Percent Complete
114
+ - **Description**: Detailed task description
115
+ - **Priority**: Select from dropdown
116
+ - Urgent (highest priority)
117
+ - Important
118
+ - Medium (default)
119
+ - Low
120
+ - **Assigned To**: Comma-separated list of user emails (e.g., `user1@domain.com, user2@domain.com`)
121
+ - **Due Date Time**: When the task should be completed
122
+ - **Start Date Time**: When work on the task should begin
123
+ - **Percent Complete**: Task completion percentage (0-100)
108
124
 
109
125
  ### Getting Tasks
110
126
 
111
- You can retrieve tasks by:
112
- - **Task ID**: Get a specific task
113
- - **Plan ID**: Get all tasks in a plan
114
- - **Bucket ID**: Get all tasks in a bucket
127
+ **Get a single task:**
128
+ - Select the task from the dropdown (automatically loaded from your plans)
129
+
130
+ **Get many tasks:**
131
+ - Filter by **Plan** (select from dropdown)
132
+ - Or filter by **Bucket** (select from dropdown)
133
+ - Set a limit or return all tasks
115
134
 
116
135
  ### Updating a Task
117
136
 
118
- To update a task, provide the Task ID and the fields you want to update.
137
+ To update a task:
138
+ 1. Select the **Task** from the dropdown
139
+ 2. Update any of these fields:
140
+ - Title
141
+ - Description
142
+ - Priority (via dropdown)
143
+ - Assigned users (via email list)
144
+ - Due Date Time
145
+ - Start Date Time
146
+ - Percent Complete
147
+ - Move to different bucket
119
148
 
120
- ### Finding Plan and Bucket IDs
149
+ ### How Dynamic Dropdowns Work
121
150
 
122
- You can find Plan IDs and Bucket IDs using the Microsoft Graph Explorer:
151
+ The node automatically fetches your data from Microsoft 365:
152
+ - **Plans**: Loaded from your account when you open the Plan dropdown
153
+ - **Buckets**: Dynamically loaded based on the selected Plan
154
+ - **Tasks**: Loaded from the selected Plan or Bucket
123
155
 
124
- 1. Go to [Microsoft Graph Explorer](https://developer.microsoft.com/en-us/graph/graph-explorer)
125
- 2. Sign in with your Microsoft account
126
- 3. Use these queries:
127
- - Get all plans: `GET https://graph.microsoft.com/v1.0/me/planner/plans`
128
- - Get buckets in a plan: `GET https://graph.microsoft.com/v1.0/planner/plans/{plan-id}/buckets`
156
+ No need to manually look up IDs anymore! Just select from the dropdown menus.
129
157
 
130
158
  ## Compatibility
131
159
 
@@ -139,10 +167,34 @@ Tested with n8n version 1.0.0 and above.
139
167
 
140
168
  ## Version History
141
169
 
170
+ ### 1.2.0
171
+ - **Major UX Improvement**: Added dynamic dropdowns for Plans, Buckets, and Tasks
172
+ - No more manual ID lookup - select from dropdown menus
173
+ - Plans load automatically from your Microsoft 365 account
174
+ - Buckets load dynamically based on selected Plan
175
+ - Tasks load from selected Plan or Bucket
176
+ - Improved user experience with clearer field labels
177
+
178
+ ### 1.1.1
179
+ - Fixed startDateTime not being set (was returning null)
180
+ - Fixed user assignment by adding User.Read.All permission requirement
181
+ - Added error logging for failed user lookups
182
+ - Improved README documentation with all required permissions
183
+
184
+ ### 1.1.0
185
+ - Added priority dropdown (Urgent, Important, Medium, Low) instead of number input
186
+ - Added user assignment functionality (assign tasks by email)
187
+ - Improved user experience with better field descriptions
188
+
189
+ ### 1.0.1
190
+ - Fixed DateTime format handling (automatic conversion to ISO 8601)
191
+ - Improved error handling for invalid date formats
192
+
142
193
  ### 1.0.0
143
194
  - Initial release
144
195
  - Support for creating and retrieving Planner tasks
145
196
  - OAuth2 authentication
197
+ - CRUD operations (Create, Read, Update, Delete)
146
198
 
147
199
  ## License
148
200
 
@@ -70,6 +70,8 @@ async function getUserIdByEmail(email) {
70
70
  return response.id;
71
71
  }
72
72
  catch (error) {
73
+ // Log error for debugging
74
+ console.error(`Failed to get user ID for email ${email}:`, error.message || error);
73
75
  return null;
74
76
  }
75
77
  }
@@ -1,5 +1,12 @@
1
- import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
1
+ import { IExecuteFunctions, ILoadOptionsFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
2
  export declare class MicrosoftPlanner implements INodeType {
3
3
  description: INodeTypeDescription;
4
+ methods: {
5
+ loadOptions: {
6
+ getPlans(this: ILoadOptionsFunctions): Promise<any>;
7
+ getBuckets(this: ILoadOptionsFunctions): Promise<any>;
8
+ getTasks(this: ILoadOptionsFunctions): Promise<any>;
9
+ };
10
+ };
4
11
  execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
12
  }
@@ -43,6 +43,47 @@ class MicrosoftPlanner {
43
43
  ...TaskDescription_1.taskFields,
44
44
  ],
45
45
  };
46
+ this.methods = {
47
+ loadOptions: {
48
+ async getPlans() {
49
+ const plans = await GenericFunctions_1.microsoftApiRequestAllItems.call(this, 'value', 'GET', '/me/planner/plans');
50
+ return plans.map((plan) => ({
51
+ name: plan.title,
52
+ value: plan.id,
53
+ }));
54
+ },
55
+ async getBuckets() {
56
+ const planId = this.getNodeParameter('planId', 0);
57
+ if (!planId) {
58
+ return [];
59
+ }
60
+ const buckets = await GenericFunctions_1.microsoftApiRequestAllItems.call(this, 'value', 'GET', `/planner/plans/${planId}/buckets`);
61
+ return buckets.map((bucket) => ({
62
+ name: bucket.name,
63
+ value: bucket.id,
64
+ }));
65
+ },
66
+ async getTasks() {
67
+ const planId = this.getNodeParameter('planId', 0);
68
+ const bucketId = this.getNodeParameter('bucketId', 0);
69
+ let endpoint = '';
70
+ if (bucketId) {
71
+ endpoint = `/planner/buckets/${bucketId}/tasks`;
72
+ }
73
+ else if (planId) {
74
+ endpoint = `/planner/plans/${planId}/tasks`;
75
+ }
76
+ else {
77
+ return [];
78
+ }
79
+ const tasks = await GenericFunctions_1.microsoftApiRequestAllItems.call(this, 'value', 'GET', endpoint);
80
+ return tasks.map((task) => ({
81
+ name: task.title,
82
+ value: task.id,
83
+ }));
84
+ },
85
+ },
86
+ };
46
87
  }
47
88
  async execute() {
48
89
  const items = this.getInputData();
@@ -68,11 +109,13 @@ class MicrosoftPlanner {
68
109
  if (additionalFields.priority !== undefined) {
69
110
  body.priority = additionalFields.priority;
70
111
  }
71
- if (additionalFields.dueDateTime) {
72
- body.dueDateTime = (0, GenericFunctions_1.formatDateTime)(additionalFields.dueDateTime);
112
+ const formattedDueDateTime = (0, GenericFunctions_1.formatDateTime)(additionalFields.dueDateTime);
113
+ if (formattedDueDateTime) {
114
+ body.dueDateTime = formattedDueDateTime;
73
115
  }
74
- if (additionalFields.startDateTime) {
75
- body.startDateTime = (0, GenericFunctions_1.formatDateTime)(additionalFields.startDateTime);
116
+ const formattedStartDateTime = (0, GenericFunctions_1.formatDateTime)(additionalFields.startDateTime);
117
+ if (formattedStartDateTime) {
118
+ body.startDateTime = formattedStartDateTime;
76
119
  }
77
120
  if (additionalFields.percentComplete !== undefined) {
78
121
  body.percentComplete = additionalFields.percentComplete;
@@ -86,10 +129,16 @@ class MicrosoftPlanner {
86
129
  if (userId) {
87
130
  userIds.push(userId);
88
131
  }
132
+ else {
133
+ console.warn(`Could not find user ID for email: ${email}`);
134
+ }
89
135
  }
90
136
  if (userIds.length > 0) {
91
137
  body.assignments = (0, GenericFunctions_1.createAssignmentsObject)(userIds);
92
138
  }
139
+ else if (emails.length > 0) {
140
+ console.warn('No valid user IDs found for assignment. Check if User.Read.All permission is granted.');
141
+ }
93
142
  }
94
143
  const responseData = await GenericFunctions_1.microsoftApiRequest.call(this, 'POST', '/planner/tasks', body);
95
144
  // Add description if provided
@@ -162,11 +211,13 @@ class MicrosoftPlanner {
162
211
  if (updateFields.priority !== undefined) {
163
212
  body.priority = updateFields.priority;
164
213
  }
165
- if (updateFields.dueDateTime) {
166
- body.dueDateTime = (0, GenericFunctions_1.formatDateTime)(updateFields.dueDateTime);
214
+ const formattedDueDateTime = (0, GenericFunctions_1.formatDateTime)(updateFields.dueDateTime);
215
+ if (formattedDueDateTime) {
216
+ body.dueDateTime = formattedDueDateTime;
167
217
  }
168
- if (updateFields.startDateTime) {
169
- body.startDateTime = (0, GenericFunctions_1.formatDateTime)(updateFields.startDateTime);
218
+ const formattedStartDateTime = (0, GenericFunctions_1.formatDateTime)(updateFields.startDateTime);
219
+ if (formattedStartDateTime) {
220
+ body.startDateTime = formattedStartDateTime;
170
221
  }
171
222
  if (updateFields.percentComplete !== undefined) {
172
223
  body.percentComplete = updateFields.percentComplete;
@@ -183,10 +234,16 @@ class MicrosoftPlanner {
183
234
  if (userId) {
184
235
  userIds.push(userId);
185
236
  }
237
+ else {
238
+ console.warn(`Could not find user ID for email: ${email}`);
239
+ }
186
240
  }
187
241
  if (userIds.length > 0) {
188
242
  body.assignments = (0, GenericFunctions_1.createAssignmentsObject)(userIds);
189
243
  }
244
+ else if (emails.length > 0) {
245
+ console.warn('No valid user IDs found for assignment. Check if User.Read.All permission is granted.');
246
+ }
190
247
  }
191
248
  const responseData = await GenericFunctions_1.microsoftApiRequest.call(this, 'PATCH', `/planner/tasks/${taskId}`, body, {}, undefined, {
192
249
  'If-Match': eTag,
@@ -52,9 +52,12 @@ exports.taskFields = [
52
52
  // task:create
53
53
  // ----------------------------------
54
54
  {
55
- displayName: 'Plan ID',
55
+ displayName: 'Plan',
56
56
  name: 'planId',
57
- type: 'string',
57
+ type: 'options',
58
+ typeOptions: {
59
+ loadOptionsMethod: 'getPlans',
60
+ },
58
61
  required: true,
59
62
  displayOptions: {
60
63
  show: {
@@ -63,12 +66,15 @@ exports.taskFields = [
63
66
  },
64
67
  },
65
68
  default: '',
66
- description: 'The ID of the plan to which the task belongs',
69
+ description: 'The plan to which the task belongs',
67
70
  },
68
71
  {
69
- displayName: 'Bucket ID',
72
+ displayName: 'Bucket',
70
73
  name: 'bucketId',
71
- type: 'string',
74
+ type: 'options',
75
+ typeOptions: {
76
+ loadOptionsMethod: 'getBuckets',
77
+ },
72
78
  required: true,
73
79
  displayOptions: {
74
80
  show: {
@@ -77,7 +83,7 @@ exports.taskFields = [
77
83
  },
78
84
  },
79
85
  default: '',
80
- description: 'The ID of the bucket to which the task belongs',
86
+ description: 'The bucket to which the task belongs',
81
87
  },
82
88
  {
83
89
  displayName: 'Title',
@@ -176,9 +182,12 @@ exports.taskFields = [
176
182
  // task:get
177
183
  // ----------------------------------
178
184
  {
179
- displayName: 'Task ID',
185
+ displayName: 'Task',
180
186
  name: 'taskId',
181
- type: 'string',
187
+ type: 'options',
188
+ typeOptions: {
189
+ loadOptionsMethod: 'getTasks',
190
+ },
182
191
  required: true,
183
192
  displayOptions: {
184
193
  show: {
@@ -187,7 +196,7 @@ exports.taskFields = [
187
196
  },
188
197
  },
189
198
  default: '',
190
- description: 'The ID of the task',
199
+ description: 'The task to operate on',
191
200
  },
192
201
  {
193
202
  displayName: 'Additional Fields',
@@ -259,18 +268,24 @@ exports.taskFields = [
259
268
  },
260
269
  options: [
261
270
  {
262
- displayName: 'Plan ID',
271
+ displayName: 'Plan',
263
272
  name: 'planId',
264
- type: 'string',
273
+ type: 'options',
274
+ typeOptions: {
275
+ loadOptionsMethod: 'getPlans',
276
+ },
265
277
  default: '',
266
- description: 'Filter tasks by plan ID',
278
+ description: 'Filter tasks by plan',
267
279
  },
268
280
  {
269
- displayName: 'Bucket ID',
281
+ displayName: 'Bucket',
270
282
  name: 'bucketId',
271
- type: 'string',
283
+ type: 'options',
284
+ typeOptions: {
285
+ loadOptionsMethod: 'getBuckets',
286
+ },
272
287
  default: '',
273
- description: 'Filter tasks by bucket ID',
288
+ description: 'Filter tasks by bucket',
274
289
  },
275
290
  ],
276
291
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blickwerk/n8n-nodes-microsoft-planner",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "n8n node for Microsoft Planner - Create and retrieve tasks",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",