-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add Salesforce Trigger Node (#8920)
Co-authored-by: Michael Kret <michael.k@radency.com>
- Loading branch information
1 parent
3fd97e4
commit 571b613
Showing
3 changed files
with
296 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
290 changes: 290 additions & 0 deletions
290
packages/nodes-base/nodes/Salesforce/SalesforceTrigger.node.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,290 @@ | ||
import { NodeApiError } 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: ['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 <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.', | ||
}, | ||
], | ||
}; | ||
|
||
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<INodePropertyOptions[]> { | ||
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<INodeExecutionData[][] | null> { | ||
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters