chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import { SalesforceIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
buildSalesforceAuthOnlyFields,
|
||||
buildSalesforceCaseStatusOutputs,
|
||||
salesforceSetupInstructions,
|
||||
salesforceTriggerOptions,
|
||||
} from '@/triggers/salesforce/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Salesforce Case Status Changed Trigger
|
||||
*/
|
||||
export const salesforceCaseStatusChangedTrigger: TriggerConfig = {
|
||||
id: 'salesforce_case_status_changed',
|
||||
name: 'Salesforce Case Status Changed',
|
||||
provider: 'salesforce',
|
||||
description: 'Trigger workflow when a case status changes',
|
||||
version: '1.0.0',
|
||||
icon: SalesforceIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'salesforce_case_status_changed',
|
||||
triggerOptions: salesforceTriggerOptions,
|
||||
setupInstructions: salesforceSetupInstructions('Case Status Changed'),
|
||||
extraFields: buildSalesforceAuthOnlyFields('salesforce_case_status_changed'),
|
||||
}),
|
||||
|
||||
outputs: buildSalesforceCaseStatusOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export { salesforceCaseStatusChangedTrigger } from './case_status_changed'
|
||||
export { salesforceOpportunityStageChangedTrigger } from './opportunity_stage_changed'
|
||||
export { salesforceRecordCreatedTrigger } from './record_created'
|
||||
export { salesforceRecordDeletedTrigger } from './record_deleted'
|
||||
export { salesforceRecordUpdatedTrigger } from './record_updated'
|
||||
export { salesforceWebhookTrigger } from './webhook'
|
||||
@@ -0,0 +1,37 @@
|
||||
import { SalesforceIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
buildSalesforceAuthOnlyFields,
|
||||
buildSalesforceOpportunityStageOutputs,
|
||||
salesforceSetupInstructions,
|
||||
salesforceTriggerOptions,
|
||||
} from '@/triggers/salesforce/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Salesforce Opportunity Stage Changed Trigger
|
||||
*/
|
||||
export const salesforceOpportunityStageChangedTrigger: TriggerConfig = {
|
||||
id: 'salesforce_opportunity_stage_changed',
|
||||
name: 'Salesforce Opportunity Stage Changed',
|
||||
provider: 'salesforce',
|
||||
description: 'Trigger workflow when an opportunity stage changes',
|
||||
version: '1.0.0',
|
||||
icon: SalesforceIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'salesforce_opportunity_stage_changed',
|
||||
triggerOptions: salesforceTriggerOptions,
|
||||
setupInstructions: salesforceSetupInstructions('Opportunity Stage Changed'),
|
||||
extraFields: buildSalesforceAuthOnlyFields('salesforce_opportunity_stage_changed'),
|
||||
}),
|
||||
|
||||
outputs: buildSalesforceOpportunityStageOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { SalesforceIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
buildSalesforceExtraFields,
|
||||
buildSalesforceRecordOutputs,
|
||||
salesforceSetupInstructions,
|
||||
salesforceTriggerOptions,
|
||||
} from '@/triggers/salesforce/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Salesforce Record Created Trigger
|
||||
*
|
||||
* PRIMARY trigger — includes the dropdown for selecting trigger type.
|
||||
*/
|
||||
export const salesforceRecordCreatedTrigger: TriggerConfig = {
|
||||
id: 'salesforce_record_created',
|
||||
name: 'Salesforce Record Created',
|
||||
provider: 'salesforce',
|
||||
description: 'Trigger workflow when a Salesforce record is created',
|
||||
version: '1.0.0',
|
||||
icon: SalesforceIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'salesforce_record_created',
|
||||
triggerOptions: salesforceTriggerOptions,
|
||||
includeDropdown: true,
|
||||
setupInstructions: salesforceSetupInstructions('Record Created'),
|
||||
extraFields: buildSalesforceExtraFields('salesforce_record_created'),
|
||||
}),
|
||||
|
||||
outputs: buildSalesforceRecordOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { SalesforceIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
buildSalesforceExtraFields,
|
||||
buildSalesforceRecordOutputs,
|
||||
salesforceSetupInstructions,
|
||||
salesforceTriggerOptions,
|
||||
} from '@/triggers/salesforce/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Salesforce Record Deleted Trigger
|
||||
*/
|
||||
export const salesforceRecordDeletedTrigger: TriggerConfig = {
|
||||
id: 'salesforce_record_deleted',
|
||||
name: 'Salesforce Record Deleted',
|
||||
provider: 'salesforce',
|
||||
description: 'Trigger workflow when a Salesforce record is deleted',
|
||||
version: '1.0.0',
|
||||
icon: SalesforceIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'salesforce_record_deleted',
|
||||
triggerOptions: salesforceTriggerOptions,
|
||||
setupInstructions: salesforceSetupInstructions('Record Deleted'),
|
||||
extraFields: buildSalesforceExtraFields('salesforce_record_deleted'),
|
||||
}),
|
||||
|
||||
outputs: buildSalesforceRecordOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { SalesforceIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
buildSalesforceExtraFields,
|
||||
buildSalesforceRecordOutputs,
|
||||
salesforceSetupInstructions,
|
||||
salesforceTriggerOptions,
|
||||
} from '@/triggers/salesforce/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Salesforce Record Updated Trigger
|
||||
*/
|
||||
export const salesforceRecordUpdatedTrigger: TriggerConfig = {
|
||||
id: 'salesforce_record_updated',
|
||||
name: 'Salesforce Record Updated',
|
||||
provider: 'salesforce',
|
||||
description: 'Trigger workflow when a Salesforce record is updated',
|
||||
version: '1.0.0',
|
||||
icon: SalesforceIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'salesforce_record_updated',
|
||||
triggerOptions: salesforceTriggerOptions,
|
||||
setupInstructions: salesforceSetupInstructions('Record Updated'),
|
||||
extraFields: buildSalesforceExtraFields('salesforce_record_updated'),
|
||||
}),
|
||||
|
||||
outputs: buildSalesforceRecordOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
import { extractSalesforceObjectTypeFromPayload } from '@/lib/webhooks/providers/salesforce'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import type { TriggerOutput } from '@/triggers/types'
|
||||
import { normalizeToken } from '@/triggers/utils/tokens'
|
||||
|
||||
/**
|
||||
* Dropdown options for the Salesforce trigger type selector.
|
||||
*/
|
||||
export const salesforceTriggerOptions = [
|
||||
{ label: 'Record Created', id: 'salesforce_record_created' },
|
||||
{ label: 'Record Updated', id: 'salesforce_record_updated' },
|
||||
{ label: 'Record Deleted', id: 'salesforce_record_deleted' },
|
||||
{ label: 'Opportunity Stage Changed', id: 'salesforce_opportunity_stage_changed' },
|
||||
{ label: 'Case Status Changed', id: 'salesforce_case_status_changed' },
|
||||
{ label: 'Generic Webhook (All Events)', id: 'salesforce_webhook' },
|
||||
]
|
||||
|
||||
const RECORD_CREATED = new Set([
|
||||
'record_created',
|
||||
'created',
|
||||
'create',
|
||||
'after_insert',
|
||||
'afterinsert',
|
||||
'insert',
|
||||
])
|
||||
|
||||
const RECORD_UPDATED = new Set([
|
||||
'record_updated',
|
||||
'updated',
|
||||
'update',
|
||||
'after_update',
|
||||
'afterupdate',
|
||||
])
|
||||
|
||||
const RECORD_DELETED = new Set([
|
||||
'record_deleted',
|
||||
'deleted',
|
||||
'delete',
|
||||
'after_delete',
|
||||
'afterdelete',
|
||||
])
|
||||
|
||||
const OPP_STAGE = new Set([
|
||||
'opportunity_stage_changed',
|
||||
'stage_changed',
|
||||
'stage_change',
|
||||
'opportunity_stage_change',
|
||||
'opportunitystagechanged',
|
||||
])
|
||||
|
||||
const CASE_STATUS = new Set([
|
||||
'case_status_changed',
|
||||
'status_changed',
|
||||
'status_change',
|
||||
'case_status_change',
|
||||
'casestatuschanged',
|
||||
])
|
||||
|
||||
function matchesRecordTrigger(triggerId: string, normalizedEvent: string): boolean {
|
||||
if (triggerId === 'salesforce_record_created') {
|
||||
return RECORD_CREATED.has(normalizedEvent)
|
||||
}
|
||||
if (triggerId === 'salesforce_record_updated') {
|
||||
return RECORD_UPDATED.has(normalizedEvent)
|
||||
}
|
||||
if (triggerId === 'salesforce_record_deleted') {
|
||||
return RECORD_DELETED.has(normalizedEvent)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Server-side filter for Salesforce Flow (JSON) payloads.
|
||||
* Users should send a string `eventType` (or `simEventType`) from the Flow body.
|
||||
* Optional `objectType` in provider config is enforced against payload when set.
|
||||
*/
|
||||
export function isSalesforceEventMatch(
|
||||
triggerId: string,
|
||||
body: Record<string, unknown>,
|
||||
configuredObjectType?: string
|
||||
): boolean {
|
||||
if (triggerId === 'salesforce_webhook') {
|
||||
const want = configuredObjectType?.trim()
|
||||
if (!want) {
|
||||
return true
|
||||
}
|
||||
const got = extractSalesforceObjectTypeFromPayload(body)
|
||||
if (!got) {
|
||||
return false
|
||||
}
|
||||
return normalizeToken(got) === normalizeToken(want)
|
||||
}
|
||||
|
||||
const wantType = configuredObjectType?.trim()
|
||||
const gotType = extractSalesforceObjectTypeFromPayload(body)
|
||||
if (wantType) {
|
||||
if (!gotType) {
|
||||
return false
|
||||
}
|
||||
if (normalizeToken(gotType) !== normalizeToken(wantType)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (triggerId === 'salesforce_opportunity_stage_changed') {
|
||||
if (gotType && normalizeToken(gotType) !== 'opportunity') {
|
||||
return false
|
||||
}
|
||||
const etRaw =
|
||||
(typeof body.eventType === 'string' && body.eventType) ||
|
||||
(typeof body.simEventType === 'string' && body.simEventType) ||
|
||||
''
|
||||
if (!etRaw.trim()) {
|
||||
return Boolean(gotType && normalizeToken(gotType) === 'opportunity')
|
||||
}
|
||||
return OPP_STAGE.has(normalizeToken(etRaw))
|
||||
}
|
||||
|
||||
if (triggerId === 'salesforce_case_status_changed') {
|
||||
if (gotType && normalizeToken(gotType) !== 'case') {
|
||||
return false
|
||||
}
|
||||
const etRaw =
|
||||
(typeof body.eventType === 'string' && body.eventType) ||
|
||||
(typeof body.simEventType === 'string' && body.simEventType) ||
|
||||
''
|
||||
if (!etRaw.trim()) {
|
||||
return Boolean(gotType && normalizeToken(gotType) === 'case')
|
||||
}
|
||||
return CASE_STATUS.has(normalizeToken(etRaw))
|
||||
}
|
||||
|
||||
const etRaw =
|
||||
(typeof body.eventType === 'string' && body.eventType) ||
|
||||
(typeof body.simEventType === 'string' && body.simEventType) ||
|
||||
''
|
||||
|
||||
if (!etRaw.trim()) {
|
||||
return false
|
||||
}
|
||||
|
||||
const normalized = normalizeToken(etRaw)
|
||||
return matchesRecordTrigger(triggerId, normalized)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML setup instructions for the Salesforce trigger.
|
||||
* Use Flow HTTP Callouts with a JSON body. Outbound Messages are SOAP/XML and are not supported.
|
||||
*/
|
||||
export function salesforceSetupInstructions(eventType: string): string {
|
||||
const isGeneric = eventType === 'All Events'
|
||||
|
||||
const instructions = isGeneric
|
||||
? [
|
||||
'Copy the <strong>Webhook URL</strong> above and generate a <strong>Webhook Secret</strong> (any strong random string). Paste the secret in the <strong>Webhook Secret</strong> field here.',
|
||||
"In Salesforce, go to <strong>Setup → Named Credentials</strong>. Create an <strong>External Credential</strong> with a custom Authentication Parameter holding your secret, then a <strong>Named Credential</strong> whose URL is the Webhook URL above, with a custom header <code>Authorization</code> set to <code>{!'Bearer ' & $Credential.YourExternalCredential.YourParameter}</code> (or a header named <code>X-Sim-Webhook-Secret</code> with the raw secret). When adding that custom header, check <strong>Allow Formulas in HTTP Header</strong> — if left unchecked, Salesforce sends the literal <code>{!$Credential…}</code> text instead of evaluating it, and authentication silently fails. Flow’s HTTP Callout action requires a Named Credential — it cannot call an arbitrary URL directly.",
|
||||
'Under the External Credential, add a <strong>Permission Set</strong> and enable <strong>External Credential Principal Access</strong> for its principal, then assign that permission set to the user the Flow runs as (the <strong>Automated Process</strong> user for background/record-triggered flows). Without this the callout fails authentication even though the Named Credential is configured correctly.',
|
||||
'Go to <strong>Setup → Flows</strong> and click <strong>New Flow</strong>.',
|
||||
'Select <strong>Record-Triggered Flow</strong> and choose the object(s) you want to monitor.',
|
||||
'Add an <strong>Action</strong> that performs an <strong>HTTP Callout</strong> using the Named Credential from step 2 — method <strong>POST</strong>, <code>Content-Type: application/json</code>, path <code>/</code> (the base URL already points at your webhook). Record-triggered flows can only make callouts on the <strong>Run Asynchronously</strong> path, so place this action there.',
|
||||
'Build the request body as <strong>JSON</strong> (not SOAP/XML). Include <code>eventType</code> and record fields (e.g. <code>Id</code>, <code>Name</code>). Outbound Messages use SOAP and will not work with this trigger.',
|
||||
'Save and <strong>Activate</strong> the Flow(s).',
|
||||
'<strong>Save this trigger in Sim first</strong> so the URL is registered before you build the Named Credential and Flow.',
|
||||
'Click <strong>"Save"</strong> above to activate your trigger.',
|
||||
]
|
||||
: [
|
||||
'Copy the <strong>Webhook URL</strong> above and set a <strong>Webhook Secret</strong>. In Salesforce, create an <strong>External Credential</strong> (holding the secret) and a <strong>Named Credential</strong> whose URL is this Webhook URL, with a custom header sending the same value as <code>Authorization: Bearer …</code> or <code>X-Sim-Webhook-Secret: …</code>. If the header value uses a <code>{!$Credential…}</code> formula, check <strong>Allow Formulas in HTTP Header</strong> when adding it — otherwise Salesforce sends the literal formula text instead of the secret. Flow’s HTTP Callout action requires a Named Credential — it cannot call an arbitrary URL directly.',
|
||||
'Under the External Credential, add a <strong>Permission Set</strong> with <strong>External Credential Principal Access</strong> enabled and assign it to the user the Flow runs as (the <strong>Automated Process</strong> user for record-triggered flows) — otherwise the callout fails authentication.',
|
||||
'Go to <strong>Setup → Flows</strong> and click <strong>New Flow</strong>.',
|
||||
`Select <strong>Record-Triggered Flow</strong> for the right object and <strong>${eventType}</strong> as the entry condition.`,
|
||||
'Add an <strong>HTTP Callout</strong> using the Named Credential from step 1 — <strong>POST</strong>, JSON body, path <code>/</code>. Record-triggered flows can only make callouts on the <strong>Run Asynchronously</strong> path, so place this action there.',
|
||||
`Include <code>eventType</code> in the JSON body using a value this trigger accepts (e.g. for Record Created use <code>record_created</code>, <code>created</code>, or <code>after_insert</code>).`,
|
||||
'If you use <strong>Object Type (Optional)</strong>, you must also include matching type metadata in the JSON body (for example <code>objectType</code>, <code>sobjectType</code>, or <code>attributes.type</code>) or the event will be rejected.',
|
||||
'Save and <strong>Activate</strong> the Flow.',
|
||||
'Click <strong>"Save"</strong> above to activate your trigger.',
|
||||
]
|
||||
|
||||
return instructions
|
||||
.map(
|
||||
(instruction, index) =>
|
||||
`<div class="mb-3"><strong>${index + 1}.</strong> ${instruction}</div>`
|
||||
)
|
||||
.join('')
|
||||
}
|
||||
|
||||
function salesforceWebhookSecretField(triggerId: string): SubBlockConfig {
|
||||
return {
|
||||
id: 'webhookSecret',
|
||||
title: 'Webhook Secret',
|
||||
type: 'short-input',
|
||||
placeholder: 'Generate a secret and paste it here',
|
||||
description:
|
||||
'Required. Use the same value in your Salesforce HTTP Callout as Bearer token or X-Sim-Webhook-Secret.',
|
||||
password: true,
|
||||
required: true,
|
||||
mode: 'trigger',
|
||||
condition: { field: 'selectedTriggerId', value: triggerId },
|
||||
}
|
||||
}
|
||||
|
||||
function salesforceObjectTypeField(triggerId: string): SubBlockConfig {
|
||||
return {
|
||||
id: 'objectType',
|
||||
title: 'Object Type (Optional)',
|
||||
type: 'short-input',
|
||||
placeholder: 'e.g., Account, Contact, Opportunity',
|
||||
description:
|
||||
'When set, the payload must include matching object type metadata (for example objectType, sobjectType, or attributes.type) or the event is rejected.',
|
||||
mode: 'trigger',
|
||||
condition: { field: 'selectedTriggerId', value: triggerId },
|
||||
}
|
||||
}
|
||||
|
||||
/** Secret + optional object filter (record triggers and generic webhook). */
|
||||
export function buildSalesforceExtraFields(triggerId: string): SubBlockConfig[] {
|
||||
return [salesforceWebhookSecretField(triggerId), salesforceObjectTypeField(triggerId)]
|
||||
}
|
||||
|
||||
/** Webhook secret only (Opportunity / Case specialized triggers — object is implied). */
|
||||
export function buildSalesforceAuthOnlyFields(triggerId: string): SubBlockConfig[] {
|
||||
return [salesforceWebhookSecretField(triggerId)]
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs for record lifecycle events (created, updated, deleted).
|
||||
*/
|
||||
export function buildSalesforceRecordOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
eventType: {
|
||||
type: 'string',
|
||||
description: 'The type of event (e.g., created, updated, deleted)',
|
||||
},
|
||||
/** Present when the Flow JSON body uses `simEventType` instead of or in addition to `eventType`. */
|
||||
simEventType: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Optional alias from the payload (`simEventType`). Empty when only `eventType` is sent.',
|
||||
},
|
||||
objectType: {
|
||||
type: 'string',
|
||||
description: 'Salesforce object type (e.g., Account, Contact, Lead)',
|
||||
},
|
||||
recordId: { type: 'string', description: 'ID of the affected record' },
|
||||
timestamp: { type: 'string', description: 'When the event occurred (ISO 8601)' },
|
||||
record: {
|
||||
Id: { type: 'string', description: 'Record ID' },
|
||||
Name: { type: 'string', description: 'Record name' },
|
||||
CreatedDate: { type: 'string', description: 'Record creation date' },
|
||||
LastModifiedDate: { type: 'string', description: 'Last modification date' },
|
||||
OwnerId: {
|
||||
type: 'string',
|
||||
description: 'Record owner ID (standard field when sent in the Flow body)',
|
||||
},
|
||||
SystemModstamp: {
|
||||
type: 'string',
|
||||
description: 'System modstamp from the record (ISO 8601) when included in the payload',
|
||||
},
|
||||
},
|
||||
changedFields: { type: 'json', description: 'Fields that were changed (for update events)' },
|
||||
payload: { type: 'json', description: 'Full webhook payload' },
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs for opportunity stage change events.
|
||||
*/
|
||||
export function buildSalesforceOpportunityStageOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
eventType: { type: 'string', description: 'The type of event' },
|
||||
simEventType: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Optional alias from the payload (`simEventType`). Empty when only `eventType` is sent.',
|
||||
},
|
||||
objectType: { type: 'string', description: 'Salesforce object type (Opportunity)' },
|
||||
recordId: { type: 'string', description: 'Opportunity ID' },
|
||||
timestamp: { type: 'string', description: 'When the event occurred (ISO 8601)' },
|
||||
record: {
|
||||
Id: { type: 'string', description: 'Opportunity ID' },
|
||||
Name: { type: 'string', description: 'Opportunity name' },
|
||||
StageName: { type: 'string', description: 'Current stage name' },
|
||||
Amount: { type: 'string', description: 'Deal amount' },
|
||||
CloseDate: { type: 'string', description: 'Expected close date' },
|
||||
Probability: { type: 'string', description: 'Win probability' },
|
||||
AccountId: { type: 'string', description: 'Related Account ID (standard Opportunity field)' },
|
||||
OwnerId: { type: 'string', description: 'Opportunity owner ID' },
|
||||
},
|
||||
previousStage: { type: 'string', description: 'Previous stage name' },
|
||||
newStage: { type: 'string', description: 'New stage name' },
|
||||
payload: { type: 'json', description: 'Full webhook payload' },
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs for case status change events.
|
||||
*/
|
||||
export function buildSalesforceCaseStatusOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
eventType: { type: 'string', description: 'The type of event' },
|
||||
simEventType: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Optional alias from the payload (`simEventType`). Empty when only `eventType` is sent.',
|
||||
},
|
||||
objectType: { type: 'string', description: 'Salesforce object type (Case)' },
|
||||
recordId: { type: 'string', description: 'Case ID' },
|
||||
timestamp: { type: 'string', description: 'When the event occurred (ISO 8601)' },
|
||||
record: {
|
||||
Id: { type: 'string', description: 'Case ID' },
|
||||
Subject: { type: 'string', description: 'Case subject' },
|
||||
Status: { type: 'string', description: 'Current case status' },
|
||||
Priority: { type: 'string', description: 'Case priority' },
|
||||
CaseNumber: { type: 'string', description: 'Case number' },
|
||||
AccountId: { type: 'string', description: 'Related Account ID' },
|
||||
ContactId: { type: 'string', description: 'Related Contact ID' },
|
||||
OwnerId: { type: 'string', description: 'Case owner ID' },
|
||||
},
|
||||
previousStatus: { type: 'string', description: 'Previous case status' },
|
||||
newStatus: { type: 'string', description: 'New case status' },
|
||||
payload: { type: 'json', description: 'Full webhook payload' },
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs for the generic webhook trigger.
|
||||
*/
|
||||
export function buildSalesforceWebhookOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
eventType: { type: 'string', description: 'The type of event' },
|
||||
simEventType: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Optional alias from the payload (`simEventType`). Empty when only `eventType` is sent.',
|
||||
},
|
||||
objectType: { type: 'string', description: 'Salesforce object type' },
|
||||
recordId: { type: 'string', description: 'ID of the affected record' },
|
||||
timestamp: { type: 'string', description: 'When the event occurred (ISO 8601)' },
|
||||
record: { type: 'json', description: 'Full record data' },
|
||||
payload: { type: 'json', description: 'Full webhook payload' },
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { SalesforceIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
buildSalesforceExtraFields,
|
||||
buildSalesforceWebhookOutputs,
|
||||
salesforceSetupInstructions,
|
||||
salesforceTriggerOptions,
|
||||
} from '@/triggers/salesforce/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Salesforce Generic Webhook Trigger
|
||||
*
|
||||
* Receives all Salesforce events via a single webhook endpoint.
|
||||
*/
|
||||
export const salesforceWebhookTrigger: TriggerConfig = {
|
||||
id: 'salesforce_webhook',
|
||||
name: 'Salesforce Webhook (All Events)',
|
||||
provider: 'salesforce',
|
||||
description: 'Trigger workflow on any Salesforce webhook event',
|
||||
version: '1.0.0',
|
||||
icon: SalesforceIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'salesforce_webhook',
|
||||
triggerOptions: salesforceTriggerOptions,
|
||||
setupInstructions: salesforceSetupInstructions('All Events'),
|
||||
extraFields: buildSalesforceExtraFields('salesforce_webhook'),
|
||||
}),
|
||||
|
||||
outputs: buildSalesforceWebhookOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user