@blickwerk/n8n-nodes-microsoft-planner 1.3.9 → 1.5.5

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.
@@ -4,6 +4,9 @@ exports.MicrosoftPlanner = void 0;
4
4
  const n8n_workflow_1 = require("n8n-workflow");
5
5
  const GenericFunctions_1 = require("./GenericFunctions");
6
6
  const TaskDescription_1 = require("./TaskDescription");
7
+ const PlanDescription_1 = require("./PlanDescription");
8
+ const BucketDescription_1 = require("./BucketDescription");
9
+ const CommentDescription_1 = require("./CommentDescription");
7
10
  class MicrosoftPlanner {
8
11
  constructor() {
9
12
  this.description = {
@@ -36,11 +39,30 @@ class MicrosoftPlanner {
36
39
  name: 'Task',
37
40
  value: 'task',
38
41
  },
42
+ {
43
+ name: 'Plan',
44
+ value: 'plan',
45
+ },
46
+ {
47
+ name: 'Bucket',
48
+ value: 'bucket',
49
+ },
50
+ {
51
+ name: 'Comment',
52
+ value: 'comment',
53
+ },
39
54
  ],
40
55
  default: 'task',
41
56
  },
57
+ // Grouped by resource so they appear nicely in the n8n UI
42
58
  ...TaskDescription_1.taskOperations,
43
59
  ...TaskDescription_1.taskFields,
60
+ ...PlanDescription_1.planOperations,
61
+ ...PlanDescription_1.planFields,
62
+ ...BucketDescription_1.bucketOperations,
63
+ ...BucketDescription_1.bucketFields,
64
+ ...CommentDescription_1.commentOperations,
65
+ ...CommentDescription_1.commentFields,
44
66
  ],
45
67
  };
46
68
  this.methods = {
@@ -63,7 +85,6 @@ class MicrosoftPlanner {
63
85
  };
64
86
  }
65
87
  catch (error) {
66
- console.error('Error loading buckets:', error);
67
88
  return { results: [] };
68
89
  }
69
90
  },
@@ -106,7 +127,26 @@ class MicrosoftPlanner {
106
127
  };
107
128
  }
108
129
  catch (error) {
109
- console.error('Error loading tasks:', error);
130
+ return { results: [] };
131
+ }
132
+ },
133
+ async getGroups() {
134
+ try {
135
+ const groups = await GenericFunctions_1.microsoftApiRequestAllItems.call(this, 'value', 'GET', '/me/memberOf/$/microsoft.graph.group');
136
+ if (!groups || groups.length === 0) {
137
+ return { results: [] };
138
+ }
139
+ // Filter for Microsoft 365 Groups (Unified Groups)
140
+ // Security groups are not supported for Planner plans
141
+ const unifiedGroups = groups.filter((group) => { var _a; return (_a = group.groupTypes) === null || _a === void 0 ? void 0 : _a.includes('Unified'); });
142
+ return {
143
+ results: unifiedGroups.map((group) => ({
144
+ name: group.displayName || group.id,
145
+ value: group.id,
146
+ })),
147
+ };
148
+ }
149
+ catch (error) {
110
150
  return { results: [] };
111
151
  }
112
152
  },
@@ -157,31 +197,131 @@ class MicrosoftPlanner {
157
197
  const userIds = [];
158
198
  for (const email of emails) {
159
199
  const userId = await GenericFunctions_1.getUserIdByEmail.call(this, email);
160
- if (userId) {
161
- userIds.push(userId);
162
- }
163
- else {
164
- console.warn(`Could not find user ID for email: ${email}`);
165
- }
200
+ userIds.push(userId);
166
201
  }
167
202
  if (userIds.length > 0) {
168
203
  body.assignments = (0, GenericFunctions_1.createAssignmentsObject)(userIds);
169
204
  }
170
- else if (emails.length > 0) {
171
- console.warn('No valid user IDs found for assignment. Check if User.Read.All permission is granted.');
172
- }
173
205
  }
174
206
  const responseData = await GenericFunctions_1.microsoftApiRequest.call(this, 'POST', '/planner/tasks', body);
