d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
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
241 lines
7.4 KiB
TypeScript
241 lines
7.4 KiB
TypeScript
import { generateMockPayloadFromOutputsDefinition } from '@/lib/workflows/triggers/trigger-utils'
|
|
import type { SubBlockConfig } from '@/blocks/types'
|
|
import { TRIGGER_REGISTRY } from '@/triggers/registry'
|
|
import type { TriggerConfig } from '@/triggers/types'
|
|
|
|
/**
|
|
* IDs that should NOT be namespaced because they are shared across triggers
|
|
* or are the control mechanism for trigger selection
|
|
*/
|
|
const SHARED_SUBBLOCK_IDS = new Set(['selectedTriggerId'])
|
|
|
|
/**
|
|
* Checks if a subBlock is display-only (not user-editable).
|
|
* Display-only subBlocks should be namespaced to avoid conflicts when
|
|
* multiple triggers show different content for the same conceptual field.
|
|
*/
|
|
function isDisplayOnlySubBlock(subBlock: SubBlockConfig): boolean {
|
|
// Text type is always display-only
|
|
if (subBlock.type === 'text') {
|
|
return true
|
|
}
|
|
|
|
// ReadOnly inputs are display-only
|
|
if (subBlock.readOnly === true) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* Namespaces a subBlock ID with the trigger ID to avoid conflicts when
|
|
* multiple triggers are merged into a single block.
|
|
*
|
|
* Only namespaces display-only subBlocks (readOnly or text type) that have
|
|
* a condition on selectedTriggerId. User-input fields are NOT namespaced
|
|
* so their values persist when switching between triggers.
|
|
*/
|
|
function namespaceSubBlockId(subBlock: SubBlockConfig, triggerId: string): SubBlockConfig {
|
|
// Don't namespace shared IDs
|
|
if (SHARED_SUBBLOCK_IDS.has(subBlock.id)) {
|
|
return subBlock
|
|
}
|
|
|
|
// Only namespace display-only subBlocks to avoid content conflicts
|
|
// User-input fields should remain shared so values persist across trigger switches
|
|
if (!isDisplayOnlySubBlock(subBlock)) {
|
|
return subBlock
|
|
}
|
|
|
|
// Only namespace if the subBlock has a condition on selectedTriggerId
|
|
// These are the ones that are trigger-specific and will conflict when merged
|
|
const condition =
|
|
typeof subBlock.condition === 'function' ? subBlock.condition() : subBlock.condition
|
|
if (condition?.field === 'selectedTriggerId') {
|
|
return {
|
|
...subBlock,
|
|
id: `${subBlock.id}_${triggerId}`,
|
|
}
|
|
}
|
|
|
|
return subBlock
|
|
}
|
|
|
|
/**
|
|
* Gets a trigger config and injects samplePayload subblock with condition.
|
|
* Also namespaces subBlock IDs to avoid conflicts when multiple triggers
|
|
* are merged into a single block (e.g., ...getTrigger('a').subBlocks, ...getTrigger('b').subBlocks).
|
|
*/
|
|
export function getTrigger(triggerId: string): TriggerConfig {
|
|
const trigger = TRIGGER_REGISTRY[triggerId]
|
|
if (!trigger) {
|
|
throw new Error(`Trigger not found: ${triggerId}`)
|
|
}
|
|
|
|
// Filter out deprecated trigger-save subblocks from legacy stored data
|
|
const subBlocks = trigger.subBlocks
|
|
.filter(
|
|
(subBlock) => subBlock.id !== 'triggerSave' && (subBlock.type as string) !== 'trigger-save'
|
|
)
|
|
.map((subBlock) => namespaceSubBlockId(subBlock, triggerId))
|
|
|
|
const clonedTrigger = { ...trigger, subBlocks }
|
|
|
|
// Inject samplePayload for webhooks/pollers with condition
|
|
if (trigger.webhook || trigger.id.includes('webhook') || trigger.id.includes('poller')) {
|
|
const samplePayloadExists = clonedTrigger.subBlocks.some(
|
|
(sb) => sb.id === 'samplePayload' || sb.id === `samplePayload_${triggerId}`
|
|
)
|
|
|
|
if (!samplePayloadExists && trigger.outputs) {
|
|
const mockPayload = generateMockPayloadFromOutputsDefinition(trigger.outputs)
|
|
const generatedPayload = JSON.stringify(mockPayload, null, 2)
|
|
|
|
const samplePayloadSubBlock: SubBlockConfig = {
|
|
id: `samplePayload_${triggerId}`,
|
|
title: 'Event Payload Example',
|
|
type: 'code',
|
|
language: 'json',
|
|
defaultValue: generatedPayload,
|
|
readOnly: true,
|
|
collapsible: true,
|
|
defaultCollapsed: true,
|
|
hideFromPreview: true,
|
|
mode: 'trigger',
|
|
condition: {
|
|
field: 'selectedTriggerId',
|
|
value: trigger.id,
|
|
},
|
|
}
|
|
|
|
clonedTrigger.subBlocks.push(samplePayloadSubBlock)
|
|
}
|
|
}
|
|
|
|
return clonedTrigger
|
|
}
|
|
|
|
export function getTriggersByProvider(provider: string): TriggerConfig[] {
|
|
return Object.values(TRIGGER_REGISTRY)
|
|
.filter((trigger) => trigger.provider === provider)
|
|
.map((trigger) => getTrigger(trigger.id))
|
|
}
|
|
|
|
export function getAllTriggers(): TriggerConfig[] {
|
|
return Object.keys(TRIGGER_REGISTRY).map((triggerId) => getTrigger(triggerId))
|
|
}
|
|
|
|
export function getTriggerIds(): string[] {
|
|
return Object.keys(TRIGGER_REGISTRY)
|
|
}
|
|
|
|
export function isTriggerValid(triggerId: string): boolean {
|
|
return triggerId in TRIGGER_REGISTRY
|
|
}
|
|
|
|
export type { TriggerConfig } from '@/triggers/types'
|
|
|
|
/**
|
|
* Options for building trigger subBlocks
|
|
*/
|
|
export interface BuildTriggerSubBlocksOptions {
|
|
/** The trigger ID (e.g., 'lemlist_email_replied') */
|
|
triggerId: string
|
|
/** Dropdown options for selecting trigger type */
|
|
triggerOptions: Array<{ label: string; id: string }>
|
|
/** Whether to include the trigger type dropdown (only for primary trigger) */
|
|
includeDropdown?: boolean
|
|
/** HTML setup instructions to display */
|
|
setupInstructions: string
|
|
/** Additional fields to insert before the save button (e.g., campaign filters) */
|
|
extraFields?: SubBlockConfig[]
|
|
/** Webhook URL placeholder text */
|
|
webhookPlaceholder?: string
|
|
}
|
|
|
|
/**
|
|
* Generic builder for trigger subBlocks.
|
|
* Creates a consistent structure: [dropdown?] -> webhookUrl -> extraFields -> instructions
|
|
*
|
|
* Usage:
|
|
* - Primary trigger: `buildTriggerSubBlocks({ ...options, includeDropdown: true })`
|
|
* - Secondary triggers: `buildTriggerSubBlocks({ ...options })` (no dropdown)
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* // Primary trigger (with dropdown)
|
|
* subBlocks: buildTriggerSubBlocks({
|
|
* triggerId: 'service_event_a',
|
|
* triggerOptions: serviceTriggerOptions,
|
|
* includeDropdown: true,
|
|
* setupInstructions: serviceSetupInstructions('eventA'),
|
|
* })
|
|
*
|
|
* // Secondary trigger (no dropdown)
|
|
* subBlocks: buildTriggerSubBlocks({
|
|
* triggerId: 'service_event_b',
|
|
* triggerOptions: serviceTriggerOptions,
|
|
* setupInstructions: serviceSetupInstructions('eventB'),
|
|
* })
|
|
* ```
|
|
*/
|
|
export function buildTriggerSubBlocks(options: BuildTriggerSubBlocksOptions): SubBlockConfig[] {
|
|
const {
|
|
triggerId,
|
|
triggerOptions,
|
|
includeDropdown = false,
|
|
setupInstructions,
|
|
extraFields = [],
|
|
webhookPlaceholder = 'Webhook URL will be generated',
|
|
} = options
|
|
|
|
const blocks: SubBlockConfig[] = []
|
|
|
|
// Only the primary trigger includes the dropdown
|
|
if (includeDropdown) {
|
|
blocks.push({
|
|
id: 'selectedTriggerId',
|
|
title: 'Trigger Type',
|
|
type: 'dropdown',
|
|
mode: 'trigger',
|
|
options: triggerOptions,
|
|
value: () => triggerId,
|
|
required: true,
|
|
})
|
|
}
|
|
|
|
// Webhook URL display (common to all triggers)
|
|
// ID will be namespaced by getTrigger() when merged into blocks
|
|
blocks.push({
|
|
id: 'webhookUrlDisplay',
|
|
title: 'Webhook URL',
|
|
type: 'short-input',
|
|
readOnly: true,
|
|
showCopyButton: true,
|
|
useWebhookUrl: true,
|
|
placeholder: webhookPlaceholder,
|
|
mode: 'trigger',
|
|
condition: { field: 'selectedTriggerId', value: triggerId },
|
|
})
|
|
|
|
// Insert any extra fields (campaign filters, event types, etc.)
|
|
if (extraFields.length > 0) {
|
|
blocks.push(...extraFields)
|
|
}
|
|
|
|
// Setup instructions
|
|
// ID will be namespaced by getTrigger() when merged into blocks
|
|
blocks.push({
|
|
id: 'triggerInstructions',
|
|
title: 'Setup Instructions',
|
|
hideFromPreview: true,
|
|
type: 'text',
|
|
defaultValue: setupInstructions,
|
|
mode: 'trigger',
|
|
condition: { field: 'selectedTriggerId', value: triggerId },
|
|
})
|
|
|
|
return blocks
|
|
}
|