mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-08 22:48:14 -05:00
fix(grain): updated grain trigger to auto-establish trigger (#2666)
Co-authored-by: aadamgough <adam@sim.ai>
This commit is contained in:
@@ -581,6 +581,56 @@ export async function POST(request: NextRequest) {
|
|||||||
}
|
}
|
||||||
// --- End RSS specific logic ---
|
// --- End RSS specific logic ---
|
||||||
|
|
||||||
|
if (savedWebhook && provider === 'grain') {
|
||||||
|
logger.info(`[${requestId}] Grain provider detected. Creating Grain webhook subscription.`)
|
||||||
|
try {
|
||||||
|
const grainHookId = await createGrainWebhookSubscription(
|
||||||
|
request,
|
||||||
|
{
|
||||||
|
id: savedWebhook.id,
|
||||||
|
path: savedWebhook.path,
|
||||||
|
providerConfig: savedWebhook.providerConfig,
|
||||||
|
},
|
||||||
|
requestId
|
||||||
|
)
|
||||||
|
|
||||||
|
if (grainHookId) {
|
||||||
|
// Update the webhook record with the external Grain hook ID
|
||||||
|
const updatedConfig = {
|
||||||
|
...(savedWebhook.providerConfig as Record<string, any>),
|
||||||
|
externalId: grainHookId,
|
||||||
|
}
|
||||||
|
await db
|
||||||
|
.update(webhook)
|
||||||
|
.set({
|
||||||
|
providerConfig: updatedConfig,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
})
|
||||||
|
.where(eq(webhook.id, savedWebhook.id))
|
||||||
|
|
||||||
|
savedWebhook.providerConfig = updatedConfig
|
||||||
|
logger.info(`[${requestId}] Successfully created Grain webhook`, {
|
||||||
|
grainHookId,
|
||||||
|
webhookId: savedWebhook.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(
|
||||||
|
`[${requestId}] Error creating Grain webhook subscription, rolling back webhook`,
|
||||||
|
err
|
||||||
|
)
|
||||||
|
await db.delete(webhook).where(eq(webhook.id, savedWebhook.id))
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: 'Failed to create webhook in Grain',
|
||||||
|
details: err instanceof Error ? err.message : 'Unknown error',
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// --- End Grain specific logic ---
|
||||||
|
|
||||||
const status = targetWebhookId ? 200 : 201
|
const status = targetWebhookId ? 200 : 201
|
||||||
return NextResponse.json({ webhook: savedWebhook }, { status })
|
return NextResponse.json({ webhook: savedWebhook }, { status })
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -947,3 +997,103 @@ async function createWebflowWebhookSubscription(
|
|||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to create the webhook subscription in Grain
|
||||||
|
async function createGrainWebhookSubscription(
|
||||||
|
request: NextRequest,
|
||||||
|
webhookData: any,
|
||||||
|
requestId: string
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
try {
|
||||||
|
const { path, providerConfig } = webhookData
|
||||||
|
const { apiKey, includeHighlights, includeParticipants, includeAiSummary } =
|
||||||
|
providerConfig || {}
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
logger.warn(`[${requestId}] Missing apiKey for Grain webhook creation.`, {
|
||||||
|
webhookId: webhookData.id,
|
||||||
|
})
|
||||||
|
throw new Error(
|
||||||
|
'Grain API Key is required. Please provide your Grain Personal Access Token in the trigger configuration.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const notificationUrl = `${getBaseUrl()}/api/webhooks/trigger/${path}`
|
||||||
|
|
||||||
|
const grainApiUrl = 'https://api.grain.com/_/public-api/v2/hooks/create'
|
||||||
|
|
||||||
|
const requestBody: Record<string, any> = {
|
||||||
|
hook_url: notificationUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build include object based on configuration
|
||||||
|
const include: Record<string, boolean> = {}
|
||||||
|
if (includeHighlights) {
|
||||||
|
include.highlights = true
|
||||||
|
}
|
||||||
|
if (includeParticipants) {
|
||||||
|
include.participants = true
|
||||||
|
}
|
||||||
|
if (includeAiSummary) {
|
||||||
|
include.ai_summary = true
|
||||||
|
}
|
||||||
|
if (Object.keys(include).length > 0) {
|
||||||
|
requestBody.include = include
|
||||||
|
}
|
||||||
|
|
||||||
|
const grainResponse = await fetch(grainApiUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Public-Api-Version': '2025-10-31',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(requestBody),
|
||||||
|
})
|
||||||
|
|
||||||
|
const responseBody = await grainResponse.json()
|
||||||
|
|
||||||
|
if (!grainResponse.ok || responseBody.error) {
|
||||||
|
const errorMessage =
|
||||||
|
responseBody.error?.message ||
|
||||||
|
responseBody.error ||
|
||||||
|
responseBody.message ||
|
||||||
|
'Unknown Grain API error'
|
||||||
|
logger.error(
|
||||||
|
`[${requestId}] Failed to create webhook in Grain for webhook ${webhookData.id}. Status: ${grainResponse.status}`,
|
||||||
|
{ message: errorMessage, response: responseBody }
|
||||||
|
)
|
||||||
|
|
||||||
|
let userFriendlyMessage = 'Failed to create webhook subscription in Grain'
|
||||||
|
if (grainResponse.status === 401) {
|
||||||
|
userFriendlyMessage =
|
||||||
|
'Invalid Grain API Key. Please verify your Personal Access Token is correct.'
|
||||||
|
} else if (grainResponse.status === 403) {
|
||||||
|
userFriendlyMessage =
|
||||||
|
'Access denied. Please ensure your Grain API Key has appropriate permissions.'
|
||||||
|
} else if (errorMessage && errorMessage !== 'Unknown Grain API error') {
|
||||||
|
userFriendlyMessage = `Grain error: ${errorMessage}`
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(userFriendlyMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`[${requestId}] Successfully created webhook in Grain for webhook ${webhookData.id}.`,
|
||||||
|
{
|
||||||
|
grainWebhookId: responseBody.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return responseBody.id
|
||||||
|
} catch (error: any) {
|
||||||
|
logger.error(
|
||||||
|
`[${requestId}] Exception during Grain webhook creation for webhook ${webhookData.id}.`,
|
||||||
|
{
|
||||||
|
message: error.message,
|
||||||
|
stack: error.stack,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const telegramLogger = createLogger('TelegramWebhook')
|
|||||||
const airtableLogger = createLogger('AirtableWebhook')
|
const airtableLogger = createLogger('AirtableWebhook')
|
||||||
const typeformLogger = createLogger('TypeformWebhook')
|
const typeformLogger = createLogger('TypeformWebhook')
|
||||||
const calendlyLogger = createLogger('CalendlyWebhook')
|
const calendlyLogger = createLogger('CalendlyWebhook')
|
||||||
|
const grainLogger = createLogger('GrainWebhook')
|
||||||
|
|
||||||
function getProviderConfig(webhook: any): Record<string, any> {
|
function getProviderConfig(webhook: any): Record<string, any> {
|
||||||
return (webhook.providerConfig as Record<string, any>) || {}
|
return (webhook.providerConfig as Record<string, any>) || {}
|
||||||
@@ -661,9 +662,58 @@ export async function deleteCalendlyWebhook(webhook: any, requestId: string): Pr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a Grain webhook
|
||||||
|
* Don't fail webhook deletion if cleanup fails
|
||||||
|
*/
|
||||||
|
export async function deleteGrainWebhook(webhook: any, requestId: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const config = getProviderConfig(webhook)
|
||||||
|
const apiKey = config.apiKey as string | undefined
|
||||||
|
const externalId = config.externalId as string | undefined
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
grainLogger.warn(
|
||||||
|
`[${requestId}] Missing apiKey for Grain webhook deletion ${webhook.id}, skipping cleanup`
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!externalId) {
|
||||||
|
grainLogger.warn(
|
||||||
|
`[${requestId}] Missing externalId for Grain webhook deletion ${webhook.id}, skipping cleanup`
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const grainApiUrl = `https://api.grain.com/_/public-api/v2/hooks/${externalId}`
|
||||||
|
|
||||||
|
const grainResponse = await fetch(grainApiUrl, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Public-Api-Version': '2025-10-31',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!grainResponse.ok && grainResponse.status !== 404) {
|
||||||
|
const responseBody = await grainResponse.json().catch(() => ({}))
|
||||||
|
grainLogger.warn(
|
||||||
|
`[${requestId}] Failed to delete Grain webhook (non-fatal): ${grainResponse.status}`,
|
||||||
|
{ response: responseBody }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
grainLogger.info(`[${requestId}] Successfully deleted Grain webhook ${externalId}`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
grainLogger.warn(`[${requestId}] Error deleting Grain webhook (non-fatal)`, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clean up external webhook subscriptions for a webhook
|
* Clean up external webhook subscriptions for a webhook
|
||||||
* Handles Airtable, Teams, Telegram, Typeform, and Calendly cleanup
|
* Handles Airtable, Teams, Telegram, Typeform, Calendly, and Grain cleanup
|
||||||
* Don't fail deletion if cleanup fails
|
* Don't fail deletion if cleanup fails
|
||||||
*/
|
*/
|
||||||
export async function cleanupExternalWebhook(
|
export async function cleanupExternalWebhook(
|
||||||
@@ -681,5 +731,7 @@ export async function cleanupExternalWebhook(
|
|||||||
await deleteTypeformWebhook(webhook, requestId)
|
await deleteTypeformWebhook(webhook, requestId)
|
||||||
} else if (webhook.provider === 'calendly') {
|
} else if (webhook.provider === 'calendly') {
|
||||||
await deleteCalendlyWebhook(webhook, requestId)
|
await deleteCalendlyWebhook(webhook, requestId)
|
||||||
|
} else if (webhook.provider === 'grain') {
|
||||||
|
await deleteGrainWebhook(webhook, requestId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ export const grainHighlightCreatedTrigger: TriggerConfig = {
|
|||||||
|
|
||||||
subBlocks: [
|
subBlocks: [
|
||||||
{
|
{
|
||||||
id: 'webhookUrlDisplay',
|
id: 'apiKey',
|
||||||
title: 'Webhook URL',
|
title: 'API Key',
|
||||||
type: 'short-input',
|
type: 'short-input',
|
||||||
readOnly: true,
|
placeholder: 'Enter your Grain API key (Personal Access Token)',
|
||||||
showCopyButton: true,
|
description: 'Required to create the webhook in Grain.',
|
||||||
useWebhookUrl: true,
|
password: true,
|
||||||
placeholder: 'Webhook URL will be generated',
|
required: true,
|
||||||
mode: 'trigger',
|
mode: 'trigger',
|
||||||
condition: {
|
condition: {
|
||||||
field: 'selectedTriggerId',
|
field: 'selectedTriggerId',
|
||||||
@@ -26,13 +26,35 @@ export const grainHighlightCreatedTrigger: TriggerConfig = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'webhookSecret',
|
id: 'includeHighlights',
|
||||||
title: 'Webhook Secret',
|
title: 'Include Highlights',
|
||||||
type: 'short-input',
|
type: 'switch',
|
||||||
placeholder: 'Enter a strong secret',
|
description: 'Include highlights/clips in webhook payload.',
|
||||||
description: 'Validates that webhook deliveries originate from Grain.',
|
defaultValue: false,
|
||||||
password: true,
|
mode: 'trigger',
|
||||||
required: false,
|
condition: {
|
||||||
|
field: 'selectedTriggerId',
|
||||||
|
value: 'grain_highlight_created',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'includeParticipants',
|
||||||
|
title: 'Include Participants',
|
||||||
|
type: 'switch',
|
||||||
|
description: 'Include participant list in webhook payload.',
|
||||||
|
defaultValue: false,
|
||||||
|
mode: 'trigger',
|
||||||
|
condition: {
|
||||||
|
field: 'selectedTriggerId',
|
||||||
|
value: 'grain_highlight_created',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'includeAiSummary',
|
||||||
|
title: 'Include AI Summary',
|
||||||
|
type: 'switch',
|
||||||
|
description: 'Include AI-generated summary in webhook payload.',
|
||||||
|
defaultValue: false,
|
||||||
mode: 'trigger',
|
mode: 'trigger',
|
||||||
condition: {
|
condition: {
|
||||||
field: 'selectedTriggerId',
|
field: 'selectedTriggerId',
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ export const grainHighlightUpdatedTrigger: TriggerConfig = {
|
|||||||
|
|
||||||
subBlocks: [
|
subBlocks: [
|
||||||
{
|
{
|
||||||
id: 'webhookUrlDisplay',
|
id: 'apiKey',
|
||||||
title: 'Webhook URL',
|
title: 'API Key',
|
||||||
type: 'short-input',
|
type: 'short-input',
|
||||||
readOnly: true,
|
placeholder: 'Enter your Grain API key (Personal Access Token)',
|
||||||
showCopyButton: true,
|
description: 'Required to create the webhook in Grain.',
|
||||||
useWebhookUrl: true,
|
password: true,
|
||||||
placeholder: 'Webhook URL will be generated',
|
required: true,
|
||||||
mode: 'trigger',
|
mode: 'trigger',
|
||||||
condition: {
|
condition: {
|
||||||
field: 'selectedTriggerId',
|
field: 'selectedTriggerId',
|
||||||
@@ -26,13 +26,35 @@ export const grainHighlightUpdatedTrigger: TriggerConfig = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'webhookSecret',
|
id: 'includeHighlights',
|
||||||
title: 'Webhook Secret',
|
title: 'Include Highlights',
|
||||||
type: 'short-input',
|
type: 'switch',
|
||||||
placeholder: 'Enter a strong secret',
|
description: 'Include highlights/clips in webhook payload.',
|
||||||
description: 'Validates that webhook deliveries originate from Grain.',
|
defaultValue: false,
|
||||||
password: true,
|
mode: 'trigger',
|
||||||
required: false,
|
condition: {
|
||||||
|
field: 'selectedTriggerId',
|
||||||
|
value: 'grain_highlight_updated',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'includeParticipants',
|
||||||
|
title: 'Include Participants',
|
||||||
|
type: 'switch',
|
||||||
|
description: 'Include participant list in webhook payload.',
|
||||||
|
defaultValue: false,
|
||||||
|
mode: 'trigger',
|
||||||
|
condition: {
|
||||||
|
field: 'selectedTriggerId',
|
||||||
|
value: 'grain_highlight_updated',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'includeAiSummary',
|
||||||
|
title: 'Include AI Summary',
|
||||||
|
type: 'switch',
|
||||||
|
description: 'Include AI-generated summary in webhook payload.',
|
||||||
|
defaultValue: false,
|
||||||
mode: 'trigger',
|
mode: 'trigger',
|
||||||
condition: {
|
condition: {
|
||||||
field: 'selectedTriggerId',
|
field: 'selectedTriggerId',
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ export const grainRecordingCreatedTrigger: TriggerConfig = {
|
|||||||
|
|
||||||
subBlocks: [
|
subBlocks: [
|
||||||
{
|
{
|
||||||
id: 'webhookUrlDisplay',
|
id: 'apiKey',
|
||||||
title: 'Webhook URL',
|
title: 'API Key',
|
||||||
type: 'short-input',
|
type: 'short-input',
|
||||||
readOnly: true,
|
placeholder: 'Enter your Grain API key (Personal Access Token)',
|
||||||
showCopyButton: true,
|
description: 'Required to create the webhook in Grain.',
|
||||||
useWebhookUrl: true,
|
password: true,
|
||||||
placeholder: 'Webhook URL will be generated',
|
required: true,
|
||||||
mode: 'trigger',
|
mode: 'trigger',
|
||||||
condition: {
|
condition: {
|
||||||
field: 'selectedTriggerId',
|
field: 'selectedTriggerId',
|
||||||
@@ -26,13 +26,35 @@ export const grainRecordingCreatedTrigger: TriggerConfig = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'webhookSecret',
|
id: 'includeHighlights',
|
||||||
title: 'Webhook Secret',
|
title: 'Include Highlights',
|
||||||
type: 'short-input',
|
type: 'switch',
|
||||||
placeholder: 'Enter a strong secret',
|
description: 'Include highlights/clips in webhook payload.',
|
||||||
description: 'Validates that webhook deliveries originate from Grain.',
|
defaultValue: false,
|
||||||
password: true,
|
mode: 'trigger',
|
||||||
required: false,
|
condition: {
|
||||||
|
field: 'selectedTriggerId',
|
||||||
|
value: 'grain_recording_created',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'includeParticipants',
|
||||||
|
title: 'Include Participants',
|
||||||
|
type: 'switch',
|
||||||
|
description: 'Include participant list in webhook payload.',
|
||||||
|
defaultValue: false,
|
||||||
|
mode: 'trigger',
|
||||||
|
condition: {
|
||||||
|
field: 'selectedTriggerId',
|
||||||
|
value: 'grain_recording_created',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'includeAiSummary',
|
||||||
|
title: 'Include AI Summary',
|
||||||
|
type: 'switch',
|
||||||
|
description: 'Include AI-generated summary in webhook payload.',
|
||||||
|
defaultValue: false,
|
||||||
mode: 'trigger',
|
mode: 'trigger',
|
||||||
condition: {
|
condition: {
|
||||||
field: 'selectedTriggerId',
|
field: 'selectedTriggerId',
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ export const grainRecordingUpdatedTrigger: TriggerConfig = {
|
|||||||
|
|
||||||
subBlocks: [
|
subBlocks: [
|
||||||
{
|
{
|
||||||
id: 'webhookUrlDisplay',
|
id: 'apiKey',
|
||||||
title: 'Webhook URL',
|
title: 'API Key',
|
||||||
type: 'short-input',
|
type: 'short-input',
|
||||||
readOnly: true,
|
placeholder: 'Enter your Grain API key (Personal Access Token)',
|
||||||
showCopyButton: true,
|
description: 'Required to create the webhook in Grain.',
|
||||||
useWebhookUrl: true,
|
password: true,
|
||||||
placeholder: 'Webhook URL will be generated',
|
required: true,
|
||||||
mode: 'trigger',
|
mode: 'trigger',
|
||||||
condition: {
|
condition: {
|
||||||
field: 'selectedTriggerId',
|
field: 'selectedTriggerId',
|
||||||
@@ -26,13 +26,35 @@ export const grainRecordingUpdatedTrigger: TriggerConfig = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'webhookSecret',
|
id: 'includeHighlights',
|
||||||
title: 'Webhook Secret',
|
title: 'Include Highlights',
|
||||||
type: 'short-input',
|
type: 'switch',
|
||||||
placeholder: 'Enter a strong secret',
|
description: 'Include highlights/clips in webhook payload.',
|
||||||
description: 'Validates that webhook deliveries originate from Grain.',
|
defaultValue: false,
|
||||||
password: true,
|
mode: 'trigger',
|
||||||
required: false,
|
condition: {
|
||||||
|
field: 'selectedTriggerId',
|
||||||
|
value: 'grain_recording_updated',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'includeParticipants',
|
||||||
|
title: 'Include Participants',
|
||||||
|
type: 'switch',
|
||||||
|
description: 'Include participant list in webhook payload.',
|
||||||
|
defaultValue: false,
|
||||||
|
mode: 'trigger',
|
||||||
|
condition: {
|
||||||
|
field: 'selectedTriggerId',
|
||||||
|
value: 'grain_recording_updated',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'includeAiSummary',
|
||||||
|
title: 'Include AI Summary',
|
||||||
|
type: 'switch',
|
||||||
|
description: 'Include AI-generated summary in webhook payload.',
|
||||||
|
defaultValue: false,
|
||||||
mode: 'trigger',
|
mode: 'trigger',
|
||||||
condition: {
|
condition: {
|
||||||
field: 'selectedTriggerId',
|
field: 'selectedTriggerId',
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ export const grainStoryCreatedTrigger: TriggerConfig = {
|
|||||||
|
|
||||||
subBlocks: [
|
subBlocks: [
|
||||||
{
|
{
|
||||||
id: 'webhookUrlDisplay',
|
id: 'apiKey',
|
||||||
title: 'Webhook URL',
|
title: 'API Key',
|
||||||
type: 'short-input',
|
type: 'short-input',
|
||||||
readOnly: true,
|
placeholder: 'Enter your Grain API key (Personal Access Token)',
|
||||||
showCopyButton: true,
|
description: 'Required to create the webhook in Grain.',
|
||||||
useWebhookUrl: true,
|
password: true,
|
||||||
placeholder: 'Webhook URL will be generated',
|
required: true,
|
||||||
mode: 'trigger',
|
mode: 'trigger',
|
||||||
condition: {
|
condition: {
|
||||||
field: 'selectedTriggerId',
|
field: 'selectedTriggerId',
|
||||||
@@ -26,13 +26,35 @@ export const grainStoryCreatedTrigger: TriggerConfig = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'webhookSecret',
|
id: 'includeHighlights',
|
||||||
title: 'Webhook Secret',
|
title: 'Include Highlights',
|
||||||
type: 'short-input',
|
type: 'switch',
|
||||||
placeholder: 'Enter a strong secret',
|
description: 'Include highlights/clips in webhook payload.',
|
||||||
description: 'Validates that webhook deliveries originate from Grain.',
|
defaultValue: false,
|
||||||
password: true,
|
mode: 'trigger',
|
||||||
required: false,
|
condition: {
|
||||||
|
field: 'selectedTriggerId',
|
||||||
|
value: 'grain_story_created',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'includeParticipants',
|
||||||
|
title: 'Include Participants',
|
||||||
|
type: 'switch',
|
||||||
|
description: 'Include participant list in webhook payload.',
|
||||||
|
defaultValue: false,
|
||||||
|
mode: 'trigger',
|
||||||
|
condition: {
|
||||||
|
field: 'selectedTriggerId',
|
||||||
|
value: 'grain_story_created',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'includeAiSummary',
|
||||||
|
title: 'Include AI Summary',
|
||||||
|
type: 'switch',
|
||||||
|
description: 'Include AI-generated summary in webhook payload.',
|
||||||
|
defaultValue: false,
|
||||||
mode: 'trigger',
|
mode: 'trigger',
|
||||||
condition: {
|
condition: {
|
||||||
field: 'selectedTriggerId',
|
field: 'selectedTriggerId',
|
||||||
|
|||||||
@@ -17,19 +17,17 @@ export const grainTriggerOptions = [
|
|||||||
*/
|
*/
|
||||||
export function grainSetupInstructions(eventType: string): string {
|
export function grainSetupInstructions(eventType: string): string {
|
||||||
const instructions = [
|
const instructions = [
|
||||||
'<strong>Note:</strong> You need admin permissions in your Grain workspace to create webhooks.',
|
'Enter your Grain API Key (Personal Access Token) above.',
|
||||||
'In Grain, navigate to <strong>Settings > Integrations > Webhooks</strong>.',
|
'You can find or create your API key in Grain at <strong>Settings > Integrations > API</strong>.',
|
||||||
'Click <strong>"Create webhook"</strong> or <strong>"Add webhook"</strong>.',
|
'Optionally configure filters to narrow which recordings trigger the webhook.',
|
||||||
'Paste the <strong>Webhook URL</strong> from above into the URL field.',
|
`Click <strong>"Save Configuration"</strong> to automatically create the webhook in Grain for <strong>${eventType}</strong> events.`,
|
||||||
'Optionally, enter the <strong>Webhook Secret</strong> from above for signature validation.',
|
'The webhook will be automatically deleted when you remove this trigger.',
|
||||||
`Select the event types this webhook should listen to. For this trigger, select <strong>${eventType}</strong>.`,
|
|
||||||
'Click <strong>"Save"</strong> to activate the webhook.',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
return instructions
|
return instructions
|
||||||
.map(
|
.map(
|
||||||
(instruction, index) =>
|
(instruction, index) =>
|
||||||
`<div class="mb-3">${index === 0 ? instruction : `<strong>${index}.</strong> ${instruction}`}</div>`
|
`<div class="mb-3"><strong>${index + 1}.</strong> ${instruction}</div>`
|
||||||
)
|
)
|
||||||
.join('')
|
.join('')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ export const grainWebhookTrigger: TriggerConfig = {
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'webhookUrlDisplay',
|
id: 'apiKey',
|
||||||
title: 'Webhook URL',
|
title: 'API Key',
|
||||||
type: 'short-input',
|
type: 'short-input',
|
||||||
readOnly: true,
|
placeholder: 'Enter your Grain API key (Personal Access Token)',
|
||||||
showCopyButton: true,
|
description: 'Required to create the webhook in Grain.',
|
||||||
useWebhookUrl: true,
|
password: true,
|
||||||
placeholder: 'Webhook URL will be generated',
|
required: true,
|
||||||
mode: 'trigger',
|
mode: 'trigger',
|
||||||
condition: {
|
condition: {
|
||||||
field: 'selectedTriggerId',
|
field: 'selectedTriggerId',
|
||||||
@@ -35,13 +35,35 @@ export const grainWebhookTrigger: TriggerConfig = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'webhookSecret',
|
id: 'includeHighlights',
|
||||||
title: 'Webhook Secret',
|
title: 'Include Highlights',
|
||||||
type: 'short-input',
|
type: 'switch',
|
||||||
placeholder: 'Enter a strong secret',
|
description: 'Include highlights/clips in webhook payload.',
|
||||||
description: 'Validates that webhook deliveries originate from Grain.',
|
defaultValue: false,
|
||||||
password: true,
|
mode: 'trigger',
|
||||||
required: false,
|
condition: {
|
||||||
|
field: 'selectedTriggerId',
|
||||||
|
value: 'grain_webhook',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'includeParticipants',
|
||||||
|
title: 'Include Participants',
|
||||||
|
type: 'switch',
|
||||||
|
description: 'Include participant list in webhook payload.',
|
||||||
|
defaultValue: false,
|
||||||
|
mode: 'trigger',
|
||||||
|
condition: {
|
||||||
|
field: 'selectedTriggerId',
|
||||||
|
value: 'grain_webhook',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'includeAiSummary',
|
||||||
|
title: 'Include AI Summary',
|
||||||
|
type: 'switch',
|
||||||
|
description: 'Include AI-generated summary in webhook payload.',
|
||||||
|
defaultValue: false,
|
||||||
mode: 'trigger',
|
mode: 'trigger',
|
||||||
condition: {
|
condition: {
|
||||||
field: 'selectedTriggerId',
|
field: 'selectedTriggerId',
|
||||||
|
|||||||
Reference in New Issue
Block a user