175
- // Add description if provided
176
- if (additionalFields.description) {
207
+ // Add description, references, or checklist if provided
208
+ if (additionalFields.description ||
209
+ additionalFields.attachmentsUi ||
210
+ additionalFields.checklistUi) {
177
211
  const details = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', `/planner/tasks/${responseData.id}/details`);
178
212
  const eTag = (0, GenericFunctions_1.cleanETag)(details['@odata.etag']);
179
- await GenericFunctions_1.microsoftApiRequest.call(this, 'PATCH', `/planner/tasks/${responseData.id}/details`, {
180
- description: additionalFields.description,
181
- }, {}, undefined, {
213
+ const detailsBody = {};
214
+ if (additionalFields.description) {
215
+ detailsBody.description = additionalFields.description;
216
+ responseData.description = additionalFields.description;
217
+ }
218
+ // Handle Attachments (Manual or JSON)
219
+ const referencesBody = {};
220
+ const attachmentsUi = additionalFields.attachmentsUi;
221
+ if (attachmentsUi && attachmentsUi.attachments) {
222
+ const attachments = attachmentsUi.attachments;
223
+ const mode = attachments.mode;
224
+ if (mode === 'manual' && attachments.items) {
225
+ const items = attachments.items;
226
+ if (items && items.reference) {
227
+ const referenceList = items.reference;
228
+ for (const reference of referenceList) {
229
+ const url = reference.url;
230
+ const alias = reference.alias;
231
+ const type = reference.type;
232
+ const encodedUrl = (0, GenericFunctions_1.encodeReferenceKey)(url);
233
+ referencesBody[encodedUrl] = {
234
+ '@odata.type': '#microsoft.graph.plannerExternalReference',
235
+ alias,
236
+ type,
237
+ };
238
+ }
239
+ }
240
+ }
241
+ else if (mode === 'json' && attachments.json) {
242
+ const referencesJson = attachments.json;
243
+ let referenceList;
244
+ try {
245
+ referenceList = typeof referencesJson === 'string'
246
+ ? JSON.parse(referencesJson)
247
+ : referencesJson;
248
+ if (!Array.isArray(referenceList))
249
+ throw new Error('Not an array');
250
+ }
251
+ catch (error) {
252
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid JSON in Attachments (JSON) field. It must be an array of objects.', { itemIndex: i });
253
+ }
254
+ for (const reference of referenceList) {
255
+ const url = reference.url;
256
+ if (!url)
257
+ continue;
258
+ const encodedUrl = (0, GenericFunctions_1.encodeReferenceKey)(url);
259
+ referencesBody[encodedUrl] = {
260
+ '@odata.type': '#microsoft.graph.plannerExternalReference',
261
+ alias: reference.alias || '',
262
+ type: reference.type || 'Other',
263
+ };
264
+ }
265
+ }
266
+ }
267
+ if (Object.keys(referencesBody).length > 0) {
268
+ detailsBody.references = referencesBody;
269
+ }
270
+ // Handle Checklist (Manual or JSON)
271
+ const checklistBody = {};
272
+ const checklistUi = additionalFields.checklistUi;
273
+ if (checklistUi && checklistUi.checklist) {
274
+ const checklist = checklistUi.checklist;
275
+ const mode = checklist.mode;
276
+ if (mode === 'manual' && checklist.items) {
277
+ const items = checklist.items;
278
+ if (items && items.item) {
279
+ const checklistList = items.item;
280
+ for (const item of checklistList) {
281
+ const id = item.id || (0, GenericFunctions_1.generateGuid)();
282
+ checklistBody[id] = {
283
+ '@odata.type': '#microsoft.graph.plannerChecklistItem',
284
+ title: item.title,
285
+ isChecked: item.isChecked,
286
+ };
287
+ }
288
+ }
289
+ }
290
+ else if (mode === 'json' && checklist.json) {
291
+ const checklistJson = checklist.json;
292
+ let checklistList;
293
+ try {
294
+ checklistList = typeof checklistJson === 'string'
295
+ ? JSON.parse(checklistJson)
296
+ : checklistJson;
297
+ if (!Array.isArray(checklistList))
298
+ throw new Error('Not an array');
299
+ }
300
+ catch (error) {
301
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid JSON in Checklist (JSON) field. It must be an array of objects.', { itemIndex: i });
302
+ }
303
+ for (const item of checklistList) {
304
+ const title = item.title;
305
+ if (!title)
306
+ continue;
307
+ const id = item.id || (0, GenericFunctions_1.generateGuid)();
308
+ checklistBody[id] = {
309
+ '@odata.type': '#microsoft.graph.plannerChecklistItem',
310
+ title,
311
+ isChecked: !!item.isChecked,
312
+ };
313
+ }
314
+ }
315
+ }
316
+ if (Object.keys(checklistBody).length > 0) {
317
+ detailsBody.checklist = checklistBody;
318
+ }
319
+ await GenericFunctions_1.microsoftApiRequest.call(this, 'PATCH', `/planner/tasks/${responseData.id}/details`, detailsBody, {}, undefined, {
182
320
  'If-Match': eTag,
183
321
  });
184
- responseData.description = additionalFields.description;
322
+ // Fetch latest details to return in response
323
+ const finalDetails = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', `/planner/tasks/${responseData.id}/details`);
324
+ responseData.details = finalDetails;
185
325
  }
186
326
  returnData.push(responseData);
187
327
  }
@@ -202,8 +342,8 @@ class MicrosoftPlanner {
202
342
  // ----------------------------------
203
343
  // task:getAll
204
344
  // ----------------------------------
345
+ // Always return all tasks for the given scope; Graph does not honor $top/$limit
205
346
  if (operation === 'getAll') {
206
- const returnAll = this.getNodeParameter('returnAll', i);
207
347
  const filterBy = this.getNodeParameter('filterBy', i);
208
348
  const planId = this.getNodeParameter('planId', i);
209
349
  let endpoint = '';
@@ -220,17 +360,13 @@ class MicrosoftPlanner {
220
360
  else {
221
361
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'You must specify either a Plan ID or Bucket ID to retrieve tasks', { itemIndex: i });
222
362
  }
223
- if (returnAll) {
224
- const responseData = await GenericFunctions_1.microsoftApiRequestAllItems.call(this, 'value', 'GET', endpoint);
225
- returnData.push(...responseData);
226
- }
227
- else {
228
- const limit = this.getNodeParameter('limit', i);
229
- const responseData = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', endpoint, {}, {
230
- $top: limit,
231
- });
232
- returnData.push(...responseData.value);
363
+ const additionalFields = this.getNodeParameter('additionalFields', i);
364
+ const qs = {};
365
+ if (additionalFields === null || additionalFields === void 0 ? void 0 : additionalFields.select) {
366
+ qs.$select = additionalFields.select;
233
367
  }
368
+ const responseData = await GenericFunctions_1.microsoftApiRequestAllItems.call(this, 'value', 'GET', endpoint, {}, qs);
369
+ returnData.push(...responseData);
234
370
  }
235
371
  // ----------------------------------
236
372
  // task:update
@@ -269,19 +405,11 @@ class MicrosoftPlanner {
269
405
  const userIds = [];
270
406
  for (const email of emails) {
271
407
  const userId = await GenericFunctions_1.getUserIdByEmail.call(this, email);
272
- if (userId) {
273
- userIds.push(userId);
274
- }
275
- else {
276
- console.warn(`Could not find user ID for email: ${email}`);
277
- }
408
+ userIds.push(userId);
278
409
  }
279
410
  if (userIds.length > 0) {
280
411
  body.assignments = (0, GenericFunctions_1.createAssignmentsObject)(userIds);
281
412
  }
282
- else if (emails.length > 0) {
283
- console.warn('No valid user IDs found for assignment. Check if User.Read.All permission is granted.');
284
- }
285
413
  }
286
414
  // Only send PATCH request if there are fields to update (excluding description)
287
415
  if (Object.keys(body).length > 0) {
@@ -289,18 +417,148 @@ class MicrosoftPlanner {
289
417
  'If-Match': eTag,
290
418
  });
291
419
  }
292
- // Update description if provided
293
- if (updateFields.description) {
420
+ // Update description, references, or checklist if provided
421
+ if (updateFields.description ||
422
+ updateFields.attachmentsUi ||
423
+ updateFields.checklistUi) {
294
424
  const details = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', `/planner/tasks/${taskId}/details`);
295
425
  const detailsETag = (0, GenericFunctions_1.cleanETag)(details['@odata.etag']);
296
- await GenericFunctions_1.microsoftApiRequest.call(this, 'PATCH', `/planner/tasks/${taskId}/details`, {
297
- description: updateFields.description,
298
- }, {}, undefined, {
426
+ const detailsBody = {};
427
+ if (updateFields.description) {
428
+ detailsBody.description = updateFields.description;
429
+ }
430
+ const referencesBody = {};
431
+ // 1. If operationMode is 'replace', set all existing references to null
432
+ const attachmentsUiForReplace = updateFields.attachmentsUi;
433
+ const attachmentsForReplace = attachmentsUiForReplace === null || attachmentsUiForReplace === void 0 ? void 0 : attachmentsUiForReplace.attachments;
434
+ const attachmentsOperationMode = attachmentsForReplace === null || attachmentsForReplace === void 0 ? void 0 : attachmentsForReplace.operationMode;
435
+ if (attachmentsOperationMode === 'replace' && details.references) {
436
+ for (const key of Object.keys(details.references)) {
437
+ referencesBody[key] = null;
438
+ }
439
+ }
440
+ // 2. Add new/updated references (Manual or JSON)
441
+ const attachmentsUi = updateFields.attachmentsUi;
442
+ if (attachmentsUi && attachmentsUi.attachments) {
443
+ const attachments = attachmentsUi.attachments;
444
+ const mode = attachments.mode;
445
+ if (mode === 'manual' && attachments.items) {
446
+ const items = attachments.items;
447
+ if (items && items.reference) {
448
+ const referenceList = items.reference;
449
+ for (const reference of referenceList) {
450
+ const url = reference.url;
451
+ const alias = reference.alias;
452
+ const type = reference.type;
453
+ const encodedUrl = (0, GenericFunctions_1.encodeReferenceKey)(url);
454
+ referencesBody[encodedUrl] = {
455
+ '@odata.type': '#microsoft.graph.plannerExternalReference',
456
+ alias,
457
+ type,
458
+ };
459
+ }
460
+ }
461
+ }
462
+ else if (mode === 'json' && attachments.json) {
463
+ const referencesJson = attachments.json;
464
+ let referenceList;
465
+ try {
466
+ referenceList = typeof referencesJson === 'string'
467
+ ? JSON.parse(referencesJson)
468
+ : referencesJson;
469
+ if (!Array.isArray(referenceList))
470
+ throw new Error('Not an array');
471
+ }
472
+ catch (error) {
473
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid JSON in Attachments (JSON) field. It must be an array of objects.', { itemIndex: i });
474
+ }
475
+ for (const reference of referenceList) {
476
+ const url = reference.url;
477
+ if (!url)
478
+ continue;
479
+ const encodedUrl = (0, GenericFunctions_1.encodeReferenceKey)(url);
480
+ referencesBody[encodedUrl] = {
481
+ '@odata.type': '#microsoft.graph.plannerExternalReference',
482
+ alias: reference.alias || '',
483
+ type: reference.type || 'Other',
484
+ };
485
+ }
486
+ }
487
+ }
488
+ if (Object.keys(referencesBody).length > 0) {
489
+ detailsBody.references = referencesBody;
490
+ }
491
+ const checklistBody = {};
492
+ // 1. If operationMode is 'replace', set all existing checklist items to null
493
+ const checklistUiForReplace = updateFields.checklistUi;
494
+ const checklistForReplace = checklistUiForReplace === null || checklistUiForReplace === void 0 ? void 0 : checklistUiForReplace.checklist;
495
+ const checklistOperationMode = checklistForReplace === null || checklistForReplace === void 0 ? void 0 : checklistForReplace.operationMode;
496
+ if (checklistOperationMode === 'replace' && details.checklist) {
497
+ for (const key of Object.keys(details.checklist)) {
498
+ checklistBody[key] = null;
499
+ }
500
+ }
501
+ // 2. Add new/updated checklist items (Manual or JSON)
502
+ const checklistUi = updateFields.checklistUi;
503
+ if (checklistUi && checklistUi.checklist) {
504
+ const checklist = checklistUi.checklist;
505
+ const mode = checklist.mode;
506
+ if (mode === 'manual' && checklist.items) {
507
+ const items = checklist.items;
508
+ if (items && items.item) {
509
+ const checklistList = items.item;
510
+ for (const item of checklistList) {
511
+ const id = item.id || (0, GenericFunctions_1.generateGuid)();
512
+ checklistBody[id] = {
513
+ '@odata.type': '#microsoft.graph.plannerChecklistItem',
514
+ title: item.title,
515
+ isChecked: item.isChecked,
516
+ };
517
+ }
518
+ }
519
+ }
520
+ else if (mode === 'json' && checklist.json) {
521
+ const checklistJson = checklist.json;
522
+ let checklistList;
523
+ try {
524
+ checklistList = typeof checklistJson === 'string'
525
+ ? JSON.parse(checklistJson)
526
+ : checklistJson;
527
+ if (!Array.isArray(checklistList))
528
+ throw new Error('Not an array');
529
+ }
530
+ catch (error) {
531
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid JSON in Checklist (JSON) field. It must be an array of objects.', { itemIndex: i });
532
+ }
533
+ for (const item of checklistList) {
534
+ const title = item.title;
535
+ if (!title)
536
+ continue;
537
+ const id = item.id || (0, GenericFunctions_1.generateGuid)();
538
+ checklistBody[id] = {
539
+ '@odata.type': '#microsoft.graph.plannerChecklistItem',
540
+ title,
541
+ isChecked: !!item.isChecked,
542
+ };
543
+ }
544
+ }
545
+ }
546
+ if (Object.keys(checklistBody).length > 0) {
547
+ detailsBody.checklist = checklistBody;
548
+ }
549
+ await GenericFunctions_1.microsoftApiRequest.call(this, 'PATCH', `/planner/tasks/${taskId}/details`, detailsBody, {}, undefined, {
299
550
  'If-Match': detailsETag,
300
551
  });
301
552
  }
302
553
  // Fetch updated task to return complete data
303
554
  const responseData = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', `/planner/tasks/${taskId}`);
555
+ // If description (task details) was updated, also return latest details
556
+ if (Object.prototype.hasOwnProperty.call(updateFields, 'description') ||
557
+ Object.prototype.hasOwnProperty.call(updateFields, 'attachmentsUi') ||
558
+ Object.prototype.hasOwnProperty.call(updateFields, 'checklistUi')) {
559
+ const details = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', `/planner/tasks/${taskId}/details`);
560
+ responseData.details = details;
561
+ }
304
562
  returnData.push(responseData);
305
563
  }
306
564
  // ----------------------------------
@@ -345,6 +603,313 @@ class MicrosoftPlanner {
345
603
  });
346
604
  }
347
605
  }
