Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion apps/sim/blocks/blocks/gitlab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { GitLabIcon } from '@/components/icons'
import type { BlockConfig, BlockMeta } from '@/blocks/types'
import { AuthMode, IntegrationType } from '@/blocks/types'
import type { GitLabResponse } from '@/tools/gitlab/types'
import { getTrigger } from '@/triggers'

export const GitLabBlock: BlockConfig<GitLabResponse> = {
type: 'gitlab',
name: 'GitLab',
description: 'Interact with GitLab projects, issues, merge requests, and pipelines',
authMode: AuthMode.ApiKey,
triggerAllowed: false,
triggerAllowed: true,
longDescription:
'Integrate GitLab into the workflow. Can manage projects, issues, merge requests, pipelines, and add comments. Supports all core GitLab DevOps operations.',
docsLink: 'https://docs.sim.ai/integrations/gitlab',
Expand Down Expand Up @@ -437,6 +438,12 @@ Return ONLY the commit message - no explanations, no extra text.`,
],
},
},
...getTrigger('gitlab_push').subBlocks,
...getTrigger('gitlab_merge_request').subBlocks,
...getTrigger('gitlab_issue').subBlocks,
...getTrigger('gitlab_pipeline').subBlocks,
...getTrigger('gitlab_comment').subBlocks,
...getTrigger('gitlab_webhook').subBlocks,
],
tools: {
access: [
Expand Down Expand Up @@ -746,6 +753,18 @@ Return ONLY the commit message - no explanations, no extra text.`,
// Success indicator
success: { type: 'boolean', description: 'Operation success status' },
},

triggers: {
enabled: true,
available: [
'gitlab_push',
'gitlab_merge_request',
'gitlab_issue',
'gitlab_pipeline',
'gitlab_comment',
'gitlab_webhook',
],
},
}

export const GitLabBlockMeta = {
Expand Down
20 changes: 20 additions & 0 deletions apps/sim/blocks/blocks/pagerduty.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { PagerDutyIcon } from '@/components/icons'
import { AuthMode, type BlockConfig, type BlockMeta, IntegrationType } from '@/blocks/types'
import { getTrigger } from '@/triggers'

export const PagerDutyBlock: BlockConfig = {
type: 'pagerduty',
name: 'PagerDuty',
description: 'Manage incidents and on-call schedules with PagerDuty',
triggerAllowed: true,
longDescription:
'Integrate PagerDuty into your workflow to list, create, and update incidents, add notes, list services, and check on-call schedules.',
docsLink: 'https://docs.sim.ai/integrations/pagerduty',
Expand Down Expand Up @@ -315,6 +317,12 @@ export const PagerDutyBlock: BlockConfig = {
generationType: 'timestamp',
},
},
...getTrigger('pagerduty_incident_triggered').subBlocks,
...getTrigger('pagerduty_incident_acknowledged').subBlocks,
...getTrigger('pagerduty_incident_resolved').subBlocks,
...getTrigger('pagerduty_incident_escalated').subBlocks,
...getTrigger('pagerduty_incident_reassigned').subBlocks,
...getTrigger('pagerduty_webhook').subBlocks,
],

tools: {
Expand Down Expand Up @@ -481,6 +489,18 @@ export const PagerDutyBlock: BlockConfig = {
description: 'Array of on-call entries (list_oncalls)',
},
},

triggers: {
enabled: true,
available: [
'pagerduty_incident_triggered',
'pagerduty_incident_acknowledged',
'pagerduty_incident_resolved',
'pagerduty_incident_escalated',
'pagerduty_incident_reassigned',
'pagerduty_webhook',
],
},
}

export const PagerDutyBlockMeta = {
Expand Down
18 changes: 18 additions & 0 deletions apps/sim/blocks/blocks/zendesk.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { ZendeskIcon } from '@/components/icons'
import type { BlockConfig, BlockMeta } from '@/blocks/types'
import { AuthMode, IntegrationType } from '@/blocks/types'
import { getTrigger } from '@/triggers'

export const ZendeskBlock: BlockConfig = {
type: 'zendesk',
name: 'Zendesk',
description: 'Manage support tickets, users, and organizations in Zendesk',
triggerAllowed: true,
longDescription:
'Integrate Zendesk into the workflow. Can get tickets, get ticket, create ticket, create tickets bulk, update ticket, update tickets bulk, delete ticket, merge tickets, get users, get user, get current user, search users, create user, create users bulk, update user, update users bulk, delete user, get organizations, get organization, autocomplete organizations, create organization, create organizations bulk, update organization, delete organization, search, search count.',
docsLink: 'https://docs.sim.ai/integrations/zendesk',
Expand Down Expand Up @@ -529,6 +531,11 @@ Return ONLY the search query - no explanations.`,
},
mode: 'advanced',
},
...getTrigger('zendesk_ticket_created').subBlocks,
...getTrigger('zendesk_ticket_status_changed').subBlocks,
...getTrigger('zendesk_ticket_comment_added').subBlocks,
...getTrigger('zendesk_ticket_priority_changed').subBlocks,
...getTrigger('zendesk_webhook').subBlocks,
],
tools: {
access: [
Expand Down Expand Up @@ -695,6 +702,17 @@ Return ONLY the search query - no explanations.`,
// Metadata (shared across all operations)
metadata: { type: 'json', description: 'Operation metadata including operation type' },
},

triggers: {
enabled: true,
available: [
'zendesk_ticket_created',
'zendesk_ticket_status_changed',
'zendesk_ticket_comment_added',
'zendesk_ticket_priority_changed',
'zendesk_webhook',
],
},
}

