@trg-admin/n8n-nodes-zoho-desk 0.1.11 → 0.1.13
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.
|
@@ -168,6 +168,15 @@ class ZohoDesk {
|
|
|
168
168
|
default: '',
|
|
169
169
|
description: 'Custom field Task ID (maps to customFields.cf_task_id)',
|
|
170
170
|
},
|
|
171
|
+
{
|
|
172
|
+
displayName: 'Custom Fields (JSON)',
|
|
173
|
+
name: 'customFieldsJson',
|
|
174
|
+
type: 'string',
|
|
175
|
+
typeOptions: { alwaysOpenEditWindow: true },
|
|
176
|
+
default: '',
|
|
177
|
+
placeholder: '{"cf_serial_number":"SN-99821","cf_warranty_expiry":"2025-12-31"}',
|
|
178
|
+
description: 'Additional custom fields to update, as a JSON object with Zoho API names (for example cf_serial_number)',
|
|
179
|
+
},
|
|
171
180
|
{
|
|
172
181
|
displayName: 'Due Date',
|
|
173
182
|
name: 'dueDate',
|
|
@@ -393,6 +402,29 @@ class ZohoDesk {
|
|
|
393
402
|
async execute() {
|
|
394
403
|
const items = this.getInputData();
|
|
395
404
|
const returnData = [];
|
|
405
|
+
const parseCustomFields = (raw, itemIndex) => {
|
|
406
|
+
if (!raw)
|
|
407
|
+
return {};
|
|
408
|
+
if (typeof raw === 'object' && raw !== null) {
|
|
409
|
+
return raw;
|
|
410
|
+
}
|
|
411
|
+
if (typeof raw !== 'string' || raw.trim() === '') {
|
|
412
|
+
return {};
|
|
413
|
+
}
|
|
414
|
+
try {
|
|
415
|
+
const parsed = JSON.parse(raw);
|
|
416
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
417
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), new Error('Custom Fields (JSON) must be a JSON object, for example {"cf_serial_number":"SN-99821"}'), { itemIndex });
|
|
418
|
+
}
|
|
419
|
+
return parsed;
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
if (error instanceof n8n_workflow_1.NodeOperationError) {
|
|
423
|
+
throw error;
|
|
424
|
+
}
|
|
425
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), new Error(`Invalid Custom Fields (JSON): ${error.message}`), { itemIndex });
|
|
426
|
+
}
|
|
427
|
+
};
|
|
396
428
|
for (let i = 0; i < items.length; i++) {
|
|
397
429
|
const resource = this.getNodeParameter('resource', i);
|
|
398
430
|
const operation = this.getNodeParameter('operation', i);
|
|
@@ -430,17 +462,22 @@ class ZohoDesk {
|
|
|
430
462
|
const departmentId = this.getNodeParameter('departmentId', i);
|
|
431
463
|
const description = this.getNodeParameter('description', i, '');
|
|
432
464
|
const additionalFields = this.getNodeParameter('additionalFields', i, {});
|
|
433
|
-
const { cfTaskId, ...otherAdditionalFields } = additionalFields;
|
|
465
|
+
const { cfTaskId, customFieldsJson, ...otherAdditionalFields } = additionalFields;
|
|
466
|
+
const parsedCustomFields = parseCustomFields(customFieldsJson, i);
|
|
434
467
|
const body = {
|
|
435
468
|
subject,
|
|
436
469
|
email,
|
|
437
470
|
departmentId,
|
|
438
471
|
...otherAdditionalFields,
|
|
439
472
|
};
|
|
473
|
+
const customFields = {
|
|
474
|
+
...parsedCustomFields,
|
|
475
|
+
};
|
|
440
476
|
if (cfTaskId) {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
477
|
+
customFields.cf_task_id = cfTaskId;
|
|
478
|
+
}
|
|
479
|
+
if (Object.keys(customFields).length > 0) {
|
|
480
|
+
body.customFields = customFields;
|
|
444
481
|
}
|
|
445
482
|
if (description) {
|
|
446
483
|
body.description = description;
|
|
@@ -452,14 +489,19 @@ class ZohoDesk {
|
|
|
452
489
|
const ticketId = this.getNodeParameter('ticketId', i);
|
|
453
490
|
const description = this.getNodeParameter('description', i, '');
|
|
454
491
|
const additionalFields = this.getNodeParameter('additionalFields', i, {});
|
|
455
|
-
const { cfTaskId, ...otherAdditionalFields } = additionalFields;
|
|
492
|
+
const { cfTaskId, customFieldsJson, ...otherAdditionalFields } = additionalFields;
|
|
493
|
+
const parsedCustomFields = parseCustomFields(customFieldsJson, i);
|
|
456
494
|
const body = {
|
|
457
495
|
...otherAdditionalFields,
|
|
458
496
|
};
|
|
497
|
+
const customFields = {
|
|
498
|
+
...parsedCustomFields,
|
|
499
|
+
};
|
|
459
500
|
if (cfTaskId) {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
501
|
+
customFields.cf_task_id = cfTaskId;
|
|
502
|
+
}
|
|
503
|
+
if (Object.keys(customFields).length > 0) {
|
|
504
|
+
body.customFields = customFields;
|
|
463
505
|
}
|
|
464
506
|
if (description) {
|
|
465
507
|
body.description = description;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IHookFunctions, IWebhookFunctions, INodeType, INodeTypeDescription, IWebhookResponseData } from 'n8n-workflow';
|
|
1
|
+
import type { IHookFunctions, IWebhookFunctions, ILoadOptionsFunctions, INodeType, INodeTypeDescription, IWebhookResponseData } from 'n8n-workflow';
|
|
2
2
|
export declare class ZohoDeskTrigger implements INodeType {
|
|
3
3
|
description: INodeTypeDescription;
|
|
4
4
|
webhookMethods: {
|
|
@@ -8,5 +8,13 @@ export declare class ZohoDeskTrigger implements INodeType {
|
|
|
8
8
|
delete(this: IHookFunctions): Promise<boolean>;
|
|
9
9
|
};
|
|
10
10
|
};
|
|
11
|
+
methods: {
|
|
12
|
+
loadOptions: {
|
|
13
|
+
getDepartments(this: ILoadOptionsFunctions): Promise<{
|
|
14
|
+
name: string;
|
|
15
|
+
value: string;
|
|
16
|
+
}[]>;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
11
19
|
webhook(this: IWebhookFunctions): Promise<IWebhookResponseData>;
|
|
12
20
|
}
|
|
@@ -27,10 +27,9 @@ 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
|
|
30
|
+
async function deskRequest(context, method, endpoint, organizationId, body = {}) {
|
|
31
31
|
const credentials = (await context.getCredentials('zohoCRMDeskOAuth2Api'));
|
|
32
32
|
const { oauthTokenData, accessTokenUrl, authUrl, deskBaseUrl } = credentials;
|
|
33
|
-
const organizationId = context.getNodeParameter('organizationId');
|
|
34
33
|
const baseUrl = getDeskBaseUrl(accessTokenUrl || authUrl || '', oauthTokenData?.api_domain || '', deskBaseUrl || '');
|
|
35
34
|
const options = {
|
|
36
35
|
method,
|
|
@@ -51,6 +50,10 @@ async function deskWebhookRequest(context, method, endpoint, body = {}) {
|
|
|
51
50
|
throw new n8n_workflow_1.NodeApiError(context.getNode(), error);
|
|
52
51
|
}
|
|
53
52
|
}
|
|
53
|
+
async function deskWebhookRequest(context, method, endpoint, body = {}) {
|
|
54
|
+
const organizationId = context.getNodeParameter('organizationId');
|
|
55
|
+
return deskRequest(context, method, endpoint, organizationId, body);
|
|
56
|
+
}
|
|
54
57
|
class ZohoDeskTrigger {
|
|
55
58
|
constructor() {
|
|
56
59
|
this.description = {
|
|
@@ -101,12 +104,16 @@ class ZohoDeskTrigger {
|
|
|
101
104
|
],
|
|
102
105
|
},
|
|
103
106
|
{
|
|
104
|
-
displayName: '
|
|
107
|
+
displayName: 'Departments (Optional)',
|
|
105
108
|
name: 'departmentIds',
|
|
106
|
-
type: '
|
|
107
|
-
default:
|
|
108
|
-
description: '
|
|
109
|
-
|
|
109
|
+
type: 'multiOptions',
|
|
110
|
+
default: [],
|
|
111
|
+
description: 'Filter ticket/comment events to specific departments. Leave blank to receive events from all departments. Only applies to Ticket and Comment events.',
|
|
112
|
+
typeOptions: {
|
|
113
|
+
loadOptionsMethod: 'getDepartments',
|
|
114
|
+
loadOptionsDependsOn: ['organizationId'],
|
|
115
|
+
},
|
|
116
|
+
options: [],
|
|
110
117
|
},
|
|
111
118
|
{
|
|
112
119
|
displayName: 'Webhook Name',
|
|
@@ -145,15 +152,12 @@ class ZohoDeskTrigger {
|
|
|
145
152
|
async create() {
|
|
146
153
|
const webhookUrl = this.getNodeWebhookUrl('default');
|
|
147
154
|
const events = this.getNodeParameter('events');
|
|
148
|
-
const
|
|
155
|
+
const deptIds = this.getNodeParameter('departmentIds', []);
|
|
149
156
|
const webhookName = this.getNodeParameter('webhookName');
|
|
150
157
|
const webhookData = this.getWorkflowStaticData('node');
|
|
151
158
|
// Build subscriptions object per API spec
|
|
152
159
|
// Ticket/Comment events support optional departmentIds filter
|
|
153
160
|
// Ref: https://desk.zoho.com/support/WebhookDocument.do#Webhook
|
|
154
|
-
const deptIds = departmentIdsRaw
|
|
155
|
-
? departmentIdsRaw.split(',').map((id) => id.trim()).filter(Boolean)
|
|
156
|
-
: [];
|
|
157
161
|
const ticketEvents = new Set([
|
|
158
162
|
'Ticket_Add', 'Ticket_Update', 'Ticket_Delete',
|
|
159
163
|
'Ticket_Comment_Add', 'Ticket_Comment_Update', 'Ticket_Thread_Add',
|
|
@@ -199,6 +203,28 @@ class ZohoDeskTrigger {
|
|
|
199
203
|
},
|
|
200
204
|
},
|
|
201
205
|
};
|
|
206
|
+
this.methods = {
|
|
207
|
+
loadOptions: {
|
|
208
|
+
async getDepartments() {
|
|
209
|
+
const organizationId = this.getNodeParameter('organizationId', 0, '');
|
|
210
|
+
try {
|
|
211
|
+
const responseData = await deskRequest(this, 'GET', '/departments', organizationId);
|
|
212
|
+
const departments = Array.isArray(responseData.data)
|
|
213
|
+
? responseData.data
|
|
214
|
+
: Array.isArray(responseData)
|
|
215
|
+
? responseData
|
|
216
|
+
: [];
|
|
217
|
+
return departments.map((dept) => ({
|
|
218
|
+
name: dept.name || dept.departmentName || String(dept.id),
|
|
219
|
+
value: dept.id,
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
throw new Error(`Failed to load departments: ${error.message}. Ensure Organization ID is set correctly.`);
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
};
|
|
202
228
|
}
|
|
203
229
|
// Receive and process incoming webhook payload from Zoho Desk
|
|
204
230
|
// Ref: https://desk.zoho.com/support/WebhookDocument.do#EventsSupported
|