Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
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
14 changes: 14 additions & 0 deletions apps/sim/blocks/blocks/confluence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -979,17 +979,24 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
...getTrigger('confluence_page_updated').subBlocks,
...getTrigger('confluence_page_removed').subBlocks,
...getTrigger('confluence_page_moved').subBlocks,
...getTrigger('confluence_page_restored').subBlocks,
...getTrigger('confluence_page_permissions_updated').subBlocks,
...getTrigger('confluence_comment_created').subBlocks,
...getTrigger('confluence_comment_removed').subBlocks,
...getTrigger('confluence_comment_updated').subBlocks,
...getTrigger('confluence_blog_created').subBlocks,
...getTrigger('confluence_blog_updated').subBlocks,
...getTrigger('confluence_blog_removed').subBlocks,
...getTrigger('confluence_blog_restored').subBlocks,
...getTrigger('confluence_attachment_created').subBlocks,
...getTrigger('confluence_attachment_removed').subBlocks,
...getTrigger('confluence_attachment_updated').subBlocks,
...getTrigger('confluence_space_created').subBlocks,
...getTrigger('confluence_space_updated').subBlocks,
...getTrigger('confluence_space_removed').subBlocks,
...getTrigger('confluence_label_added').subBlocks,
...getTrigger('confluence_label_removed').subBlocks,
...getTrigger('confluence_user_created').subBlocks,
...getTrigger('confluence_webhook').subBlocks,
],
triggers: {
Expand All @@ -999,17 +1006,24 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
'confluence_page_updated',
'confluence_page_removed',
'confluence_page_moved',
'confluence_page_restored',
'confluence_page_permissions_updated',
'confluence_comment_created',
'confluence_comment_removed',
'confluence_comment_updated',
'confluence_blog_created',
'confluence_blog_updated',
'confluence_blog_removed',
'confluence_blog_restored',
'confluence_attachment_created',
'confluence_attachment_removed',
'confluence_attachment_updated',
'confluence_space_created',
'confluence_space_updated',
'confluence_space_removed',
'confluence_label_added',
'confluence_label_removed',
'confluence_user_created',
'confluence_webhook',
],
},
Expand Down
21 changes: 21 additions & 0 deletions apps/sim/blocks/blocks/jira.ts
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,16 @@ Return ONLY the comment text - no explanations.`,
...getTrigger('jira_issue_updated').subBlocks,
...getTrigger('jira_issue_deleted').subBlocks,
...getTrigger('jira_issue_commented').subBlocks,
...getTrigger('jira_comment_updated').subBlocks,
...getTrigger('jira_comment_deleted').subBlocks,
...getTrigger('jira_worklog_created').subBlocks,
...getTrigger('jira_worklog_updated').subBlocks,
...getTrigger('jira_worklog_deleted').subBlocks,
...getTrigger('jira_sprint_created').subBlocks,
...getTrigger('jira_sprint_started').subBlocks,
...getTrigger('jira_sprint_closed').subBlocks,
...getTrigger('jira_project_created').subBlocks,
...getTrigger('jira_version_released').subBlocks,
...getTrigger('jira_webhook').subBlocks,
],
tools: {
Expand Down Expand Up @@ -1268,6 +1277,9 @@ Return ONLY the comment text - no explanations.`,
time_spent: { type: 'string', description: 'Time spent (for worklog events)' },
changelog: { type: 'json', description: 'Changelog object (for update events)' },
issue: { type: 'json', description: 'Complete issue object from webhook' },
sprint: { type: 'json', description: 'Sprint object (for sprint events)' },
project: { type: 'json', description: 'Project object (for project events)' },
version: { type: 'json', description: 'Version object (for version events)' },
jira: { type: 'json', description: 'Complete webhook payload' },
user: { type: 'json', description: 'User object who triggered the event' },
webhook: { type: 'json', description: 'Webhook metadata' },
Expand All @@ -1279,7 +1291,16 @@ Return ONLY the comment text - no explanations.`,
'jira_issue_updated',
'jira_issue_deleted',
'jira_issue_commented',
'jira_comment_updated',
'jira_comment_deleted',
'jira_worklog_created',
'jira_worklog_updated',
'jira_worklog_deleted',
'jira_sprint_created',
'jira_sprint_started',
'jira_sprint_closed',
'jira_project_created',
'jira_version_released',
'jira_webhook',
],
},
Expand Down
16 changes: 16 additions & 0 deletions apps/sim/blocks/blocks/jira_service_management.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { getScopesForService } from '@/lib/oauth/utils'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode, IntegrationType } from '@/blocks/types'
import type { JsmResponse } from '@/tools/jsm/types'
import { getTrigger } from '@/triggers'

export const JiraServiceManagementBlock: BlockConfig<JsmResponse> = {
type: 'jira_service_management',
Expand Down Expand Up @@ -564,6 +565,11 @@ Return ONLY the comment text - no explanations.`,
],
},
},
...getTrigger('jsm_request_created').subBlocks,
...getTrigger('jsm_request_updated').subBlocks,
...getTrigger('jsm_request_commented').subBlocks,
...getTrigger('jsm_request_resolved').subBlocks,
...getTrigger('jsm_webhook').subBlocks,
],
tools: {
access: [
Expand Down Expand Up @@ -1246,4 +1252,14 @@ Return ONLY the comment text - no explanations.`,
sourceIssueIdOrKey: { type: 'string', description: 'Source issue ID or key' },
targetIssueIdOrKey: { type: 'string', description: 'Target issue ID or key' },
},
triggers: {
enabled: true,
available: [
'jsm_request_created',
'jsm_request_updated',
'jsm_request_commented',
'jsm_request_resolved',
'jsm_webhook',
],
},
}
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 @@ -454,6 +454,7 @@ export class IdempotencyService {
normalizedHeaders?.['linear-delivery'] ||
normalizedHeaders?.['greenhouse-event-id'] ||
normalizedHeaders?.['x-zm-request-id'] ||
normalizedHeaders?.['x-atlassian-webhook-identifier'] ||
normalizedHeaders?.['idempotency-key']

if (webhookIdHeader) {
Expand Down
31 changes: 31 additions & 0 deletions apps/sim/lib/webhooks/providers/confluence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export const confluenceHandler: WebhookProviderHandler = {
extractAttachmentData,
extractSpaceData,
extractLabelData,
extractPagePermissionsData,
extractUserData,
} = await import('@/triggers/confluence/utils')
const providerConfig = (webhook.providerConfig as Record<string, unknown>) || {}
const triggerId = providerConfig.triggerId as string | undefined
Expand All @@ -45,6 +47,12 @@ export const confluenceHandler: WebhookProviderHandler = {
if (triggerId?.startsWith('confluence_label_')) {
return { input: extractLabelData(body) }
}
if (triggerId === 'confluence_page_permissions_updated') {
return { input: extractPagePermissionsData(body) }
}
if (triggerId === 'confluence_user_created') {
return { input: extractUserData(body) }
}
if (triggerId === 'confluence_webhook') {
const b = body as Record<string, unknown>
return {
Expand All @@ -59,12 +67,35 @@ export const confluenceHandler: WebhookProviderHandler = {
space: b.space || null,
label: b.label || null,
content: b.content || null,
user: b.user || null,
},
}
}
return { input: extractPageData(body) }
},

extractIdempotencyId(body: unknown) {
const obj = body as Record<string, unknown>
const event = obj.event as string | undefined
const timestamp = obj.timestamp ?? ''
const page = obj.page as Record<string, unknown> | undefined
const comment = obj.comment as Record<string, unknown> | undefined
const attachment = obj.attachment as Record<string, unknown> | undefined
const blog = (obj.blog || obj.blogpost) as Record<string, unknown> | undefined
const space = obj.space as Record<string, unknown> | undefined
const user = obj.user as Record<string, unknown> | undefined

const entityId =
comment?.id || attachment?.id || blog?.id || page?.id || space?.id || user?.accountId
if (event && entityId) {
return `confluence:${event}:${entityId}:${timestamp}`
}
if (event && timestamp) {
return `confluence:${event}:${timestamp}`
}
return null
},

async matchEvent({ webhook, workflow, body, requestId, providerConfig }: EventMatchContext) {
const triggerId = providerConfig.triggerId as string | undefined
const obj = body as Record<string, unknown>
Expand Down
71 changes: 61 additions & 10 deletions apps/sim/lib/webhooks/providers/jira.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,8 @@ export function validateJiraSignature(secret: string, signature: string, body: s
const providedSignature = signature.substring(7)
const computedHash = crypto.createHmac('sha256', secret).update(body, 'utf8').digest('hex')
logger.debug('Jira signature comparison', {
computedSignature: `${computedHash.substring(0, 10)}...`,
providedSignature: `${providedSignature.substring(0, 10)}...`,
computedLength: computedHash.length,
providedLength: providedSignature.length,
match: computedHash === providedSignature,
})
return safeCompare(computedHash, providedSignature)
} catch (error) {
Expand All @@ -52,17 +49,64 @@ export const jiraHandler: WebhookProviderHandler = {
}),

async formatInput({ body, webhook }: FormatInputContext): Promise<FormatInputResult> {
const { extractIssueData, extractCommentData, extractWorklogData } = await import(
'@/triggers/jira/utils'
)
const {
extractIssueData,
extractCommentData,
extractWorklogData,
extractSprintData,
extractProjectData,
extractVersionData,
} = await import('@/triggers/jira/utils')
const providerConfig = (webhook.providerConfig as Record<string, unknown>) || {}
const triggerId = providerConfig.triggerId as string | undefined
if (triggerId === 'jira_issue_commented') {

if (
triggerId === 'jira_issue_commented' ||
triggerId === 'jira_comment_updated' ||
triggerId === 'jira_comment_deleted'
) {
return { input: extractCommentData(body) }
}
if (triggerId === 'jira_worklog_created') {
if (
triggerId === 'jira_worklog_created' ||
triggerId === 'jira_worklog_updated' ||
triggerId === 'jira_worklog_deleted'
) {
return { input: extractWorklogData(body) }
}
if (
triggerId === 'jira_sprint_created' ||
triggerId === 'jira_sprint_started' ||
triggerId === 'jira_sprint_closed'
) {
return { input: extractSprintData(body) }
}
if (triggerId === 'jira_project_created') {
return { input: extractProjectData(body) }
}
if (triggerId === 'jira_version_released') {
return { input: extractVersionData(body) }
}

if (!triggerId || triggerId === 'jira_webhook') {
const obj = body as Record<string, unknown>
return {
input: {
webhookEvent: obj.webhookEvent,
timestamp: obj.timestamp,
user: obj.user || null,
issue_event_type_name: obj.issue_event_type_name,
issue: obj.issue || {},
changelog: obj.changelog,
comment: obj.comment,
worklog: obj.worklog,
sprint: obj.sprint,
project: obj.project,
version: obj.version,
},
}
}

return { input: extractIssueData(body) }
},

Expand Down Expand Up @@ -95,9 +139,16 @@ export const jiraHandler: WebhookProviderHandler = {
extractIdempotencyId(body: unknown) {
const obj = body as Record<string, unknown>
const issue = obj.issue as Record<string, unknown> | undefined
const comment = obj.comment as Record<string, unknown> | undefined
const worklog = obj.worklog as Record<string, unknown> | undefined
const project = obj.project as Record<string, unknown> | undefined
if (obj.webhookEvent && (issue?.id || project?.id)) {
return `${obj.webhookEvent}:${issue?.id || project?.id}`
const sprint = obj.sprint as Record<string, unknown> | undefined
const version = obj.version as Record<string, unknown> | undefined
const entityId =
comment?.id || worklog?.id || issue?.id || project?.id || sprint?.id || version?.id
if (obj.webhookEvent && entityId) {
const ts = obj.timestamp ?? ''
return `${obj.webhookEvent}:${entityId}:${ts}`
}
return null
},
Expand Down
97 changes: 97 additions & 0 deletions apps/sim/lib/webhooks/providers/jsm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { createLogger } from '@sim/logger'
import { validateJiraSignature } from '@/lib/webhooks/providers/jira'
import type {
EventMatchContext,
FormatInputContext,
FormatInputResult,
WebhookProviderHandler,
} from '@/lib/webhooks/providers/types'
import { createHmacVerifier } from '@/lib/webhooks/providers/utils'

const logger = createLogger('WebhookProvider:JSM')

/**
* Jira Service Management webhook handler.
*
* JSM uses the Jira webhook infrastructure. The handler reuses the same HMAC
* signature validation as Jira and adds JSM-specific event matching logic
* to route events to the correct trigger based on event type and changelog context.
*/
export const jsmHandler: WebhookProviderHandler = {
verifyAuth: createHmacVerifier({
configKey: 'webhookSecret',
headerName: 'X-Hub-Signature',
validateFn: validateJiraSignature,
providerLabel: 'JSM',
}),

async formatInput({ body, webhook }: FormatInputContext): Promise<FormatInputResult> {
const { extractRequestData, extractCommentData } = await import('@/triggers/jsm/utils')
const providerConfig = (webhook.providerConfig as Record<string, unknown>) || {}
const triggerId = providerConfig.triggerId as string | undefined

if (triggerId === 'jsm_request_commented') {
return { input: extractCommentData(body as Record<string, unknown>) }
}

// For the generic webhook, pass through the full payload so no data is lost
if (!triggerId || triggerId === 'jsm_webhook') {
const obj = body as Record<string, unknown>
return {
input: {
webhookEvent: obj.webhookEvent,
timestamp: obj.timestamp,
user: obj.user || null,
issue_event_type_name: obj.issue_event_type_name,
issue: obj.issue || {},
changelog: obj.changelog,
comment: obj.comment,
},
}
}

return { input: extractRequestData(body as Record<string, unknown>) }
Comment thread
waleedlatif1 marked this conversation as resolved.
},

async matchEvent({ webhook, workflow, body, requestId, providerConfig }: EventMatchContext) {
const triggerId = providerConfig.triggerId as string | undefined
const obj = body as Record<string, unknown>

if (triggerId && triggerId !== 'jsm_webhook') {
const webhookEvent = obj.webhookEvent as string | undefined
const issueEventTypeName = obj.issue_event_type_name as string | undefined
const changelog = obj.changelog as
| { items?: Array<{ field?: string; toString?: string }> }
| undefined

const { isJsmEventMatch } = await import('@/triggers/jsm/utils')
if (!isJsmEventMatch(triggerId, webhookEvent || '', issueEventTypeName, changelog)) {
logger.debug(
`[${requestId}] JSM event mismatch for trigger ${triggerId}. Event: ${webhookEvent}. Skipping execution.`,
{
webhookId: webhook.id,
workflowId: workflow.id,
triggerId,
receivedEvent: webhookEvent,
}
)
return false
}
}

return true
},

extractIdempotencyId(body: unknown) {
const obj = body as Record<string, unknown>
const issue = obj.issue as Record<string, unknown> | undefined
const ts = obj.timestamp ?? ''
if (obj.webhookEvent && issue?.id) {
return `jsm:${obj.webhookEvent}:${issue.id}:${ts}`
}
if (obj.webhookEvent && ts) {
return `jsm:${obj.webhookEvent}:${ts}`
}
return null
},
Comment thread
waleedlatif1 marked this conversation as resolved.
}
Loading
Loading