Skip to content

Commit fac26f8

Browse files
waleedlatif1claude
authored andcommitted
refactor(triggers): consolidate v2 Linear triggers into same files as v1 (#4010)
* refactor(triggers): consolidate v2 Linear triggers into same files as v1 Move v2 trigger exports from separate _v2.ts files into their corresponding v1 files, matching the block v2 convention where LinearV2Block lives alongside LinearBlock in the same file. * updated * fix: restore staging registry entries accidentally removed Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs * fix: restore integrations.json to staging version Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(generate-docs): extract all trigger configs from multi-export files The buildTriggerRegistry function used a single regex exec per file, which only captured the first TriggerConfig export. Files that export both v1 and v2 triggers (consolidated same-file convention) had their v2 triggers silently dropped from integrations.json. Split each file into segments per export and parse each independently. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: restore staging linear handler and utils with teamId support Restores the staging version of linear provider handler and trigger utils that were accidentally regressed. Key restorations: - teamId sub-block and allPublicTeams fallback in createSubscription - Timestamp skew validation in verifyAuth - actorType renaming in formatInput (avoids TriggerOutput collision) - url field in formatInput and all output builders - edited field in comment outputs - externalId validation after webhook creation - isLinearEventMatch returns false (not true) for unknown triggers Adds extractIdempotencyId to the linear provider handler for webhook deduplication support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: restore non-Linear files accidentally modified Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: remove redundant extractIdempotencyId from linear handler The idempotency service already uses the Linear-Delivery header (which Linear always sends) as the primary dedup key. The body-based fallback was unnecessary defensive code. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * idempotency * tets --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 650a104 commit fac26f8

39 files changed

+580
-612
lines changed

apps/sim/app/(landing)/integrations/data/integrations.json

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4015,8 +4015,14 @@
40154015
}
40164016
],
40174017
"operationCount": 12,
4018-
"triggers": [],
4019-
"triggerCount": 0,
4018+
"triggers": [
4019+
{
4020+
"id": "gmail_poller",
4021+
"name": "Gmail Email Trigger",
4022+
"description": "Triggers when new emails are received in Gmail (requires Gmail credentials)"
4023+
}
4024+
],
4025+
"triggerCount": 1,
40204026
"authType": "oauth",
40214027
"category": "tools",
40224028
"integrationType": "email",
@@ -7256,7 +7262,7 @@
72567262
{
72577263
"id": "linear_webhook_v2",
72587264
"name": "Linear Webhook",
7259-
"description": "Trigger workflow from Linear data-change events included in this webhook subscription (Issues, Comments, Projects, etc.—not every Linear model)."
7265+
"description": "Trigger workflow from Linear events you select when creating the webhook in Linear (not guaranteed to be every model or event type)."
72607266
}
72617267
],
72627268
"triggerCount": 15,
@@ -8580,8 +8586,14 @@
85808586
}
85818587
],
85828588
"operationCount": 9,
8583-
"triggers": [],
8584-
"triggerCount": 0,
8589+
"triggers": [
8590+
{
8591+
"id": "outlook_poller",
8592+
"name": "Outlook Email Trigger",
8593+
"description": "Triggers when new emails are received in Outlook (requires Microsoft credentials)"
8594+
}
8595+
],
8596+
"triggerCount": 1,
85858597
"authType": "oauth",
85868598
"category": "tools",
85878599
"integrationType": "email",

