@trg-admin/n8n-nodes-zoho-desk 0.1.19 → 0.1.21
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.
|
@@ -85,17 +85,25 @@ async function zohoDeskApiRequest(method, endpoint, body = {}, qs = {}, itemInde
|
|
|
85
85
|
async function zohoDeskApiRequestAllItems(method, endpoint, body = {}, qs = {}, itemIndex = 0) {
|
|
86
86
|
const returnData = [];
|
|
87
87
|
let responseData;
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
const query = {
|
|
89
|
+
...qs,
|
|
90
|
+
limit: qs.limit || 100,
|
|
91
|
+
from: qs.from || 0,
|
|
92
|
+
};
|
|
90
93
|
do {
|
|
91
|
-
responseData = await zohoDeskApiRequest.call(this, method, endpoint, body,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
94
|
+
responseData = await zohoDeskApiRequest.call(this, method, endpoint, body, query, itemIndex);
|
|
95
|
+
const items = Array.isArray(responseData?.data)
|
|
96
|
+
? responseData.data
|
|
97
|
+
: Array.isArray(responseData)
|
|
98
|
+
? responseData
|
|
99
|
+
: [];
|
|
100
|
+
if (items.length > 0) {
|
|
101
|
+
returnData.push(...items);
|
|
102
|
+
if (items.length < query.limit) {
|
|
95
103
|
// Last page
|
|
96
104
|
break;
|
|
97
105
|
}
|
|
98
|
-
|
|
106
|
+
query.from = query.from + items.length;
|
|
99
107
|
}
|
|
100
108
|
else {
|
|
101
109
|
break;
|
|
@@ -127,8 +127,8 @@ class ZohoDesk {
|
|
|
127
127
|
},
|
|
128
128
|
default: '',
|
|
129
129
|
required: true,
|
|
130
|
-
displayOptions: { show: { resource: ['ticket'], operation: ['create'
|
|
131
|
-
description: 'Department to assign
|
|
130
|
+
displayOptions: { show: { resource: ['ticket'], operation: ['create'] } },
|
|
131
|
+
description: 'Department to assign ticket to. Make sure Organization ID is filled in first.',
|
|
132
132
|
},
|
|
133
133
|
{
|
|
134
134
|
displayName: 'Description',
|
|
@@ -241,22 +241,162 @@ class ZohoDesk {
|
|
|
241
241
|
},
|
|
242
242
|
],
|
|
243
243
|
},
|
|
244
|
+
{
|
|
245
|
+
displayName: 'Return All',
|
|
246
|
+
name: 'returnAll',
|
|
247
|
+
type: 'boolean',
|
|
248
|
+
default: false,
|
|
249
|
+
displayOptions: { show: { resource: ['ticket'], operation: ['getAll'] } },
|
|
250
|
+
description: 'Whether to return all results or limit to specified number',
|
|
251
|
+
},
|
|
244
252
|
{
|
|
245
253
|
displayName: 'Limit',
|
|
246
254
|
name: 'limit',
|
|
247
255
|
type: 'number',
|
|
248
256
|
typeOptions: { minValue: 1, maxValue: 100 },
|
|
249
|
-
default:
|
|
250
|
-
displayOptions: {
|
|
251
|
-
|
|
257
|
+
default: 50,
|
|
258
|
+
displayOptions: {
|
|
259
|
+
show: {
|
|
260
|
+
resource: ['ticket'],
|
|
261
|
+
operation: ['getAll'],
|
|
262
|
+
returnAll: [false],
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
description: 'Max number of results to return (1-100)',
|
|
252
266
|
},
|
|
253
267
|
{
|
|
254
|
-
displayName: '
|
|
255
|
-
name: '
|
|
256
|
-
type: '
|
|
257
|
-
|
|
268
|
+
displayName: 'Options',
|
|
269
|
+
name: 'options',
|
|
270
|
+
type: 'collection',
|
|
271
|
+
placeholder: 'Add Option',
|
|
272
|
+
default: {},
|
|
258
273
|
displayOptions: { show: { resource: ['ticket'], operation: ['getAll'] } },
|
|
259
|
-
|
|
274
|
+
options: [
|
|
275
|
+
{
|
|
276
|
+
displayName: 'Assignee',
|
|
277
|
+
name: 'assignee',
|
|
278
|
+
type: 'string',
|
|
279
|
+
default: '',
|
|
280
|
+
placeholder: 'Unassigned or Agent ID',
|
|
281
|
+
description: 'ID of the assignee, or "Unassigned" for tickets not assigned to any agent. Multiple values can be separated with commas.',
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
displayName: 'Channel',
|
|
285
|
+
name: 'channel',
|
|
286
|
+
type: 'string',
|
|
287
|
+
default: '',
|
|
288
|
+
placeholder: 'Email, Phone, Web, Chat',
|
|
289
|
+
description: 'Channel through which the ticket was created. Multiple values can be separated with commas.',
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
displayName: 'Department',
|
|
293
|
+
name: 'departmentId',
|
|
294
|
+
type: 'options',
|
|
295
|
+
typeOptions: {
|
|
296
|
+
loadOptionsMethod: 'getDepartments',
|
|
297
|
+
loadOptionsDependsOn: ['organizationId'],
|
|
298
|
+
},
|
|
299
|
+
default: '',
|
|
300
|
+
description: 'Filter tickets by department. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
displayName: 'Department IDs',
|
|
304
|
+
name: 'departmentIds',
|
|
305
|
+
type: 'string',
|
|
306
|
+
default: '',
|
|
307
|
+
placeholder: '[BR_PHONE_LANDLINE_REDACTED]5,[BR_PHONE_LANDLINE_REDACTED]6',
|
|
308
|
+
description: 'Comma-separated department IDs to fetch tickets from (e.g. for "All Departments" view)',
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
displayName: 'Fields',
|
|
312
|
+
name: 'fields',
|
|
313
|
+
type: 'string',
|
|
314
|
+
default: '',
|
|
315
|
+
placeholder: 'statusType,webUrl,layoutId',
|
|
316
|
+
description: 'Comma-separated list of system and custom fields to include in the response (maximum 30 fields)',
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
displayName: 'From (Offset)',
|
|
320
|
+
name: 'from',
|
|
321
|
+
type: 'number',
|
|
322
|
+
typeOptions: { minValue: 0 },
|
|
323
|
+
default: 0,
|
|
324
|
+
description: 'Index number from which the tickets should be fetched. Only applied when Return All is disabled.',
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
displayName: 'Include',
|
|
328
|
+
name: 'include',
|
|
329
|
+
type: 'multiOptions',
|
|
330
|
+
default: [],
|
|
331
|
+
options: [
|
|
332
|
+
{ name: 'Assignee', value: 'assignee' },
|
|
333
|
+
{ name: 'Contacts', value: 'contacts' },
|
|
334
|
+
{ name: 'Departments', value: 'departments' },
|
|
335
|
+
{ name: 'Is Read', value: 'isRead' },
|
|
336
|
+
{ name: 'Products', value: 'products' },
|
|
337
|
+
{ name: 'Team', value: 'team' },
|
|
338
|
+
],
|
|
339
|
+
description: 'Additional related information to include in the response',
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
displayName: 'Priority',
|
|
343
|
+
name: 'priority',
|
|
344
|
+
type: 'string',
|
|
345
|
+
default: '',
|
|
346
|
+
placeholder: 'Low, Medium, High',
|
|
347
|
+
description: 'Priority of the ticket (e.g., Low, Medium, High). Multiple values can be separated with commas.',
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
displayName: 'Received in Days',
|
|
351
|
+
name: 'receivedInDays',
|
|
352
|
+
type: 'options',
|
|
353
|
+
default: 30,
|
|
354
|
+
options: [
|
|
355
|
+
{ name: '15 Days', value: 15 },
|
|
356
|
+
{ name: '30 Days', value: 30 },
|
|
357
|
+
{ name: '90 Days', value: 90 },
|
|
358
|
+
],
|
|
359
|
+
description: 'Time period (in days) for fetching tickets based on customerResponseTime',
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
displayName: 'Sort By',
|
|
363
|
+
name: 'sortBy',
|
|
364
|
+
type: 'options',
|
|
365
|
+
default: '-createdTime',
|
|
366
|
+
options: [
|
|
367
|
+
{ name: 'Created Time (Newest First)', value: '-createdTime' },
|
|
368
|
+
{ name: 'Created Time (Oldest First)', value: 'createdTime' },
|
|
369
|
+
{ name: 'Customer Response Time (Newest First)', value: '-customerResponseTime' },
|
|
370
|
+
{ name: 'Customer Response Time (Oldest First)', value: 'customerResponseTime' },
|
|
371
|
+
{ name: 'Response Due Date (Ascending)', value: 'responseDueDate' },
|
|
372
|
+
{ name: 'Response Due Date (Descending)', value: '-responseDueDate' },
|
|
373
|
+
],
|
|
374
|
+
description: 'Sort tickets by the specified attribute. Prefix with (-) for descending order.',
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
displayName: 'Status',
|
|
378
|
+
name: 'status',
|
|
379
|
+
type: 'string',
|
|
380
|
+
default: '',
|
|
381
|
+
placeholder: 'Open, On Hold, Escalated, Closed',
|
|
382
|
+
description: 'Status of the ticket (e.g., Open, On Hold, Escalated, Closed). Multiple values can be separated with commas.',
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
displayName: 'Team IDs',
|
|
386
|
+
name: 'teamIds',
|
|
387
|
+
type: 'string',
|
|
388
|
+
default: '',
|
|
389
|
+
placeholder: 'Unassigned or Team ID',
|
|
390
|
+
description: 'ID(s) of the team, or "Unassigned". Multiple values can be separated with commas.',
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
displayName: 'View ID',
|
|
394
|
+
name: 'viewId',
|
|
395
|
+
type: 'string',
|
|
396
|
+
default: '',
|
|
397
|
+
description: 'ID of the Zoho Desk view based on which tickets must be fetched',
|
|
398
|
+
},
|
|
399
|
+
],
|
|
260
400
|
},
|
|
261
401
|
// ================== Contact Resource Parameters ==================
|
|
262
402
|
{
|
|
@@ -459,13 +599,9 @@ class ZohoDesk {
|
|
|
459
599
|
loadOptions: {
|
|
460
600
|
async getDepartments() {
|
|
461
601
|
try {
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
: Array.isArray(responseData)
|
|
466
|
-
? responseData
|
|
467
|
-
: [];
|
|
468
|
-
return departments.map((department) => {
|
|
602
|
+
const departments = (await GenericFunctions_1.zohoDeskApiRequestAllItems.call(this, 'GET', '/departments', {}, {}, 0));
|
|
603
|
+
return departments
|
|
604
|
+
.map((department) => {
|
|
469
605
|
const name = department.name ||
|
|
470
606
|
department.departmentName ||
|
|
471
607
|
String(department.id);
|
|
@@ -473,7 +609,8 @@ class ZohoDesk {
|
|
|
473
609
|
name,
|
|
474
610
|
value: department.id,
|
|
475
611
|
};
|
|
476
|
-
})
|
|
612
|
+
})
|
|
613
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
477
614
|
}
|
|
478
615
|
catch (error) {
|
|
479
616
|
throw new Error(`Failed to load departments. Error: ${error.message}. Ensure Organization ID is correct and Desk API Base URL matches your region.`);
|
|
@@ -623,19 +760,74 @@ class ZohoDesk {
|
|
|
623
760
|
}
|
|
624
761
|
if (operation === 'getAll') {
|
|
625
762
|
const returnAll = this.getNodeParameter('returnAll', i, false);
|
|
626
|
-
const
|
|
763
|
+
const options = this.getNodeParameter('options', i, {});
|
|
764
|
+
const qs = {};
|
|
765
|
+
// Department filtering: options.departmentIds, options.departmentId, or legacy parameter
|
|
766
|
+
const topLevelDepartmentId = this.getNodeParameter('departmentId', i, '');
|
|
767
|
+
if (options.departmentIds) {
|
|
768
|
+
qs.departmentIds = options.departmentIds;
|
|
769
|
+
}
|
|
770
|
+
else if (options.departmentId) {
|
|
771
|
+
qs.departmentId = options.departmentId;
|
|
772
|
+
}
|
|
773
|
+
else if (topLevelDepartmentId) {
|
|
774
|
+
qs.departmentId = topLevelDepartmentId;
|
|
775
|
+
}
|
|
776
|
+
if (options.viewId) {
|
|
777
|
+
qs.viewId = options.viewId;
|
|
778
|
+
}
|
|
779
|
+
if (options.assignee) {
|
|
780
|
+
qs.assignee = options.assignee;
|
|
781
|
+
}
|
|
782
|
+
if (options.teamIds) {
|
|
783
|
+
qs.teamIds = options.teamIds;
|
|
784
|
+
}
|
|
785
|
+
if (options.channel) {
|
|
786
|
+
qs.channel = options.channel;
|
|
787
|
+
}
|
|
788
|
+
if (options.status) {
|
|
789
|
+
qs.status = options.status;
|
|
790
|
+
}
|
|
791
|
+
if (options.priority) {
|
|
792
|
+
qs.priority = options.priority;
|
|
793
|
+
}
|
|
794
|
+
if (options.sortBy) {
|
|
795
|
+
qs.sortBy = options.sortBy;
|
|
796
|
+
}
|
|
797
|
+
if (options.receivedInDays) {
|
|
798
|
+
qs.receivedInDays = options.receivedInDays;
|
|
799
|
+
}
|
|
800
|
+
if (options.fields) {
|
|
801
|
+
qs.fields = options.fields;
|
|
802
|
+
}
|
|
803
|
+
if (options.include) {
|
|
804
|
+
if (Array.isArray(options.include)) {
|
|
805
|
+
if (options.include.length > 0) {
|
|
806
|
+
qs.include = options.include.join(',');
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
else if (typeof options.include === 'string' && options.include.trim()) {
|
|
810
|
+
qs.include = options.include.trim();
|
|
811
|
+
}
|
|
812
|
+
}
|
|
627
813
|
if (returnAll) {
|
|
628
|
-
const responseData = await GenericFunctions_1.zohoDeskApiRequestAllItems.call(this, 'GET', '/tickets', {},
|
|
814
|
+
const responseData = await GenericFunctions_1.zohoDeskApiRequestAllItems.call(this, 'GET', '/tickets', {}, qs, i);
|
|
629
815
|
for (const row of responseData) {
|
|
630
816
|
returnData.push({ json: row });
|
|
631
817
|
}
|
|
632
818
|
}
|
|
633
819
|
else {
|
|
634
|
-
const limit = this.getNodeParameter('limit', i);
|
|
635
|
-
|
|
820
|
+
const limit = this.getNodeParameter('limit', i, 50);
|
|
821
|
+
qs.limit = limit;
|
|
822
|
+
if (options.from !== undefined && options.from !== null && options.from !== '') {
|
|
823
|
+
qs.from = options.from;
|
|
824
|
+
}
|
|
825
|
+
const responseData = await GenericFunctions_1.zohoDeskApiRequest.call(this, 'GET', '/tickets', {}, qs, i);
|
|
636
826
|
const data = Array.isArray(responseData.data)
|
|
637
827
|
? responseData.data
|
|
638
|
-
:
|
|
828
|
+
: Array.isArray(responseData)
|
|
829
|
+
? responseData
|
|
830
|
+
: [responseData];
|
|
639
831
|
for (const row of data) {
|
|
640
832
|
returnData.push({ json: row });
|
|
641
833
|
}
|
|
@@ -27,7 +27,7 @@ function getDeskBaseUrl(tokenUrl = '', apiDomain = '', overrideBaseUrl = '') {
|
|
|
27
27
|
return 'https://desk.zoho.com.cn';
|
|
28
28
|
return 'https://desk.zoho.com';
|
|
29
29
|
}
|
|
30
|
-
async function deskRequest(context, method, endpoint, organizationId, body = {}) {
|
|
30
|
+
async function deskRequest(context, method, endpoint, organizationId, body = {}, qs = {}) {
|
|
31
31
|
const credentials = (await context.getCredentials('zohoCRMDeskOAuth2Api'));
|
|
32
32
|
const { oauthTokenData, accessTokenUrl, authUrl, deskBaseUrl } = credentials;
|
|
33
33
|
const baseUrl = getDeskBaseUrl(accessTokenUrl || authUrl || '', oauthTokenData?.api_domain || '', deskBaseUrl || '');
|
|
@@ -40,6 +40,9 @@ async function deskRequest(context, method, endpoint, organizationId, body = {})
|
|
|
40
40
|
},
|
|
41
41
|
json: true,
|
|
42
42
|
};
|
|
43
|
+
if (Object.keys(qs).length) {
|
|
44
|
+
options.qs = qs;
|
|
45
|
+
}
|
|
43
46
|
if (method !== 'GET' && method !== 'DELETE' && Object.keys(body).length) {
|
|
44
47
|
options.body = body;
|
|
45
48
|
}
|
|
@@ -50,6 +53,34 @@ async function deskRequest(context, method, endpoint, organizationId, body = {})
|
|
|
50
53
|
throw new n8n_workflow_1.NodeApiError(context.getNode(), error);
|
|
51
54
|
}
|
|
52
55
|
}
|
|
56
|
+
async function deskRequestAllItems(context, method, endpoint, organizationId, body = {}, qs = {}) {
|
|
57
|
+
const returnData = [];
|
|
58
|
+
let responseData;
|
|
59
|
+
const query = {
|
|
60
|
+
...qs,
|
|
61
|
+
limit: qs.limit || 100,
|
|
62
|
+
from: qs.from || 0,
|
|
63
|
+
};
|
|
64
|
+
do {
|
|
65
|
+
responseData = await deskRequest(context, method, endpoint, organizationId, body, query);
|
|
66
|
+
const items = Array.isArray(responseData?.data)
|
|
67
|
+
? responseData.data
|
|
68
|
+
: Array.isArray(responseData)
|
|
69
|
+
? responseData
|
|
70
|
+
: [];
|
|
71
|
+
if (items.length > 0) {
|
|
72
|
+
returnData.push(...items);
|
|
73
|
+
if (items.length < query.limit) {
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
query.from = query.from + items.length;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
} while (true);
|
|
82
|
+
return returnData;
|
|
83
|
+
}
|
|
53
84
|
async function deskWebhookRequest(context, method, endpoint, body = {}) {
|
|
54
85
|
const organizationId = context.getNodeParameter('organizationId');
|
|
55
86
|
return deskRequest(context, method, endpoint, organizationId, body);
|
|
@@ -208,16 +239,13 @@ class ZohoDeskTrigger {
|
|
|
208
239
|
async getDepartments() {
|
|
209
240
|
const organizationId = this.getNodeParameter('organizationId', 0, '');
|
|
210
241
|
try {
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
: Array.isArray(responseData)
|
|
215
|
-
? responseData
|
|
216
|
-
: [];
|
|
217
|
-
return departments.map((dept) => ({
|
|
242
|
+
const departments = await deskRequestAllItems(this, 'GET', '/departments', organizationId);
|
|
243
|
+
return departments
|
|
244
|
+
.map((dept) => ({
|
|
218
245
|
name: dept.name || dept.departmentName || String(dept.id),
|
|
219
246
|
value: dept.id,
|
|
220
|
-
}))
|
|
247
|
+
}))
|
|
248
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
221
249
|
}
|
|
222
250
|
catch (error) {
|
|
223
251
|
throw new Error(`Failed to load departments: ${error.message}. Ensure Organization ID is set correctly.`);
|