Skip to content

Commit ebbe67a

Browse files
fix(triggers): cleanup trigger outputs formatting, fix display name issues (#2801)
* fix(triggers): package lemlist data, cleanup trigger outputs formatting, fix display name issues * cleanup trigger outputs * fix tests * more test fixes * remove branch field for ones where it's not relevant * remove branch from unrelated ops
1 parent 2b49d15 commit ebbe67a

38 files changed

+548
-984
lines changed

.claude/commands/add-trigger.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,53 @@ All fields automatically have:
552552
- `mode: 'trigger'` - Only shown in trigger mode
553553
- `condition: { field: 'selectedTriggerId', value: triggerId }` - Only shown when this trigger is selected
554554

555+
## Trigger Outputs & Webhook Input Formatting
556+
557+
### Important: Two Sources of Truth
558+
559+
There are two related but separate concerns:
560+
561+
1. **Trigger `outputs`** - Schema/contract defining what fields SHOULD be available. Used by UI for tag dropdown.
562+
2. **`formatWebhookInput`** - Implementation that transforms raw webhook payload into actual data. Located in `apps/sim/lib/webhooks/utils.server.ts`.
563+
564+
**These MUST be aligned.** The fields returned by `formatWebhookInput` should match what's defined in trigger `outputs`. If they differ:
565+
- Tag dropdown shows fields that don't exist (broken variable resolution)
566+
- Or actual data has fields not shown in dropdown (users can't discover them)
567+
568+
### When to Add a formatWebhookInput Handler
569+
570+
- **Simple providers**: If the raw webhook payload structure already matches your outputs, you don't need a handler. The generic fallback returns `body` directly.
571+
- **Complex providers**: If you need to transform, flatten, extract nested data, compute fields, or handle conditional logic, add a handler.
572+
573+
### Adding a Handler
574+
575+
In `apps/sim/lib/webhooks/utils.server.ts`, add a handler block:
576+
577+
```typescript
578+
if (foundWebhook.provider === '{service}') {
579+
// Transform raw webhook body to match trigger outputs
580+
return {
581+
eventType: body.type,
582+
resourceId: body.data?.id || '',
583+
timestamp: body.created_at,
584+
resource: body.data,
585+
}
586+
}
587+
```
588+
589+
**Key rules:**
590+
- Return fields that match your trigger `outputs` definition exactly
591+
- No wrapper objects like `webhook: { data: ... }` or `{service}: { ... }`
592+
- No duplication (don't spread body AND add individual fields)
593+
- Use `null` for missing optional data, not empty objects with empty strings
594+
595+
### Verify Alignment
596+
597+
Run the alignment checker:
598+
```bash
599+
bunx scripts/check-trigger-alignment.ts {service}
600+
```
601+
555602
## Trigger Outputs
556603

557604
Trigger outputs use the same schema as block outputs (NOT tool outputs).
@@ -649,6 +696,11 @@ export const {service}WebhookTrigger: TriggerConfig = {
649696
- [ ] Added `delete{Service}Webhook` function to `provider-subscriptions.ts`
650697
- [ ] Added provider to `cleanupExternalWebhook` function
651698
699+
### Webhook Input Formatting
700+
- [ ] Added handler in `apps/sim/lib/webhooks/utils.server.ts` (if custom formatting needed)
701+
- [ ] Handler returns fields matching trigger `outputs` exactly
702+
- [ ] Run `bunx scripts/check-trigger-alignment.ts {service}` to verify alignment
703+
652704
### Testing
653705
- [ ] Run `bun run type-check` to verify no TypeScript errors
654706
- [ ] Restart dev server to pick up new triggers

apps/sim/blocks/registry.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,26 @@ export const getBlock = (type: string): BlockConfig | undefined => {
313313
return registry[normalized]
314314
}
315315

316+
export const getLatestBlock = (baseType: string): BlockConfig | undefined => {
317+
const normalized = baseType.replace(/-/g, '_')
318+
319+
const versionedKeys = Object.keys(registry).filter((key) => {
320+
const match = key.match(new RegExp(`^${normalized}_v(\\d+)$`))
321+
return match !== null
322+
})
323+
324+
if (versionedKeys.length > 0) {
325+
const sorted = versionedKeys.sort((a, b) => {
326+
const versionA = Number.parseInt(a.match(/_v(\d+)$/)?.[1] || '0', 10)
327+
const versionB = Number.parseInt(b.match(/_v(\d+)$/)?.[1] || '0', 10)
328+
return versionB - versionA
329+
})
330+
return registry[sorted[0]]
331+
}
332+
333+
return registry[normalized]
334+
}
335+
316336
export const getBlockByToolName = (toolName: string): BlockConfig | undefined => {
317337
return Object.values(registry).find((block) => block.tools?.access?.includes(toolName))
318338
}

apps/sim/executor/utils/start-block.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -378,21 +378,10 @@ function buildManualTriggerOutput(
378378
}
379379

380380
function buildIntegrationTriggerOutput(
381-
finalInput: unknown,
381+
_finalInput: unknown,
382382
workflowInput: unknown
383383
): NormalizedBlockOutput {
384-
const base: NormalizedBlockOutput = isPlainObject(workflowInput)
385-
? ({ ...(workflowInput as Record<string, unknown>) } as NormalizedBlockOutput)
386-
: {}
387-
388-
if (isPlainObject(finalInput)) {
389-
Object.assign(base, finalInput as Record<string, unknown>)
390-
base.input = { ...(finalInput as Record<string, unknown>) }
391-
} else {
392-
base.input = finalInput
393-
}
394-
395-
return mergeFilesIntoOutput(base, workflowInput)
384+
return isPlainObject(workflowInput) ? (workflowInput as NormalizedBlockOutput) : {}
396385
}
397386

398387
function extractSubBlocks(block: SerializedBlock): Record<string, unknown> | undefined {

apps/sim/lib/logs/get-trigger-options.ts

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getBlock } from '@/blocks/registry'
1+
import { getLatestBlock } from '@/blocks/registry'
22
import { getAllTriggers } from '@/triggers'
33

44
export interface TriggerOption {
@@ -49,22 +49,13 @@ export function getTriggerOptions(): TriggerOption[] {
4949
continue
5050
}
5151

52-
const block = getBlock(provider)
53-
54-
if (block) {
55-
providerMap.set(provider, {
56-
value: provider,
57-
label: block.name, // Use block's display name (e.g., "Slack", "GitHub")
58-
color: block.bgColor || '#6b7280', // Use block's hex color, fallback to gray
59-
})
60-
} else {
61-
const label = formatProviderName(provider)
62-
providerMap.set(provider, {
63-
value: provider,
64-
label,
65-
color: '#6b7280', // gray fallback
66-
})
67-
}
52+
const block = getLatestBlock(provider)
53+
54+
providerMap.set(provider, {
55+
value: provider,
56+
label: block?.name || formatProviderName(provider),
57+
color: block?.bgColor || '#6b7280',
58+
})
6859
}
6960

7061
const integrationOptions = Array.from(providerMap.values()).sort((a, b) =>

0 commit comments

Comments
 (0)