apps/sim/lib/core/idempotency/service.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,9 @@ export class IdempotencyService {
422422
normalizedHeaders?.['x-teams-notification-id'] ||
423423
normalizedHeaders?.['svix-id'] ||
424424
normalizedHeaders?.['linear-delivery'] ||
425-
normalizedHeaders?.['greenhouse-event-id']
425+
normalizedHeaders?.['greenhouse-event-id'] ||
426+
normalizedHeaders?.['x-zm-request-id'] ||
427+
normalizedHeaders?.['idempotency-key']
426428

427429
if (webhookIdHeader) {
428430
return `${webhookId}:${webhookIdHeader}`

apps/sim/lib/webhooks/providers/ashby.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ function validateAshbySignature(secretToken: string, signature: string, body: st
3333
}
3434

3535
export const ashbyHandler: WebhookProviderHandler = {
36+
extractIdempotencyId(body: unknown): string | null {
37+
const obj = body as Record<string, unknown>
38+
const webhookActionId = obj.webhookActionId
39+
if (typeof webhookActionId === 'string' && webhookActionId) {
40+
return `ashby:${webhookActionId}`
41+
}
42+
return null
43+
},
44+
3645
async formatInput({ body }: FormatInputContext): Promise<FormatInputResult> {
3746
const b = body as Record<string, unknown>
3847
return {

apps/sim/lib/webhooks/providers/gong.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,20 @@ export async function verifyGongJwtAuth(ctx: AuthContext): Promise<NextResponse
123123
export const gongHandler: WebhookProviderHandler = {
124124
verifyAuth: verifyGongJwtAuth,
125125

126+
extractIdempotencyId(body: unknown): string | null {
127+
const obj = body as Record<string, unknown>
128+
const callData = obj.callData as Record<string, unknown> | undefined
129+
const metaData = callData?.metaData as Record<string, unknown> | undefined
130+
const id = metaData?.id
131+
if (typeof id === 'string' && id) {
132+
return `gong:${id}`
133+
}
134+
if (typeof id === 'number') {
135+
return `gong:${id}`
136+
}
137+
return null
138+
},
139+
126140
async formatInput({ body }: FormatInputContext): Promise<FormatInputResult> {
127141
const b = body as Record<string, unknown>
128142
const callData = b.callData as Record<string, unknown> | undefined

apps/sim/lib/webhooks/providers/telegram.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ export const telegramHandler: WebhookProviderHandler = {
2323
return null
2424
},
2525

26+
extractIdempotencyId(body: unknown): string | null {
27+
const obj = body as Record<string, unknown>
28+
const updateId = obj.update_id
29+
if (typeof updateId === 'number') {
30+
return `telegram:${updateId}`
31+
}
32+
return null
33+
},
34+
2635
async formatInput({ body }: FormatInputContext): Promise<FormatInputResult> {
2736
const b = body as Record<string, unknown>
2837
const rawMessage = (b?.message ||

apps/sim/lib/webhooks/providers/zoom.test.ts

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,29 +27,8 @@ describe('Zoom webhook provider', () => {
2727
expect(validateZoomSignature(secret, hashA, timestamp, rawB)).toBe(false)
2828
})
2929

30-
it('extractIdempotencyId prefers meeting uuid', () => {
31-
const zid = zoomHandler.extractIdempotencyId!({
32-
event: 'meeting.started',
33-
event_ts: 123,
34-
payload: { object: { uuid: 'u1', id: 55 } },
35-
})
36-
expect(zid).toBe('zoom:meeting.started:123:u1')
37-
})
38-
39-
it('extractIdempotencyId uses participant identity when available', () => {
40-
const zid = zoomHandler.extractIdempotencyId!({
41-
event: 'meeting.participant_joined',
42-
event_ts: 123,
43-
payload: {
44-
object: {
45-
uuid: 'meeting-uuid',
46-
participant: {
47-
user_id: 'participant-1',
48-
},
49-
},
50-
},
51-
})
52-
expect(zid).toBe('zoom:meeting.participant_joined:123:participant-1')
30+
it('does not implement extractIdempotencyId (x-zm-request-id handled at service level)', () => {
31+
expect(zoomHandler.extractIdempotencyId).toBeUndefined()
5332
})
5433

5534
it('formatInput passes through the Zoom webhook envelope', async () => {

apps/sim/lib/webhooks/providers/zoom.ts

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -128,36 +128,6 @@ export const zoomHandler: WebhookProviderHandler = {
128128
return null
129129
},
130130

131-
extractIdempotencyId(body: unknown): string | null {
132-
const obj = body as Record<string, unknown>
133-
const event = obj.event
134-
const ts = obj.event_ts
135-
if (typeof event !== 'string' || ts === undefined || ts === null) {
136-
return null
137-
}
138-
const payload = obj.payload as Record<string, unknown> | undefined
139-
const inner = payload?.object as Record<string, unknown> | undefined
140-
const participant =
141-
inner?.participant &&
142-
typeof inner.participant === 'object' &&
143-
!Array.isArray(inner.participant)
144-
? (inner.participant as Record<string, unknown>)
145-
: null
146-
const participantStable =
147-
(typeof participant?.user_id === 'string' && participant.user_id) ||
148-
(typeof participant?.id === 'string' && participant.id) ||
149-
(typeof participant?.email === 'string' && participant.email) ||
150-
(typeof participant?.join_time === 'string' && participant.join_time) ||
151-
(typeof participant?.leave_time === 'string' && participant.leave_time) ||
152-
''
153-
const stable =
154-
participantStable ||
155-
(typeof inner?.uuid === 'string' && inner.uuid) ||
156-
(inner?.id !== undefined && inner.id !== null ? String(inner.id) : '') ||
157-
''
158-
return `zoom:${event}:${String(ts)}:${stable}`
159-
},
160-
161131
async matchEvent({ webhook: wh, workflow, body, requestId, providerConfig }: EventMatchContext) {
162132
const triggerId = providerConfig.triggerId as string | undefined
163133
const obj = body as Record<string, unknown>

apps/sim/triggers/linear/comment_created.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { LinearIcon } from '@/components/icons'
2-
import { buildCommentOutputs, linearSetupInstructions } from '@/triggers/linear/utils'
2+
import {
3+
buildCommentOutputs,
4+
buildLinearV2SubBlocks,
5+
linearSetupInstructions,
6+
} from '@/triggers/linear/utils'
37
import type { TriggerConfig } from '@/triggers/types'
48

59
export const linearCommentCreatedTrigger: TriggerConfig = {
@@ -78,3 +82,27 @@ export const linearCommentCreatedTrigger: TriggerConfig = {
7882
},
7983
},
8084
}
85+
86+
export const linearCommentCreatedV2Trigger: TriggerConfig = {
87+
id: 'linear_comment_created_v2',
88+
name: 'Linear Comment Created',
89+
provider: 'linear',
90+
description: 'Trigger workflow when a new comment is created in Linear',
91+
version: '2.0.0',
92+
icon: LinearIcon,
93+
subBlocks: buildLinearV2SubBlocks({
94+
triggerId: 'linear_comment_created_v2',
95+
eventType: 'Comment (create)',
96+
}),
97+
outputs: buildCommentOutputs(),
98+
webhook: {
99+
method: 'POST',
100+
headers: {
101+
'Content-Type': 'application/json',
102+
'Linear-Event': 'Comment',
103+
'Linear-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
104+
'Linear-Signature': 'sha256...',
105+
'User-Agent': 'Linear-Webhook',
106+
},
107+
},
108+
}

apps/sim/triggers/linear/comment_created_v2.ts

Lines changed: 0 additions & 30 deletions
This file was deleted.

apps/sim/triggers/linear/comment_updated.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { LinearIcon } from '@/components/icons'
2-
import { buildCommentOutputs, linearSetupInstructions } from '@/triggers/linear/utils'
2+
import {
3+
buildCommentOutputs,
4+
buildLinearV2SubBlocks,
5+
linearSetupInstructions,
6+
} from '@/triggers/linear/utils'
37
import type { TriggerConfig } from '@/triggers/types'
48

59
export const linearCommentUpdatedTrigger: TriggerConfig = {
@@ -78,3 +82,27 @@ export const linearCommentUpdatedTrigger: TriggerConfig = {
7882
},
7983
},
8084
}
85+
86+
export const linearCommentUpdatedV2Trigger: TriggerConfig = {
87+
id: 'linear_comment_updated_v2',
88+
name: 'Linear Comment Updated',
89+
provider: 'linear',
90+
description: 'Trigger workflow when a comment is updated in Linear',
91+
version: '2.0.0',
92+
icon: LinearIcon,
93+
subBlocks: buildLinearV2SubBlocks({
94+
triggerId: 'linear_comment_updated_v2',
95+
eventType: 'Comment (update)',
96+
}),
97+
outputs: buildCommentOutputs(),
98+
webhook: {
99+
method: 'POST',
100+
headers: {
101+
'Content-Type': 'application/json',
102+
'Linear-Event': 'Comment',
103+
'Linear-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
104+
'Linear-Signature': 'sha256...',
105+
'User-Agent': 'Linear-Webhook',
106+
},
107+
},
108+
}

0 commit comments

Comments
 (0)