export const ZendeskBlockMeta = {
Expand Down
1 change: 1 addition & 0 deletions apps/sim/lib/core/idempotency/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,7 @@ export class IdempotencyService {
normalizedHeaders?.['x-webhook-id'] ||
normalizedHeaders?.['x-shopify-webhook-id'] ||
normalizedHeaders?.['x-github-delivery'] ||
normalizedHeaders?.['x-gitlab-event-uuid'] ||
normalizedHeaders?.['x-event-id'] ||
normalizedHeaders?.['x-teams-notification-id'] ||
normalizedHeaders?.['svix-id'] ||
Expand Down
68 changes: 68 additions & 0 deletions apps/sim/lib/webhooks/providers/gitlab.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { createLogger } from '@sim/logger'
import { safeCompare } from '@sim/security/compare'
import { NextResponse } from 'next/server'
import type {
AuthContext,
EventMatchContext,
FormatInputContext,
FormatInputResult,
WebhookProviderHandler,
} from '@/lib/webhooks/providers/types'

const logger = createLogger('WebhookProvider:GitLab')

function asRecord(value: unknown): Record<string, unknown> {
return (value as Record<string, unknown>) || {}
}

export const gitlabHandler: WebhookProviderHandler = {
/**
* GitLab echoes the configured "Secret token" verbatim in the `X-Gitlab-Token`
* header (plain equality, not an HMAC). Skip verification when no token is set.
*/
verifyAuth({ request, requestId, providerConfig }: AuthContext) {
const secret = providerConfig.webhookSecret as string | undefined
if (!secret) {
return null
}

const token = request.headers.get('X-Gitlab-Token')
if (!token) {
logger.warn(`[${requestId}] GitLab webhook missing X-Gitlab-Token header`)
return new NextResponse('Unauthorized - Missing GitLab token', { status: 401 })
}

if (!safeCompare(token, secret)) {
logger.warn(`[${requestId}] GitLab token verification failed`)
return new NextResponse('Unauthorized - Invalid GitLab token', { status: 401 })
}

return null
},

async matchEvent({ body, requestId, providerConfig }: EventMatchContext) {
const triggerId = providerConfig.triggerId as string | undefined
if (!triggerId || triggerId === 'gitlab_webhook') return true

const objectKind = asRecord(body).object_kind as string | undefined

const { isGitLabEventMatch } = await import('@/triggers/gitlab/utils')
if (!isGitLabEventMatch(triggerId, objectKind || '')) {
logger.debug(
`[${requestId}] GitLab event '${objectKind}' does not match trigger ${triggerId}, skipping`
)
return false
}
return true
},

async formatInput({ body, headers }: FormatInputContext): Promise<FormatInputResult> {
const b = asRecord(body)
const eventType = headers['x-gitlab-event'] || ''
const ref = (b.ref as string) || ''
const branch = ref.replace('refs/heads/', '')
return {
input: { ...b, event_type: eventType, branch },
}
},
}
99 changes: 99 additions & 0 deletions apps/sim/lib/webhooks/providers/pagerduty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import crypto from 'crypto'
import { createLogger } from '@sim/logger'
import { safeCompare } from '@sim/security/compare'
import type {
EventMatchContext,
FormatInputContext,
FormatInputResult,
WebhookProviderHandler,
} from '@/lib/webhooks/providers/types'
import { createHmacVerifier } from '@/lib/webhooks/providers/utils'

const logger = createLogger('WebhookProvider:PagerDuty')

/**
* PagerDuty V3 signs the raw body with HMAC-SHA256 and sends it in the
* `X-PagerDuty-Signature` header as one or more comma-separated `v1=<hex>`
* values (multiple appear during signing-secret rotation). The delivery is
* valid when our computed signature matches any of them.
*/
function validatePagerDutySignature(secret: string, signature: string, body: string): boolean {
if (!secret || !signature || !body) return false
const computed = crypto.createHmac('sha256', secret).update(body, 'utf8').digest('hex')
return signature
.split(',')
.map((part) => part.trim())
.filter((part) => part.startsWith('v1='))
.some((part) => safeCompare(part.slice(3), computed))
}

function asRecord(value: unknown): Record<string, unknown> {
return (value as Record<string, unknown>) || {}
}

function referenceSummary(
value: unknown
): { id?: unknown; summary?: unknown; html_url?: unknown } | null {
if (!value || typeof value !== 'object') return null
const ref = value as Record<string, unknown>
return { id: ref.id, summary: ref.summary, html_url: ref.html_url }
}

export const pagerdutyHandler: WebhookProviderHandler = {
verifyAuth: createHmacVerifier({
configKey: 'webhookSecret',
headerName: 'X-PagerDuty-Signature',
validateFn: validatePagerDutySignature,
providerLabel: 'PagerDuty',
}),

async matchEvent({ body, requestId, providerConfig }: EventMatchContext) {
const triggerId = providerConfig.triggerId as string | undefined
if (!triggerId || triggerId === 'pagerduty_webhook') return true

const event = asRecord(asRecord(body).event)
const eventType = event.event_type as string | undefined

const { isPagerDutyEventMatch } = await import('@/triggers/pagerduty/utils')
if (!isPagerDutyEventMatch(triggerId, eventType || '')) {
logger.debug(
`[${requestId}] PagerDuty event '${eventType}' does not match trigger ${triggerId}, skipping`
)
return false
}
return true
},

async formatInput({ body }: FormatInputContext): Promise<FormatInputResult> {
const event = asRecord(asRecord(body).event)
const data = asRecord(event.data)
const priority = referenceSummary(data.priority)

return {
input: {
event_id: event.id,
event_type: event.event_type,
occurred_at: event.occurred_at,
agent: event.agent ?? null,
incident: {
id: data.id,
number: data.number,
title: data.title,
status: data.status,
urgency: data.urgency,
html_url: data.html_url,
created_at: data.created_at,
priority: priority?.summary ?? null,
service: referenceSummary(data.service),
escalation_policy: referenceSummary(data.escalation_policy),
assignees: Array.isArray(data.assignees) ? data.assignees : [],
},
},
}
},

extractIdempotencyId(body: unknown) {
const event = asRecord(asRecord(body).event)
return (event.id as string | undefined) || null
},
}
6 changes: 6 additions & 0 deletions apps/sim/lib/webhooks/providers/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { fathomHandler } from '@/lib/webhooks/providers/fathom'
import { firefliesHandler } from '@/lib/webhooks/providers/fireflies'
import { genericHandler } from '@/lib/webhooks/providers/generic'
import { githubHandler } from '@/lib/webhooks/providers/github'
import { gitlabHandler } from '@/lib/webhooks/providers/gitlab'
import { gmailHandler } from '@/lib/webhooks/providers/gmail'
import { gongHandler } from '@/lib/webhooks/providers/gong'
import { googleFormsHandler } from '@/lib/webhooks/providers/google-forms'
Expand All @@ -29,6 +30,7 @@ import { microsoftTeamsHandler } from '@/lib/webhooks/providers/microsoft-teams'
import { mondayHandler } from '@/lib/webhooks/providers/monday'
import { notionHandler } from '@/lib/webhooks/providers/notion'
import { outlookHandler } from '@/lib/webhooks/providers/outlook'
import { pagerdutyHandler } from '@/lib/webhooks/providers/pagerduty'
import { resendHandler } from '@/lib/webhooks/providers/resend'
import { rssHandler } from '@/lib/webhooks/providers/rss'
import { salesforceHandler } from '@/lib/webhooks/providers/salesforce'
Expand All @@ -46,6 +48,7 @@ import { verifyTokenAuth } from '@/lib/webhooks/providers/utils'
import { vercelHandler } from '@/lib/webhooks/providers/vercel'
import { webflowHandler } from '@/lib/webhooks/providers/webflow'
import { whatsappHandler } from '@/lib/webhooks/providers/whatsapp'
import { zendeskHandler } from '@/lib/webhooks/providers/zendesk'
import { zoomHandler } from '@/lib/webhooks/providers/zoom'

const logger = createLogger('WebhookProviderRegistry')
Expand All @@ -64,6 +67,7 @@ const PROVIDER_HANDLERS: Record<string, WebhookProviderHandler> = {
generic: genericHandler,
gmail: gmailHandler,
github: githubHandler,
gitlab: gitlabHandler,
gong: gongHandler,
google_forms: googleFormsHandler,
fathom: fathomHandler,
Expand All @@ -81,6 +85,7 @@ const PROVIDER_HANDLERS: Record<string, WebhookProviderHandler> = {
'microsoft-teams': microsoftTeamsHandler,
notion: notionHandler,
outlook: outlookHandler,
pagerduty: pagerdutyHandler,
rss: rssHandler,
salesforce: salesforceHandler,
sendblue: sendblueHandler,
Expand All @@ -95,6 +100,7 @@ const PROVIDER_HANDLERS: Record<string, WebhookProviderHandler> = {
vercel: vercelHandler,
webflow: webflowHandler,
whatsapp: whatsappHandler,
zendesk: zendeskHandler,
zoom: zoomHandler,
}

Expand Down
Loading
Loading