mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
feat(confluence): add webhook triggers for Confluence events (#3318)
* feat(confluence): add webhook triggers for Confluence events Adds 16 Confluence triggers: page CRUD, comments, blogs, attachments, spaces, and labels — plus a generic webhook trigger. * feat(confluence): wire triggers into block and webhook processor Add trigger subBlocks and triggers config to ConfluenceV2Block so triggers appear in the UI. Add Confluence signature verification and event filtering to the webhook processor. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(confluence): align trigger outputs with actual webhook payloads - Rewrite output builders to match real Confluence webhook payload structure (flat spaceKey, numeric version, actual API fields) - Remove fabricated fields (nested space/version objects, comment.body) - Add missing fields (creatorAccountId, lastModifierAccountId, self, creationDate, modificationDate, accountType) - Add extractor functions (extractPageData, extractCommentData, etc.) following the same pattern as Jira - Add formatWebhookInput handler for Confluence in utils.server.ts so payloads are properly destructured before reaching workflows - Make event field matching resilient (check both event and webhookEvent) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(confluence): handle generic webhook in formatWebhookInput The generic webhook (confluence_webhook) was falling through to extractPageData, which only returns the page field. For a catch-all trigger that accepts all event types, preserve all entity fields (page, comment, blog, attachment, space, label, content). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(confluence): use payload-based filtering instead of nonexistent event field Confluence Cloud webhooks don't include an event/webhookEvent field in the body (unlike Jira). Replaced broken event string matching with structural payload filtering that checks which entity key is present. * lint * fix(confluence): read webhookSecret instead of secret in signature verification * fix(webhooks): read webhookSecret for jira, linear, and github signature verification These providers define their secret subBlock with id: 'webhookSecret' but the processor was reading providerConfig.secret which is always undefined, silently skipping signature verification even when a secret is configured. * fix(confluence): use event field for exact matching with entity-category fallback Admin REST API webhooks (Settings > Webhooks) include an event field for action-level filtering (page_created vs page_updated). Connect app webhooks omit it, so we fall back to entity-category matching. --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ import type { BlockConfig } from '@/blocks/types'
|
||||
import { AuthMode } from '@/blocks/types'
|
||||
import { normalizeFileInput } from '@/blocks/utils'
|
||||
import type { ConfluenceResponse } from '@/tools/confluence/types'
|
||||
import { getTrigger } from '@/triggers'
|
||||
|
||||
export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
|
||||
type: 'confluence',
|
||||
@@ -838,7 +839,46 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// Trigger subBlocks
|
||||
...getTrigger('confluence_page_created').subBlocks,
|
||||
...getTrigger('confluence_page_updated').subBlocks,
|
||||
...getTrigger('confluence_page_removed').subBlocks,
|
||||
...getTrigger('confluence_page_moved').subBlocks,
|
||||
...getTrigger('confluence_comment_created').subBlocks,
|
||||
...getTrigger('confluence_comment_removed').subBlocks,
|
||||
...getTrigger('confluence_blog_created').subBlocks,
|
||||
...getTrigger('confluence_blog_updated').subBlocks,
|
||||
...getTrigger('confluence_blog_removed').subBlocks,
|
||||
...getTrigger('confluence_attachment_created').subBlocks,
|
||||
...getTrigger('confluence_attachment_removed').subBlocks,
|
||||
...getTrigger('confluence_space_created').subBlocks,
|
||||
...getTrigger('confluence_space_updated').subBlocks,
|
||||
...getTrigger('confluence_label_added').subBlocks,
|
||||
...getTrigger('confluence_label_removed').subBlocks,
|
||||
...getTrigger('confluence_webhook').subBlocks,
|
||||
],
|
||||
triggers: {
|
||||
enabled: true,
|
||||
available: [
|
||||
'confluence_page_created',
|
||||
'confluence_page_updated',
|
||||
'confluence_page_removed',
|
||||
'confluence_page_moved',
|
||||
'confluence_comment_created',
|
||||
'confluence_comment_removed',
|
||||
'confluence_blog_created',
|
||||
'confluence_blog_updated',
|
||||
'confluence_blog_removed',
|
||||
'confluence_attachment_created',
|
||||
'confluence_attachment_removed',
|
||||
'confluence_space_created',
|
||||
'confluence_space_updated',
|
||||
'confluence_label_added',
|
||||
'confluence_label_removed',
|
||||
'confluence_webhook',
|
||||
],
|
||||
},
|
||||
tools: {
|
||||
access: [
|
||||
// Page Tools
|
||||
|
||||
@@ -28,6 +28,7 @@ import { getWorkspaceBilledAccountUserId } from '@/lib/workspaces/utils'
|
||||
import { resolveOAuthAccountId } from '@/app/api/auth/oauth/utils'
|
||||
import { executeWebhookJob } from '@/background/webhook-execution'
|
||||
import { resolveEnvVarReferences } from '@/executor/utils/reference-validation'
|
||||
import { isConfluencePayloadMatch } from '@/triggers/confluence/utils'
|
||||
import { isGitHubEventMatch } from '@/triggers/github/utils'
|
||||
import { isHubSpotContactEventMatch } from '@/triggers/hubspot/utils'
|
||||
import { isJiraEventMatch } from '@/triggers/jira/utils'
|
||||
@@ -608,7 +609,7 @@ export async function verifyProviderAuth(
|
||||
}
|
||||
|
||||
if (foundWebhook.provider === 'linear') {
|
||||
const secret = providerConfig.secret as string | undefined
|
||||
const secret = providerConfig.webhookSecret as string | undefined
|
||||
|
||||
if (secret) {
|
||||
const signature = request.headers.get('Linear-Signature')
|
||||
@@ -683,7 +684,7 @@ export async function verifyProviderAuth(
|
||||
}
|
||||
|
||||
if (foundWebhook.provider === 'jira') {
|
||||
const secret = providerConfig.secret as string | undefined
|
||||
const secret = providerConfig.webhookSecret as string | undefined
|
||||
|
||||
if (secret) {
|
||||
const signature = request.headers.get('X-Hub-Signature')
|
||||
@@ -707,8 +708,33 @@ export async function verifyProviderAuth(
|
||||
}
|
||||
}
|
||||
|
||||
if (foundWebhook.provider === 'confluence') {
|
||||
const secret = providerConfig.webhookSecret as string | undefined
|
||||
|
||||
if (secret) {
|
||||
const signature = request.headers.get('X-Hub-Signature')
|
||||
|
||||
if (!signature) {
|
||||
logger.warn(`[${requestId}] Confluence webhook missing signature header`)
|
||||
return new NextResponse('Unauthorized - Missing Confluence signature', { status: 401 })
|
||||
}
|
||||
|
||||
const isValidSignature = validateJiraSignature(secret, signature, rawBody)
|
||||
|
||||
if (!isValidSignature) {
|
||||
logger.warn(`[${requestId}] Confluence signature verification failed`, {
|
||||
signatureLength: signature.length,
|
||||
secretLength: secret.length,
|
||||
})
|
||||
return new NextResponse('Unauthorized - Invalid Confluence signature', { status: 401 })
|
||||
}
|
||||
|
||||
logger.debug(`[${requestId}] Confluence signature verified successfully`)
|
||||
}
|
||||
}
|
||||
|
||||
if (foundWebhook.provider === 'github') {
|
||||
const secret = providerConfig.secret as string | undefined
|
||||
const secret = providerConfig.webhookSecret as string | undefined
|
||||
|
||||
if (secret) {
|
||||
// GitHub supports both SHA-256 (preferred) and SHA-1 (legacy)
|
||||
@@ -930,6 +956,27 @@ export async function queueWebhookExecution(
|
||||
}
|
||||
}
|
||||
|
||||
if (foundWebhook.provider === 'confluence') {
|
||||
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
|
||||
const triggerId = providerConfig.triggerId as string | undefined
|
||||
|
||||
if (triggerId && !isConfluencePayloadMatch(triggerId, body)) {
|
||||
logger.debug(
|
||||
`[${options.requestId}] Confluence payload mismatch for trigger ${triggerId}. Skipping execution.`,
|
||||
{
|
||||
webhookId: foundWebhook.id,
|
||||
workflowId: foundWorkflow.id,
|
||||
triggerId,
|
||||
bodyKeys: Object.keys(body),
|
||||
}
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
message: 'Payload does not match trigger configuration. Ignoring.',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (foundWebhook.provider === 'hubspot') {
|
||||
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
|
||||
const triggerId = providerConfig.triggerId as string | undefined
|
||||
|
||||
@@ -1197,6 +1197,53 @@ export async function formatWebhookInput(
|
||||
return extractIssueData(body)
|
||||
}
|
||||
|
||||
if (foundWebhook.provider === 'confluence') {
|
||||
const {
|
||||
extractPageData,
|
||||
extractCommentData,
|
||||
extractBlogData,
|
||||
extractAttachmentData,
|
||||
extractSpaceData,
|
||||
extractLabelData,
|
||||
} = await import('@/triggers/confluence/utils')
|
||||
|
||||
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
|
||||
const triggerId = providerConfig.triggerId as string | undefined
|
||||
|
||||
if (triggerId?.startsWith('confluence_comment_')) {
|
||||
return extractCommentData(body)
|
||||
}
|
||||
if (triggerId?.startsWith('confluence_blog_')) {
|
||||
return extractBlogData(body)
|
||||
}
|
||||
if (triggerId?.startsWith('confluence_attachment_')) {
|
||||
return extractAttachmentData(body)
|
||||
}
|
||||
if (triggerId?.startsWith('confluence_space_')) {
|
||||
return extractSpaceData(body)
|
||||
}
|
||||
if (triggerId?.startsWith('confluence_label_')) {
|
||||
return extractLabelData(body)
|
||||
}
|
||||
// Generic webhook — preserve all entity fields since event type varies
|
||||
if (triggerId === 'confluence_webhook') {
|
||||
return {
|
||||
timestamp: body.timestamp,
|
||||
userAccountId: body.userAccountId,
|
||||
accountType: body.accountType,
|
||||
page: body.page || null,
|
||||
comment: body.comment || null,
|
||||
blog: body.blog || body.blogpost || null,
|
||||
attachment: body.attachment || null,
|
||||
space: body.space || null,
|
||||
label: body.label || null,
|
||||
content: body.content || null,
|
||||
}
|
||||
}
|
||||
// Default: page events
|
||||
return extractPageData(body)
|
||||
}
|
||||
|
||||
if (foundWebhook.provider === 'stripe') {
|
||||
return body
|
||||
}
|
||||
|
||||
41
apps/sim/triggers/confluence/attachment_created.ts
Normal file
41
apps/sim/triggers/confluence/attachment_created.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ConfluenceIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
buildAttachmentOutputs,
|
||||
buildConfluenceAttachmentExtraFields,
|
||||
confluenceSetupInstructions,
|
||||
confluenceTriggerOptions,
|
||||
} from '@/triggers/confluence/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Confluence Attachment Created Trigger
|
||||
*
|
||||
* Triggers when a new attachment is uploaded to a page or blog post in Confluence.
|
||||
*/
|
||||
export const confluenceAttachmentCreatedTrigger: TriggerConfig = {
|
||||
id: 'confluence_attachment_created',
|
||||
name: 'Confluence Attachment Created',
|
||||
provider: 'confluence',
|
||||
description: 'Trigger workflow when an attachment is uploaded in Confluence',
|
||||
version: '1.0.0',
|
||||
icon: ConfluenceIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'confluence_attachment_created',
|
||||
triggerOptions: confluenceTriggerOptions,
|
||||
setupInstructions: confluenceSetupInstructions('attachment_created'),
|
||||
extraFields: buildConfluenceAttachmentExtraFields('confluence_attachment_created'),
|
||||
}),
|
||||
|
||||
outputs: buildAttachmentOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Hub-Signature': 'sha256=...',
|
||||
'X-Atlassian-Webhook-Identifier': 'unique-webhook-id',
|
||||
},
|
||||
},
|
||||
}
|
||||
41
apps/sim/triggers/confluence/attachment_removed.ts
Normal file
41
apps/sim/triggers/confluence/attachment_removed.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ConfluenceIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
buildAttachmentOutputs,
|
||||
buildConfluenceAttachmentExtraFields,
|
||||
confluenceSetupInstructions,
|
||||
confluenceTriggerOptions,
|
||||
} from '@/triggers/confluence/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Confluence Attachment Removed Trigger
|
||||
*
|
||||
* Triggers when an attachment is removed or trashed from a page or blog post in Confluence.
|
||||
*/
|
||||
export const confluenceAttachmentRemovedTrigger: TriggerConfig = {
|
||||
id: 'confluence_attachment_removed',
|
||||
name: 'Confluence Attachment Removed',
|
||||
provider: 'confluence',
|
||||
description: 'Trigger workflow when an attachment is removed in Confluence',
|
||||
version: '1.0.0',
|
||||
icon: ConfluenceIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'confluence_attachment_removed',
|
||||
triggerOptions: confluenceTriggerOptions,
|
||||
setupInstructions: confluenceSetupInstructions('attachment_removed'),
|
||||
extraFields: buildConfluenceAttachmentExtraFields('confluence_attachment_removed'),
|
||||
}),
|
||||
|
||||
outputs: buildAttachmentOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Hub-Signature': 'sha256=...',
|
||||
'X-Atlassian-Webhook-Identifier': 'unique-webhook-id',
|
||||
},
|
||||
},
|
||||
}
|
||||
41
apps/sim/triggers/confluence/blog_created.ts
Normal file
41
apps/sim/triggers/confluence/blog_created.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ConfluenceIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
buildBlogOutputs,
|
||||
buildConfluenceExtraFields,
|
||||
confluenceSetupInstructions,
|
||||
confluenceTriggerOptions,
|
||||
} from '@/triggers/confluence/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Confluence Blog Post Created Trigger
|
||||
*
|
||||
* Triggers when a new blog post is created in Confluence.
|
||||
*/
|
||||
export const confluenceBlogCreatedTrigger: TriggerConfig = {
|
||||
id: 'confluence_blog_created',
|
||||
name: 'Confluence Blog Post Created',
|
||||
provider: 'confluence',
|
||||
description: 'Trigger workflow when a blog post is created in Confluence',
|
||||
version: '1.0.0',
|
||||
icon: ConfluenceIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'confluence_blog_created',
|
||||
triggerOptions: confluenceTriggerOptions,
|
||||
setupInstructions: confluenceSetupInstructions('blog_created'),
|
||||
extraFields: buildConfluenceExtraFields('confluence_blog_created'),
|
||||
}),
|
||||
|
||||
outputs: buildBlogOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Hub-Signature': 'sha256=...',
|
||||
'X-Atlassian-Webhook-Identifier': 'unique-webhook-id',
|
||||
},
|
||||
},
|
||||
}
|
||||
41
apps/sim/triggers/confluence/blog_removed.ts
Normal file
41
apps/sim/triggers/confluence/blog_removed.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ConfluenceIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
buildBlogOutputs,
|
||||
buildConfluenceExtraFields,
|
||||
confluenceSetupInstructions,
|
||||
confluenceTriggerOptions,
|
||||
} from '@/triggers/confluence/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Confluence Blog Post Removed Trigger
|
||||
*
|
||||
* Triggers when a blog post is removed or trashed in Confluence.
|
||||
*/
|
||||
export const confluenceBlogRemovedTrigger: TriggerConfig = {
|
||||
id: 'confluence_blog_removed',
|
||||
name: 'Confluence Blog Post Removed',
|
||||
provider: 'confluence',
|
||||
description: 'Trigger workflow when a blog post is removed in Confluence',
|
||||
version: '1.0.0',
|
||||
icon: ConfluenceIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'confluence_blog_removed',
|
||||
triggerOptions: confluenceTriggerOptions,
|
||||
setupInstructions: confluenceSetupInstructions('blog_removed'),
|
||||
extraFields: buildConfluenceExtraFields('confluence_blog_removed'),
|
||||
}),
|
||||
|
||||
outputs: buildBlogOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Hub-Signature': 'sha256=...',
|
||||
'X-Atlassian-Webhook-Identifier': 'unique-webhook-id',
|
||||
},
|
||||
},
|
||||
}
|
||||
41
apps/sim/triggers/confluence/blog_updated.ts
Normal file
41
apps/sim/triggers/confluence/blog_updated.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ConfluenceIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
buildBlogOutputs,
|
||||
buildConfluenceExtraFields,
|
||||
confluenceSetupInstructions,
|
||||
confluenceTriggerOptions,
|
||||
} from '@/triggers/confluence/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Confluence Blog Post Updated Trigger
|
||||
*
|
||||
* Triggers when a blog post is updated in Confluence.
|
||||
*/
|
||||
export const confluenceBlogUpdatedTrigger: TriggerConfig = {
|
||||
id: 'confluence_blog_updated',
|
||||
name: 'Confluence Blog Post Updated',
|
||||
provider: 'confluence',
|
||||
description: 'Trigger workflow when a blog post is updated in Confluence',
|
||||
version: '1.0.0',
|
||||
icon: ConfluenceIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'confluence_blog_updated',
|
||||
triggerOptions: confluenceTriggerOptions,
|
||||
setupInstructions: confluenceSetupInstructions('blog_updated'),
|
||||
extraFields: buildConfluenceExtraFields('confluence_blog_updated'),
|
||||
}),
|
||||
|
||||
outputs: buildBlogOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Hub-Signature': 'sha256=...',
|
||||
'X-Atlassian-Webhook-Identifier': 'unique-webhook-id',
|
||||
},
|
||||
},
|
||||
}
|
||||
41
apps/sim/triggers/confluence/comment_created.ts
Normal file
41
apps/sim/triggers/confluence/comment_created.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ConfluenceIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
buildCommentOutputs,
|
||||
buildConfluenceExtraFields,
|
||||
confluenceSetupInstructions,
|
||||
confluenceTriggerOptions,
|
||||
} from '@/triggers/confluence/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Confluence Comment Created Trigger
|
||||
*
|
||||
* Triggers when a new comment is created on a page or blog post in Confluence.
|
||||
*/
|
||||
export const confluenceCommentCreatedTrigger: TriggerConfig = {
|
||||
id: 'confluence_comment_created',
|
||||
name: 'Confluence Comment Created',
|
||||
provider: 'confluence',
|
||||
description: 'Trigger workflow when a comment is created in Confluence',
|
||||
version: '1.0.0',
|
||||
icon: ConfluenceIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'confluence_comment_created',
|
||||
triggerOptions: confluenceTriggerOptions,
|
||||
setupInstructions: confluenceSetupInstructions('comment_created'),
|
||||
extraFields: buildConfluenceExtraFields('confluence_comment_created'),
|
||||
}),
|
||||
|
||||
outputs: buildCommentOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Hub-Signature': 'sha256=...',
|
||||
'X-Atlassian-Webhook-Identifier': 'unique-webhook-id',
|
||||
},
|
||||
},
|
||||
}
|
||||
41
apps/sim/triggers/confluence/comment_removed.ts
Normal file
41
apps/sim/triggers/confluence/comment_removed.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ConfluenceIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
buildCommentOutputs,
|
||||
buildConfluenceExtraFields,
|
||||
confluenceSetupInstructions,
|
||||
confluenceTriggerOptions,
|
||||
} from '@/triggers/confluence/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Confluence Comment Removed Trigger
|
||||
*
|
||||
* Triggers when a comment is removed from a page or blog post in Confluence.
|
||||
*/
|
||||
export const confluenceCommentRemovedTrigger: TriggerConfig = {
|
||||
id: 'confluence_comment_removed',
|
||||
name: 'Confluence Comment Removed',
|
||||
provider: 'confluence',
|
||||
description: 'Trigger workflow when a comment is removed in Confluence',
|
||||
version: '1.0.0',
|
||||
icon: ConfluenceIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'confluence_comment_removed',
|
||||
triggerOptions: confluenceTriggerOptions,
|
||||
setupInstructions: confluenceSetupInstructions('comment_removed'),
|
||||
extraFields: buildConfluenceExtraFields('confluence_comment_removed'),
|
||||
}),
|
||||
|
||||
outputs: buildCommentOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Hub-Signature': 'sha256=...',
|
||||
'X-Atlassian-Webhook-Identifier': 'unique-webhook-id',
|
||||
},
|
||||
},
|
||||
}
|
||||
21
apps/sim/triggers/confluence/index.ts
Normal file
21
apps/sim/triggers/confluence/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Confluence Triggers
|
||||
* Export all Confluence webhook triggers
|
||||
*/
|
||||
|
||||
export { confluenceAttachmentCreatedTrigger } from './attachment_created'
|
||||
export { confluenceAttachmentRemovedTrigger } from './attachment_removed'
|
||||
export { confluenceBlogCreatedTrigger } from './blog_created'
|
||||
export { confluenceBlogRemovedTrigger } from './blog_removed'
|
||||
export { confluenceBlogUpdatedTrigger } from './blog_updated'
|
||||
export { confluenceCommentCreatedTrigger } from './comment_created'
|
||||
export { confluenceCommentRemovedTrigger } from './comment_removed'
|
||||
export { confluenceLabelAddedTrigger } from './label_added'
|
||||
export { confluenceLabelRemovedTrigger } from './label_removed'
|
||||
export { confluencePageCreatedTrigger } from './page_created'
|
||||
export { confluencePageMovedTrigger } from './page_moved'
|
||||
export { confluencePageRemovedTrigger } from './page_removed'
|
||||
export { confluencePageUpdatedTrigger } from './page_updated'
|
||||
export { confluenceSpaceCreatedTrigger } from './space_created'
|
||||
export { confluenceSpaceUpdatedTrigger } from './space_updated'
|
||||
export { confluenceWebhookTrigger } from './webhook'
|
||||
41
apps/sim/triggers/confluence/label_added.ts
Normal file
41
apps/sim/triggers/confluence/label_added.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ConfluenceIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
buildConfluenceExtraFields,
|
||||
buildLabelOutputs,
|
||||
confluenceSetupInstructions,
|
||||
confluenceTriggerOptions,
|
||||
} from '@/triggers/confluence/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Confluence Label Added Trigger
|
||||
*
|
||||
* Triggers when a label is added to a page, blog post, or other content in Confluence.
|
||||
*/
|
||||
export const confluenceLabelAddedTrigger: TriggerConfig = {
|
||||
id: 'confluence_label_added',
|
||||
name: 'Confluence Label Added',
|
||||
provider: 'confluence',
|
||||
description: 'Trigger workflow when a label is added to content in Confluence',
|
||||
version: '1.0.0',
|
||||
icon: ConfluenceIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'confluence_label_added',
|
||||
triggerOptions: confluenceTriggerOptions,
|
||||
setupInstructions: confluenceSetupInstructions('label_added'),
|
||||
extraFields: buildConfluenceExtraFields('confluence_label_added'),
|
||||
}),
|
||||
|
||||
outputs: buildLabelOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Hub-Signature': 'sha256=...',
|
||||
'X-Atlassian-Webhook-Identifier': 'unique-webhook-id',
|
||||
},
|
||||
},
|
||||
}
|
||||
41
apps/sim/triggers/confluence/label_removed.ts
Normal file
41
apps/sim/triggers/confluence/label_removed.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ConfluenceIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
buildConfluenceExtraFields,
|
||||
buildLabelOutputs,
|
||||
confluenceSetupInstructions,
|
||||
confluenceTriggerOptions,
|
||||
} from '@/triggers/confluence/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Confluence Label Removed Trigger
|
||||
*
|
||||
* Triggers when a label is removed from a page, blog post, or other content in Confluence.
|
||||
*/
|
||||
export const confluenceLabelRemovedTrigger: TriggerConfig = {
|
||||
id: 'confluence_label_removed',
|
||||
name: 'Confluence Label Removed',
|
||||
provider: 'confluence',
|
||||
description: 'Trigger workflow when a label is removed from content in Confluence',
|
||||
version: '1.0.0',
|
||||
icon: ConfluenceIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'confluence_label_removed',
|
||||
triggerOptions: confluenceTriggerOptions,
|
||||
setupInstructions: confluenceSetupInstructions('label_removed'),
|
||||
extraFields: buildConfluenceExtraFields('confluence_label_removed'),
|
||||
}),
|
||||
|
||||
outputs: buildLabelOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Hub-Signature': 'sha256=...',
|
||||
'X-Atlassian-Webhook-Identifier': 'unique-webhook-id',
|
||||
},
|
||||
},
|
||||
}
|
||||
43
apps/sim/triggers/confluence/page_created.ts
Normal file
43
apps/sim/triggers/confluence/page_created.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { ConfluenceIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
buildConfluenceExtraFields,
|
||||
buildPageOutputs,
|
||||
confluenceSetupInstructions,
|
||||
confluenceTriggerOptions,
|
||||
} from '@/triggers/confluence/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Confluence Page Created Trigger
|
||||
*
|
||||
* This is the PRIMARY trigger - it includes the dropdown for selecting trigger type.
|
||||
* Triggers when a new page is created in Confluence.
|
||||
*/
|
||||
export const confluencePageCreatedTrigger: TriggerConfig = {
|
||||
id: 'confluence_page_created',
|
||||
name: 'Confluence Page Created',
|
||||
provider: 'confluence',
|
||||
description: 'Trigger workflow when a new page is created in Confluence',
|
||||
version: '1.0.0',
|
||||
icon: ConfluenceIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'confluence_page_created',
|
||||
triggerOptions: confluenceTriggerOptions,
|
||||
includeDropdown: true,
|
||||
setupInstructions: confluenceSetupInstructions('page_created'),
|
||||
extraFields: buildConfluenceExtraFields('confluence_page_created'),
|
||||
}),
|
||||
|
||||
outputs: buildPageOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Hub-Signature': 'sha256=...',
|
||||
'X-Atlassian-Webhook-Identifier': 'unique-webhook-id',
|
||||
},
|
||||
},
|
||||
}
|
||||
41
apps/sim/triggers/confluence/page_moved.ts
Normal file
41
apps/sim/triggers/confluence/page_moved.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ConfluenceIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
buildConfluenceExtraFields,
|
||||
buildPageOutputs,
|
||||
confluenceSetupInstructions,
|
||||
confluenceTriggerOptions,
|
||||
} from '@/triggers/confluence/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Confluence Page Moved Trigger
|
||||
*
|
||||
* Triggers when a page is moved to a different space or parent in Confluence.
|
||||
*/
|
||||
export const confluencePageMovedTrigger: TriggerConfig = {
|
||||
id: 'confluence_page_moved',
|
||||
name: 'Confluence Page Moved',
|
||||
provider: 'confluence',
|
||||
description: 'Trigger workflow when a page is moved in Confluence',
|
||||
version: '1.0.0',
|
||||
icon: ConfluenceIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'confluence_page_moved',
|
||||
triggerOptions: confluenceTriggerOptions,
|
||||
setupInstructions: confluenceSetupInstructions('page_moved'),
|
||||
extraFields: buildConfluenceExtraFields('confluence_page_moved'),
|
||||
}),
|
||||
|
||||
outputs: buildPageOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Hub-Signature': 'sha256=...',
|
||||
'X-Atlassian-Webhook-Identifier': 'unique-webhook-id',
|
||||
},
|
||||
},
|
||||
}
|
||||
41
apps/sim/triggers/confluence/page_removed.ts
Normal file
41
apps/sim/triggers/confluence/page_removed.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ConfluenceIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
buildConfluenceExtraFields,
|
||||
buildPageOutputs,
|
||||
confluenceSetupInstructions,
|
||||
confluenceTriggerOptions,
|
||||
} from '@/triggers/confluence/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Confluence Page Removed Trigger
|
||||
*
|
||||
* Triggers when a page is removed or trashed in Confluence.
|
||||
*/
|
||||
export const confluencePageRemovedTrigger: TriggerConfig = {
|
||||
id: 'confluence_page_removed',
|
||||
name: 'Confluence Page Removed',
|
||||
provider: 'confluence',
|
||||
description: 'Trigger workflow when a page is removed or trashed in Confluence',
|
||||
version: '1.0.0',
|
||||
icon: ConfluenceIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'confluence_page_removed',
|
||||
triggerOptions: confluenceTriggerOptions,
|
||||
setupInstructions: confluenceSetupInstructions('page_removed'),
|
||||
extraFields: buildConfluenceExtraFields('confluence_page_removed'),
|
||||
}),
|
||||
|
||||
outputs: buildPageOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Hub-Signature': 'sha256=...',
|
||||
'X-Atlassian-Webhook-Identifier': 'unique-webhook-id',
|
||||
},
|
||||
},
|
||||
}
|
||||
41
apps/sim/triggers/confluence/page_updated.ts
Normal file
41
apps/sim/triggers/confluence/page_updated.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ConfluenceIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
buildConfluenceExtraFields,
|
||||
buildPageOutputs,
|
||||
confluenceSetupInstructions,
|
||||
confluenceTriggerOptions,
|
||||
} from '@/triggers/confluence/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Confluence Page Updated Trigger
|
||||
*
|
||||
* Triggers when an existing page is updated in Confluence.
|
||||
*/
|
||||
export const confluencePageUpdatedTrigger: TriggerConfig = {
|
||||
id: 'confluence_page_updated',
|
||||
name: 'Confluence Page Updated',
|
||||
provider: 'confluence',
|
||||
description: 'Trigger workflow when a page is updated in Confluence',
|
||||
version: '1.0.0',
|
||||
icon: ConfluenceIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'confluence_page_updated',
|
||||
triggerOptions: confluenceTriggerOptions,
|
||||
setupInstructions: confluenceSetupInstructions('page_updated'),
|
||||
extraFields: buildConfluenceExtraFields('confluence_page_updated'),
|
||||
}),
|
||||
|
||||
outputs: buildPageOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Hub-Signature': 'sha256=...',
|
||||
'X-Atlassian-Webhook-Identifier': 'unique-webhook-id',
|
||||
},
|
||||
},
|
||||
}
|
||||
41
apps/sim/triggers/confluence/space_created.ts
Normal file
41
apps/sim/triggers/confluence/space_created.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ConfluenceIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
buildConfluenceExtraFields,
|
||||
buildSpaceOutputs,
|
||||
confluenceSetupInstructions,
|
||||
confluenceTriggerOptions,
|
||||
} from '@/triggers/confluence/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Confluence Space Created Trigger
|
||||
*
|
||||
* Triggers when a new space is created in Confluence.
|
||||
*/
|
||||
export const confluenceSpaceCreatedTrigger: TriggerConfig = {
|
||||
id: 'confluence_space_created',
|
||||
name: 'Confluence Space Created',
|
||||
provider: 'confluence',
|
||||
description: 'Trigger workflow when a new space is created in Confluence',
|
||||
version: '1.0.0',
|
||||
icon: ConfluenceIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'confluence_space_created',
|
||||
triggerOptions: confluenceTriggerOptions,
|
||||
setupInstructions: confluenceSetupInstructions('space_created'),
|
||||
extraFields: buildConfluenceExtraFields('confluence_space_created'),
|
||||
}),
|
||||
|
||||
outputs: buildSpaceOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Hub-Signature': 'sha256=...',
|
||||
'X-Atlassian-Webhook-Identifier': 'unique-webhook-id',
|
||||
},
|
||||
},
|
||||
}
|
||||
41
apps/sim/triggers/confluence/space_updated.ts
Normal file
41
apps/sim/triggers/confluence/space_updated.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ConfluenceIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
buildConfluenceExtraFields,
|
||||
buildSpaceOutputs,
|
||||
confluenceSetupInstructions,
|
||||
confluenceTriggerOptions,
|
||||
} from '@/triggers/confluence/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Confluence Space Updated Trigger
|
||||
*
|
||||
* Triggers when a space is updated (settings, permissions, etc.) in Confluence.
|
||||
*/
|
||||
export const confluenceSpaceUpdatedTrigger: TriggerConfig = {
|
||||
id: 'confluence_space_updated',
|
||||
name: 'Confluence Space Updated',
|
||||
provider: 'confluence',
|
||||
description: 'Trigger workflow when a space is updated in Confluence',
|
||||
version: '1.0.0',
|
||||
icon: ConfluenceIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'confluence_space_updated',
|
||||
triggerOptions: confluenceTriggerOptions,
|
||||
setupInstructions: confluenceSetupInstructions('space_updated'),
|
||||
extraFields: buildConfluenceExtraFields('confluence_space_updated'),
|
||||
}),
|
||||
|
||||
outputs: buildSpaceOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Hub-Signature': 'sha256=...',
|
||||
'X-Atlassian-Webhook-Identifier': 'unique-webhook-id',
|
||||
},
|
||||
},
|
||||
}
|
||||
392
apps/sim/triggers/confluence/utils.ts
Normal file
392
apps/sim/triggers/confluence/utils.ts
Normal file
@@ -0,0 +1,392 @@
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import type { TriggerOutput } from '@/triggers/types'
|
||||
|
||||
export const confluenceTriggerOptions = [
|
||||
{ label: 'Page Created', id: 'confluence_page_created' },
|
||||
{ label: 'Page Updated', id: 'confluence_page_updated' },
|
||||
{ label: 'Page Removed', id: 'confluence_page_removed' },
|
||||
{ label: 'Page Moved', id: 'confluence_page_moved' },
|
||||
{ label: 'Comment Created', id: 'confluence_comment_created' },
|
||||
{ label: 'Comment Removed', id: 'confluence_comment_removed' },
|
||||
{ label: 'Blog Post Created', id: 'confluence_blog_created' },
|
||||
{ label: 'Blog Post Updated', id: 'confluence_blog_updated' },
|
||||
{ label: 'Blog Post Removed', id: 'confluence_blog_removed' },
|
||||
{ label: 'Attachment Created', id: 'confluence_attachment_created' },
|
||||
{ label: 'Attachment Removed', id: 'confluence_attachment_removed' },
|
||||
{ label: 'Space Created', id: 'confluence_space_created' },
|
||||
{ label: 'Space Updated', id: 'confluence_space_updated' },
|
||||
{ label: 'Label Added', id: 'confluence_label_added' },
|
||||
{ label: 'Label Removed', id: 'confluence_label_removed' },
|
||||
{ label: 'Generic Webhook (All Events)', id: 'confluence_webhook' },
|
||||
]
|
||||
|
||||
export function confluenceSetupInstructions(eventType: string): string {
|
||||
const instructions = [
|
||||
'<strong>Note:</strong> You must have admin permissions in your Confluence workspace to create webhooks. See the <a href="https://developer.atlassian.com/cloud/confluence/modules/webhook/" target="_blank" rel="noopener noreferrer">Confluence webhook documentation</a> for details.',
|
||||
'In Confluence, navigate to <strong>Settings > Webhooks</strong>.',
|
||||
'Click <strong>"Create a Webhook"</strong> to add a new webhook.',
|
||||
'Paste the <strong>Webhook URL</strong> from above into the URL field.',
|
||||
'Optionally, enter the <strong>Webhook Secret</strong> from above into the secret field for added security.',
|
||||
`Select the events you want to trigger this workflow. For this trigger, select <strong>${eventType}</strong>.`,
|
||||
'Click <strong>"Create"</strong> to activate the webhook.',
|
||||
]
|
||||
|
||||
return instructions
|
||||
.map(
|
||||
(instruction, index) =>
|
||||
`<div class="mb-3">${index === 0 ? instruction : `<strong>${index}.</strong> ${instruction}`}</div>`
|
||||
)
|
||||
.join('')
|
||||
}
|
||||
|
||||
export function buildConfluenceExtraFields(triggerId: string): SubBlockConfig[] {
|
||||
return [
|
||||
{
|
||||
id: 'webhookSecret',
|
||||
title: 'Webhook Secret',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter a strong secret',
|
||||
description:
|
||||
'Optional secret to validate webhook deliveries from Confluence using HMAC signature',
|
||||
password: true,
|
||||
required: false,
|
||||
mode: 'trigger',
|
||||
condition: { field: 'selectedTriggerId', value: triggerId },
|
||||
},
|
||||
{
|
||||
id: 'confluenceDomain',
|
||||
title: 'Confluence Domain',
|
||||
type: 'short-input',
|
||||
placeholder: 'your-company.atlassian.net',
|
||||
description: 'Your Confluence Cloud domain',
|
||||
required: false,
|
||||
mode: 'trigger',
|
||||
condition: { field: 'selectedTriggerId', value: triggerId },
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export function buildConfluenceAttachmentExtraFields(triggerId: string): SubBlockConfig[] {
|
||||
return [
|
||||
...buildConfluenceExtraFields(triggerId),
|
||||
{
|
||||
id: 'confluenceEmail',
|
||||
title: 'Confluence Email',
|
||||
type: 'short-input',
|
||||
placeholder: 'user@example.com',
|
||||
description:
|
||||
'Your Atlassian account email. Required together with API token to download attachment files.',
|
||||
required: false,
|
||||
mode: 'trigger',
|
||||
condition: { field: 'selectedTriggerId', value: triggerId },
|
||||
},
|
||||
{
|
||||
id: 'confluenceApiToken',
|
||||
title: 'API Token',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter your Atlassian API token',
|
||||
description:
|
||||
'API token from https://id.atlassian.com/manage-profile/security/api-tokens. Required to download attachment file content.',
|
||||
password: true,
|
||||
required: false,
|
||||
mode: 'trigger',
|
||||
condition: { field: 'selectedTriggerId', value: triggerId },
|
||||
},
|
||||
{
|
||||
id: 'includeFileContent',
|
||||
title: 'Include File Content',
|
||||
type: 'switch',
|
||||
defaultValue: false,
|
||||
description:
|
||||
'Download and include actual file content from attachments. Requires email, API token, and domain.',
|
||||
required: false,
|
||||
mode: 'trigger',
|
||||
condition: { field: 'selectedTriggerId', value: triggerId },
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Base webhook outputs common to all Confluence triggers.
|
||||
*/
|
||||
function buildBaseWebhookOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
timestamp: {
|
||||
type: 'number',
|
||||
description: 'Timestamp of the webhook event (Unix epoch milliseconds)',
|
||||
},
|
||||
userAccountId: {
|
||||
type: 'string',
|
||||
description: 'Account ID of the user who triggered the event',
|
||||
},
|
||||
accountType: {
|
||||
type: 'string',
|
||||
description: 'Account type (e.g., customer)',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared content-entity output fields present on page, blog, comment, and attachment objects.
|
||||
*/
|
||||
function buildContentEntityFields(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
id: { type: 'number', description: 'Content ID' },
|
||||
title: { type: 'string', description: 'Content title' },
|
||||
contentType: {
|
||||
type: 'string',
|
||||
description: 'Content type (page, blogpost, comment, attachment)',
|
||||
},
|
||||
version: { type: 'number', description: 'Version number' },
|
||||
spaceKey: { type: 'string', description: 'Space key the content belongs to' },
|
||||
creatorAccountId: { type: 'string', description: 'Account ID of the creator' },
|
||||
lastModifierAccountId: { type: 'string', description: 'Account ID of the last modifier' },
|
||||
self: { type: 'string', description: 'URL link to the content' },
|
||||
creationDate: { type: 'number', description: 'Creation timestamp (Unix epoch milliseconds)' },
|
||||
modificationDate: {
|
||||
type: 'number',
|
||||
description: 'Last modification timestamp (Unix epoch milliseconds)',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/** Page-related outputs for page events. */
|
||||
export function buildPageOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
...buildBaseWebhookOutputs(),
|
||||
page: buildContentEntityFields(),
|
||||
}
|
||||
}
|
||||
|
||||
/** Comment-related outputs for comment events. */
|
||||
export function buildCommentOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
...buildBaseWebhookOutputs(),
|
||||
comment: {
|
||||
...buildContentEntityFields(),
|
||||
parent: {
|
||||
id: { type: 'number', description: 'Parent page/blog ID' },
|
||||
title: { type: 'string', description: 'Parent page/blog title' },
|
||||
contentType: { type: 'string', description: 'Parent content type (page or blogpost)' },
|
||||
spaceKey: { type: 'string', description: 'Space key of the parent' },
|
||||
self: { type: 'string', description: 'URL link to the parent content' },
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/** Blog post outputs for blog events. */
|
||||
export function buildBlogOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
...buildBaseWebhookOutputs(),
|
||||
blog: buildContentEntityFields(),
|
||||
}
|
||||
}
|
||||
|
||||
/** Attachment-related outputs for attachment events. */
|
||||
export function buildAttachmentOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
...buildBaseWebhookOutputs(),
|
||||
attachment: {
|
||||
...buildContentEntityFields(),
|
||||
mediaType: { type: 'string', description: 'MIME type of the attachment' },
|
||||
fileSize: { type: 'number', description: 'File size in bytes' },
|
||||
parent: {
|
||||
id: { type: 'number', description: 'Container page/blog ID' },
|
||||
title: { type: 'string', description: 'Container page/blog title' },
|
||||
contentType: { type: 'string', description: 'Container content type' },
|
||||
},
|
||||
},
|
||||
files: {
|
||||
type: 'file[]',
|
||||
description:
|
||||
'Attachment file content downloaded from Confluence (if includeFileContent is enabled with credentials)',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/** Space-related outputs for space events. */
|
||||
export function buildSpaceOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
...buildBaseWebhookOutputs(),
|
||||
space: {
|
||||
key: { type: 'string', description: 'Space key' },
|
||||
name: { type: 'string', description: 'Space name' },
|
||||
self: { type: 'string', description: 'URL link to the space' },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/** Label-related outputs for label events. */
|
||||
export function buildLabelOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
...buildBaseWebhookOutputs(),
|
||||
label: {
|
||||
name: { type: 'string', description: 'Label name' },
|
||||
id: { type: 'string', description: 'Label ID' },
|
||||
prefix: { type: 'string', description: 'Label prefix (global, my, team)' },
|
||||
},
|
||||
content: {
|
||||
id: { type: 'number', description: 'Content ID the label was added to or removed from' },
|
||||
title: { type: 'string', description: 'Content title' },
|
||||
contentType: { type: 'string', description: 'Content type (page, blogpost)' },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/** Combined outputs for the generic webhook trigger (all events). */
|
||||
export function buildGenericWebhookOutputs(): Record<string, TriggerOutput> {
|
||||
return {
|
||||
...buildBaseWebhookOutputs(),
|
||||
page: { type: 'json', description: 'Page object (present in page events)' },
|
||||
comment: { type: 'json', description: 'Comment object (present in comment events)' },
|
||||
blog: { type: 'json', description: 'Blog post object (present in blog events)' },
|
||||
attachment: { type: 'json', description: 'Attachment object (present in attachment events)' },
|
||||
space: { type: 'json', description: 'Space object (present in space events)' },
|
||||
label: { type: 'json', description: 'Label object (present in label events)' },
|
||||
content: { type: 'json', description: 'Content object (present in label events)' },
|
||||
files: {
|
||||
type: 'file[]',
|
||||
description:
|
||||
'Attachment file content (present in attachment events when includeFileContent is enabled)',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function extractPageData(body: any) {
|
||||
return {
|
||||
timestamp: body.timestamp,
|
||||
userAccountId: body.userAccountId,
|
||||
accountType: body.accountType,
|
||||
page: body.page || {},
|
||||
}
|
||||
}
|
||||
|
||||
export function extractCommentData(body: any) {
|
||||
return {
|
||||
timestamp: body.timestamp,
|
||||
userAccountId: body.userAccountId,
|
||||
accountType: body.accountType,
|
||||
comment: body.comment || {},
|
||||
}
|
||||
}
|
||||
|
||||
export function extractBlogData(body: any) {
|
||||
return {
|
||||
timestamp: body.timestamp,
|
||||
userAccountId: body.userAccountId,
|
||||
accountType: body.accountType,
|
||||
blog: body.blog || body.blogpost || {},
|
||||
}
|
||||
}
|
||||
|
||||
export function extractAttachmentData(body: any) {
|
||||
return {
|
||||
timestamp: body.timestamp,
|
||||
userAccountId: body.userAccountId,
|
||||
accountType: body.accountType,
|
||||
attachment: body.attachment || {},
|
||||
}
|
||||
}
|
||||
|
||||
export function extractSpaceData(body: any) {
|
||||
return {
|
||||
timestamp: body.timestamp,
|
||||
userAccountId: body.userAccountId,
|
||||
accountType: body.accountType,
|
||||
space: body.space || {},
|
||||
}
|
||||
}
|
||||
|
||||
export function extractLabelData(body: any) {
|
||||
return {
|
||||
timestamp: body.timestamp,
|
||||
userAccountId: body.userAccountId,
|
||||
accountType: body.accountType,
|
||||
label: body.label || {},
|
||||
content: body.content || body.page || body.blog || {},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps trigger IDs to the exact Confluence event strings they accept.
|
||||
* Admin REST API webhooks include an `event` field (e.g. `"event": "page_created"`).
|
||||
* Connect app webhooks do NOT — for those we fall back to entity-category matching.
|
||||
*/
|
||||
const TRIGGER_EVENT_MAP: Record<string, string[]> = {
|
||||
confluence_page_created: ['page_created'],
|
||||
confluence_page_updated: ['page_updated'],
|
||||
confluence_page_removed: ['page_removed', 'page_trashed'],
|
||||
confluence_page_moved: ['page_moved'],
|
||||
confluence_comment_created: ['comment_created'],
|
||||
confluence_comment_removed: ['comment_removed'],
|
||||
confluence_blog_created: ['blog_created'],
|
||||
confluence_blog_updated: ['blog_updated'],
|
||||
confluence_blog_removed: ['blog_removed', 'blog_trashed'],
|
||||
confluence_attachment_created: ['attachment_created'],
|
||||
confluence_attachment_removed: ['attachment_removed', 'attachment_trashed'],
|
||||
confluence_space_created: ['space_created'],
|
||||
confluence_space_updated: ['space_updated'],
|
||||
confluence_label_added: ['label_added', 'label_created'],
|
||||
confluence_label_removed: ['label_removed', 'label_deleted'],
|
||||
}
|
||||
|
||||
const TRIGGER_CATEGORY_MAP: Record<string, string> = {
|
||||
confluence_page_created: 'page',
|
||||
confluence_page_updated: 'page',
|
||||
confluence_page_removed: 'page',
|
||||
confluence_page_moved: 'page',
|
||||
confluence_comment_created: 'comment',
|
||||
confluence_comment_removed: 'comment',
|
||||
confluence_blog_created: 'blog',
|
||||
confluence_blog_updated: 'blog',
|
||||
confluence_blog_removed: 'blog',
|
||||
confluence_attachment_created: 'attachment',
|
||||
confluence_attachment_removed: 'attachment',
|
||||
confluence_space_created: 'space',
|
||||
confluence_space_updated: 'space',
|
||||
confluence_label_added: 'label',
|
||||
confluence_label_removed: 'label',
|
||||
}
|
||||
|
||||
/**
|
||||
* Infers the entity category from a Confluence webhook payload by checking
|
||||
* which entity key is present in the body.
|
||||
*/
|
||||
function inferEntityCategory(body: Record<string, unknown>): string | null {
|
||||
if (body.comment) return 'comment'
|
||||
if (body.attachment) return 'attachment'
|
||||
if (body.blog || body.blogpost) return 'blog'
|
||||
if (body.label) return 'label'
|
||||
if (body.page) return 'page'
|
||||
if (body.space) return 'space'
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a Confluence webhook payload matches a trigger.
|
||||
*
|
||||
* Admin REST API webhooks (Settings > Webhooks) include an `event` field
|
||||
* for exact action-level matching. Connect app webhooks omit it, so we
|
||||
* fall back to entity-category matching (page vs comment vs blog, etc.).
|
||||
*/
|
||||
export function isConfluencePayloadMatch(
|
||||
triggerId: string,
|
||||
body: Record<string, unknown>
|
||||
): boolean {
|
||||
if (triggerId === 'confluence_webhook') {
|
||||
return true
|
||||
}
|
||||
|
||||
const event = body.event as string | undefined
|
||||
if (event) {
|
||||
const acceptedEvents = TRIGGER_EVENT_MAP[triggerId]
|
||||
return acceptedEvents ? acceptedEvents.includes(event) : false
|
||||
}
|
||||
|
||||
const expectedCategory = TRIGGER_CATEGORY_MAP[triggerId]
|
||||
if (!expectedCategory) {
|
||||
return false
|
||||
}
|
||||
return inferEntityCategory(body) === expectedCategory
|
||||
}
|
||||
41
apps/sim/triggers/confluence/webhook.ts
Normal file
41
apps/sim/triggers/confluence/webhook.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ConfluenceIcon } from '@/components/icons'
|
||||
import { buildTriggerSubBlocks } from '@/triggers'
|
||||
import {
|
||||
buildConfluenceAttachmentExtraFields,
|
||||
buildGenericWebhookOutputs,
|
||||
confluenceSetupInstructions,
|
||||
confluenceTriggerOptions,
|
||||
} from '@/triggers/confluence/utils'
|
||||
import type { TriggerConfig } from '@/triggers/types'
|
||||
|
||||
/**
|
||||
* Generic Confluence Webhook Trigger
|
||||
*
|
||||
* Captures all Confluence webhook events without filtering.
|
||||
*/
|
||||
export const confluenceWebhookTrigger: TriggerConfig = {
|
||||
id: 'confluence_webhook',
|
||||
name: 'Confluence Webhook (All Events)',
|
||||
provider: 'confluence',
|
||||
description: 'Trigger workflow on any Confluence webhook event',
|
||||
version: '1.0.0',
|
||||
icon: ConfluenceIcon,
|
||||
|
||||
subBlocks: buildTriggerSubBlocks({
|
||||
triggerId: 'confluence_webhook',
|
||||
triggerOptions: confluenceTriggerOptions,
|
||||
setupInstructions: confluenceSetupInstructions('All Events'),
|
||||
extraFields: buildConfluenceAttachmentExtraFields('confluence_webhook'),
|
||||
}),
|
||||
|
||||
outputs: buildGenericWebhookOutputs(),
|
||||
|
||||
webhook: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Hub-Signature': 'sha256=...',
|
||||
'X-Atlassian-Webhook-Identifier': 'unique-webhook-id',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -21,6 +21,24 @@ import {
|
||||
circlebackMeetingNotesTrigger,
|
||||
circlebackWebhookTrigger,
|
||||
} from '@/triggers/circleback'
|
||||
import {
|
||||
confluenceAttachmentCreatedTrigger,
|
||||
confluenceAttachmentRemovedTrigger,
|
||||
confluenceBlogCreatedTrigger,
|
||||
confluenceBlogRemovedTrigger,
|
||||
confluenceBlogUpdatedTrigger,
|
||||
confluenceCommentCreatedTrigger,
|
||||
confluenceCommentRemovedTrigger,
|
||||
confluenceLabelAddedTrigger,
|
||||
confluenceLabelRemovedTrigger,
|
||||
confluencePageCreatedTrigger,
|
||||
confluencePageMovedTrigger,
|
||||
confluencePageRemovedTrigger,
|
||||
confluencePageUpdatedTrigger,
|
||||
confluenceSpaceCreatedTrigger,
|
||||
confluenceSpaceUpdatedTrigger,
|
||||
confluenceWebhookTrigger,
|
||||
} from '@/triggers/confluence'
|
||||
import { firefliesTranscriptionCompleteTrigger } from '@/triggers/fireflies'
|
||||
import { genericWebhookTrigger } from '@/triggers/generic'
|
||||
import {
|
||||
@@ -140,6 +158,22 @@ export const TRIGGER_REGISTRY: TriggerRegistry = {
|
||||
calcom_meeting_ended: calcomMeetingEndedTrigger,
|
||||
calcom_recording_ready: calcomRecordingReadyTrigger,
|
||||
calcom_webhook: calcomWebhookTrigger,
|
||||
confluence_webhook: confluenceWebhookTrigger,
|
||||
confluence_page_created: confluencePageCreatedTrigger,
|
||||
confluence_page_updated: confluencePageUpdatedTrigger,
|
||||
confluence_page_removed: confluencePageRemovedTrigger,
|
||||
confluence_page_moved: confluencePageMovedTrigger,
|
||||
confluence_comment_created: confluenceCommentCreatedTrigger,
|
||||
confluence_comment_removed: confluenceCommentRemovedTrigger,
|
||||
confluence_blog_created: confluenceBlogCreatedTrigger,
|
||||
confluence_blog_updated: confluenceBlogUpdatedTrigger,
|
||||
confluence_blog_removed: confluenceBlogRemovedTrigger,
|
||||
confluence_attachment_created: confluenceAttachmentCreatedTrigger,
|
||||
confluence_attachment_removed: confluenceAttachmentRemovedTrigger,
|
||||
confluence_space_created: confluenceSpaceCreatedTrigger,
|
||||
confluence_space_updated: confluenceSpaceUpdatedTrigger,
|
||||
confluence_label_added: confluenceLabelAddedTrigger,
|
||||
confluence_label_removed: confluenceLabelRemovedTrigger,
|
||||
generic_webhook: genericWebhookTrigger,
|
||||
github_webhook: githubWebhookTrigger,
|
||||
github_issue_opened: githubIssueOpenedTrigger,
|
||||
|
||||
Reference in New Issue
Block a user