Files
simstudioai--sim/apps/sim/triggers/jsm/utils.ts
T
wehub-resource-sync d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

413 lines
12 KiB
TypeScript

import type { SubBlockConfig } from '@/blocks/types'
import type { TriggerOutput } from '@/triggers/types'
/**
* Shared trigger dropdown options for all JSM triggers
*/
export const jsmTriggerOptions = [
{ label: 'Request Created', id: 'jsm_request_created' },
{ label: 'Request Updated', id: 'jsm_request_updated' },
{ label: 'Request Commented', id: 'jsm_request_commented' },
{ label: 'Request Resolved', id: 'jsm_request_resolved' },
{ label: 'Generic Webhook (All Events)', id: 'jsm_webhook' },
]
/**
* Generates setup instructions for JSM webhooks.
* JSM uses the Jira webhook infrastructure with service desk context.
*/
export function jsmSetupInstructions(eventType: string, additionalNotes?: string): string {
const instructions = [
'<strong>Note:</strong> You must have admin permissions in your Jira workspace to create webhooks. JSM uses the Jira webhook system. See the <a href="https://support.atlassian.com/jira-cloud-administration/docs/manage-webhooks/" target="_blank" rel="noopener noreferrer">Jira webhook documentation</a> for details.',
'In Jira, navigate to <strong>Settings > System > 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>.`,
'Optionally add a JQL filter to restrict webhooks to your service desk project (e.g., <code>project = SD</code>).',
'Click <strong>"Create"</strong> to activate the webhook.',
]
if (additionalNotes) {
instructions.push(additionalNotes)
}
return instructions
.map(
(instruction, index) =>
`<div class="mb-3">${index === 0 ? instruction : `<strong>${index}.</strong> ${instruction}`}</div>`
)
.join('')
}
/**
* Webhook secret field for JSM triggers
*/
function jsmWebhookSecretField(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 Jira using HMAC signature',
password: true,
required: false,
mode: 'trigger',
condition: { field: 'selectedTriggerId', value: triggerId },
}
}
/**
* Extra fields for JSM triggers (webhook secret + JQL filter)
*/
export function buildJsmExtraFields(triggerId: string): SubBlockConfig[] {
return [
jsmWebhookSecretField(triggerId),
{
id: 'jqlFilter',
title: 'JQL Filter',
type: 'long-input',
placeholder: 'project = SD AND issuetype = "Service Request"',
description:
'Filter which service desk requests trigger this workflow using JQL (Jira Query Language)',
required: false,
mode: 'trigger',
condition: { field: 'selectedTriggerId', value: triggerId },
},
]
}
/**
* Base webhook output fields shared across all JSM triggers
*/
function buildBaseWebhookOutputs(): Record<string, TriggerOutput> {
return {
webhookEvent: {
type: 'string',
description:
'The webhook event type (e.g., jira:issue_created, jira:issue_updated, comment_created)',
},
timestamp: {
type: 'number',
description: 'Timestamp of the webhook event',
},
user: {
displayName: {
type: 'string',
description: 'Display name of the user who triggered the event',
},
accountId: {
type: 'string',
description: 'Account ID of the user who triggered the event',
},
},
issue: {
id: {
type: 'string',
description: 'Jira issue ID',
},
key: {
type: 'string',
description: 'Issue key (e.g., SD-123)',
},
self: {
type: 'string',
description: 'REST API URL for this issue',
},
fields: {
summary: {
type: 'string',
description: 'Request summary/title',
},
status: {
name: {
type: 'string',
description: 'Current status name',
},
id: {
type: 'string',
description: 'Status ID',
},
statusCategory: {
type: 'json',
description: 'Status category information',
},
},
priority: {
name: {
type: 'string',
description: 'Priority name',
},
id: {
type: 'string',
description: 'Priority ID',
},
},
issuetype: {
name: {
type: 'string',
description: 'Issue type name (e.g., Service Request, Incident)',
},
id: {
type: 'string',
description: 'Issue type ID',
},
},
project: {
key: {
type: 'string',
description: 'Project key',
},
name: {
type: 'string',
description: 'Project name',
},
id: {
type: 'string',
description: 'Project ID',
},
},
reporter: {
displayName: {
type: 'string',
description: 'Reporter display name',
},
accountId: {
type: 'string',
description: 'Reporter account ID',
},
emailAddress: {
type: 'string',
description:
'Email address (Jira Server only — not available in Jira Cloud webhook payloads)',
},
},
assignee: {
displayName: {
type: 'string',
description: 'Assignee display name',
},
accountId: {
type: 'string',
description: 'Assignee account ID',
},
emailAddress: {
type: 'string',
description:
'Email address (Jira Server only — not available in Jira Cloud webhook payloads)',
},
},
creator: {
displayName: {
type: 'string',
description: 'Creator display name',
},
accountId: {
type: 'string',
description: 'Creator account ID',
},
emailAddress: {
type: 'string',
description:
'Email address (Jira Server only — not available in Jira Cloud webhook payloads)',
},
},
created: {
type: 'string',
description: 'Request creation date (ISO format)',
},
updated: {
type: 'string',
description: 'Last updated date (ISO format)',
},
duedate: {
type: 'string',
description: 'Due date for the request',
},
labels: {
type: 'array',
description: 'Array of labels applied to this request',
},
resolution: {
name: {
type: 'string',
description: 'Resolution name (e.g., Done, Fixed)',
},
id: {
type: 'string',
description: 'Resolution ID',
},
},
},
},
}
}
/**
* Outputs for request created triggers
*/
export function buildJsmRequestOutputs(): Record<string, TriggerOutput> {
return {
...buildBaseWebhookOutputs(),
issue_event_type_name: {
type: 'string',
description: 'Issue event type name from Jira',
},
}
}
/**
* Outputs for request updated/resolved triggers (includes changelog)
*/
export function buildJsmRequestUpdatedOutputs(): Record<string, TriggerOutput> {
return {
...buildBaseWebhookOutputs(),
issue_event_type_name: {
type: 'string',
description: 'Issue event type name from Jira',
},
changelog: {
id: {
type: 'string',
description: 'Changelog ID',
},
items: {
type: 'array',
description:
'Array of changed items. Each item contains field, fieldtype, from, fromString, to, toString',
},
},
}
}
/**
* Outputs for comment triggers
*/
export function buildJsmCommentOutputs(): Record<string, TriggerOutput> {
return {
...buildBaseWebhookOutputs(),
comment: {
id: {
type: 'string',
description: 'Comment ID',
},
body: {
type: 'json',
description:
'Comment body in Atlassian Document Format (ADF). On Jira Server this may be a plain string.',
},
author: {
displayName: {
type: 'string',
description: 'Comment author display name',
},
accountId: {
type: 'string',
description: 'Comment author account ID',
},
emailAddress: {
type: 'string',
description: 'Comment author email address',
},
},
updateAuthor: {
displayName: {
type: 'string',
description: 'Display name of the user who last updated the comment',
},
accountId: {
type: 'string',
description: 'Account ID of the user who last updated the comment',
},
},
created: {
type: 'string',
description: 'Comment creation date (ISO format)',
},
updated: {
type: 'string',
description: 'Comment last updated date (ISO format)',
},
},
}
}
/**
* Checks whether a JSM webhook event matches the configured trigger.
*
* JSM events come through Jira's webhook system. The matching logic considers:
* - The webhook event type (jira:issue_created, jira:issue_updated, comment_created)
* - The issue_event_type_name for finer-grained matching
* - Changelog items for approval, SLA, and resolution events
*/
export function isJsmEventMatch(
triggerId: string,
webhookEvent: string,
issueEventTypeName?: string,
changelog?: { items?: Array<{ field?: string; toString?: string }> }
): boolean {
switch (triggerId) {
case 'jsm_request_created':
return webhookEvent === 'jira:issue_created' || issueEventTypeName === 'issue_created'
case 'jsm_request_updated':
return (
webhookEvent === 'jira:issue_updated' ||
issueEventTypeName === 'issue_updated' ||
issueEventTypeName === 'issue_generic'
)
case 'jsm_request_commented':
return webhookEvent === 'comment_created'
case 'jsm_request_resolved': {
if (webhookEvent !== 'jira:issue_updated' && issueEventTypeName !== 'issue_updated') {
return false
}
const resolvedItems = changelog?.items ?? []
return resolvedItems.some(
(item) =>
item.field === 'status' &&
(item.toString?.toLowerCase() === 'resolved' ||
item.toString?.toLowerCase() === 'done' ||
item.toString?.toLowerCase() === 'closed')
)
}
case 'jsm_webhook':
return true
default:
return false
}
}
/**
* Extracts request data from a JSM webhook payload
*/
export function extractRequestData(body: Record<string, unknown>) {
return {
webhookEvent: body.webhookEvent,
timestamp: body.timestamp,
user: body.user || null,
issue_event_type_name: body.issue_event_type_name,
issue: body.issue || {},
changelog: body.changelog,
}
}
/**
* Extracts comment data from a JSM webhook payload
*/
export function extractCommentData(body: Record<string, unknown>) {
return {
webhookEvent: body.webhookEvent,
timestamp: body.timestamp,
user: body.user || null,
issue: body.issue || {},
comment: body.comment || {},
}
}