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
275 lines
10 KiB
TypeScript
275 lines
10 KiB
TypeScript
import type { SubBlockConfig } from '@/blocks/types'
|
|
import type { TriggerOutput } from '@/triggers/types'
|
|
import { normalizeToken } from '@/triggers/utils/tokens'
|
|
|
|
/**
|
|
* Shared trigger dropdown options for all ServiceNow triggers
|
|
*/
|
|
export const servicenowTriggerOptions = [
|
|
{ label: 'Incident Created', id: 'servicenow_incident_created' },
|
|
{ label: 'Incident Updated', id: 'servicenow_incident_updated' },
|
|
{ label: 'Change Request Created', id: 'servicenow_change_request_created' },
|
|
{ label: 'Change Request Updated', id: 'servicenow_change_request_updated' },
|
|
{ label: 'Generic Webhook (All Events)', id: 'servicenow_webhook' },
|
|
]
|
|
|
|
/**
|
|
* Generates setup instructions for ServiceNow webhooks.
|
|
* ServiceNow uses Business Rules with RESTMessageV2 for outbound webhooks.
|
|
*/
|
|
export function servicenowSetupInstructions(eventType: string): string {
|
|
const instructions = [
|
|
'<strong>Note:</strong> You need admin or developer permissions in your ServiceNow instance to create Business Rules.',
|
|
'Navigate to <strong>System Definition > Business Rules</strong> and create a new Business Rule.',
|
|
`Set the table (e.g., <strong>incident</strong>, <strong>change_request</strong>), set <strong>When</strong> to <strong>after</strong>, and check <strong>${eventType}</strong>.`,
|
|
'Check the <strong>Advanced</strong> checkbox to enable the script editor.',
|
|
'Copy the <strong>Webhook URL</strong> above and generate a <strong>Webhook Secret</strong> (any strong random string). Paste the secret in the <strong>Webhook Secret</strong> field here.',
|
|
`In the script, use <strong>RESTMessageV2</strong> to POST the record data as JSON to the <strong>Webhook URL</strong> above. Include the secret as <code>Authorization: Bearer <your secret></code> or <code>X-Sim-Webhook-Secret: <your secret></code>. Example:<br/><code style="font-size: 0.85em; display: block; margin-top: 4px; white-space: pre-wrap;">var r = new sn_ws.RESTMessageV2();\nr.setEndpoint("<webhook_url>");\nr.setHttpMethod("POST");\nr.setRequestHeader("Content-Type", "application/json");\nr.setRequestHeader("Authorization", "Bearer <your_webhook_secret>");\nr.setRequestBody(JSON.stringify({\n sysId: current.sys_id.toString(),\n number: current.number.toString(),\n shortDescription: current.short_description.toString(),\n state: current.state.toString(),\n priority: current.priority.toString()\n}));\nr.execute();</code>`,
|
|
'Activate the Business Rule and click "Save" above to activate your trigger.',
|
|
]
|
|
|
|
return instructions
|
|
.map(
|
|
(instruction, index) =>
|
|
`<div class="mb-3">${index === 0 ? instruction : `<strong>${index}.</strong> ${instruction}`}</div>`
|
|
)
|
|
.join('')
|
|
}
|
|
|
|
/**
|
|
* Webhook secret field for ServiceNow triggers
|
|
*/
|
|
function servicenowWebhookSecretField(triggerId: string): SubBlockConfig {
|
|
return {
|
|
id: 'webhookSecret',
|
|
title: 'Webhook Secret',
|
|
type: 'short-input',
|
|
placeholder: 'Generate a secret and paste it here',
|
|
description:
|
|
'Required. Use the same value in your ServiceNow Business Rule as Bearer token or X-Sim-Webhook-Secret.',
|
|
password: true,
|
|
required: true,
|
|
mode: 'trigger',
|
|
condition: { field: 'selectedTriggerId', value: triggerId },
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extra fields for ServiceNow triggers (webhook secret + optional table filter)
|
|
*/
|
|
export function buildServiceNowExtraFields(triggerId: string): SubBlockConfig[] {
|
|
return [
|
|
servicenowWebhookSecretField(triggerId),
|
|
{
|
|
id: 'tableName',
|
|
title: 'Table Name (Optional)',
|
|
type: 'short-input',
|
|
placeholder: 'e.g., incident, change_request',
|
|
description: 'Optionally filter to a specific ServiceNow table',
|
|
mode: 'trigger',
|
|
condition: { field: 'selectedTriggerId', value: triggerId },
|
|
},
|
|
]
|
|
}
|
|
|
|
/**
|
|
* Common record fields shared across ServiceNow trigger outputs
|
|
*/
|
|
function buildRecordOutputs(): Record<string, TriggerOutput> {
|
|
return {
|
|
sysId: { type: 'string', description: 'Unique system ID of the record' },
|
|
number: { type: 'string', description: 'Record number (e.g., INC0010001, CHG0010001)' },
|
|
tableName: { type: 'string', description: 'ServiceNow table name' },
|
|
shortDescription: { type: 'string', description: 'Short description of the record' },
|
|
description: { type: 'string', description: 'Full description of the record' },
|
|
state: { type: 'string', description: 'Current state of the record' },
|
|
priority: {
|
|
type: 'string',
|
|
description: 'Priority level (1=Critical, 2=High, 3=Moderate, 4=Low, 5=Planning)',
|
|
},
|
|
assignedTo: { type: 'string', description: 'User assigned to this record' },
|
|
assignmentGroup: { type: 'string', description: 'Group assigned to this record' },
|
|
createdBy: { type: 'string', description: 'User who created the record' },
|
|
createdOn: { type: 'string', description: 'When the record was created (ISO 8601)' },
|
|
updatedBy: { type: 'string', description: 'User who last updated the record' },
|
|
updatedOn: { type: 'string', description: 'When the record was last updated (ISO 8601)' },
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Outputs for incident triggers
|
|
*/
|
|
export function buildIncidentOutputs(): Record<string, TriggerOutput> {
|
|
return {
|
|
...buildRecordOutputs(),
|
|
urgency: { type: 'string', description: 'Urgency level (1=High, 2=Medium, 3=Low)' },
|
|
impact: { type: 'string', description: 'Impact level (1=High, 2=Medium, 3=Low)' },
|
|
category: { type: 'string', description: 'Incident category' },
|
|
subcategory: { type: 'string', description: 'Incident subcategory' },
|
|
caller: { type: 'string', description: 'Caller/requester of the incident' },
|
|
resolvedBy: { type: 'string', description: 'User who resolved the incident' },
|
|
resolvedAt: { type: 'string', description: 'When the incident was resolved' },
|
|
closeNotes: { type: 'string', description: 'Notes added when the incident was closed' },
|
|
record: { type: 'json', description: 'Full incident record data' },
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Outputs for change request triggers
|
|
*/
|
|
export function buildChangeRequestOutputs(): Record<string, TriggerOutput> {
|
|
return {
|
|
...buildRecordOutputs(),
|
|
type: { type: 'string', description: 'Change type (Normal, Standard, Emergency)' },
|
|
risk: { type: 'string', description: 'Risk level of the change' },
|
|
impact: { type: 'string', description: 'Impact level of the change' },
|
|
approval: { type: 'string', description: 'Approval status' },
|
|
startDate: { type: 'string', description: 'Planned start date' },
|
|
endDate: { type: 'string', description: 'Planned end date' },
|
|
category: { type: 'string', description: 'Change category' },
|
|
record: { type: 'json', description: 'Full change request record data' },
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extracts the table name from a ServiceNow webhook payload.
|
|
* Business Rule scripts can send tableName in multiple formats.
|
|
*/
|
|
function extractTableName(body: Record<string, unknown>): string | undefined {
|
|
const candidates = [body.tableName, body.table_name, body.table, body.sys_class_name]
|
|
for (const c of candidates) {
|
|
if (typeof c === 'string' && c.trim()) {
|
|
return c.trim()
|
|
}
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
/**
|
|
* Extracts the event type from a ServiceNow webhook payload.
|
|
*/
|
|
function extractEventType(body: Record<string, unknown>): string | undefined {
|
|
const candidates = [body.eventType, body.event_type, body.action, body.operation]
|
|
for (const c of candidates) {
|
|
if (typeof c === 'string' && c.trim()) {
|
|
return c.trim()
|
|
}
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
const INCIDENT_CREATED = new Set([
|
|
'incident_created',
|
|
'insert',
|
|
'created',
|
|
'create',
|
|
'after_insert',
|
|
'afterinsert',
|
|
])
|
|
|
|
const INCIDENT_UPDATED = new Set([
|
|
'incident_updated',
|
|
'update',
|
|
'updated',
|
|
'after_update',
|
|
'afterupdate',
|
|
])
|
|
|
|
const CHANGE_REQUEST_CREATED = new Set([
|
|
'change_request_created',
|
|
'insert',
|
|
'created',
|
|
'create',
|
|
'after_insert',
|
|
'afterinsert',
|
|
])
|
|
|
|
const CHANGE_REQUEST_UPDATED = new Set([
|
|
'change_request_updated',
|
|
'update',
|
|
'updated',
|
|
'after_update',
|
|
'afterupdate',
|
|
])
|
|
|
|
/**
|
|
* Checks whether a ServiceNow webhook payload matches the configured trigger.
|
|
* Used by the ServiceNow provider handler to filter events at runtime.
|
|
*/
|
|
export function isServiceNowEventMatch(
|
|
triggerId: string,
|
|
body: Record<string, unknown>,
|
|
configuredTableName?: string
|
|
): boolean {
|
|
const payloadTable = extractTableName(body)
|
|
const eventType = extractEventType(body)
|
|
|
|
if (triggerId === 'servicenow_webhook') {
|
|
if (!configuredTableName?.trim()) {
|
|
return true
|
|
}
|
|
if (!payloadTable) {
|
|
return true
|
|
}
|
|
return normalizeToken(payloadTable) === normalizeToken(configuredTableName)
|
|
}
|
|
|
|
if (triggerId === 'servicenow_incident_created' || triggerId === 'servicenow_incident_updated') {
|
|
if (configuredTableName?.trim()) {
|
|
if (payloadTable && normalizeToken(payloadTable) !== normalizeToken(configuredTableName)) {
|
|
return false
|
|
}
|
|
} else if (payloadTable && normalizeToken(payloadTable) !== 'incident') {
|
|
return false
|
|
}
|
|
|
|
if (!eventType) {
|
|
return true
|
|
}
|
|
|
|
const normalized = normalizeToken(eventType)
|
|
return triggerId === 'servicenow_incident_created'
|
|
? INCIDENT_CREATED.has(normalized)
|
|
: INCIDENT_UPDATED.has(normalized)
|
|
}
|
|
|
|
if (
|
|
triggerId === 'servicenow_change_request_created' ||
|
|
triggerId === 'servicenow_change_request_updated'
|
|
) {
|
|
if (configuredTableName?.trim()) {
|
|
if (payloadTable && normalizeToken(payloadTable) !== normalizeToken(configuredTableName)) {
|
|
return false
|
|
}
|
|
} else if (payloadTable && normalizeToken(payloadTable) !== 'change_request') {
|
|
return false
|
|
}
|
|
|
|
if (!eventType) {
|
|
return true
|
|
}
|
|
|
|
const normalized = normalizeToken(eventType)
|
|
return triggerId === 'servicenow_change_request_created'
|
|
? CHANGE_REQUEST_CREATED.has(normalized)
|
|
: CHANGE_REQUEST_UPDATED.has(normalized)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* Outputs for the generic webhook trigger (all events)
|
|
*/
|
|
export function buildServiceNowWebhookOutputs(): Record<string, TriggerOutput> {
|
|
return {
|
|
...buildRecordOutputs(),
|
|
eventType: {
|
|
type: 'string',
|
|
description: 'The type of event that triggered this workflow (e.g., insert, update, delete)',
|
|
},
|
|
category: { type: 'string', description: 'Record category' },
|
|
record: { type: 'json', description: 'Full record data from the webhook payload' },
|
|
}
|
|
}
|