diff --git a/packages/nodes-base/credentials/FreshworksCrmApi.credentials.ts b/packages/nodes-base/credentials/FreshworksCrmApi.credentials.ts new file mode 100644 index 0000000000..115fb998f4 --- /dev/null +++ b/packages/nodes-base/credentials/FreshworksCrmApi.credentials.ts @@ -0,0 +1,27 @@ +import { + ICredentialType, + INodeProperties, +} from 'n8n-workflow'; + +export class FreshworksCrmApi implements ICredentialType { + name = 'freshworksCrmApi'; + displayName = 'Freshworks CRM API'; + documentationUrl = 'freshdesk'; + properties: INodeProperties[] = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string', + default: '', + placeholder: 'BDsTn15vHezBlt_XGp3Tig', + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + default: '', + placeholder: 'n8n-org', + description: 'Domain in the Freshworks CRM org URL. For example, in https://n8n-org.myfreshworks.com, the domain is n8n-org.', + }, + ]; +} diff --git a/packages/nodes-base/nodes/FreshworksCrm/FreshworksCrm.node.ts b/packages/nodes-base/nodes/FreshworksCrm/FreshworksCrm.node.ts new file mode 100644 index 0000000000..b4b6255c1a --- /dev/null +++ b/packages/nodes-base/nodes/FreshworksCrm/FreshworksCrm.node.ts @@ -0,0 +1,996 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + adjustAccounts, + adjustAttendees, + freshworksCrmApiRequest, + getAllItemsViewId, + handleListing, + loadResource, + throwOnEmptyFilter, + throwOnEmptyUpdate, +} from './GenericFunctions'; + +import { + accountFields, + accountOperations, + appointmentFields, + appointmentOperations, + contactFields, + contactOperations, + dealFields, + dealOperations, + noteFields, + noteOperations, + salesActivityFields, + salesActivityOperations, + taskFields, + taskOperations, +} from './descriptions'; + +import { + FreshworksConfigResponse, + LoadedCurrency, + LoadedUser, + LoadOption, +} from './types'; + +import { + tz, +} from 'moment-timezone'; + +export class FreshworksCrm implements INodeType { + description: INodeTypeDescription = { + displayName: 'Freshworks CRM', + name: 'freshworksCrm', + icon: 'file:freshworksCrm.svg', + group: ['transform'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume the Freshworks CRM API', + defaults: { + name: 'Freshworks CRM', + color: '#ffa800', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'freshworksCrmApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Account', + value: 'account', + }, + { + name: 'Appointment', + value: 'appointment', + }, + { + name: 'Contact', + value: 'contact', + }, + { + name: 'Deal', + value: 'deal', + }, + { + name: 'Note', + value: 'note', + }, + { + name: 'Sales Activity', + value: 'salesActivity', + }, + { + name: 'Task', + value: 'task', + }, + ], + default: 'account', + }, + ...accountOperations, + ...accountFields, + ...appointmentOperations, + ...appointmentFields, + ...contactOperations, + ...contactFields, + ...dealOperations, + ...dealFields, + ...noteOperations, + ...noteFields, + ...salesActivityOperations, + ...salesActivityFields, + ...taskOperations, + ...taskFields, + ], + }; + + methods = { + loadOptions: { + async getAccounts(this: ILoadOptionsFunctions) { + const viewId = await getAllItemsViewId.call(this, { fromLoadOptions: true }); + const responseData = await handleListing.call(this, 'GET', `/sales_accounts/view/${viewId}`); + + return responseData.map(({ name, id }) => ({ name, value: id })) as LoadOption[]; + }, + + async getAccountViews(this: ILoadOptionsFunctions) { + const responseData = await handleListing.call(this, 'GET', '/sales_accounts/filters'); + return responseData.map(({ name, id }) => ({ name, value: id })) as LoadOption[]; + }, + + async getBusinessTypes(this: ILoadOptionsFunctions) { + return await loadResource.call(this, 'business_types'); + }, + + async getCampaigns(this: ILoadOptionsFunctions) { + return await loadResource.call(this, 'campaigns'); + }, + + async getContactStatuses(this: ILoadOptionsFunctions) { + return await loadResource.call(this, 'contact_statuses'); + }, + + async getContactViews(this: ILoadOptionsFunctions) { + const responseData = await handleListing.call(this, 'GET', '/contacts/filters'); + + return responseData.map(({ name, id }) => ({ name, value: id })) as LoadOption[]; + }, + + async getCurrencies(this: ILoadOptionsFunctions) { + const response = await freshworksCrmApiRequest.call( + this, 'GET', '/selector/currencies', + ) as FreshworksConfigResponse; + + const key = Object.keys(response)[0]; + + return response[key].map(({ currency_code, id }) => ({ name: currency_code, value: id })); + }, + + async getDealPaymentStatuses(this: ILoadOptionsFunctions) { + return await loadResource.call(this, 'deal_payment_statuses'); + }, + + async getDealPipelines(this: ILoadOptionsFunctions) { + return await loadResource.call(this, 'deal_pipelines'); + }, + + async getDealProducts(this: ILoadOptionsFunctions) { + return await loadResource.call(this, 'deal_products'); + }, + + async getDealReasons(this: ILoadOptionsFunctions) { + return await loadResource.call(this, 'deal_reasons'); + }, + + async getDealStages(this: ILoadOptionsFunctions) { + return await loadResource.call(this, 'deal_stages'); + }, + + async getDealTypes(this: ILoadOptionsFunctions) { + return await loadResource.call(this, 'deal_types'); + }, + + async getDealViews(this: ILoadOptionsFunctions) { + const responseData = await handleListing.call(this, 'GET', '/deals/filters'); + + return responseData.map(({ name, id }) => ({ name, value: id })) as LoadOption[]; + }, + + async getIndustryTypes(this: ILoadOptionsFunctions) { + return await loadResource.call(this, 'industry_types'); + }, + + async getLifecycleStages(this: ILoadOptionsFunctions) { + return await loadResource.call(this, 'lifecycle_stages'); + }, + + async getOutcomes(this: ILoadOptionsFunctions) { + return await loadResource.call(this, 'sales_activity_outcomes'); + }, + + async getSalesActivityTypes(this: ILoadOptionsFunctions) { + return await loadResource.call(this, 'sales_activity_types'); + }, + + async getTerritories(this: ILoadOptionsFunctions) { + return await loadResource.call(this, 'territories'); + }, + + async getUsers(this: ILoadOptionsFunctions) { // for attendees, owners, and creators + const response = await freshworksCrmApiRequest.call( + this, 'GET', `/selector/owners`, + ) as FreshworksConfigResponse; + + const key = Object.keys(response)[0]; + + return response[key].map( + ({ display_name, id }) => ({ name: display_name, value: id }), + ); + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + const defaultTimezone = this.getTimezone(); + + let responseData; + + for (let i = 0; i < items.length; i++) { + + try { + + if (resource === 'account') { + + // ********************************************************************** + // account + // ********************************************************************** + + // https://developers.freshworks.com/crm/api/#accounts + + if (operation === 'create') { + + // ---------------------------------------- + // account: create + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#create_account + + const body = { + name: this.getNodeParameter('name', i), + } as IDataObject; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (Object.keys(additionalFields).length) { + Object.assign(body, additionalFields); + } + + responseData = await freshworksCrmApiRequest.call(this, 'POST', '/sales_accounts', body); + responseData = responseData.sales_account; + + } else if (operation === 'delete') { + + // ---------------------------------------- + // account: delete + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#delete_account + + const accountId = this.getNodeParameter('accountId', i); + + const endpoint = `/sales_accounts/${accountId}`; + await freshworksCrmApiRequest.call(this, 'DELETE', endpoint); + responseData = { success: true }; + + } else if (operation === 'get') { + + // ---------------------------------------- + // account: get + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#view_account + + const accountId = this.getNodeParameter('accountId', i); + + const endpoint = `/sales_accounts/${accountId}`; + responseData = await freshworksCrmApiRequest.call(this, 'GET', endpoint); + responseData = responseData.sales_account; + + } else if (operation === 'getAll') { + + // ---------------------------------------- + // account: getAll + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#list_all_accounts + + const view = this.getNodeParameter('view', i) as string; + + responseData = await handleListing.call(this, 'GET', `/sales_accounts/view/${view}`); + + } else if (operation === 'update') { + + // ---------------------------------------- + // account: update + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#update_a_account + + const body = {} as IDataObject; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + if (Object.keys(updateFields).length) { + Object.assign(body, updateFields); + } else { + throwOnEmptyUpdate.call(this, resource); + } + + const accountId = this.getNodeParameter('accountId', i); + + const endpoint = `/sales_accounts/${accountId}`; + responseData = await freshworksCrmApiRequest.call(this, 'PUT', endpoint, body); + responseData = responseData.sales_account; + + } + + } else if (resource === 'appointment') { + + // ********************************************************************** + // appointment + // ********************************************************************** + + // https://developers.freshworks.com/crm/api/#appointments + + if (operation === 'create') { + + // ---------------------------------------- + // appointment: create + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#create_appointment + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject & { + time_zone: string; + is_allday: boolean; + }; + + const startDate = this.getNodeParameter('fromDate', i) as string; + const endDate = this.getNodeParameter('endDate', i) as string; + const attendees = this.getNodeParameter('attendees.attendee', i, []) as [{ type: string, contactId: string, userId: string }]; + + const timezone = additionalFields.time_zone ?? defaultTimezone; + + let allDay = false; + + if (additionalFields.is_allday) { + allDay = additionalFields.is_allday as boolean; + } + + const start = tz(startDate, timezone); + const end = tz(endDate, timezone); + + const body = { + title: this.getNodeParameter('title', i), + from_date: start.format(), + end_date: (allDay) ? start.format() : end.format(), + } as IDataObject; + + Object.assign(body, additionalFields); + + if (attendees.length) { + body['appointment_attendees_attributes'] = adjustAttendees(attendees); + } + responseData = await freshworksCrmApiRequest.call(this, 'POST', '/appointments', body); + responseData = responseData.appointment; + + } else if (operation === 'delete') { + + // ---------------------------------------- + // appointment: delete + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#delete_a_appointment + + const appointmentId = this.getNodeParameter('appointmentId', i); + + const endpoint = `/appointments/${appointmentId}`; + await freshworksCrmApiRequest.call(this, 'DELETE', endpoint); + responseData = { success: true }; + + } else if (operation === 'get') { + + // ---------------------------------------- + // appointment: get + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#view_a_appointment + + const appointmentId = this.getNodeParameter('appointmentId', i); + + const endpoint = `/appointments/${appointmentId}`; + responseData = await freshworksCrmApiRequest.call(this, 'GET', endpoint); + responseData = responseData.appointment; + + } else if (operation === 'getAll') { + + // ---------------------------------------- + // appointment: getAll + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#list_all_appointments + + const { filter, include } = this.getNodeParameter('filters', i) as { + filter: string; + include: string[]; + }; + + const qs: IDataObject = {}; + + if (filter) { + qs.filter = filter; + } + + if (include) { + qs.include = include; + } + responseData = await handleListing.call(this, 'GET', '/appointments', {}, qs); + + } else if (operation === 'update') { + + // ---------------------------------------- + // appointment: update + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#update_a_appointment + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject & { + from_date: string; + end_date: string; + time_zone: string; + }; + + const attendees = this.getNodeParameter('updateFields.attendees.attendee', i, []) as [{ type: string, contactId: string, userId: string }]; + + if (!Object.keys(updateFields).length) { + throwOnEmptyUpdate.call(this, resource); + } + + const body = {} as IDataObject; + const { from_date, end_date, ...rest } = updateFields; + + const timezone = rest.time_zone ?? defaultTimezone; + + if (from_date) { + body.from_date = tz(from_date, timezone).format(); + } + + if (end_date) { + body.end_date = tz(end_date, timezone).format(); + } + + Object.assign(body, rest); + + if (attendees.length) { + body['appointment_attendees_attributes'] = adjustAttendees(attendees); + delete body.attendees; + } + + const appointmentId = this.getNodeParameter('appointmentId', i); + + const endpoint = `/appointments/${appointmentId}`; + responseData = await freshworksCrmApiRequest.call(this, 'PUT', endpoint, body); + responseData = responseData.appointment; + + } + + } else if (resource === 'contact') { + + // ********************************************************************** + // contact + // ********************************************************************** + + // https://developers.freshworks.com/crm/api/#contacts + + if (operation === 'create') { + + // ---------------------------------------- + // contact: create + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#create_contact + + const body = { + first_name: this.getNodeParameter('firstName', i), + last_name: this.getNodeParameter('lastName', i), + emails: this.getNodeParameter('emails', i), + } as IDataObject; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (Object.keys(additionalFields).length) { + Object.assign(body, adjustAccounts(additionalFields)); + } + + responseData = await freshworksCrmApiRequest.call(this, 'POST', '/contacts', body); + responseData = responseData.contact; + + } else if (operation === 'delete') { + + // ---------------------------------------- + // contact: delete + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#delete_a_contact + + const contactId = this.getNodeParameter('contactId', i); + + const endpoint = `/contacts/${contactId}`; + await freshworksCrmApiRequest.call(this, 'DELETE', endpoint); + responseData = { success: true }; + + } else if (operation === 'get') { + + // ---------------------------------------- + // contact: get + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#view_a_contact + + const contactId = this.getNodeParameter('contactId', i); + + const endpoint = `/contacts/${contactId}`; + responseData = await freshworksCrmApiRequest.call(this, 'GET', endpoint); + responseData = responseData.contact; + + } else if (operation === 'getAll') { + + // ---------------------------------------- + // contact: getAll + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#list_all_contacts + + const view = this.getNodeParameter('view', i) as string; + + responseData = await handleListing.call(this, 'GET', `/contacts/view/${view}`); + + } else if (operation === 'update') { + + // ---------------------------------------- + // contact: update + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#update_a_contact + + const body = {} as IDataObject; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + if (Object.keys(updateFields).length) { + Object.assign(body, adjustAccounts(updateFields)); + } else { + throwOnEmptyUpdate.call(this, resource); + } + + const contactId = this.getNodeParameter('contactId', i); + + const endpoint = `/contacts/${contactId}`; + responseData = await freshworksCrmApiRequest.call(this, 'PUT', endpoint, body); + responseData = responseData.contact; + + } + + } else if (resource === 'deal') { + + // ********************************************************************** + // deal + // ********************************************************************** + + // https://developers.freshworks.com/crm/api/#deals + + if (operation === 'create') { + + // ---------------------------------------- + // deal: create + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#create_deal + + const body = { + name: this.getNodeParameter('name', i), + amount: this.getNodeParameter('amount', i), + } as IDataObject; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (Object.keys(additionalFields).length) { + Object.assign(body, adjustAccounts(additionalFields)); + } + + responseData = await freshworksCrmApiRequest.call(this, 'POST', '/deals', body); + responseData = responseData.deal; + + } else if (operation === 'delete') { + + // ---------------------------------------- + // deal: delete + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#delete_a_deal + + const dealId = this.getNodeParameter('dealId', i); + + await freshworksCrmApiRequest.call(this, 'DELETE', `/deals/${dealId}`); + responseData = { success: true }; + + } else if (operation === 'get') { + + // ---------------------------------------- + // deal: get + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#view_a_deal + + const dealId = this.getNodeParameter('dealId', i); + + responseData = await freshworksCrmApiRequest.call(this, 'GET', `/deals/${dealId}`); + responseData = responseData.deal; + + } else if (operation === 'getAll') { + + // ---------------------------------------- + // deal: getAll + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#list_all_deals + + const view = this.getNodeParameter('view', i) as string; + + responseData = await handleListing.call(this, 'GET', `/deals/view/${view}`); + + } else if (operation === 'update') { + + // ---------------------------------------- + // deal: update + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#update_a_deal + + const body = {} as IDataObject; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + if (Object.keys(updateFields).length) { + Object.assign(body, adjustAccounts(updateFields)); + } else { + throwOnEmptyUpdate.call(this, resource); + } + + const dealId = this.getNodeParameter('dealId', i); + + responseData = await freshworksCrmApiRequest.call(this, 'PUT', `/deals/${dealId}`, body); + responseData = responseData.deal; + + } + + } else if (resource === 'note') { + + // ********************************************************************** + // note + // ********************************************************************** + + // https://developers.freshworks.com/crm/api/#notes + + if (operation === 'create') { + + // ---------------------------------------- + // note: create + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#create_note + + const body = { + description: this.getNodeParameter('description', i), + targetable_id: this.getNodeParameter('targetable_id', i), + targetable_type: this.getNodeParameter('targetableType', i), + } as IDataObject; + + responseData = await freshworksCrmApiRequest.call(this, 'POST', '/notes', body); + responseData = responseData.note; + + } else if (operation === 'delete') { + + // ---------------------------------------- + // note: delete + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#delete_a_note + + const noteId = this.getNodeParameter('noteId', i); + + await freshworksCrmApiRequest.call(this, 'DELETE', `/notes/${noteId}`); + responseData = { success: true }; + + } else if (operation === 'update') { + + // ---------------------------------------- + // note: update + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#update_a_note + + const body = {} as IDataObject; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + if (Object.keys(updateFields).length) { + Object.assign(body, updateFields); + } else { + throwOnEmptyUpdate.call(this, resource); + } + + const noteId = this.getNodeParameter('noteId', i); + + responseData = await freshworksCrmApiRequest.call(this, 'PUT', `/notes/${noteId}`, body); + responseData = responseData.note; + + } + + } else if (resource === 'salesActivity') { + + // ********************************************************************** + // salesActivity + // ********************************************************************** + + // https://developers.freshworks.com/crm/api/#sales-activities + + if (operation === 'create') { + + // ---------------------------------------- + // salesActivity: create + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#create_sales_activity + + const startDate = this.getNodeParameter('from_date', i) as string; + const endDate = this.getNodeParameter('end_date', i) as string; + + const body = { + sales_activity_type_id: this.getNodeParameter('sales_activity_type_id', i), + title: this.getNodeParameter('title', i), + owner_id: this.getNodeParameter('ownerId', i), + start_date: tz(startDate, defaultTimezone).format(), + end_date: tz(endDate, defaultTimezone).format(), + targetable_type: this.getNodeParameter('targetableType', i), + targetable_id: this.getNodeParameter('targetable_id', i), + } as IDataObject; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (Object.keys(additionalFields).length) { + Object.assign(body, additionalFields); + } + + responseData = await freshworksCrmApiRequest.call(this, 'POST', '/sales_activities', { sales_activity: body }); + responseData = responseData.sales_activity; + + } else if (operation === 'delete') { + + // ---------------------------------------- + // salesActivity: delete + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#delete_a_sales_activity + + const salesActivityId = this.getNodeParameter('salesActivityId', i); + + const endpoint = `/sales_activities/${salesActivityId}`; + await freshworksCrmApiRequest.call(this, 'DELETE', endpoint); + responseData = { success: true }; + + } else if (operation === 'get') { + + // ---------------------------------------- + // salesActivity: get + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#view_a_sales_activity + + const salesActivityId = this.getNodeParameter('salesActivityId', i); + + const endpoint = `/sales_activities/${salesActivityId}`; + responseData = await freshworksCrmApiRequest.call(this, 'GET', endpoint); + responseData = responseData.sales_activity; + + } else if (operation === 'getAll') { + + // ---------------------------------------- + // salesActivity: getAll + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#list_all_sales_activities + + responseData = await handleListing.call(this, 'GET', '/sales_activities'); + + } else if (operation === 'update') { + + // ---------------------------------------- + // salesActivity: update + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#update_a_sales_activity + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject & { + from_date: string; + end_date: string; + time_zone: string; + }; + + if (!Object.keys(updateFields).length) { + throwOnEmptyUpdate.call(this, resource); + } + + const body = {} as IDataObject; + const { from_date, end_date, ...rest } = updateFields; + + if (from_date) { + body.from_date = tz(from_date, defaultTimezone).format(); + } + + if (end_date) { + body.end_date = tz(end_date, defaultTimezone).format(); + } + + if (Object.keys(rest).length) { + Object.assign(body, rest); + } + + const salesActivityId = this.getNodeParameter('salesActivityId', i); + + const endpoint = `/sales_activities/${salesActivityId}`; + responseData = await freshworksCrmApiRequest.call(this, 'PUT', endpoint, body); + responseData = responseData.sales_activity; + + } + + } else if (resource === 'task') { + + // ********************************************************************** + // task + // ********************************************************************** + + // https://developers.freshworks.com/crm/api/#tasks + + if (operation === 'create') { + + // ---------------------------------------- + // task: create + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#create_task + + const dueDate = this.getNodeParameter('dueDate', i); + + const body = { + title: this.getNodeParameter('title', i), + owner_id: this.getNodeParameter('ownerId', i), + due_date: tz(dueDate, defaultTimezone).format(), + targetable_type: this.getNodeParameter('targetableType', i), + targetable_id: this.getNodeParameter('targetable_id', i), + } as IDataObject; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (Object.keys(additionalFields).length) { + Object.assign(body, additionalFields); + } + + responseData = await freshworksCrmApiRequest.call(this, 'POST', '/tasks', body); + responseData = responseData.task; + + } else if (operation === 'delete') { + + // ---------------------------------------- + // task: delete + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#delete_a_task + + const taskId = this.getNodeParameter('taskId', i); + + await freshworksCrmApiRequest.call(this, 'DELETE', `/tasks/${taskId}`); + responseData = { success: true }; + + } else if (operation === 'get') { + + // ---------------------------------------- + // task: get + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#view_a_task + + const taskId = this.getNodeParameter('taskId', i); + + responseData = await freshworksCrmApiRequest.call(this, 'GET', `/tasks/${taskId}`); + responseData = responseData.task; + + } else if (operation === 'getAll') { + + // ---------------------------------------- + // task: getAll + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#list_all_tasks + + const { filter, include } = this.getNodeParameter('filters', i) as { + filter: string; + include: string; + }; + + const qs: IDataObject = { + filter: 'open', + }; + + if (filter) { + qs.filter = filter; + } + + if (include) { + qs.include = include; + } + + responseData = await handleListing.call(this, 'GET', '/tasks', {}, qs); + + } else if (operation === 'update') { + + // ---------------------------------------- + // task: update + // ---------------------------------------- + + // https://developers.freshworks.com/crm/api/#update_a_task + + const body = {} as IDataObject; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + if (!Object.keys(updateFields).length) { + throwOnEmptyUpdate.call(this, resource); + } + + const { dueDate, ...rest } = updateFields; + + if (dueDate) { + body.due_date = tz(dueDate, defaultTimezone).format(); + } + + if (Object.keys(rest).length) { + Object.assign(body, rest); + } + + const taskId = this.getNodeParameter('taskId', i); + + responseData = await freshworksCrmApiRequest.call(this, 'PUT', `/tasks/${taskId}`, body); + responseData = responseData.task; + + } + + } + + } catch (error) { + if (this.continueOnFail()) { + returnData.push({ json: { error: error.message } }); + continue; + } + throw error; + } + + Array.isArray(responseData) + ? returnData.push(...responseData) + : returnData.push(responseData); + + } + + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/FreshworksCrm/GenericFunctions.ts b/packages/nodes-base/nodes/FreshworksCrm/GenericFunctions.ts new file mode 100644 index 0000000000..b7d27d093b --- /dev/null +++ b/packages/nodes-base/nodes/FreshworksCrm/GenericFunctions.ts @@ -0,0 +1,215 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + NodeApiError, + NodeOperationError, +} from 'n8n-workflow'; + +import { + OptionsWithUri, +} from 'request'; + +import { + FreshworksConfigResponse, + FreshworksCrmApiCredentials, + SalesAccounts, + ViewsResponse, +} from './types'; + +import { + omit, +} from 'lodash'; + +export async function freshworksCrmApiRequest( + this: IExecuteFunctions | ILoadOptionsFunctions, + method: string, + endpoint: string, + body: IDataObject = {}, + qs: IDataObject = {}, +) { + const { apiKey, domain } = this.getCredentials('freshworksCrmApi') as FreshworksCrmApiCredentials; + + const options: OptionsWithUri = { + headers: { + Authorization: `Token token=${apiKey}`, + }, + method, + body, + qs, + uri: `https://${domain}.myfreshworks.com/crm/sales/api${endpoint}`, + json: true, + }; + + if (!Object.keys(body).length) { + delete options.body; + } + + if (!Object.keys(qs).length) { + delete options.qs; + } + try { + return await this.helpers.request!(options); + } catch (error) { + throw new NodeApiError(this.getNode(), error); + } +} + +export async function getAllItemsViewId( + this: IExecuteFunctions | ILoadOptionsFunctions, + { fromLoadOptions } = { fromLoadOptions: false }, +) { + let resource = this.getNodeParameter('resource', 0) as string; + let keyword = 'All'; + + if (resource === 'account' || fromLoadOptions) { + resource = 'sales_account'; // adjust resource to endpoint + } + + if (resource === 'deal') { + keyword = 'My Deals'; // no 'All Deals' available + } + + const response = await freshworksCrmApiRequest.call(this, 'GET', `/${resource}s/filters`) as ViewsResponse; + + const view = response.filters.find(v => v.name.includes(keyword)); + + if (!view) { + throw new NodeOperationError(this.getNode(), 'Failed to get all items view'); + } + + return view.id.toString(); +} + +export async function freshworksCrmApiRequestAllItems( + this: IExecuteFunctions | ILoadOptionsFunctions, + method: string, + endpoint: string, + body: IDataObject = {}, + qs: IDataObject = {}, +) { + const returnData: IDataObject[] = []; + let response: any; // tslint:disable-line: no-any + + qs.page = 1; + + do { + response = await freshworksCrmApiRequest.call(this, method, endpoint, body, qs); + const key = Object.keys(response)[0]; + returnData.push(...response[key]); + qs.page++; + } while ( + response.meta.total_pages && qs.page <= response.meta.total_pages + ); + + return returnData; +} + +export async function handleListing( + this: IExecuteFunctions | ILoadOptionsFunctions, + method: string, + endpoint: string, + body: IDataObject = {}, + qs: IDataObject = {}, +) { + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + + if (returnAll) { + return await freshworksCrmApiRequestAllItems.call(this, method, endpoint, body, qs); + } + + const responseData = await freshworksCrmApiRequestAllItems.call(this, method, endpoint, body, qs); + const limit = this.getNodeParameter('limit', 0) as number; + + if (limit) return responseData.slice(0, limit); + + return responseData; +} + +/** + * Load resources for options, except users. + * + * See: https://developers.freshworks.com/crm/api/#admin_configuration + */ +export async function loadResource( + this: ILoadOptionsFunctions, + resource: string, +) { + const response = await freshworksCrmApiRequest.call( + this, 'GET', `/selector/${resource}`, + ) as FreshworksConfigResponse; + + const key = Object.keys(response)[0]; + return response[key].map(({ name, id }) => ({ name, value: id })); +} + +export function adjustAttendees(attendees: [{ type: string, contactId: string, userId: string }]) { + return attendees.map((attendee) => { + if (attendee.type === 'contact') { + return { + attendee_type: 'Contact', + attendee_id: attendee.contactId.toString(), + }; + } else if (attendee.type === 'user') { + return { + attendee_type: 'FdMultitenant::User', + attendee_id: attendee.userId.toString(), + }; + } + }); +} + + +// /** +// * Adjust attendee data from n8n UI to the format expected by Freshworks CRM API. +// */ +// export function adjustAttendees(additionalFields: IDataObject & Attendees) { +// if (!additionalFields?.appointment_attendees_attributes) return additionalFields; + +// return { +// ...omit(additionalFields, ['appointment_attendees_attributes']), +// appointment_attendees_attributes: additionalFields.appointment_attendees_attributes.map(attendeeId => { +// return { type: 'user', id: attendeeId }; +// }), +// }; +// } + +/** + * Adjust account data from n8n UI to the format expected by Freshworks CRM API. + */ +export function adjustAccounts(additionalFields: IDataObject & SalesAccounts) { + if (!additionalFields?.sales_accounts) return additionalFields; + + const adjusted = additionalFields.sales_accounts.map(accountId => { + return { id: accountId, is_primary: false }; + }); + + adjusted[0].is_primary = true; + + return { + ...omit(additionalFields, ['sales_accounts']), + sales_accounts: adjusted, + }; +} + +export function throwOnEmptyUpdate( + this: IExecuteFunctions, + resource: string, +) { + throw new NodeOperationError( + this.getNode(), + `Please enter at least one field to update for the ${resource}.`, + ); +} + +export function throwOnEmptyFilter( + this: IExecuteFunctions, +) { + throw new NodeOperationError( + this.getNode(), + `Please select at least one filter.`, + ); +} diff --git a/packages/nodes-base/nodes/FreshworksCrm/descriptions/AccountDescription.ts b/packages/nodes-base/nodes/FreshworksCrm/descriptions/AccountDescription.ts new file mode 100644 index 0000000000..645aae75e2 --- /dev/null +++ b/packages/nodes-base/nodes/FreshworksCrm/descriptions/AccountDescription.ts @@ -0,0 +1,507 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const accountOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'account', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create an account', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete an account', + }, + { + name: 'Get', + value: 'get', + description: 'Retrieve an account', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Retrieve all accounts', + }, + { + name: 'Update', + value: 'update', + description: 'Update an account', + }, + ], + default: 'create', + }, +] as INodeProperties[]; + +export const accountFields = [ + // ---------------------------------------- + // account: create + // ---------------------------------------- + { + displayName: 'Name', + name: 'name', + description: 'Name of the account', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'account', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'account', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Address', + name: 'address', + type: 'string', + default: '', + description: 'Address of the account', + }, + { + displayName: 'Annual Revenue', + name: 'annual_revenue', + type: 'number', + default: 0, + description: 'Annual revenue of the account', + }, + { + displayName: 'Business Type ID', + name: 'business_type_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getBusinessTypes', + }, + description: 'ID of the business that the account belongs to', + }, + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + description: 'City that the account belongs to', + }, + { + displayName: 'Country', + name: 'country', + type: 'string', + default: '', + description: 'Country that the account belongs to', + }, + { + displayName: 'Facebook', + name: 'facebook', + type: 'string', + default: '', + description: 'Facebook username of the account', + }, + { + displayName: 'Industry Type ID', + name: 'industry_type_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getIndustryTypes', + }, + description: 'ID of the industry that the account belongs to', + }, + { + displayName: 'LinkedIn', + name: 'linkedin', + type: 'string', + default: '', + description: 'LinkedIn account of the account', + }, + { + displayName: 'Number of Employees', + name: 'number_of_employees', + type: 'number', + default: 0, + description: 'Number of employees in the account', + }, + { + displayName: 'Owner ID', + name: 'owner_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + description: 'ID of the user to whom the account is assigned', + }, + { + displayName: 'Parent Sales Account ID', + name: 'parent_sales_account_id', + type: 'string', + default: '', + description: 'Parent account ID of the account', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + description: 'Phone number of the account', + }, + { + displayName: 'State', + name: 'state', + type: 'string', + default: '', + description: 'State that the account belongs to', + }, + { + displayName: 'Territory ID', + name: 'territory_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getTerritories', + }, + description: 'ID of the territory that the account belongs to', + }, + { + displayName: 'Twitter', + name: 'twitter', + type: 'string', + default: '', + description: 'Twitter username of the account', + }, + { + displayName: 'Website', + name: 'website', + type: 'string', + default: '', + description: 'Website of the account', + }, + { + displayName: 'Zipcode', + name: 'zipcode', + type: 'string', + default: '', + description: 'Zipcode of the region that the account belongs to', + }, + ], + }, + + // ---------------------------------------- + // account: delete + // ---------------------------------------- + { + displayName: 'Account ID', + name: 'accountId', + description: 'ID of the account to delete', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'account', + ], + operation: [ + 'delete', + ], + }, + }, + }, + + // ---------------------------------------- + // account: get + // ---------------------------------------- + { + displayName: 'Account ID', + name: 'accountId', + description: 'ID of the account to retrieve', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'account', + ], + operation: [ + 'get', + ], + }, + }, + }, + + // ---------------------------------------- + // account: getAll + // ---------------------------------------- + { + displayName: 'View', + name: 'view', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getAccountViews', + }, + displayOptions: { + show: { + resource: [ + 'account', + ], + operation: [ + 'getAll', + ], + }, + }, + default: '', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Whether to return all results or only up to a given limit', + displayOptions: { + show: { + resource: [ + 'account', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + description: 'How many results to return', + typeOptions: { + minValue: 1, + }, + displayOptions: { + show: { + resource: [ + 'account', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + + // ---------------------------------------- + // account: update + // ---------------------------------------- + { + displayName: 'Account ID', + name: 'accountId', + description: 'ID of the account to update', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'account', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'account', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Address', + name: 'address', + type: 'string', + default: '', + description: 'Address of the account', + }, + { + displayName: 'Annual Revenue', + name: 'annual_revenue', + type: 'number', + default: 0, + description: 'Annual revenue of the account', + }, + { + displayName: 'Business Type ID', + name: 'business_type_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getBusinessTypes', + }, + description: 'ID of the business that the account belongs to', + }, + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + description: 'City that the account belongs to', + }, + { + displayName: 'Country', + name: 'country', + type: 'string', + default: '', + description: 'Country that the account belongs to', + }, + { + displayName: 'Facebook', + name: 'facebook', + type: 'string', + default: '', + description: 'Facebook username of the account', + }, + { + displayName: 'Industry Type ID', + name: 'industry_type_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getIndustryTypes', + }, + description: 'ID of the industry that the account belongs to', + }, + { + displayName: 'LinkedIn', + name: 'linkedin', + type: 'string', + default: '', + description: 'LinkedIn account of the account', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'Name of the account', + }, + { + displayName: 'Number of Employees', + name: 'number_of_employees', + type: 'number', + default: 0, + description: 'Number of employees in the account', + }, + { + displayName: 'Owner ID', + name: 'owner_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + description: 'ID of the user to whom the account is assigned', + }, + { + displayName: 'Parent Sales Account ID', + name: 'parent_sales_account_id', + type: 'string', + default: '', + description: 'Parent account ID of the account', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + description: 'Phone number of the account', + }, + { + displayName: 'State', + name: 'state', + type: 'string', + default: '', + description: 'State that the account belongs to', + }, + { + displayName: 'Territory ID', + name: 'territory_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getTerritories', + }, + description: 'ID of the territory that the account belongs to', + }, + { + displayName: 'Twitter', + name: 'twitter', + type: 'string', + default: '', + description: 'Twitter username of the account', + }, + { + displayName: 'Website', + name: 'website', + type: 'string', + default: '', + description: 'Website of the account', + }, + { + displayName: 'Zipcode', + name: 'zipcode', + type: 'string', + default: '', + description: 'Zipcode of the region that the account belongs to', + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/FreshworksCrm/descriptions/AppointmentDescription.ts b/packages/nodes-base/nodes/FreshworksCrm/descriptions/AppointmentDescription.ts new file mode 100644 index 0000000000..a2878172c2 --- /dev/null +++ b/packages/nodes-base/nodes/FreshworksCrm/descriptions/AppointmentDescription.ts @@ -0,0 +1,636 @@ +import { + tz, +} from 'moment-timezone'; + +import { + INodeProperties, +} from 'n8n-workflow'; + +export const appointmentOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'appointment', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create an appointment', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete an appointment', + }, + { + name: 'Get', + value: 'get', + description: 'Retrieve an appointment', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Retrieve all appointments', + }, + { + name: 'Update', + value: 'update', + description: 'Update an appointment', + }, + ], + default: 'create', + }, +] as INodeProperties[]; + +export const appointmentFields = [ + // ---------------------------------------- + // appointment: create + // ---------------------------------------- + { + displayName: 'Title', + name: 'title', + description: 'Title of the appointment', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'appointment', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Start Date', + name: 'fromDate', + description: 'Timestamp that denotes the start of appointment. Start date if this is an all-day appointment.', + type: 'dateTime', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'appointment', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'End Date', + name: 'endDate', + description: 'Timestamp that denotes the end of appointment. End date if this is an all-day appointment.', + type: 'dateTime', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'appointment', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Attendees', + name: 'attendees', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + resource: [ + 'appointment', + ], + operation: [ + 'create', + ], + }, + }, + placeholder: 'Add Attendee', + default: {}, + options: [ + { + name: 'attendee', + displayName: 'Attendee', + values: [ + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { + name: 'Contact', + value: 'contact', + }, + { + name: 'User', + value: 'user', + }, + ], + default: 'contact', + }, + { + displayName: 'User ID', + name: 'userId', + type: 'options', + displayOptions: { + show: { + type: [ + 'user', + ], + }, + }, + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + default: '', + }, + { + displayName: 'Contact ID', + name: 'contactId', + displayOptions: { + show: { + type: [ + 'contact', + ], + }, + }, + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'appointment', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Creator ID', + name: 'creater_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + description: 'ID of the user who created the appointment', + }, + { + displayName: 'Is All-Day', + name: 'is_allday', + type: 'boolean', + default: false, + description: 'Whether it is an all-day appointment or not', + }, + { + displayName: 'Latitude', + name: 'latitude', + type: 'string', + default: '', + description: 'Latitude of the location when you check in for an appointment', + }, + { + displayName: 'Location', + name: 'location', + type: 'string', + default: '', + description: 'Location of the appointment', + }, + { + displayName: 'Longitude', + name: 'longitude', + type: 'string', + default: '', + description: 'Longitude of the location when you check in for an appointment', + }, + { + displayName: 'Outcome ID', + name: 'outcome_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getOutcomes', + }, + description: 'ID of outcome of Appointment sales activity type', + }, + { + displayName: 'Targetable ID', + name: 'targetable_id', + type: 'string', + default: '', + description: 'ID of contact/account against whom appointment is created', + }, + { + displayName: 'Targetable Type', + name: 'targetable_type', + type: 'options', + default: 'Contact', + options: [ + { + name: 'Contact', + value: 'Contact', + }, + { + name: 'Deal', + value: 'Deal', + }, + { + name: 'SalesAccount', + value: 'SalesAccount', + }, + ], + }, + { + displayName: 'Time Zone', + name: 'time_zone', + type: 'options', + default: '', + description: 'Timezone that the appointment is scheduled in', + options: tz.names().map(tz => ({ name: tz, value: tz })), + }, + ], + }, + + // ---------------------------------------- + // appointment: delete + // ---------------------------------------- + { + displayName: 'Appointment ID', + name: 'appointmentId', + description: 'ID of the appointment to delete', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'appointment', + ], + operation: [ + 'delete', + ], + }, + }, + }, + + // ---------------------------------------- + // appointment: get + // ---------------------------------------- + { + displayName: 'Appointment ID', + name: 'appointmentId', + description: 'ID of the appointment to retrieve', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'appointment', + ], + operation: [ + 'get', + ], + }, + }, + }, + + // ---------------------------------------- + // appointment: getAll + // ---------------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Whether to return all results or only up to a given limit', + displayOptions: { + show: { + resource: [ + 'appointment', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + description: 'How many results to return', + typeOptions: { + minValue: 1, + }, + displayOptions: { + show: { + resource: [ + 'appointment', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + default: '', + placeholder: 'Add Filter', + displayOptions: { + show: { + resource: [ + 'appointment', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Include', + name: 'include', + type: 'options', + default: 'creater', + options: [ + { + name: 'Appointment Attendees', + value: 'appointment_attendees', + }, + { + name: 'Creator', + value: 'creater', + }, + { + name: 'Targetable', + value: 'targetable', + }, + ], + }, + { + displayName: 'Time', + name: 'filter', + type: 'options', + default: 'upcoming', + options: [ + { + name: 'Past', + value: 'past', + }, + { + name: 'Upcoming', + value: 'upcoming', + }, + ], + }, + ], + }, + + // ---------------------------------------- + // appointment: update + // ---------------------------------------- + { + displayName: 'Appointment ID', + name: 'appointmentId', + description: 'ID of the appointment to update', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'appointment', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'appointment', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Attendees', + name: 'attendees', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + placeholder: 'Add Attendee', + default: {}, + options: [ + { + name: 'attendee', + displayName: 'Attendee', + values: [ + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { + name: 'Contact', + value: 'contact', + }, + { + name: 'User', + value: 'user', + }, + ], + default: 'contact', + }, + { + displayName: 'User ID', + name: 'userId', + type: 'options', + displayOptions: { + show: { + type: [ + 'user', + ], + }, + }, + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + default: '', + }, + { + displayName: 'Contact ID', + name: 'contactId', + displayOptions: { + show: { + type: [ + 'contact', + ], + }, + }, + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Creator ID', + name: 'creater_id', + type: 'options', + default: [], + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + description: 'ID of the user who created the appointment', + }, + { + displayName: 'End Date', + name: 'endDate', + description: 'Timestamp that denotes the end of appointment. End date if this is an all-day appointment.', + type: 'dateTime', + default: '', + }, + { + displayName: 'Is All-Day', + name: 'is_allday', + type: 'boolean', + default: false, + description: 'Whether it is an all-day appointment or not', + }, + { + displayName: 'Latitude', + name: 'latitude', + type: 'string', + default: '', + description: 'Latitude of the location when you check in for an appointment', + }, + { + displayName: 'Location', + name: 'location', + type: 'string', + default: '', + description: 'Location of the appointment', + }, + { + displayName: 'Longitude', + name: 'longitude', + type: 'string', + default: '', + description: 'Longitude of the location when you check in for an appointment', + }, + { + displayName: 'Outcome ID', + name: 'outcome_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getOutcomes', + }, + description: 'ID of outcome of Appointment sales activity type', + }, + { + displayName: 'Start Date', + name: 'fromDate', + description: 'Timestamp that denotes the start of appointment. Start date if this is an all-day appointment.', + type: 'dateTime', + default: '', + }, + { + displayName: 'Targetable ID', + name: 'targetable_id', + type: 'string', + default: '', + description: 'ID of contact/account against whom appointment is created', + }, + { + displayName: 'Targetable Type', + name: 'targetable_type', + type: 'options', + default: 'Contact', + options: [ + { + name: 'Contact', + value: 'Contact', + }, + { + name: 'Deal', + value: 'Deal', + }, + { + name: 'SalesAccount', + value: 'SalesAccount', + }, + ], + }, + { + displayName: 'Time Zone', + name: 'time_zone', + type: 'options', + default: '', + description: 'Timezone that the appointment is scheduled in', + options: tz.names().map(tz => ({ name: tz, value: tz })), + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + description: 'Title of the appointment', + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/FreshworksCrm/descriptions/ContactDescription.ts b/packages/nodes-base/nodes/FreshworksCrm/descriptions/ContactDescription.ts new file mode 100644 index 0000000000..9d53155360 --- /dev/null +++ b/packages/nodes-base/nodes/FreshworksCrm/descriptions/ContactDescription.ts @@ -0,0 +1,668 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const contactOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'contact', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a contact', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a contact', + }, + { + name: 'Get', + value: 'get', + description: 'Retrieve a contact', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Retrieve all contacts', + }, + { + name: 'Update', + value: 'update', + description: 'Update a contact', + }, + ], + default: 'create', + }, +] as INodeProperties[]; + +export const contactFields = [ + // ---------------------------------------- + // contact: create + // ---------------------------------------- + { + displayName: 'First Name', + name: 'firstName', + description: 'First name of the contact', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Last Name', + name: 'lastName', + description: 'Last name of the contact', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Email Address', + name: 'emails', + type: 'string', + default: '', + description: 'Email addresses of the contact', + required: true, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Address', + name: 'address', + type: 'string', + default: '', + description: 'Address of the contact', + }, + { + displayName: 'Campaign ID', + name: 'campaign_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getCampaigns', + }, + description: 'ID of the campaign that led your contact to your webapp', + }, + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + description: 'City that the contact belongs to', + }, + { + displayName: 'Contact Status ID', + name: 'contact_status_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getContactStatuses', + }, + description: 'ID of the contact status that the contact belongs to', + }, + { + displayName: 'Country', + name: 'country', + type: 'string', + default: '', + description: 'Country that the contact belongs to', + }, + { + displayName: 'External ID', + name: 'external_id', + type: 'string', + default: '', + description: 'External ID of the contact', + }, + { + displayName: 'Facebook', + name: 'facebook', + type: 'string', + default: '', + description: 'Facebook username of the contact', + }, + { + displayName: 'Job Title', + name: 'job_title', + type: 'string', + default: '', + description: 'Designation of the contact in the account they belong to', + }, + { + displayName: 'Keywords', + name: 'keyword', + type: 'string', + default: '', + description: 'Keywords that the contact used to reach your website/web app', + }, + { + displayName: 'Lead Source ID', + name: 'lead_source_id', + type: 'string', // not obtainable from API + default: '', + description: 'ID of the source where contact came from', + }, + { + displayName: 'Lifecycle Stage ID', + name: 'lifecycle_stage_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getLifecycleStages', + }, + description: 'ID of the lifecycle stage that the contact belongs to', + }, + { + displayName: 'LinkedIn', + name: 'linkedin', + type: 'string', + default: '', + description: 'LinkedIn account of the contact', + }, + { + displayName: 'Medium', + name: 'medium', + type: 'string', + default: '', + description: 'Medium that led your contact to your website/webapp', + }, + { + displayName: 'Mobile Number', + name: 'mobile_number', + type: 'string', + default: '', + description: 'Mobile phone number of the contact', + }, + { + displayName: 'Owner ID', + name: 'owner_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + description: 'ID of the user to whom the contact is assigned', + }, + { + displayName: 'Sales Accounts', + name: 'sales_accounts', + type: 'multiOptions', + default: [], + typeOptions: { + loadOptionsMethod: 'getAccounts', + }, + description: 'Accounts which contact belongs to', + }, + { + displayName: 'State', + name: 'state', + type: 'string', + default: '', + description: 'State that the contact belongs to', + }, + { + displayName: 'Subscription Status', + name: 'subscription_status', + type: 'string', // not obtainable from API + default: '', + description: 'Status of subscription that the contact is in', + }, + { + displayName: 'Subscription Types', + name: 'subscription_types', + type: 'string', // not obtainable from API + default: '', + description: 'Type of subscription that the contact is in', + }, + { + displayName: 'Territory ID', + name: 'territory_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getTerritories', + }, + description: 'ID of the territory that the contact belongs to', + }, + { + displayName: 'Time Zone', + name: 'time_zone', + type: 'string', + default: '', + description: 'Timezone that the contact belongs to', + }, + { + displayName: 'Twitter', + name: 'twitter', + type: 'string', + default: '', + description: 'Twitter username of the contact', + }, + { + displayName: 'Work Number', + name: 'work_number', + type: 'string', + default: '', + description: 'Work phone number of the contact', + }, + { + displayName: 'Zipcode', + name: 'zipcode', + type: 'string', + default: '', + description: 'Zipcode of the region that the contact belongs to', + }, + ], + }, + + // ---------------------------------------- + // contact: delete + // ---------------------------------------- + { + displayName: 'Contact ID', + name: 'contactId', + description: 'ID of the contact to delete', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'delete', + ], + }, + }, + }, + + // ---------------------------------------- + // contact: get + // ---------------------------------------- + { + displayName: 'Contact ID', + name: 'contactId', + description: 'ID of the contact to retrieve', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'get', + ], + }, + }, + }, + + // ---------------------------------------- + // contact: getAll + // ---------------------------------------- + { + displayName: 'View', + name: 'view', + type: 'options', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'getAll', + ], + }, + }, + typeOptions: { + loadOptionsMethod: 'getContactViews', + }, + default: '', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Whether to return all results or only up to a given limit', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + description: 'How many results to return', + typeOptions: { + minValue: 1, + }, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + + // ---------------------------------------- + // contact: update + // ---------------------------------------- + { + displayName: 'Contact ID', + name: 'contactId', + description: 'ID of the contact to update', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'contact', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Address', + name: 'address', + type: 'string', + default: '', + description: 'Address of the contact', + }, + { + displayName: 'Campaign ID', + name: 'campaign_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getCampaigns', + }, + description: 'ID of the campaign that led your contact to your webapp', + }, + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + description: 'City that the contact belongs to', + }, + { + displayName: 'Contact Status ID', + name: 'contact_status_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getContactStatuses', + }, + description: 'ID of the contact status that the contact belongs to', + }, + { + displayName: 'Country', + name: 'country', + type: 'string', + default: '', + description: 'Country that the contact belongs to', + }, + { + displayName: 'External ID', + name: 'external_id', + type: 'string', + default: '', + description: 'External ID of the contact', + }, + { + displayName: 'Facebook', + name: 'facebook', + type: 'string', + default: '', + description: 'Facebook username of the contact', + }, + { + displayName: 'First Name', + name: 'first_name', + type: 'string', + default: '', + description: 'First name of the contact', + }, + { + displayName: 'Job Title', + name: 'job_title', + type: 'string', + default: '', + description: 'Designation of the contact in the account they belong to', + }, + { + displayName: 'Keywords', + name: 'keyword', + type: 'string', + default: '', + description: 'Keywords that the contact used to reach your website/web app', + }, + { + displayName: 'Last Name', + name: 'last_name', + type: 'string', + default: '', + description: 'Last name of the contact', + }, + { + displayName: 'Lead Source ID', + name: 'lead_source_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getLeadSources', + }, + description: 'ID of the source where contact came from', + }, + { + displayName: 'Lifecycle Stage ID', + name: 'lifecycle_stage_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getLifecycleStages', + }, + description: 'ID of the lifecycle stage that the contact belongs to', + }, + { + displayName: 'LinkedIn', + name: 'linkedin', + type: 'string', + default: '', + description: 'LinkedIn account of the contact', + }, + { + displayName: 'Medium', + name: 'medium', + type: 'string', + default: '', + description: 'Medium that led your contact to your website/webapp', + }, + { + displayName: 'Mobile Number', + name: 'mobile_number', + type: 'string', + default: '', + description: 'Mobile phone number of the contact', + }, + { + displayName: 'Owner ID', + name: 'owner_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + description: 'ID of the user to whom the contact is assigned', + }, + { + displayName: 'Sales Accounts', + name: 'sales_accounts', + type: 'multiOptions', + default: [], + typeOptions: { + loadOptionsMethod: 'getAccounts', + }, + description: 'Accounts which contact belongs to', + }, + { + displayName: 'State', + name: 'state', + type: 'string', + default: '', + description: 'State that the contact belongs to', + }, + { + displayName: 'Subscription Status', + name: 'subscription_status', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getSubscriptionStatuses', + }, + description: 'Status of subscription that the contact is in', + }, + { + displayName: 'Subscription Types', + name: 'subscription_types', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getSubscriptionTypes', + }, + description: 'Type of subscription that the contact is in', + }, + { + displayName: 'Territory ID', + name: 'territory_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getTerritories', + }, + description: 'ID of the territory that the contact belongs to', + }, + { + displayName: 'Time Zone', + name: 'time_zone', + type: 'string', + default: '', + description: 'Timezone that the contact belongs to', + }, + { + displayName: 'Twitter', + name: 'twitter', + type: 'string', + default: '', + description: 'Twitter username of the contact', + }, + { + displayName: 'Work Number', + name: 'work_number', + type: 'string', + default: '', + description: 'Work phone number of the contact', + }, + { + displayName: 'Zipcode', + name: 'zipcode', + type: 'string', + default: '', + description: 'Zipcode of the region that the contact belongs to', + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/FreshworksCrm/descriptions/DealDescription.ts b/packages/nodes-base/nodes/FreshworksCrm/descriptions/DealDescription.ts new file mode 100644 index 0000000000..ad5ca251d6 --- /dev/null +++ b/packages/nodes-base/nodes/FreshworksCrm/descriptions/DealDescription.ts @@ -0,0 +1,545 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const dealOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'deal', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a deal', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a deal', + }, + { + name: 'Get', + value: 'get', + description: 'Retrieve a deal', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Retrieve all deals', + }, + { + name: 'Update', + value: 'update', + description: 'Update a deal', + }, + ], + default: 'create', + }, +] as INodeProperties[]; + +export const dealFields = [ + // ---------------------------------------- + // deal: create + // ---------------------------------------- + { + displayName: 'Amount', + name: 'amount', + description: 'Value of the deal', + type: 'number', + required: true, + default: 0, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Name', + name: 'name', + description: 'Name of the deal', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Base Currency Amount', + name: 'base_currency_amount', + type: 'number', + default: 0, + description: 'Value of the deal in base currency', + }, + { + displayName: 'Campaign ID', + name: 'campaign_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getCampaigns', + }, + description: 'ID of the campaign that landed this deal', + }, + { + displayName: 'Currency ID', + name: 'currency_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getCurrencies', + }, + description: 'ID of the currency that the deal belongs to', + }, + { + displayName: 'Deal Payment Status ID', + name: 'deal_payment_status_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getDealPaymentStatuses', + }, + description: 'ID of the mode of payment for the deal', + }, + { + displayName: 'Deal Pipeline ID', + name: 'deal_pipeline_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getDealPipelines', + }, + description: 'ID of the deal pipeline that it belongs to', + }, + { + displayName: 'Deal Product ID', + name: 'deal_product_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getDealProducts', + }, + description: 'ID of the product that the deal belongs to (in a multi-product company)', + }, + { + displayName: 'Deal Reason ID', + name: 'deal_reason_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getDealReasons', + }, + description: 'ID of the reason for losing the deal. Can only be set if the deal is in \'Lost\' stage.', + }, + { + displayName: 'Deal Stage ID', + name: 'deal_stage_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getDealStages', + }, + description: 'ID of the deal stage that the deal belongs to', + }, + { + displayName: 'Deal Type ID', + name: 'deal_type_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getDealTypes', + }, + description: 'ID of the deal type that the deal belongs to', + }, + { + displayName: 'Lead Source ID', + name: 'lead_source_id', + type: 'string', // not obtainable from API + default: '', + description: 'ID of the source where deal came from', + }, + { + displayName: 'Owner ID', + name: 'owner_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + description: 'ID of the user to whom the deal is assigned', + }, + { + displayName: 'Probability', + name: 'probability', + type: 'number', + default: 0, + typeOptions: { + minValue: 0, + maxValue: 100, + }, + description: 'Probability of winning the deal as a number between 0 and 100', + }, + { + displayName: 'Sales Account ID', + name: 'sales_account_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getAccounts', + }, + description: 'ID of the account that the deal belongs to', + }, + { + displayName: 'Territory ID', + name: 'territory_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getTerritories', + }, + description: 'ID of the territory that the deal belongs to', + }, + ], + }, + + // ---------------------------------------- + // deal: delete + // ---------------------------------------- + { + displayName: 'Deal ID', + name: 'dealId', + description: 'ID of the deal to delete', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'delete', + ], + }, + }, + }, + + // ---------------------------------------- + // deal: get + // ---------------------------------------- + { + displayName: 'Deal ID', + name: 'dealId', + description: 'ID of the deal to retrieve', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'get', + ], + }, + }, + }, + + // ---------------------------------------- + // deal: getAll + // ---------------------------------------- + { + displayName: 'View', + name: 'view', + type: 'options', + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'getAll', + ], + }, + }, + typeOptions: { + loadOptionsMethod: 'getDealViews', + }, + default: '', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Whether to return all results or only up to a given limit', + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + description: 'How many results to return', + typeOptions: { + minValue: 1, + }, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + + // ---------------------------------------- + // deal: update + // ---------------------------------------- + { + displayName: 'Deal ID', + name: 'dealId', + description: 'ID of the deal to update', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'deal', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Amount', + name: 'amount', + type: 'number', + default: 0, + typeOptions: { + minValue: 0, + }, + description: 'Value of the deal', + }, + { + displayName: 'Base Currency Amount', + name: 'base_currency_amount', + type: 'number', + default: 0, + typeOptions: { + minValue: 0, + }, + description: 'Value of the deal in base currency', + }, + { + displayName: 'Campaign ID', + name: 'campaign_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getCampaigns', + }, + description: 'ID of the campaign that landed this deal', + }, + { + displayName: 'Currency ID', + name: 'currency_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getCurrencies', + }, + description: 'ID of the currency that the deal belongs to', + }, + { + displayName: 'Deal Payment Status ID', + name: 'deal_payment_status_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getDealPaymentStatuses', + }, + description: 'ID of the mode of payment for the deal', + }, + { + displayName: 'Deal Pipeline ID', + name: 'deal_pipeline_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getDealPipelines', + }, + description: 'ID of the deal pipeline that it belongs to', + }, + { + displayName: 'Deal Product ID', + name: 'deal_product_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getDealProducts', + }, + description: 'ID of the product that the deal belongs to (in a multi-product company)', + }, + { + displayName: 'Deal Reason ID', + name: 'deal_reason_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getDealReasons', + }, + description: 'ID of the reason for losing the deal. Can only be set if the deal is in \'Lost\' stage.', + }, + { + displayName: 'Deal Stage ID', + name: 'deal_stage_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getDealStages', + }, + description: 'ID of the deal stage that the deal belongs to', + }, + { + displayName: 'Deal Type ID', + name: 'deal_type_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getDealTypes', + }, + description: 'ID of the deal type that the deal belongs to', + }, + { + displayName: 'Lead Source ID', + name: 'lead_source_id', + type: 'string', // not obtainable from API + default: '', + description: 'ID of the source where deal came from', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'Name of the deal', + }, + { + displayName: 'Owner ID', + name: 'owner_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + description: 'ID of the user to whom the deal is assigned', + }, + { + displayName: 'Probability', + name: 'probability', + type: 'number', + default: 0, + typeOptions: { + minValue: 0, + maxValue: 100, + }, + description: 'Probability of winning the deal as a number between 0 and 100', + }, + { + displayName: 'Sales Account ID', + name: 'sales_account_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getAccounts', + }, + description: 'ID of the account that the deal belongs to', + }, + { + displayName: 'Territory ID', + name: 'territory_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getTerritories', + }, + description: 'ID of the territory that the deal belongs to', + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/FreshworksCrm/descriptions/NoteDescription.ts b/packages/nodes-base/nodes/FreshworksCrm/descriptions/NoteDescription.ts new file mode 100644 index 0000000000..d5f48085f1 --- /dev/null +++ b/packages/nodes-base/nodes/FreshworksCrm/descriptions/NoteDescription.ts @@ -0,0 +1,214 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const noteOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'note', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a note', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a note', + }, + { + name: 'Update', + value: 'update', + description: 'Update a note', + }, + ], + default: 'create', + }, +] as INodeProperties[]; + +export const noteFields = [ + // ---------------------------------------- + // note: create + // ---------------------------------------- + { + displayName: 'Content', + name: 'description', + description: 'Content of the note', + type: 'string', + required: true, + typeOptions: { + rows: 5, + }, + default: '', + displayOptions: { + show: { + resource: [ + 'note', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Target Type', + name: 'targetableType', + description: 'Type of the entity for which the note is created', + type: 'options', + required: true, + default: 'Contact', + options: [ + { + name: 'Contact', + value: 'Contact', + }, + { + name: 'Deal', + value: 'Deal', + }, + { + name: 'Sales Account', + value: 'SalesAccount', + }, + ], + displayOptions: { + show: { + resource: [ + 'note', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Target ID', + name: 'targetable_id', + description: 'ID of the entity for which note is created. The type of entity is selected in "Target Type".', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'note', + ], + operation: [ + 'create', + ], + }, + }, + }, + + // ---------------------------------------- + // note: delete + // ---------------------------------------- + { + displayName: 'Note ID', + name: 'noteId', + description: 'ID of the note to delete', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'note', + ], + operation: [ + 'delete', + ], + }, + }, + }, + + // ---------------------------------------- + // note: update + // ---------------------------------------- + { + displayName: 'Note ID', + name: 'noteId', + description: 'ID of the note to update', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'note', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'note', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Content', + name: 'description', + type: 'string', + typeOptions: { + rows: 5, + }, + default: '', + description: 'Content of the note', + }, + { + displayName: 'Target ID', + name: 'targetable_id', + type: 'string', + default: '', + description: 'ID of the entity for which the note is updated', + }, + { + displayName: 'Target Type', + name: 'targetable_type', + type: 'options', + default: 'Contact', + description: 'Type of the entity for which the note is updated', + options: [ + { + name: 'Contact', + value: 'Contact', + }, + { + name: 'Deal', + value: 'Deal', + }, + { + name: 'Sales Account', + value: 'SalesAccount', + }, + ], + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/FreshworksCrm/descriptions/SalesActivityDescription.ts b/packages/nodes-base/nodes/FreshworksCrm/descriptions/SalesActivityDescription.ts new file mode 100644 index 0000000000..13dbb2294f --- /dev/null +++ b/packages/nodes-base/nodes/FreshworksCrm/descriptions/SalesActivityDescription.ts @@ -0,0 +1,508 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const salesActivityOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'salesActivity', + ], + }, + }, + options: [ + // { + // name: 'Create', + // value: 'create', + // description: 'Create a sales activity', + // }, + // { + // name: 'Delete', + // value: 'delete', + // description: 'Delete a sales activity', + // }, + { + name: 'Get', + value: 'get', + description: 'Retrieve a sales activity', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Retrieve all sales activities', + }, + // { + // name: 'Update', + // value: 'update', + // description: 'Update a sales activity', + // }, + ], + default: 'get', + }, +] as INodeProperties[]; + +export const salesActivityFields = [ + // ---------------------------------------- + // salesActivity: create + // ---------------------------------------- + { + displayName: 'Sales Activity Type ID', + name: 'sales_activity_type_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getSalesActivityTypes', + }, + description: 'ID of a sales activity type for which the sales activity is created', + displayOptions: { + show: { + resource: [ + 'salesActivity', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Title', + name: 'title', + description: 'Title of the sales activity to create', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'salesActivity', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Owner ID', + name: 'ownerId', + description: 'ID of the user who owns the sales activity', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + required: true, + displayOptions: { + show: { + resource: [ + 'salesActivity', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Start Date', + name: 'from_date', + description: 'Timestamp that denotes the end of sales activity', + type: 'dateTime', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'salesActivity', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'End Date', + name: 'end_date', + description: 'Timestamp that denotes the end of sales activity', + type: 'dateTime', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'salesActivity', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Targetable Type', + name: 'targetableType', + description: 'Type of the entity for which the sales activity is created', + type: 'options', + required: true, + default: 'Contact', + options: [ + { + name: 'Contact', + value: 'Contact', + }, + { + name: 'Deal', + value: 'Deal', + }, + { + name: 'Sales Account', + value: 'SalesAccount', + }, + ], + displayOptions: { + show: { + resource: [ + 'salesActivity', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Targetable ID', + name: 'targetable_id', + description: 'ID of the entity for which the sales activity is created. The type of entity is selected in "Targetable Type".', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'salesActivity', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'salesActivity', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Creator ID', + name: 'creater_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + description: 'ID of the user who created the sales activity', + }, + { + displayName: 'Latitude', + name: 'latitude', + type: 'string', + default: '', + description: 'Latitude of the location when you check in on a sales activity', + }, + { + displayName: 'Location', + name: 'location', + type: 'string', + default: '', + description: 'Location of the sales activity', + }, + { + displayName: 'Longitude', + name: 'longitude', + type: 'string', + default: '', + description: 'Longitude of the location when you check in for a sales activity', + }, + { + displayName: 'Notes', + name: 'notes', + type: 'string', + default: '', + description: 'Description about the sales activity', + }, + { + displayName: 'Sales Activity Outcome ID', + name: 'sales_activity_outcome_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getOutcomes', + }, + description: 'ID of a sales activity\'s outcome', + }, + ], + }, + + // ---------------------------------------- + // salesActivity: delete + // ---------------------------------------- + { + displayName: 'Sales Activity ID', + name: 'salesActivityId', + description: 'ID of the salesActivity to delete', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'salesActivity', + ], + operation: [ + 'delete', + ], + }, + }, + }, + + // ---------------------------------------- + // salesActivity: get + // ---------------------------------------- + { + displayName: 'Sales Activity ID', + name: 'salesActivityId', + description: 'ID of the salesActivity to retrieve', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'salesActivity', + ], + operation: [ + 'get', + ], + }, + }, + }, + + // ---------------------------------------- + // salesActivity: getAll + // ---------------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Whether to return all results or only up to a given limit', + displayOptions: { + show: { + resource: [ + 'salesActivity', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + description: 'How many results to return', + typeOptions: { + minValue: 1, + }, + displayOptions: { + show: { + resource: [ + 'salesActivity', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + + // ---------------------------------------- + // salesActivity: update + // ---------------------------------------- + { + displayName: 'Sales Activity ID', + name: 'salesActivityId', + description: 'ID of the salesActivity to update', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'salesActivity', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'salesActivity', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Creator ID', + name: 'creater_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + description: 'ID of the user who created the sales activity', + }, + { + displayName: 'Start Date', + name: 'end_date', + description: 'Timestamp that denotes the start of the sales activity', + type: 'dateTime', + }, + { + displayName: 'Latitude', + name: 'latitude', + type: 'string', + default: '', + description: 'Latitude of the location when you check in on a sales activity', + }, + { + displayName: 'Location', + name: 'location', + type: 'string', + default: '', + description: 'Location of the sales activity', + }, + { + displayName: 'Longitude', + name: 'longitude', + type: 'string', + default: '', + description: 'Longitude of the location when you check in for a sales activity', + }, + { + displayName: 'Notes', + name: 'notes', + type: 'string', + default: '', + description: 'Description about the sales activity', + }, + { + displayName: 'Owner ID', + name: 'owner_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + description: 'ID of the user who owns the sales activity', + }, + { + displayName: 'Sales Activity Outcome ID', + name: 'sales_activity_outcome_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getOutcomes', + }, + description: 'ID of a sales activity\'s outcome', + }, + { + displayName: 'Sales Activity Type ID', + name: 'sales_activity_type_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getSalesActivityTypes', + }, + description: 'ID of a sales activity type for which the sales activity is updated', + }, + { + displayName: 'Start Date', + name: 'from_date', + description: 'Timestamp that denotes the start of the sales activity', + type: 'dateTime', + }, + { + displayName: 'Targetable ID', + name: 'targetable_id', + type: 'string', + default: '', + description: 'ID of the entity for which the sales activity is updated. The type of entity is selected in "Targetable Type".', + }, + { + displayName: 'Targetable Type', + name: 'targetable_type', + type: 'options', + default: 'Contact', + description: 'Type of the entity for which the sales activity is updated', + options: [ + { + name: 'Contact', + value: 'Contact', + }, + { + name: 'Deal', + value: 'Deal', + }, + { + name: 'SalesAccount', + value: 'SalesAccount', + }, + ], + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + description: 'Title of the sales activity to update', + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/FreshworksCrm/descriptions/TaskDescription.ts b/packages/nodes-base/nodes/FreshworksCrm/descriptions/TaskDescription.ts new file mode 100644 index 0000000000..74020a40c0 --- /dev/null +++ b/packages/nodes-base/nodes/FreshworksCrm/descriptions/TaskDescription.ts @@ -0,0 +1,480 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const taskOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'task', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a task', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a task', + }, + { + name: 'Get', + value: 'get', + description: 'Retrieve a task', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Retrieve all tasks', + }, + { + name: 'Update', + value: 'update', + description: 'Update a task', + }, + ], + default: 'create', + }, +] as INodeProperties[]; + +export const taskFields = [ + // ---------------------------------------- + // task: create + // ---------------------------------------- + { + displayName: 'Title', + name: 'title', + description: 'Title of the task', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Due Date', + name: 'dueDate', + description: 'Timestamp that denotes when the task is due to be completed', + type: 'dateTime', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Owner ID', + name: 'ownerId', + description: 'ID of the user to whom the task is assigned', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + required: true, + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Targetable Type', + name: 'targetableType', + description: 'Type of the entity for which the task is updated', + type: 'options', + required: true, + default: 'Contact', + options: [ + { + name: 'Contact', + value: 'Contact', + }, + { + name: 'Deal', + value: 'Deal', + }, + { + name: 'SalesAccount', + value: 'SalesAccount', + }, + ], + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Targetable ID', + name: 'targetable_id', + description: 'ID of the entity for which the task is created. The type of entity is selected in "Targetable Type".', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Creator ID', + name: 'creater_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + description: 'ID of the user who created the task', + }, + { + displayName: 'Outcome ID', + name: 'outcome_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getOutcomes', + }, + description: 'ID of the outcome of the task', + }, + { + displayName: 'Task Type ID', + name: 'task_type_id', + type: 'string', // not obtainable from API + default: '', + description: 'ID of the type of task', + }, + ], + }, + + // ---------------------------------------- + // task: delete + // ---------------------------------------- + { + displayName: 'Task ID', + name: 'taskId', + description: 'ID of the task to delete', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'delete', + ], + }, + }, + }, + + // ---------------------------------------- + // task: get + // ---------------------------------------- + { + displayName: 'Task ID', + name: 'taskId', + description: 'ID of the task to retrieve', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'get', + ], + }, + }, + }, + + // ---------------------------------------- + // task: getAll + // ---------------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Whether to return all results or only up to a given limit', + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + description: 'How many results to return', + typeOptions: { + minValue: 1, + }, + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + default: false, + placeholder: 'Add Filter', + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Include', + name: 'include', + type: 'options', + default: 'owner', + options: [ + { + name: 'Owner', + value: 'owner', + }, + { + name: 'Targetable', + value: 'targetable', + }, + { + name: 'Users', + value: 'users', + }, + ], + }, + { + displayName: 'Status', + name: 'filter', + type: 'options', + default: 'open', + options: [ + { + name: 'Completed', + value: 'completed', + }, + { + name: 'Due Today', + value: 'due_today', + }, + { + name: 'Due Tomorrow', + value: 'due_tomorrow', + }, + { + name: 'Open', + value: 'open', + }, + { + name: 'Overdue', + value: 'overdue', + }, + ], + }, + ], + }, + + // ---------------------------------------- + // task: update + // ---------------------------------------- + { + displayName: 'Task ID', + name: 'taskId', + description: 'ID of the task to update', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Creator ID', + name: 'creater_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + description: 'ID of the user who created the sales activity', + }, + { + displayName: 'Due Date', + name: 'dueDate', + description: 'Timestamp that denotes when the task is due to be completed', + type: 'dateTime', + default: '', + }, + { + displayName: 'Outcome ID', + name: 'outcome_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getOutcomes', + }, + description: 'ID of the outcome of the task', + }, + { + displayName: 'Owner ID', + name: 'owner_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + description: 'ID of the user to whom the task is assigned', + }, + { + displayName: 'Targetable ID', + name: 'targetable_id', + type: 'string', + default: '', + description: 'ID of the entity for which the task is updated. The type of entity is selected in "Targetable Type".', + }, + { + displayName: 'Targetable Type', + name: 'targetable_type', + description: 'Type of the entity for which the task is updated', + type: 'options', + default: 'Contact', + options: [ + { + name: 'Contact', + value: 'Contact', + }, + { + name: 'Deal', + value: 'Deal', + }, + { + name: 'SalesAccount', + value: 'SalesAccount', + }, + ], + }, + { + displayName: 'Task Type ID', + name: 'task_type_id', + type: 'string', // not obtainable from API + default: '', + description: 'ID of the type of task', + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + description: 'Title of the task', + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/FreshworksCrm/descriptions/index.ts b/packages/nodes-base/nodes/FreshworksCrm/descriptions/index.ts new file mode 100644 index 0000000000..70957a2c38 --- /dev/null +++ b/packages/nodes-base/nodes/FreshworksCrm/descriptions/index.ts @@ -0,0 +1,7 @@ +export * from './AccountDescription'; +export * from './AppointmentDescription'; +export * from './ContactDescription'; +export * from './DealDescription'; +export * from './NoteDescription'; +export * from './SalesActivityDescription'; +export * from './TaskDescription'; diff --git a/packages/nodes-base/nodes/FreshworksCrm/freshworksCrm.svg b/packages/nodes-base/nodes/FreshworksCrm/freshworksCrm.svg new file mode 100644 index 0000000000..06e0cf9acf --- /dev/null +++ b/packages/nodes-base/nodes/FreshworksCrm/freshworksCrm.svg @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/nodes-base/nodes/FreshworksCrm/types.d.ts b/packages/nodes-base/nodes/FreshworksCrm/types.d.ts new file mode 100644 index 0000000000..4d25342366 --- /dev/null +++ b/packages/nodes-base/nodes/FreshworksCrm/types.d.ts @@ -0,0 +1,43 @@ +export type FreshworksCrmApiCredentials = { + apiKey: string; + domain: string; +} + +export type FreshworksConfigResponse = { + [key: string]: T[]; +}; + +export type LoadOption = { + name: string; + value: string; +}; + +export type LoadedCurrency = { + currency_code: string; + id: string; +}; + +export type LoadedUser = { + id: string; + display_name: string; +}; + +export type SalesAccounts = { + sales_accounts?: number[]; +}; + +export type ViewsResponse = { + filters: View[]; + meta: object; +} + +export type View = { + id: number; + name: string; + model_class_name: string; + user_id: number; + is_default: boolean; + updated_at: string; + user_name: string; + current_user_permissions: string[]; +}; diff --git a/packages/nodes-base/nodes/Taiga/types.d.ts b/packages/nodes-base/nodes/Taiga/types.d.ts index 49c5f0e0f0..4a136dc822 100644 --- a/packages/nodes-base/nodes/Taiga/types.d.ts +++ b/packages/nodes-base/nodes/Taiga/types.d.ts @@ -7,6 +7,11 @@ type LoadedResource = { name: string; }; +type LoadOption = { + value: string; + name: string; +}; + type LoadedUser = { id: string; full_name_display: string; diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index a27bb6eeda..f2b9649991 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -85,6 +85,7 @@ "dist/credentials/FacebookGraphApi.credentials.js", "dist/credentials/FacebookGraphAppApi.credentials.js", "dist/credentials/FreshdeskApi.credentials.js", + "dist/credentials/FreshworksCrmApi.credentials.js", "dist/credentials/FileMaker.credentials.js", "dist/credentials/FlowApi.credentials.js", "dist/credentials/Ftp.credentials.js", @@ -375,6 +376,7 @@ "dist/nodes/FileMaker/FileMaker.node.js", "dist/nodes/Ftp.node.js", "dist/nodes/Freshdesk/Freshdesk.node.js", + "dist/nodes/FreshworksCrm/FreshworksCrm.node.js", "dist/nodes/Flow/Flow.node.js", "dist/nodes/Flow/FlowTrigger.node.js", "dist/nodes/Function.node.js",