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",