diff --git a/packages/nodes-base/credentials/FreshworksCrmApi.credentials.ts b/packages/nodes-base/credentials/FreshworksCrmApi.credentials.ts index 115fb998f4..f81f46e7dc 100644 --- a/packages/nodes-base/credentials/FreshworksCrmApi.credentials.ts +++ b/packages/nodes-base/credentials/FreshworksCrmApi.credentials.ts @@ -1,4 +1,6 @@ import { + IAuthenticateGeneric, + ICredentialTestRequest, ICredentialType, INodeProperties, } from 'n8n-workflow'; @@ -24,4 +26,19 @@ export class FreshworksCrmApi implements ICredentialType { description: 'Domain in the Freshworks CRM org URL. For example, in https://n8n-org.myfreshworks.com, the domain is n8n-org.', }, ]; + authenticate: IAuthenticateGeneric = { + type: 'generic', + properties: { + headers: { + 'Authorization': '=Token token={{$credentials?.apiKey}}', + }, + }, + }; + test: ICredentialTestRequest = { + request: { + baseURL: '=https://{{$credentials?.domain}}.myfreshworks.com/crm/sales/api', + url: '/tasks', + method: 'GET', + }, + }; } diff --git a/packages/nodes-base/nodes/FreshworksCrm/FreshworksCrm.node.ts b/packages/nodes-base/nodes/FreshworksCrm/FreshworksCrm.node.ts index 9dbfd1fc68..2ad3fd4e37 100644 --- a/packages/nodes-base/nodes/FreshworksCrm/FreshworksCrm.node.ts +++ b/packages/nodes-base/nodes/FreshworksCrm/FreshworksCrm.node.ts @@ -34,6 +34,8 @@ import { noteOperations, salesActivityFields, salesActivityOperations, + searchFields, + searchOperations, taskFields, taskOperations, } from './descriptions'; @@ -100,6 +102,10 @@ export class FreshworksCrm implements INodeType { name: 'Sales Activity', value: 'salesActivity', }, + { + name: 'Search', + value: 'search', + }, { name: 'Task', value: 'task', @@ -119,6 +125,8 @@ export class FreshworksCrm implements INodeType { ...noteFields, ...salesActivityOperations, ...salesActivityFields, + ...searchOperations, + ...searchFields, ...taskOperations, ...taskFields, ], @@ -855,6 +863,58 @@ export class FreshworksCrm implements INodeType { } + } else if (resource === 'search') { + + // ********************************************************************** + // search + // ********************************************************************** + + if (operation === 'query') { + // https://developers.freshworks.com/crm/api/#search + const query = this.getNodeParameter('query', i) as string; + let entities = this.getNodeParameter('entities', i); + const returnAll = this.getNodeParameter('returnAll', 0, false); + + if (Array.isArray(entities)) { + entities = entities.join(','); + } + + const qs: IDataObject = { + q: query, + include: entities, + per_page: 100, + }; + + responseData = await freshworksCrmApiRequest.call(this, 'GET', '/search', {}, qs); + + if (!returnAll) { + const limit = this.getNodeParameter('limit', 0); + responseData = responseData.slice(0, limit); + } + } + + if (operation === 'lookup') { + // https://developers.freshworks.com/crm/api/#lookup_search + let searchField = this.getNodeParameter('searchField', i) as string; + let fieldValue = this.getNodeParameter('fieldValue', i, '') as string; + let entities = this.getNodeParameter('options.entities', i) as string; + if (Array.isArray(entities)) { + entities = entities.join(','); + } + + if (searchField === 'customField') { + searchField = this.getNodeParameter('customFieldName', i) as string; + fieldValue = this.getNodeParameter('customFieldValue', i) as string; + } + + const qs: IDataObject = { + q: fieldValue, + f: searchField, + entities, + }; + + responseData = await freshworksCrmApiRequest.call(this, 'GET', '/lookup', {}, qs); + } } else if (resource === 'task') { // ********************************************************************** diff --git a/packages/nodes-base/nodes/FreshworksCrm/GenericFunctions.ts b/packages/nodes-base/nodes/FreshworksCrm/GenericFunctions.ts index 25ec4f0d87..e1d41870fd 100644 --- a/packages/nodes-base/nodes/FreshworksCrm/GenericFunctions.ts +++ b/packages/nodes-base/nodes/FreshworksCrm/GenericFunctions.ts @@ -31,12 +31,9 @@ export async function freshworksCrmApiRequest( body: IDataObject = {}, qs: IDataObject = {}, ) { - const { apiKey, domain } = await this.getCredentials('freshworksCrmApi') as FreshworksCrmApiCredentials; + const { domain } = await this.getCredentials('freshworksCrmApi') as FreshworksCrmApiCredentials; const options: OptionsWithUri = { - headers: { - Authorization: `Token token=${apiKey}`, - }, method, body, qs, @@ -52,7 +49,8 @@ export async function freshworksCrmApiRequest( delete options.qs; } try { - return await this.helpers.request!(options); + const credentialsType = 'freshworksCrmApi'; + return await this.helpers.requestWithAuthentication.call(this, credentialsType, options); } catch (error) { throw new NodeApiError(this.getNode(), error); } diff --git a/packages/nodes-base/nodes/FreshworksCrm/descriptions/SearchDescription.ts b/packages/nodes-base/nodes/FreshworksCrm/descriptions/SearchDescription.ts new file mode 100644 index 0000000000..fed7104aa9 --- /dev/null +++ b/packages/nodes-base/nodes/FreshworksCrm/descriptions/SearchDescription.ts @@ -0,0 +1,267 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const searchOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: [ + 'search', + ], + }, + }, + options: [ + { + name: 'Query', + value: 'query', + description: 'Search for records by entering search queries of your choice', + }, + { + name: 'Lookup', + value: 'lookup', + description: 'Search for the name or email address of records', + }, + ], + default: 'query', + }, +]; + +export const searchFields: INodeProperties[] = [ + // ---------------------------------------- + // Search: query + // ---------------------------------------- + { + displayName: 'Search Term', + name: 'query', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'search', + ], + operation: [ + 'query', + ], + }, + }, + description: 'Enter a term that will be used for searching entities', + }, + { + displayName: 'Search on Entities', + name: 'entities', + type: 'multiOptions', + options: [ + { + name: 'Contact', + value: 'contact', + }, + { + name: 'Deal', + value: 'deal', + }, + { + name: 'Sales Account', + value: 'sales_account', + }, + { + name: 'User', + value: 'user', + }, + ], + required: true, + default: [], + displayOptions: { + show: { + resource: [ + 'search', + ], + operation: [ + 'query', + ], + }, + }, + description: 'Enter a term that will be used for searching entities', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + displayOptions: { + show: { + resource: [ + 'search', + ], + operation: [ + 'query', + ], + }, + }, + description: 'Whether to return all results or only up to a given limit', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 25, + displayOptions: { + show: { + resource: [ + 'search', + ], + operation: [ + 'query', + ], + returnAll: [ + false, + ], + }, + }, + description: 'Max number of results to return', + }, + // ---------------------------------------- + // Search: lookup + // ---------------------------------------- + { + displayName: 'Search Field', + name: 'searchField', + type: 'options', + options: [ + { + name: 'Email', + value: 'email', + }, + { + name: 'Name', + value: 'name', + }, + { + name: 'Custom Field', + value: 'customField', + description: 'Only allowed custom fields of type "Text field", "Number", "Dropdown" or "Radio button"', + }, + ], + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'search', + ], + operation: [ + 'lookup', + ], + }, + }, + description: 'Field against which the entities have to be searched', + }, + { + displayName: 'Custom Field Name', + name: 'customFieldName', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'search', + ], + operation: [ + 'lookup', + ], + searchField: [ + 'customField', + ], + }, + }, + }, + { + displayName: 'Custom Field Value', + name: 'customFieldValue', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'search', + ], + operation: [ + 'lookup', + ], + searchField: [ + 'customField', + ], + }, + }, + }, + { + displayName: 'Field Value', + name: 'fieldValue', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'search', + ], + operation: [ + 'lookup', + ], + searchField: [ + 'email', + 'name', + ], + }, + }, + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + 'search', + ], + operation: [ + 'lookup', + ], + }, + }, + options: [ + { + displayName: 'Entities', + name: 'entities', + type: 'multiOptions', + default: [], + options: [ + { + name: 'Contact', + value: 'contact', + }, + { + name: 'Deal', + value: 'deal', + }, + { + name: 'Sales Account', + value: 'sales_account', + }, + ], + // eslint-disable-next-line n8n-nodes-base/node-param-description-unneeded-backticks + description: `Use 'entities' to query against related entities. You can include multiple entities at once, provided the field is available in both entities or else you'd receive an error response.`, + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/FreshworksCrm/descriptions/index.ts b/packages/nodes-base/nodes/FreshworksCrm/descriptions/index.ts index 70957a2c38..06a000c960 100644 --- a/packages/nodes-base/nodes/FreshworksCrm/descriptions/index.ts +++ b/packages/nodes-base/nodes/FreshworksCrm/descriptions/index.ts @@ -4,4 +4,5 @@ export * from './ContactDescription'; export * from './DealDescription'; export * from './NoteDescription'; export * from './SalesActivityDescription'; +export * from './SearchDescription'; export * from './TaskDescription';