import { NodeApiError, NodeConnectionType } from 'n8n-workflow'; import type { IDataObject, IPollFunctions, INodeExecutionData, INodeType, INodeTypeDescription, ILoadOptionsFunctions, INodePropertyOptions, JsonObject, } from 'n8n-workflow'; import { DateTime } from 'luxon'; import { getQuery, salesforceApiRequest, salesforceApiRequestAllItems, sortOptions, } from './GenericFunctions'; export class SalesforceTrigger implements INodeType { description: INodeTypeDescription = { displayName: 'Salesforce Trigger', name: 'salesforceTrigger', icon: 'file:salesforce.svg', group: ['trigger'], version: 1, description: 'Fetches data from Salesforce and starts the workflow on specified polling intervals.', subtitle: '={{($parameter["triggerOn"])}}', defaults: { name: 'Salesforce Trigger', }, credentials: [ { name: 'salesforceOAuth2Api', required: true, }, ], polling: true, inputs: [], outputs: [NodeConnectionType.Main], properties: [ { displayName: 'Trigger On', name: 'triggerOn', description: 'Which Salesforce event should trigger the node', type: 'options', default: '', options: [ { name: 'Account Created', value: 'accountCreated', description: 'When a new account is created', }, { name: 'Account Updated', value: 'accountUpdated', description: 'When an existing account is modified', }, { name: 'Attachment Created', value: 'attachmentCreated', description: 'When a file is uploaded and attached to an object', }, { name: 'Attachment Updated', value: 'attachmentUpdated', description: 'When an existing file is modified', }, { name: 'Case Created', value: 'caseCreated', description: 'When a new case is created', }, { name: 'Case Updated', value: 'caseUpdated', description: 'When an existing case is modified', }, { name: 'Contact Created', value: 'contactCreated', description: 'When a new contact is created', }, { name: 'Contact Updated', value: 'contactUpdated', description: 'When an existing contact is modified', }, { name: 'Custom Object Created', value: 'customObjectCreated', description: 'When a new object of a given type is created', }, { name: 'Custom Object Updated', value: 'customObjectUpdated', description: 'When an object of a given type is modified', }, { name: 'Lead Created', value: 'leadCreated', description: 'When a new lead is created', }, { name: 'Lead Updated', value: 'leadUpdated', description: 'When an existing lead is modified', }, { name: 'Opportunity Created', value: 'opportunityCreated', description: 'When a new opportunity is created', }, { name: 'Opportunity Updated', value: 'opportunityUpdated', description: 'When an existing opportunity is created', }, { name: 'Task Created', value: 'taskCreated', description: 'When a new task is created', }, { name: 'Task Updated', value: 'taskUpdated', description: 'When an existing task is modified', }, { name: 'User Created', value: 'userCreated', description: 'When a new user is created', }, { name: 'User Updated', value: 'userUpdated', description: 'When an existing user is modified', }, ], }, { displayName: 'Custom Object Name or ID', name: 'customObject', type: 'options', typeOptions: { loadOptionsMethod: 'getCustomObjects', }, required: true, default: '', displayOptions: { show: { triggerOn: ['customObjectUpdated', 'customObjectCreated'], }, }, description: 'Name of the custom object. Choose from the list, or specify an ID using an expression.', }, ], }; methods = { loadOptions: { // Get all the custom objects recurrence instances to display them to user so that they can // select them easily async getCustomObjects(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; // TODO: find a way to filter this object to get just the lead sources instead of the whole object const { sobjects: objects } = await salesforceApiRequest.call(this, 'GET', '/sobjects'); for (const object of objects) { if (object.custom === true) { const objectName = object.label; const objectId = object.name; returnData.push({ name: objectName, value: objectId, }); } } sortOptions(returnData); return returnData; }, }, }; async poll(this: IPollFunctions): Promise { const workflowData = this.getWorkflowStaticData('node'); let responseData; const qs: IDataObject = {}; const triggerOn = this.getNodeParameter('triggerOn') as string; let triggerResource = triggerOn.slice(0, 1).toUpperCase() + triggerOn.slice(1, -7); const changeType = triggerOn.slice(-7); if (triggerResource === 'CustomObject') { triggerResource = this.getNodeParameter('customObject') as string; } const now = DateTime.now().toISO(); const startDate = (workflowData.lastTimeChecked as string) || now; const endDate = now; try { const pollStartDate = startDate; const pollEndDate = endDate; const options = { conditionsUi: { conditionValues: [] as IDataObject[], }, }; if (this.getMode() !== 'manual') { if (changeType === 'Created') { options.conditionsUi.conditionValues.push({ field: 'CreatedDate', operation: '>=', value: pollStartDate, }); options.conditionsUi.conditionValues.push({ field: 'CreatedDate', operation: '<', value: pollEndDate, }); } else { options.conditionsUi.conditionValues.push({ field: 'LastModifiedDate', operation: '>=', value: pollStartDate, }); options.conditionsUi.conditionValues.push({ field: 'LastModifiedDate', operation: '<', value: pollEndDate, }); // make sure the resource wasn't just created. options.conditionsUi.conditionValues.push({ field: 'CreatedDate', operation: '<', value: pollStartDate, }); } } try { if (this.getMode() === 'manual') { qs.q = getQuery(options, triggerResource, false, 1); } else { qs.q = getQuery(options, triggerResource, true); } responseData = await salesforceApiRequestAllItems.call( this, 'records', 'GET', '/query', {}, qs, ); } catch (error) { throw new NodeApiError(this.getNode(), error as JsonObject); } if (!responseData?.length) { workflowData.lastTimeChecked = endDate; return null; } } catch (error) { if (this.getMode() === 'manual' || !workflowData.lastTimeChecked) { throw error; } const workflow = this.getWorkflow(); const node = this.getNode(); this.logger.error( `There was a problem in '${node.name}' node in workflow '${workflow.id}': '${error.description}'`, { node: node.name, workflowId: workflow.id, error, }, ); throw error; } workflowData.lastTimeChecked = endDate; if (Array.isArray(responseData) && responseData.length) { return [this.helpers.returnJsonArray(responseData as IDataObject[])]; } return null; } }