n8n-nodes-orboto 1.0.1

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.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +107 -0
  3. package/dist/credentials/orbotoApi.credentials.d.ts +34 -0
  4. package/dist/credentials/orbotoApi.credentials.js +76 -0
  5. package/dist/credentials/orbotoApi.credentials.js.map +1 -0
  6. package/dist/credentials/orbotoOAuth2Api.credentials.d.ts +10 -0
  7. package/dist/credentials/orbotoOAuth2Api.credentials.js +73 -0
  8. package/dist/credentials/orbotoOAuth2Api.credentials.js.map +1 -0
  9. package/dist/nodes/index.d.ts +2 -0
  10. package/dist/nodes/index.js +19 -0
  11. package/dist/nodes/index.js.map +1 -0
  12. package/dist/nodes/orboto/orboto.node.d.ts +18 -0
  13. package/dist/nodes/orboto/orboto.node.js +192 -0
  14. package/dist/nodes/orboto/orboto.node.js.map +1 -0
  15. package/dist/nodes/orboto/orboto.svg +4 -0
  16. package/dist/nodes/orboto/orbotoTrigger.node.d.ts +19 -0
  17. package/dist/nodes/orboto/orbotoTrigger.node.js +243 -0
  18. package/dist/nodes/orboto/orbotoTrigger.node.js.map +1 -0
  19. package/dist/nodes/orboto/orbotoTrigger.svg +4 -0
  20. package/dist/nodes/orboto/resources/agent/agent.resource.d.ts +7 -0
  21. package/dist/nodes/orboto/resources/agent/agent.resource.js +183 -0
  22. package/dist/nodes/orboto/resources/agent/agent.resource.js.map +1 -0
  23. package/dist/nodes/orboto/resources/doc/doc.resource.d.ts +3 -0
  24. package/dist/nodes/orboto/resources/doc/doc.resource.js +206 -0
  25. package/dist/nodes/orboto/resources/doc/doc.resource.js.map +1 -0
  26. package/dist/nodes/orboto/resources/milestone/milestone.resource.d.ts +3 -0
  27. package/dist/nodes/orboto/resources/milestone/milestone.resource.js +197 -0
  28. package/dist/nodes/orboto/resources/milestone/milestone.resource.js.map +1 -0
  29. package/dist/nodes/orboto/resources/misc/misc.resources.d.ts +8 -0
  30. package/dist/nodes/orboto/resources/misc/misc.resources.js +232 -0
  31. package/dist/nodes/orboto/resources/misc/misc.resources.js.map +1 -0
  32. package/dist/nodes/orboto/resources/project/project.resource.d.ts +3 -0
  33. package/dist/nodes/orboto/resources/project/project.resource.js +144 -0
  34. package/dist/nodes/orboto/resources/project/project.resource.js.map +1 -0
  35. package/dist/nodes/orboto/resources/ticket/Ticket.resource.d.ts +11 -0
  36. package/dist/nodes/orboto/resources/ticket/Ticket.resource.js +1072 -0
  37. package/dist/nodes/orboto/resources/ticket/Ticket.resource.js.map +1 -0
  38. package/dist/nodes/orboto/resources/time/time.resource.d.ts +3 -0
  39. package/dist/nodes/orboto/resources/time/time.resource.js +140 -0
  40. package/dist/nodes/orboto/resources/time/time.resource.js.map +1 -0
  41. package/dist/nodes/orboto/shared/ApiClient.d.ts +97 -0
  42. package/dist/nodes/orboto/shared/ApiClient.js +261 -0
  43. package/dist/nodes/orboto/shared/ApiClient.js.map +1 -0
  44. package/dist/nodes/orboto/shared/GenericFunctions.d.ts +47 -0
  45. package/dist/nodes/orboto/shared/GenericFunctions.js +186 -0
  46. package/dist/nodes/orboto/shared/GenericFunctions.js.map +1 -0
  47. package/dist/nodes/orboto/shared/Types.d.ts +54 -0
  48. package/dist/nodes/orboto/shared/Types.js +10 -0
  49. package/dist/nodes/orboto/shared/Types.js.map +1 -0
  50. package/dist/nodes/orboto/shared/loadOptions.d.ts +17 -0
  51. package/dist/nodes/orboto/shared/loadOptions.js +85 -0
  52. package/dist/nodes/orboto/shared/loadOptions.js.map +1 -0
  53. package/package.json +68 -0