606
+ // ----------------------------------
607
+ // plan resource
608
+ // ----------------------------------
609
+ if (resource === 'plan') {
610
+ // plan:create -> POST /planner/plans
611
+ if (operation === 'create') {
612
+ const ownerParam = this.getNodeParameter('owner', i);
613
+ const owner = typeof ownerParam === 'string'
614
+ ? ownerParam
615
+ : ownerParam.value;
616
+ const title = this.getNodeParameter('title', i);
617
+ const body = { owner, title };
618
+ const responseData = await GenericFunctions_1.microsoftApiRequest.call(this, 'POST', '/planner/plans', body);
619
+ returnData.push(responseData);
620
+ }
621
+ // plan:get -> GET /planner/plans/{planId}
622
+ if (operation === 'get') {
623
+ const planId = this.getNodeParameter('planId', i);
624
+ const additionalFields = this.getNodeParameter('additionalFields', i, {});
625
+ const responseData = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', `/planner/plans/${planId}`);
626
+ if (additionalFields.includeDetails) {
627
+ const details = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', `/planner/plans/${planId}/details`);
628
+ responseData.details = details;
629
+ }
630
+ returnData.push(responseData);
631
+ }
632
+ // plan:getAll -> list plans scoped to current user or a specific group
633
+ // Always return all plans; Graph does not honor $top/$limit for Planner.
634
+ if (operation === 'getAll') {
635
+ const scope = this.getNodeParameter('scope', i);
636
+ let endpoint = '';
637
+ if (scope === 'my') {
638
+ // List plans for the current user
639
+ endpoint = '/me/planner/plans';
640
+ }
641
+ else if (scope === 'group') {
642
+ // List plans owned by a specific Microsoft 365 group
643
+ const groupId = this.getNodeParameter('groupId', i);
644
+ endpoint = `/groups/${groupId}/planner/plans`;
645
+ }
646
+ else {
647
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid scope for plan getAll operation', {
648
+ itemIndex: i,
649
+ });
650
+ }
651
+ const additionalFields = this.getNodeParameter('additionalFields', i, {});
652
+ const qs = {};
653
+ if (additionalFields === null || additionalFields === void 0 ? void 0 : additionalFields.select) {
654
+ qs.$select = additionalFields.select;
655
+ }
656
+ const responseData = await GenericFunctions_1.microsoftApiRequestAllItems.call(this, 'value', 'GET', endpoint, {}, qs);
657
+ returnData.push(...responseData);
658
+ }
659
+ // plan:update -> PATCH /planner/plans/{planId} with ETag, and optionally /details
660
+ if (operation === 'update') {
661
+ const planId = this.getNodeParameter('planId', i);
662
+ const updateFields = this.getNodeParameter('updateFields', i);
663
+ // Get current plan to obtain ETag
664
+ const currentPlan = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', `/planner/plans/${planId}`);
665
+ const eTag = (0, GenericFunctions_1.cleanETag)(currentPlan['@odata.etag']);
666
+ const body = {};
667
+ if (updateFields.title) {
668
+ body.title = updateFields.title;
669
+ }
670
+ if (Object.keys(body).length > 0) {
671
+ await GenericFunctions_1.microsoftApiRequest.call(this, 'PATCH', `/planner/plans/${planId}`, body, {}, undefined, {
672
+ 'If-Match': eTag,
673
+ });
674
+ }
675
+ // Optionally update plan details if provided
676
+ const detailsBody = {};
677
+ if (updateFields.categoryDescriptions) {
678
+ const categories = updateFields.categoryDescriptions;
679
+ const cleanedCategories = {};
680
+ for (const key of Object.keys(categories)) {
681
+ const value = categories[key];
682
+ // Empty string in the UI means "unset" → send null to Graph
683
+ if (value === '') {
684
+ cleanedCategories[key] = null;
685
+ }
686
+ else if (value !== undefined) {
687
+ cleanedCategories[key] = value;
688
+ }
689
+ }
690
+ if (Object.keys(cleanedCategories).length > 0) {
691
+ detailsBody.categoryDescriptions = cleanedCategories;
692
+ }
693
+ }
694
+ if (updateFields.sharedWithJson) {
695
+ let sharedWithParsed;
696
+ try {
697
+ sharedWithParsed = JSON.parse(updateFields.sharedWithJson);
698
+ }
699
+ catch (error) {
700
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid JSON in Shared With (plannerUserIds JSON) field', {
701
+ itemIndex: i,
702
+ });
703
+ }
704
+ if (Object.keys(sharedWithParsed).length > 0) {
705
+ detailsBody.sharedWith = sharedWithParsed;
706
+ }
707
+ }
708
+ if (Object.keys(detailsBody).length > 0) {
709
+ const currentDetails = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', `/planner/plans/${planId}/details`);
710
+ const detailsETag = (0, GenericFunctions_1.cleanETag)(currentDetails['@odata.etag']);
711
+ await GenericFunctions_1.microsoftApiRequest.call(this, 'PATCH', `/planner/plans/${planId}/details`, detailsBody, {}, undefined, {
712
+ 'If-Match': detailsETag,
713
+ });
714
+ }
715
+ const responseData = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', `/planner/plans/${planId}`);
716
+ // If plan details were updated, also include latest details in the response
717
+ if (Object.keys(detailsBody).length > 0) {
718
+ const details = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', `/planner/plans/${planId}/details`);
719
+ responseData.details = details;
720
+ }
721
+ returnData.push(responseData);
722
+ }
723
+ // plan:delete -> DELETE /planner/plans/{planId} with ETag
724
+ if (operation === 'delete') {
725
+ const planId = this.getNodeParameter('planId', i);
726
+ const currentPlan = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', `/planner/plans/${planId}`);
727
+ const eTag = (0, GenericFunctions_1.cleanETag)(currentPlan['@odata.etag']);
728
+ await GenericFunctions_1.microsoftApiRequest.call(this, 'DELETE', `/planner/plans/${planId}`, {}, {}, undefined, {
729
+ 'If-Match': eTag,
730
+ });
731
+ returnData.push({ success: true, planId });
732
+ }
733
+ }
734
+ // ----------------------------------
735
+ // bucket resource
736
+ // ----------------------------------
737
+ if (resource === 'bucket') {
738
+ // bucket:create -> POST /planner/buckets
739
+ if (operation === 'create') {
740
+ const planId = this.getNodeParameter('planId', i);
741
+ const name = this.getNodeParameter('name', i);
742
+ const upsert = this.getNodeParameter('upsert', i, false);
743
+ if (upsert) {
744
+ // Check if bucket already exists
745
+ const buckets = await GenericFunctions_1.microsoftApiRequestAllItems.call(this, 'value', 'GET', `/planner/plans/${planId}/buckets`);
746
+ const existingBucket = buckets.find((b) => b.name === name);
747
+ if (existingBucket) {
748
+ returnData.push(existingBucket);
749
+ continue;
750
+ }
751
+ }
752
+ const body = {
753
+ name,
754
+ planId,
755
+ };
756
+ const responseData = await GenericFunctions_1.microsoftApiRequest.call(this, 'POST', '/planner/buckets', body);
757
+ returnData.push(responseData);
758
+ }
759
+ // bucket:get -> GET /planner/buckets/{bucketId}
760
+ if (operation === 'get') {
761
+ const bucketId = this.getNodeParameter('bucketId', i);
762
+ const responseData = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', `/planner/buckets/${bucketId}`);
763
+ returnData.push(responseData);
764
+ }
765
+ // bucket:getAll -> GET /planner/plans/{planId}/buckets
766
+ // Always return all buckets for the plan; Graph does not honor $top/$limit for Planner.
767
+ if (operation === 'getAll') {
768
+ const planId = this.getNodeParameter('planId', i);
769
+ const endpoint = `/planner/plans/${planId}/buckets`;
770
+ const additionalFields = this.getNodeParameter('additionalFields', i);
771
+ const qs = {};
772
+ if (additionalFields === null || additionalFields === void 0 ? void 0 : additionalFields.select) {
773
+ qs.$select = additionalFields.select;
774
+ }
775
+ const responseData = await GenericFunctions_1.microsoftApiRequestAllItems.call(this, 'value', 'GET', endpoint, {}, qs);
776
+ returnData.push(...responseData);
777
+ }
778
+ // bucket:update -> PATCH /planner/buckets/{bucketId} with ETag
779
+ if (operation === 'update') {
780
+ const bucketId = this.getNodeParameter('bucketId', i);
781
+ const updateFields = this.getNodeParameter('updateFields', i);
782
+ const currentBucket = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', `/planner/buckets/${bucketId}`);
783
+ const eTag = (0, GenericFunctions_1.cleanETag)(currentBucket['@odata.etag']);
784
+ const body = {};
785
+ if (updateFields.name) {
786
+ body.name = updateFields.name;
787
+ }
788
+ if (updateFields.orderHint) {
789
+ body.orderHint = updateFields.orderHint;
790
+ }
791
+ if (Object.keys(body).length > 0) {
792
+ await GenericFunctions_1.microsoftApiRequest.call(this, 'PATCH', `/planner/buckets/${bucketId}`, body, {}, undefined, {
793
+ 'If-Match': eTag,
794
+ });
795
+ }
796
+ const responseData = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', `/planner/buckets/${bucketId}`);
797
+ returnData.push(responseData);
798
+ }
799
+ // bucket:delete -> DELETE /planner/buckets/{bucketId} with ETag
800
+ if (operation === 'delete') {
801
+ const bucketId = this.getNodeParameter('bucketId', i);
802
+ const currentBucket = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', `/planner/buckets/${bucketId}`);
803
+ const eTag = (0, GenericFunctions_1.cleanETag)(currentBucket['@odata.etag']);
804
+ await GenericFunctions_1.microsoftApiRequest.call(this, 'DELETE', `/planner/buckets/${bucketId}`, {}, {}, undefined, {
805
+ 'If-Match': eTag,
806
+ });
807
+ returnData.push({ success: true, bucketId });
808
+ }
809
+ }
810
+ // ----------------------------------
811
+ // comment resource
812
+ // ----------------------------------
813
+ if (resource === 'comment') {
814
+ // ----------------------------------
815
+ // comment:create
816
+ // ----------------------------------
817
+ if (operation === 'create') {
818
+ const taskIdParam = this.getNodeParameter('taskId', i);
819
+ const taskId = typeof taskIdParam === 'string' ? taskIdParam : taskIdParam.value;
820
+ const content = this.getNodeParameter('content', i);
821
+ const additionalFields = this.getNodeParameter('additionalFields', i);
822
+ const contentType = additionalFields.contentType || 'text';
823
+ // Get the task to retrieve planId and conversationThreadId
824
+ const task = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', `/planner/tasks/${taskId}`);
825
+ // Get the plan to retrieve the group ID
826
+ const plan = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', `/planner/plans/${task.planId}`);
827
+ const groupId = plan.container.containerId;
828
+ const commentBody = (0, GenericFunctions_1.formatCommentContent)(content, contentType);
829
+ let conversationId;
830
+ let threadId;
831
+ if (!task.conversationThreadId) {
832
+ // Create a new conversation thread
833
+ const conversation = await GenericFunctions_1.microsoftApiRequest.call(this, 'POST', `/groups/${groupId}/conversations`, {
834
+ topic: `Comments on task "${task.title}"`,
835
+ threads: [
836
+ {
837
+ posts: [commentBody],
838
+ },
839
+ ],
840
+ });
841
+ conversationId = conversation.id;
842
+ threadId = conversation.threads[0].id;
843
+ // Update the task with the new conversationThreadId
844
+ const taskETag = (0, GenericFunctions_1.cleanETag)(task['@odata.etag']);
845
+ await GenericFunctions_1.microsoftApiRequest.call(this, 'PATCH', `/planner/tasks/${taskId}`, {
846
+ conversationThreadId: threadId,
847
+ }, {}, undefined, {
848
+ 'If-Match': taskETag,
849
+ });
850
+ }
851
+ else {
852
+ // Add a reply to the existing conversation thread
853
+ threadId = task.conversationThreadId;
854
+ // Get the conversation ID from the thread
855
+ const thread = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', `/groups/${groupId}/threads/${threadId}`);
856
+ conversationId = thread.conversationId || thread.id;
857
+ // Add reply to the thread
858
+ await GenericFunctions_1.microsoftApiRequest.call(this, 'POST', `/groups/${groupId}/conversations/${conversationId}/threads/${threadId}/reply`, {
859
+ post: commentBody,
860
+ });
861
+ }
862
+ returnData.push({
863
+ success: true,
864
+ taskId,
865
+ conversationId,
866
+ threadId,
867
+ content,
868
+ });
869
+ }
870
+ // ----------------------------------
871
+ // comment:getAll
872
+ // ----------------------------------
873
+ if (operation === 'getAll') {
874
+ const taskIdParam = this.getNodeParameter('taskId', i);
875
+ const taskId = typeof taskIdParam === 'string' ? taskIdParam : taskIdParam.value;
876
+ // Get the task to retrieve conversationThreadId
877
+ const task = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', `/planner/tasks/${taskId}`);
878
+ if (!task.conversationThreadId) {
879
+ // No comments on this task
880
+ returnData.push({
881
+ taskId,
882
+ comments: [],
883
+ commentCount: 0,
884
+ });
885
+ }
886
+ else {
887
+ // Get the plan to retrieve the group ID
888
+ const plan = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', `/planner/plans/${task.planId}`);
889
+ const groupId = plan.container.containerId;
890
+ const threadId = task.conversationThreadId;
891
+ const additionalFields = this.getNodeParameter('additionalFields', i);
892
+ const qs = {};
893
+ if (additionalFields === null || additionalFields === void 0 ? void 0 : additionalFields.select) {
894
+ qs.$select = additionalFields.select;
895
+ }
896
+ // Get all posts from the conversation thread
897
+ const posts = await GenericFunctions_1.microsoftApiRequest.call(this, 'GET', `/groups/${groupId}/threads/${threadId}/posts`, {}, qs);
898
+ const comments = posts.value.map((post) => ({
899
+ id: post.id,
900
+ content: post.body,
901
+ from: post.from,
902
+ createdDateTime: post.createdDateTime,
903
+ lastModifiedDateTime: post.lastModifiedDateTime,
904
+ }));
905
+ returnData.push({
906
+ taskId,
907
+ comments,
908
+ commentCount: comments.length,
909
+ });
910
+ }
911
+ }
912
+ }
348
913
  }
349
914
  catch (error) {
350
915
  if (this.continueOnFail()) {