@@ -0,0 +1,1072 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ticketOperations = void 0;
4
+ exports.buildUpdateBody = buildUpdateBody;
5
+ exports.executeTicketOperation = executeTicketOperation;
6
+ const n8n_workflow_1 = require("n8n-workflow");
7
+ const ApiClient_1 = require("../../shared/ApiClient");
8
+ const GenericFunctions_1 = require("../../shared/GenericFunctions");
9
+ const SHOW_TICKET = { resource: ['ticket'] };
10
+ /** Operations that need a project parameter (everything except OQL query). */
11
+ const PROJECT_OPERATIONS = [
12
+ 'assign',
13
+ 'addAttachment',
14
+ 'addChecklistItem',
15
+ 'addDependency',
16
+ 'addLabel',
17
+ 'bulkUpdate',
18
+ 'checkChecklistItem',
19
+ 'clearMilestone',
20
+ 'clearVersion',
21
+ 'comment',
22
+ 'create',
23
+ 'delete',
24
+ 'get',
25
+ 'getAll',
26
+ 'getAttachments',
27
+ 'getDependencies',
28
+ 'logTime',
29
+ 'move',
30
+ 'removeDependency',
31
+ 'removeLabel',
32
+ 'setMilestone',
33
+ 'setVersion',
34
+ 'setStatus',
35
+ 'unassign',
36
+ 'uncheckChecklistItem',
37
+ 'update',
38
+ ];
39
+ /** Operations that reference one ticket. */
40
+ const TICKET_OPERATIONS = PROJECT_OPERATIONS.filter((o) => o !== 'getAll' && o !== 'create' && o !== 'bulkUpdate');
41
+ /** Shared 'Project' parameter - the scope for almost every ticket operation. */
42
+ const projectParam = {
43
+ displayName: 'Project Name or ID',
44
+ name: 'project',
45
+ type: 'options',
46
+ typeOptions: {
47
+ loadOptionsMethod: 'getProjects',
48
+ },
49
+ required: true,
50
+ displayOptions: {
51
+ show: { ...SHOW_TICKET, operation: PROJECT_OPERATIONS },
52
+ },
53
+ default: '',
54
+ description: 'The orboto project the ticket belongs to. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
55
+ };
56
+ /** Shared 'Ticket' parameter - accepts a UUID, a key like ONN-42 or a bare number. */
57
+ const ticketParam = {
58
+ displayName: 'Ticket',
59
+ name: 'ticket',
60
+ type: 'string',
61
+ required: true,
62
+ displayOptions: {
63
+ show: { ...SHOW_TICKET, operation: TICKET_OPERATIONS },
64
+ },
65
+ default: '',
66
+ description: 'Ticket key (ONN-42), number (42) or ID',
67
+ };
68
+ exports.ticketOperations = [
69
+ {
70
+ displayName: 'Operation',
71
+ name: 'operation',
72
+ type: 'options',
73
+ noDataExpression: true,
74
+ displayOptions: { show: SHOW_TICKET },
75
+ required: true,
76
+ default: 'create',
77
+ options: [
78
+ { name: 'Add Attachment', value: 'addAttachment' },
79
+ { name: 'Add Checklist Item', value: 'addChecklistItem' },
80
+ { name: 'Add Dependency', value: 'addDependency' },
81
+ { name: 'Add Label', value: 'addLabel' },
82
+ { name: 'Assign', value: 'assign' },
83
+ { name: 'Bulk Update', value: 'bulkUpdate' },
84
+ { name: 'Check Checklist Item', value: 'checkChecklistItem' },
85
+ { name: 'Clear Milestone', value: 'clearMilestone' },
86
+ { name: 'Clear Version', value: 'clearVersion' },
87
+ { name: 'Comment', value: 'comment' },
88
+ { name: 'Create', value: 'create' },
89
+ { name: 'Delete', value: 'delete' },
90
+ { name: 'Get', value: 'get' },
91
+ { name: 'Get Many', value: 'getAll' },
92
+ { name: 'Get Attachments', value: 'getAttachments' },
93
+ { name: 'Get Dependencies', value: 'getDependencies' },
94
+ { name: 'Log Time', value: 'logTime' },
95
+ { name: 'Move (Change Status)', value: 'move' },
96
+ { name: 'OQL Query', value: 'query' },
97
+ { name: 'Remove Dependency', value: 'removeDependency' },
98
+ { name: 'Remove Label', value: 'removeLabel' },
99
+ { name: 'Set Milestone', value: 'setMilestone' },
100
+ { name: 'Set Version', value: 'setVersion' },
101
+ { name: 'Unassign', value: 'unassign' },
102
+ { name: 'Uncheck Checklist Item', value: 'uncheckChecklistItem' },
103
+ { name: 'Update', value: 'update' },
104
+ ].sort((a, b) => a.value.localeCompare(b.value)),
105
+ },
106
+ projectParam,
107
+ ticketParam,
108
+ // -- create / update fields -------------------------------------------------
109
+ {
110
+ displayName: 'Title',
111
+ name: 'title',
112
+ type: 'string',
113
+ required: true,
114
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['create'] } },
115
+ default: '',
116
+ description: 'Ticket title',
117
+ },
118
+ {
119
+ displayName: 'Description',
120
+ name: 'description',
121
+ type: 'string',
122
+ typeOptions: { rows: 6 },
123
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['create'] } },
124
+ default: '',
125
+ description: 'Ticket description (Markdown)',
126
+ },
127
+ {
128
+ displayName: 'Type',
129
+ name: 'type',
130
+ type: 'options',
131
+ options: ['epic', 'story', 'task', 'bug'].map((v) => ({ name: v, value: v })),
132
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['create'] } },
133
+ default: 'task',
134
+ description: 'Ticket type',
135
+ },
136
+ {
137
+ displayName: 'Priority',
138
+ name: 'priority',
139
+ type: 'options',
140
+ options: ['blocker', 'high', 'normal', 'low', 'trivial'].map((v) => ({ name: v, value: v })),
141
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['create'] } },
142
+ default: 'normal',
143
+ description: 'Ticket priority',
144
+ },
145
+ {
146
+ displayName: 'Status Name or ID',
147
+ name: 'statusId',
148
+ type: 'options',
149
+ typeOptions: { loadOptionsMethod: 'getStatuses' },
150
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['create', 'move'] } },
151
+ default: '',
152
+ description: 'Status to set (statuses come from the project). Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
153
+ },
154
+ {
155
+ displayName: 'Milestone Name or ID',
156
+ name: 'milestoneId',
157
+ type: 'options',
158
+ typeOptions: { loadOptionsMethod: 'getMilestones' },
159
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['create', 'setMilestone'] } },
160
+ default: '',
161
+ description: 'Milestone of the ticket. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
162
+ },
163
+ {
164
+ displayName: 'Version Name or ID',
165
+ name: 'versionId',
166
+ type: 'options',
167
+ typeOptions: { loadOptionsMethod: 'getVersions' },
168
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['create', 'setVersion'] } },
169
+ default: '',
170
+ description: 'Version of the ticket. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
171
+ },
172
+ {
173
+ displayName: 'Label Names or IDs',
174
+ name: 'labelNames',
175
+ type: 'multiOptions',
176
+ typeOptions: { loadOptionsMethod: 'getLabelNames' },
177
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['create'] } },
178
+ default: [],
179
+ description: 'Labels to attach. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
180
+ },
181
+ {
182
+ displayName: 'Assignee Emails',
183
+ name: 'assigneeEmails',
184
+ type: 'string',
185
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['create'] } },
186
+ default: '',
187
+ description: 'Comma-separated email addresses to assign',
188
+ },
189
+ {
190
+ displayName: 'Parent Ticket',
191
+ name: 'parentTicketId',
192
+ type: 'string',
193
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['create'] } },
194
+ default: '',
195
+ description: 'Parent ticket key or ID for sub-tickets',
196
+ },
197
+ {
198
+ displayName: 'Start Date',
199
+ name: 'startDate',
200
+ type: 'string',
201
+ placeholder: 'YYYY-MM-DD',
202
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['create'] } },
203
+ default: '',
204
+ description: 'Planned start date (YYYY-MM-DD)',
205
+ },
206
+ {
207
+ displayName: 'Due Date',
208
+ name: 'dueDate',
209
+ type: 'string',
210
+ placeholder: 'YYYY-MM-DD',
211
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['create'] } },
212
+ default: '',
213
+ description: 'Due date (YYYY-MM-DD)',
214
+ },
215
+ {
216
+ displayName: 'Estimated Time (Minutes)',
217
+ name: 'estimatedTimeMinutes',
218
+ type: 'number',
219
+ typeOptions: { numberPrecision: 0 },
220
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['create'] } },
221
+ default: 0,
222
+ description: 'Estimated effort in minutes',
223
+ },
224
+ {
225
+ displayName: 'Private',
226
+ name: 'isPrivate',
227
+ type: 'boolean',
228
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['create'] } },
229
+ default: false,
230
+ description: 'Whether the ticket is only visible to team members with access',
231
+ },
232
+ {
233
+ displayName: 'Delivery Mode',
234
+ name: 'deliveryMode',
235
+ type: 'options',
236
+ options: ['implementation', 'docs', 'review', 'admin', 'epic'].map((v) => ({ name: v, value: v })),
237
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['create'] } },
238
+ default: 'implementation',
239
+ description: 'Delivery mode of the ticket',
240
+ },
241
+ {
242
+ displayName: 'Skip Auto-Translate',
243
+ name: 'skipAutoTranslate',
244
+ type: 'boolean',
245
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['create'] } },
246
+ default: false,
247
+ description: 'Whether to skip the automatic translation of the ticket',
248
+ },
249
+ // -- update: only the fields the user explicitly adds are sent (a defaulted
250
+ // options parameter must never silently reset an untouched ticket) -----------
251
+ {
252
+ displayName: 'Update Fields',
253
+ name: 'updateFields',
254
+ type: 'collection',
255
+ placeholder: 'Add Field',
256
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['update'] } },
257
+ default: {},
258
+ description: 'Only the fields added here are changed; everything else on the ticket stays untouched',
259
+ options: [
260
+ {
261
+ displayName: 'Assignee Emails',
262
+ name: 'assigneeEmails',
263
+ type: 'string',
264
+ default: '',
265
+ description: 'Comma-separated email addresses to assign',
266
+ },
267
+ {
268
+ displayName: 'Delivery Mode',
269
+ name: 'deliveryMode',
270
+ type: 'options',
271
+ options: ['implementation', 'docs', 'review', 'admin', 'epic'].map((v) => ({ name: v, value: v })),
272
+ default: 'implementation',
273
+ description: 'Delivery mode of the ticket',
274
+ },
275
+ {
276
+ displayName: 'Description',
277
+ name: 'description',
278
+ type: 'string',
279
+ typeOptions: { rows: 6 },
280
+ default: '',
281
+ description: 'Ticket description (Markdown)',
282
+ },
283
+ {
284
+ displayName: 'Due Date',
285
+ name: 'dueDate',
286
+ type: 'string',
287
+ placeholder: 'YYYY-MM-DD',
288
+ default: '',
289
+ description: 'Due date (YYYY-MM-DD)',
290
+ },
291
+ {
292
+ displayName: 'Estimated Time (Minutes)',
293
+ name: 'estimatedTimeMinutes',
294
+ type: 'number',
295
+ typeOptions: { numberPrecision: 0 },
296
+ default: 0,
297
+ description: 'Estimated effort in minutes',
298
+ },
299
+ {
300
+ displayName: 'Label Names or IDs',
301
+ name: 'labelNames',
302
+ type: 'multiOptions',
303
+ typeOptions: { loadOptionsMethod: 'getLabelNames' },
304
+ default: [],
305
+ description: 'Labels to attach. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
306
+ },
307
+ {
308
+ displayName: 'Milestone Name or ID',
309
+ name: 'milestoneId',
310
+ type: 'options',
311
+ typeOptions: { loadOptionsMethod: 'getMilestones' },
312
+ default: '',
313
+ description: 'Milestone of the ticket. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
314
+ },
315
+ {
316
+ displayName: 'Parent Ticket',
317
+ name: 'parentTicketId',
318
+ type: 'string',
319
+ default: '',
320
+ description: 'Parent ticket key or ID for sub-tickets',
321
+ },
322
+ {
323
+ displayName: 'Priority',
324
+ name: 'priority',
325
+ type: 'options',
326
+ options: ['blocker', 'high', 'normal', 'low', 'trivial'].map((v) => ({ name: v, value: v })),
327
+ default: 'normal',
328
+ description: 'Ticket priority',
329
+ },
330
+ {
331
+ displayName: 'Private',
332
+ name: 'isPrivate',
333
+ type: 'boolean',
334
+ default: false,
335
+ description: 'Whether the ticket is only visible to team members with access',
336
+ },
337
+ {
338
+ displayName: 'Skip Auto-Translate',
339
+ name: 'skipAutoTranslate',
340
+ type: 'boolean',
341
+ default: false,
342
+ description: 'Whether to skip the automatic translation of the ticket',
343
+ },
344
+ {
345
+ displayName: 'Start Date',
346
+ name: 'startDate',
347
+ type: 'string',
348
+ placeholder: 'YYYY-MM-DD',
349
+ default: '',
350
+ description: 'Planned start date (YYYY-MM-DD)',
351
+ },
352
+ {
353
+ displayName: 'Status Name or ID',
354
+ name: 'statusId',
355
+ type: 'options',
356
+ typeOptions: { loadOptionsMethod: 'getStatuses' },
357
+ default: '',
358
+ description: 'Status to set (statuses come from the project). Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
359
+ },
360
+ {
361
+ displayName: 'Type',
362
+ name: 'type',
363
+ type: 'options',
364
+ options: ['epic', 'story', 'task', 'bug'].map((v) => ({ name: v, value: v })),
365
+ default: 'task',
366
+ description: 'Ticket type',
367
+ },
368
+ {
369
+ displayName: 'Version Name or ID',
370
+ name: 'versionId',
371
+ type: 'options',
372
+ typeOptions: { loadOptionsMethod: 'getVersions' },
373
+ default: '',
374
+ description: 'Version of the ticket. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
375
+ },
376
+ ],
377
+ },
378
+ // -- language + duplicate semantics (binding error-handling design) ----------
379
+ {
380
+ displayName: 'Allow Language Mismatch',
381
+ name: 'allowLanguageMismatch',
382
+ type: 'boolean',
383
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['create', 'update'] } },
384
+ default: false,
385
+ description: 'Whether to accept a 422 language-enforcement block and create/update anyway (sends allowLanguageMismatch=true). Without this, orboto enforces the project language.',
386
+ },
387
+ {
388
+ displayName: 'Allow Duplicate',
389
+ name: 'allowDuplicate',
390
+ type: 'boolean',
391
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['create'] } },
392
+ default: false,
393
+ description: 'Whether to create anyway when orboto blocks a hard duplicate (409 with similarWarnings). Requires a justification.',
394
+ },
395
+ {
396
+ displayName: 'Duplicate Justification',
397
+ name: 'duplicateJustification',
398
+ type: 'string',
399
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['create'], allowDuplicate: [true] } },
400
+ default: '',
401
+ description: 'Why this ticket is distinct from the flagged similar ones (required with Allow Duplicate)',
402
+ },
403
+ // -- get many filters --------------------------------------------------------
404
+ {
405
+ displayName: 'Return All',
406
+ name: 'returnAll',
407
+ type: 'boolean',
408
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['getAll', 'query'] } },
409
+ default: false,
410
+ description: 'Whether to return all results or only up to a given limit',
411
+ },
412
+ {
413
+ displayName: 'Limit',
414
+ name: 'limit',
415
+ type: 'number',
416
+ typeOptions: { numberPrecision: 0 },
417
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['getAll', 'query'], returnAll: [false] } },
418
+ default: 50,
419
+ description: 'Max number of results to return',
420
+ },
421
+ {
422
+ displayName: 'Status Category',
423
+ name: 'statusCategory',
424
+ type: 'options',
425
+ options: ['todo', 'in_progress', 'in_review', 'done', 'wont_fix'].map((v) => ({ name: v, value: v })),
426
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['getAll'] } },
427
+ default: '',
428
+ description: 'Filter by status category',
429
+ },
430
+ {
431
+ displayName: 'Assignee Name or ID',
432
+ name: 'assigneeId',
433
+ type: 'options',
434
+ typeOptions: { loadOptionsMethod: 'getMembers' },
435
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['getAll'] } },
436
+ default: '',
437
+ description: 'Filter by assignee. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
438
+ },
439
+ {
440
+ displayName: 'Milestone Filter Name or ID',
441
+ name: 'milestoneFilterId',
442
+ type: 'options',
443
+ typeOptions: { loadOptionsMethod: 'getMilestones' },
444
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['getAll'] } },
445
+ default: '',
446
+ description: 'Filter by milestone. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
447
+ },
448
+ {
449
+ displayName: 'Parent Ticket Filter',
450
+ name: 'parentFilterId',
451
+ type: 'string',
452
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['getAll'] } },
453
+ default: '',
454
+ description: 'Filter by parent ticket key or ID',
455
+ },
456
+ {
457
+ displayName: 'Search',
458
+ name: 'search',
459
+ type: 'string',
460
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['getAll'] } },
461
+ default: '',
462
+ description: 'Full-text search term',
463
+ },
464
+ {
465
+ displayName: 'Include Closed Milestones',
466
+ name: 'includeClosedMilestones',
467
+ type: 'boolean',
468
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['getAll'] } },
469
+ default: false,
470
+ description: 'Whether to include tickets of closed milestones',
471
+ },
472
+ {
473
+ displayName: 'Delete Is Permanent',
474
+ name: 'deleteNotice',
475
+ type: 'notice',
476
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['delete'] } },
477
+ default: '',
478
+ description: 'Deleting is permanent unless the ticket is under legal hold (HTTP 423). Move to wont_fix instead of deleting when in doubt.',
479
+ },
480
+ // -- assign / comment / time / label -----------------------------------------
481
+ {
482
+ displayName: 'User Name or ID',
483
+ name: 'userId',
484
+ type: 'options',
485
+ typeOptions: { loadOptionsMethod: 'getMembers' },
486
+ required: true,
487
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['assign', 'unassign'] } },
488
+ default: '',
489
+ description: 'Project member to assign or unassign. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
490
+ },
491
+ {
492
+ displayName: 'Comment',
493
+ name: 'content',
494
+ type: 'string',
495
+ typeOptions: { rows: 6 },
496
+ required: true,
497
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['comment'] } },
498
+ default: '',
499
+ description: 'Comment text (Markdown)',
500
+ },
501
+ {
502
+ displayName: 'Internal',
503
+ name: 'isInternal',
504
+ type: 'boolean',
505
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['comment'] } },
506
+ default: false,
507
+ description: 'Whether the comment is internal (not visible to customers)',
508
+ },
509
+ {
510
+ displayName: 'Duration (Minutes)',
511
+ name: 'durationMinutes',
512
+ type: 'number',
513
+ typeOptions: { numberPrecision: 0 },
514
+ required: true,
515
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['logTime'] } },
516
+ default: 30,
517
+ description: 'Time to log in minutes',
518
+ },
519
+ {
520
+ displayName: 'Time Description',
521
+ name: 'timeDescription',
522
+ type: 'string',
523
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['logTime'] } },
524
+ default: '',
525
+ description: 'What the time was spent on',
526
+ },
527
+ {
528
+ displayName: 'Logged At',
529
+ name: 'loggedAt',
530
+ type: 'dateTime',
531
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['logTime'] } },
532
+ default: '',
533
+ description: 'When the work happened (defaults to now)',
534
+ },
535
+ {
536
+ displayName: 'Label Name or ID',
537
+ name: 'labelId',
538
+ type: 'options',
539
+ typeOptions: { loadOptionsMethod: 'getLabels' },
540
+ required: true,
541
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['addLabel', 'removeLabel'] } },
542
+ default: '',
543
+ description: 'Label to add or remove. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
544
+ },
545
+ // -- OQL query ---------------------------------------------------------------
546
+ {
547
+ displayName: 'Query',
548
+ name: 'oql',
549
+ type: 'string',
550
+ typeOptions: { rows: 6 },
551
+ required: true,
552
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['query'] } },
553
+ default: '',
554
+ description: 'OQL query, for example: project = ONN and status = TODO and assignee = me order by priority',
555
+ },
556
+ {
557
+ displayName: 'Syntax',
558
+ name: 'syntax',
559
+ type: 'options',
560
+ options: [
561
+ { name: 'OQL', value: 'oql' },
562
+ { name: 'JQL', value: 'jql' },
563
+ ],
564
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['query'] } },
565
+ default: 'oql',
566
+ description: 'Query syntax to use',
567
+ },
568
+ // -- dependencies ------------------------------------------------------------
569
+ {
570
+ displayName: 'Depends On Ticket',
571
+ name: 'dependsOn',
572
+ type: 'string',
573
+ required: true,
574
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['addDependency', 'removeDependency'] } },
575
+ default: '',
576
+ description: 'The ticket this one depends on (key or ID)',
577
+ },
578
+ // -- checklists --------------------------------------------------------------
579
+ {
580
+ displayName: 'Checklist ID',
581
+ name: 'listId',
582
+ type: 'string',
583
+ required: true,
584
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['addChecklistItem'] } },
585
+ default: '',
586
+ description: 'ID of the checklist to append to',
587
+ },
588
+ {
589
+ displayName: 'Item Text',
590
+ name: 'itemContent',
591
+ type: 'string',
592
+ required: true,
593
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['addChecklistItem'] } },
594
+ default: '',
595
+ description: 'Checklist item text',
596
+ },
597
+ {
598
+ displayName: 'Item ID',
599
+ name: 'itemId',
600
+ type: 'string',
601
+ required: true,
602
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['checkChecklistItem', 'uncheckChecklistItem'] } },
603
+ default: '',
604
+ description: 'ID of the checklist item',
605
+ },
606
+ // -- attachments --------------------------------------------------------------
607
+ {
608
+ displayName: 'Binary Property',
609
+ name: 'binaryProperty',
610
+ type: 'string',
611
+ required: true,
612
+ default: 'data',
613
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['addAttachment'] } },
614
+ description: 'Name of the binary property holding the file to upload',
615
+ },
616
+ // -- bulk ----------------------------------------------------------------------
617
+ {
618
+ displayName: 'Ticket IDs',
619
+ name: 'bulkIds',
620
+ type: 'string',
621
+ required: true,
622
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['bulkUpdate'] } },
623
+ default: '',
624
+ description: 'Comma-separated ticket IDs to update',
625
+ },
626
+ {
627
+ displayName: 'Action',
628
+ name: 'bulkAction',
629
+ type: 'options',
630
+ required: true,
631
+ options: [
632
+ { name: 'Assignee', value: 'assignee' },
633
+ { name: 'Due Date', value: 'due_date' },
634
+ { name: 'Milestone', value: 'milestone' },
635
+ { name: 'Priority', value: 'priority' },
636
+ { name: 'Status', value: 'status' },
637
+ { name: 'Version', value: 'version' },
638
+ ],
639
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['bulkUpdate'] } },
640
+ default: 'status',
641
+ description: 'Field to change on every listed ticket',
642
+ },
643
+ {
644
+ displayName: 'Value Name or ID',
645
+ name: 'bulkValueStatus',
646
+ type: 'options',
647
+ typeOptions: { loadOptionsMethod: 'getStatuses' },
648
+ required: true,
649
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['bulkUpdate'], bulkAction: ['status'] } },
650
+ default: '',
651
+ description: 'Status to set. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
652
+ },
653
+ {
654
+ displayName: 'Value Name or ID',
655
+ name: 'bulkValueMilestone',
656
+ type: 'options',
657
+ typeOptions: { loadOptionsMethod: 'getMilestones' },
658
+ required: true,
659
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['bulkUpdate'], bulkAction: ['milestone'] } },
660
+ default: '',
661
+ description: 'Milestone to set. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
662
+ },
663
+ {
664
+ displayName: 'Value Name or ID',
665
+ name: 'bulkValueAssignee',
666
+ type: 'options',
667
+ typeOptions: { loadOptionsMethod: 'getMembers' },
668
+ required: true,
669
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['bulkUpdate'], bulkAction: ['assignee'] } },
670
+ default: '',
671
+ description: 'Member to assign. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
672
+ },
673
+ {
674
+ displayName: 'Value Name or ID',
675
+ name: 'bulkValueVersion',
676
+ type: 'options',
677
+ typeOptions: { loadOptionsMethod: 'getVersions' },
678
+ required: true,
679
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['bulkUpdate'], bulkAction: ['version'] } },
680
+ default: '',
681
+ description: 'Version to set. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
682
+ },
683
+ {
684
+ displayName: 'Value',
685
+ name: 'bulkValuePriority',
686
+ type: 'options',
687
+ required: true,
688
+ options: ['blocker', 'high', 'normal', 'low', 'trivial'].map((v) => ({ name: v, value: v })),
689
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['bulkUpdate'], bulkAction: ['priority'] } },
690
+ default: 'normal',
691
+ description: 'Priority to set',
692
+ },
693
+ {
694
+ displayName: 'Value',
695
+ name: 'bulkValueDueDate',
696
+ type: 'string',
697
+ placeholder: 'YYYY-MM-DD',
698
+ required: true,
699
+ displayOptions: { show: { ...SHOW_TICKET, operation: ['bulkUpdate'], bulkAction: ['due_date'] } },
700
+ default: '',
701
+ description: 'Due date to set',
702
+ },
703
+ ];
704
+ /** Reads an optional string parameter, returning undefined when empty. */
705
+ function optionalString(context, name, itemIndex) {
706
+ const value = context.getNodeParameter(name, itemIndex, '');
707
+ return value === '' ? undefined : value;
708
+ }
709
+ /** Collects the create/update body from the optional fields present in the form. */
710
+ function collectTicketBody(context, itemIndex, fields) {
711
+ const body = {};
712
+ for (const field of fields) {
713
+ if (field === 'description') {
714
+ const value = optionalString(context, 'description', itemIndex);
715
+ if (value !== undefined)
716
+ body.description = value;
717
+ }
718
+ else if (field === 'startDate' || field === 'dueDate') {
719
+ const value = optionalString(context, field, itemIndex);
720
+ if (value !== undefined)
721
+ body[field] = value;
722
+ }
723
+ else if (field === 'estimatedTimeMinutes') {
724
+ const value = context.getNodeParameter('estimatedTimeMinutes', itemIndex, 0);
725
+ if (value > 0)
726
+ body.estimatedTimeMinutes = value;
727
+ }
728
+ else if (field === 'isPrivate' || field === 'skipAutoTranslate') {
729
+ const value = context.getNodeParameter(field, itemIndex, false);
730
+ if (value)
731
+ body[field] = value;
732
+ }
733
+ else if (field === 'title') {
734
+ body.title = context.getNodeParameter('title', itemIndex);
735
+ }
736
+ else {
737
+ const value = optionalString(context, field, itemIndex);
738
+ if (value !== undefined)
739
+ body[field] = value;
740
+ }
741
+ }
742
+ const labelNames = context.getNodeParameter('labelNames', itemIndex, []);
743
+ if (Array.isArray(labelNames) && labelNames.length > 0)
744
+ body.labelNames = labelNames;
745
+ const assigneeEmails = optionalString(context, 'assigneeEmails', itemIndex);
746
+ if (assigneeEmails) {
747
+ body.assigneeEmails = assigneeEmails
748
+ .split(',')
749
+ .map((email) => email.trim())
750
+ .filter(Boolean);
751
+ }
752
+ return body;
753
+ }
754
+ /**
755
+ * Builds the PATCH body for the update operation from the Update Fields
756
+ * collection. Only fields the user explicitly added are present in the
757
+ * collection, so whatever is present is sent as-is - including an explicit
758
+ * `false` for booleans (the only way to clear them) - and untouched ticket
759
+ * fields are never reset by a defaulted options parameter.
760
+ */
761
+ function buildUpdateBody(updateFields) {
762
+ const body = {};
763
+ for (const field of ['description', 'type', 'priority', 'statusId', 'milestoneId', 'versionId', 'startDate', 'dueDate', 'deliveryMode']) {
764
+ const value = updateFields[field];
765
+ if (typeof value === 'string' && value !== '')
766
+ body[field] = value;
767
+ }
768
+ if (typeof updateFields.estimatedTimeMinutes === 'number' && updateFields.estimatedTimeMinutes > 0) {
769
+ body.estimatedTimeMinutes = updateFields.estimatedTimeMinutes;
770
+ }
771
+ for (const field of ['isPrivate', 'skipAutoTranslate']) {
772
+ if (typeof updateFields[field] === 'boolean')
773
+ body[field] = updateFields[field];
774
+ }
775
+ if (Array.isArray(updateFields.labelNames) && updateFields.labelNames.length > 0) {
776
+ body.labelNames = updateFields.labelNames;
777
+ }
778
+ if (typeof updateFields.assigneeEmails === 'string' && updateFields.assigneeEmails.trim() !== '') {
779
+ body.assigneeEmails = updateFields.assigneeEmails
780
+ .split(',')
781
+ .map((email) => email.trim())
782
+ .filter(Boolean);
783
+ }
784
+ return body;
785
+ }
786
+ async function executeTicketOperation(context, itemIndex) {
787
+ const node = context.getNode();
788
+ const operation = context.getNodeParameter('operation', itemIndex);
789
+ try {
790
+ const client = await (0, GenericFunctions_1.getClient)(context);
791
+ const project = String(context.getNodeParameter('project', itemIndex, '') ?? '');
792
+ const ticket = String(context.getNodeParameter('ticket', itemIndex, '') ?? '');
793
+ const ticketBase = `/projects/${encodeURIComponent(project)}/tickets`;
794
+ switch (operation) {
795
+ case 'create': {
796
+ const allowLanguageMismatch = context.getNodeParameter('allowLanguageMismatch', itemIndex, false);
797
+ const allowDuplicate = context.getNodeParameter('allowDuplicate', itemIndex, false);
798
+ const duplicateJustification = optionalString(context, 'duplicateJustification', itemIndex);
799
+ if (allowDuplicate && !duplicateJustification) {
800
+ throw new n8n_workflow_1.NodeOperationError(node, 'Duplicate Justification is required when Allow Duplicate is enabled', { itemIndex });
801
+ }
802
+ const body = collectTicketBody(context, itemIndex, [
803
+ 'title',
804
+ 'description',
805
+ 'type',
806
+ 'priority',
807
+ 'statusId',
808
+ 'milestoneId',
809
+ 'versionId',
810
+ 'startDate',
811
+ 'dueDate',
812
+ 'estimatedTimeMinutes',
813
+ 'isPrivate',
814
+ 'deliveryMode',
815
+ 'skipAutoTranslate',
816
+ ]);
817
+ const parent = optionalString(context, 'parentTicketId', itemIndex);
818
+ if (parent)
819
+ body.parentTicketId = await (0, GenericFunctions_1.resolveTicketId)(client, project, parent);
820
+ if (allowDuplicate && duplicateJustification)
821
+ body.duplicateJustification = duplicateJustification;
822
+ // The created ticket includes languageWarning and similarWarnings so
823
+ // workflows can branch on them (binding error-semantics design).
824
+ const created = await client.request(`/projects/${encodeURIComponent(project)}/tickets`, {
825
+ method: 'POST',
826
+ body,
827
+ query: {
828
+ allowLanguageMismatch: allowLanguageMismatch || undefined,
829
+ allowDuplicate: allowDuplicate || undefined,
830
+ },
831
+ });
832
+ return [(0, GenericFunctions_1.jsonOutput)(created)];
833
+ }
834
+ case 'get': {
835
+ const ticketId = await (0, GenericFunctions_1.resolveTicketId)(client, project, ticket);
836
+ const found = await client.request(`${ticketBase}/${ticketId}`);
837
+ return [(0, GenericFunctions_1.jsonOutput)(found)];
838
+ }
839
+ case 'getAll': {
840
+ const parentFilter = optionalString(context, 'parentFilterId', itemIndex);
841
+ const query = (0, GenericFunctions_1.collectQuery)([
842
+ ['statusCategory', optionalString(context, 'statusCategory', itemIndex)],
843
+ ['assigneeId', optionalString(context, 'assigneeId', itemIndex)],
844
+ ['milestoneId', optionalString(context, 'milestoneFilterId', itemIndex)],
845
+ ['parentTicketId', parentFilter && !(0, GenericFunctions_1.isUuid)(parentFilter) ? await (0, GenericFunctions_1.resolveTicketId)(client, project, parentFilter) : parentFilter],
846
+ ['search', optionalString(context, 'search', itemIndex)],
847
+ ['includeClosedMilestones', context.getNodeParameter('includeClosedMilestones', itemIndex, false)],
848
+ ]);
849
+ const returnAll = context.getNodeParameter('returnAll', itemIndex, false);
850
+ const limit = returnAll ? undefined : context.getNodeParameter('limit', itemIndex, 50);
851
+ const tickets = await client.paginate(`${ticketBase}`, { query, limit });
852
+ return tickets.map(GenericFunctions_1.jsonOutput);
853
+ }
854
+ case 'update': {
855
+ const ticketId = await (0, GenericFunctions_1.resolveTicketId)(client, project, ticket);
856
+ const allowLanguageMismatch = context.getNodeParameter('allowLanguageMismatch', itemIndex, false);
857
+ const updateFields = context.getNodeParameter('updateFields', itemIndex, {});
858
+ const body = buildUpdateBody(updateFields);
859
+ const parent = typeof updateFields.parentTicketId === 'string' && updateFields.parentTicketId !== '' ? updateFields.parentTicketId : undefined;
860
+ if (parent)
861
+ body.parentTicketId = await (0, GenericFunctions_1.resolveTicketId)(client, project, parent);
862
+ const updated = await client.request(`${ticketBase}/${ticketId}`, {
863
+ method: 'PATCH',
864
+ body,
865
+ query: { allowLanguageMismatch: allowLanguageMismatch || undefined },
866
+ });
867
+ return [(0, GenericFunctions_1.jsonOutput)(updated)];
868
+ }
869
+ case 'delete': {
870
+ const ticketId = await (0, GenericFunctions_1.resolveTicketId)(client, project, ticket);
871
+ await client.request(`${ticketBase}/${ticketId}`, { method: 'DELETE' });
872
+ return [(0, GenericFunctions_1.jsonOutput)({ success: true, ticketId })];
873
+ }
874
+ case 'move': {
875
+ const ticketId = await (0, GenericFunctions_1.resolveTicketId)(client, project, ticket);
876
+ const statusId = context.getNodeParameter('statusId', itemIndex);
877
+ const moved = await client.request(`${ticketBase}/${ticketId}`, {
878
+ method: 'PATCH',
879
+ body: { statusId },
880
+ });
881
+ return [(0, GenericFunctions_1.jsonOutput)(moved)];
882
+ }
883
+ case 'assign':
884
+ case 'unassign': {
885
+ const ticketId = await (0, GenericFunctions_1.resolveTicketId)(client, project, ticket);
886
+ const userId = context.getNodeParameter('userId', itemIndex);
887
+ await client.request(`${ticketBase}/${ticketId}/assignees/${encodeURIComponent(userId)}`, {
888
+ method: operation === 'assign' ? 'POST' : 'DELETE',
889
+ });
890
+ return [(0, GenericFunctions_1.jsonOutput)({ success: true, ticketId, userId, assigned: operation === 'assign' })];
891
+ }
892
+ case 'comment': {
893
+ const ticketId = await (0, GenericFunctions_1.resolveTicketId)(client, project, ticket);
894
+ const content = context.getNodeParameter('content', itemIndex);
895
+ const isInternal = context.getNodeParameter('isInternal', itemIndex, false);
896
+ const created = await client.request(`/tickets/${ticketId}/comments`, {
897
+ method: 'POST',
898
+ body: { content, isInternal },
899
+ });
900
+ return [(0, GenericFunctions_1.jsonOutput)(created)];
901
+ }
902
+ case 'logTime': {
903
+ const ticketId = await (0, GenericFunctions_1.resolveTicketId)(client, project, ticket);
904
+ const durationMinutes = context.getNodeParameter('durationMinutes', itemIndex);
905
+ const body = { durationMinutes };
906
+ const description = optionalString(context, 'timeDescription', itemIndex);
907
+ if (description)
908
+ body.description = description;
909
+ const loggedAt = optionalString(context, 'loggedAt', itemIndex);
910
+ if (loggedAt)
911
+ body.loggedAt = loggedAt;
912
+ const created = await client.request(`/tickets/${ticketId}/time-entries`, {
913
+ method: 'POST',
914
+ body,
915
+ });
916
+ return [(0, GenericFunctions_1.jsonOutput)(created)];
917
+ }
918
+ case 'addLabel':
919
+ case 'removeLabel': {
920
+ const ticketId = await (0, GenericFunctions_1.resolveTicketId)(client, project, ticket);
921
+ const labelId = context.getNodeParameter('labelId', itemIndex);
922
+ await client.request(`/projects/tickets/${ticketId}/labels/${encodeURIComponent(labelId)}`, {
923
+ method: operation === 'addLabel' ? 'POST' : 'DELETE',
924
+ });
925
+ return [(0, GenericFunctions_1.jsonOutput)({ success: true, ticketId, labelId, added: operation === 'addLabel' })];
926
+ }
927
+ case 'query': {
928
+ // /query paginates via the request BODY (cursor + limit), so this
929
+ // walks pages manually instead of using the query-param helper.
930
+ const oql = context.getNodeParameter('oql', itemIndex);
931
+ const syntax = context.getNodeParameter('syntax', itemIndex, 'oql');
932
+ const returnAll = context.getNodeParameter('returnAll', itemIndex, false);
933
+ const limit = returnAll ? undefined : context.getNodeParameter('limit', itemIndex, 50);
934
+ const collected = [];
935
+ const seenCursors = new Set();
936
+ let cursor;
937
+ while (limit === undefined || collected.length < limit) {
938
+ const remaining = limit === undefined ? 200 : Math.min(200, limit - collected.length);
939
+ const page = await client.request('/query', {
940
+ method: 'POST',
941
+ body: { oql, syntax, limit: remaining, cursor },
942
+ });
943
+ if (!Array.isArray(page?.items))
944
+ break;
945
+ collected.push(...page.items.slice(0, remaining));
946
+ if (!page.nextCursor)
947
+ break;
948
+ if (seenCursors.has(page.nextCursor)) {
949
+ throw new ApiClient_1.ApiError({ status: 200, code: 'pagination_loop', message: 'The /query endpoint returned a repeated cursor' });
950
+ }
951
+ seenCursors.add(page.nextCursor);
952
+ cursor = page.nextCursor;
953
+ }
954
+ return collected.map(GenericFunctions_1.jsonOutput);
955
+ }
956
+ case 'setMilestone':
957
+ case 'clearMilestone': {
958
+ const ticketId = await (0, GenericFunctions_1.resolveTicketId)(client, project, ticket);
959
+ const milestoneId = operation === 'setMilestone' ? context.getNodeParameter('milestoneId', itemIndex) : null;
960
+ const updated = await client.request(`${ticketBase}/${ticketId}`, {
961
+ method: 'PATCH',
962
+ body: { milestoneId },
963
+ });
964
+ return [(0, GenericFunctions_1.jsonOutput)(updated)];
965
+ }
966
+ case 'setVersion':
967
+ case 'clearVersion': {
968
+ const ticketId = await (0, GenericFunctions_1.resolveTicketId)(client, project, ticket);
969
+ const versionId = operation === 'setVersion' ? context.getNodeParameter('versionId', itemIndex) : null;
970
+ const updated = await client.request(`${ticketBase}/${ticketId}`, {
971
+ method: 'PATCH',
972
+ body: { versionId },
973
+ });
974
+ return [(0, GenericFunctions_1.jsonOutput)(updated)];
975
+ }
976
+ case 'addDependency': {
977
+ const ticketId = await (0, GenericFunctions_1.resolveTicketId)(client, project, ticket);
978
+ const dependsOn = context.getNodeParameter('dependsOn', itemIndex);
979
+ const dependsOnId = await (0, GenericFunctions_1.resolveTicketId)(client, project, dependsOn);
980
+ const created = await client.request(`${ticketBase}/${ticketId}/dependencies`, {
981
+ method: 'POST',
982
+ body: { dependsOnId },
983
+ });
984
+ return [(0, GenericFunctions_1.jsonOutput)(created)];
985
+ }
986
+ case 'removeDependency': {
987
+ const ticketId = await (0, GenericFunctions_1.resolveTicketId)(client, project, ticket);
988
+ const dependsOn = context.getNodeParameter('dependsOn', itemIndex);
989
+ const dependsOnId = await (0, GenericFunctions_1.resolveTicketId)(client, project, dependsOn);
990
+ await client.request(`${ticketBase}/${ticketId}/dependencies/${dependsOnId}`, { method: 'DELETE' });
991
+ return [(0, GenericFunctions_1.jsonOutput)({ success: true, ticketId, dependsOnId })];
992
+ }
993
+ case 'getDependencies': {
994
+ const ticketId = await (0, GenericFunctions_1.resolveTicketId)(client, project, ticket);
995
+ const dependencies = await client.request(`${ticketBase}/${ticketId}/dependencies`);
996
+ return [(0, GenericFunctions_1.jsonOutput)(dependencies)];
997
+ }
998
+ case 'addChecklistItem': {
999
+ const ticketId = await (0, GenericFunctions_1.resolveTicketId)(client, project, ticket);
1000
+ const listId = context.getNodeParameter('listId', itemIndex);
1001
+ const itemContent = context.getNodeParameter('itemContent', itemIndex);
1002
+ const created = await client.request(`/tickets/${ticketId}/checklists/${listId}/items`, {
1003
+ method: 'POST',
1004
+ body: { content: itemContent },
1005
+ });
1006
+ return [(0, GenericFunctions_1.jsonOutput)(created)];
1007
+ }
1008
+ case 'checkChecklistItem':
1009
+ case 'uncheckChecklistItem': {
1010
+ const itemId = context.getNodeParameter('itemId', itemIndex);
1011
+ const updated = await client.request(`/checklist-items/${itemId}`, {
1012
+ method: 'PATCH',
1013
+ body: { isCompleted: operation === 'checkChecklistItem' },
1014
+ });
1015
+ return [(0, GenericFunctions_1.jsonOutput)(updated)];
1016
+ }
1017
+ case 'getAttachments': {
1018
+ const ticketId = await (0, GenericFunctions_1.resolveTicketId)(client, project, ticket);
1019
+ const attachments = await client.request(`/tickets/${ticketId}/attachments`);
1020
+ const items = Array.isArray(attachments) ? attachments : [];
1021
+ return items.map(GenericFunctions_1.jsonOutput);
1022
+ }
1023
+ case 'addAttachment': {
1024
+ const ticketId = await (0, GenericFunctions_1.resolveTicketId)(client, project, ticket);
1025
+ const binaryProperty = String(context.getNodeParameter('binaryProperty', itemIndex, 'data'));
1026
+ const binaryData = context.getInputData(0)[itemIndex].binary?.[binaryProperty];
1027
+ if (!binaryData) {
1028
+ throw new n8n_workflow_1.NodeOperationError(node, `No binary data found in property "${binaryProperty}"`, { itemIndex });
1029
+ }
1030
+ const buffer = await context.helpers.getBinaryDataBuffer(itemIndex, binaryProperty);
1031
+ const form = new FormData();
1032
+ form.append('file', new Blob([new Uint8Array(buffer)], { type: binaryData.mimeType || 'application/octet-stream' }), binaryData.fileName || 'attachment');
1033
+ const uploaded = await client.request(`/tickets/${ticketId}/attachments`, {
1034
+ method: 'POST',
1035
+ body: form,
1036
+ });
1037
+ return [(0, GenericFunctions_1.jsonOutput)(uploaded)];
1038
+ }
1039
+ case 'bulkUpdate': {
1040
+ const ids = String(context.getNodeParameter('bulkIds', itemIndex) ?? '')
1041
+ .split(',')
1042
+ .map((id) => id.trim())
1043
+ .filter(Boolean);
1044
+ if (ids.length === 0) {
1045
+ throw new n8n_workflow_1.NodeOperationError(node, 'Provide at least one ticket id', { itemIndex });
1046
+ }
1047
+ const action = context.getNodeParameter('bulkAction', itemIndex);
1048
+ const valueParams = {
1049
+ status: 'bulkValueStatus',
1050
+ milestone: 'bulkValueMilestone',
1051
+ assignee: 'bulkValueAssignee',
1052
+ version: 'bulkValueVersion',
1053
+ priority: 'bulkValuePriority',
1054
+ due_date: 'bulkValueDueDate',
1055
+ };
1056
+ const valueParam = valueParams[action];
1057
+ const value = String(context.getNodeParameter(valueParam, itemIndex) ?? '');
1058
+ const result = await client.request(`/projects/${encodeURIComponent(project)}/tickets/bulk`, {
1059
+ method: 'POST',
1060
+ body: { ids, action, value },
1061
+ });
1062
+ return [(0, GenericFunctions_1.jsonOutput)(result)];
1063
+ }
1064
+ default:
1065
+ throw new n8n_workflow_1.NodeOperationError(node, `Unknown ticket operation: ${operation}`, { itemIndex });
1066
+ }
1067
+ }
1068
+ catch (error) {
1069
+ throw (0, GenericFunctions_1.toNodeError)(node, error, itemIndex);
1070
+ }
1071
+ }
1072
+ //# sourceMappingURL=Ticket.resource.js.map