diff --git a/apps/docs/content/docs/en/tools/attio.mdx b/apps/docs/content/docs/en/tools/attio.mdx index d0a76ce9f9..85ad63126b 100644 --- a/apps/docs/content/docs/en/tools/attio.mdx +++ b/apps/docs/content/docs/en/tools/attio.mdx @@ -1012,8 +1012,8 @@ Update a webhook in Attio (target URL and/or subscriptions) | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `webhookId` | string | Yes | The webhook ID to update | -| `targetUrl` | string | No | New HTTPS target URL | -| `subscriptions` | string | No | New JSON array of subscriptions | +| `targetUrl` | string | Yes | HTTPS target URL for webhook delivery | +| `subscriptions` | string | Yes | JSON array of subscriptions, e.g. \[\{"event_type":"note.created"\}\] | #### Output diff --git a/apps/sim/blocks/blocks/attio.ts b/apps/sim/blocks/blocks/attio.ts index 50dae4555d..8e51dbbe71 100644 --- a/apps/sim/blocks/blocks/attio.ts +++ b/apps/sim/blocks/blocks/attio.ts @@ -139,7 +139,7 @@ export const AttioBlock: BlockConfig = { { id: 'values', title: 'Values', - type: 'long-input', + type: 'code', placeholder: '{"name": "Acme Corp", "domains": [{"domain": "acme.com"}]}', condition: { field: 'operation', @@ -161,21 +161,25 @@ export const AttioBlock: BlockConfig = { Return ONLY the JSON object with Attio attribute values. No explanations, no markdown, no extra text. ### ATTIO VALUES STRUCTURE -Keys are attribute slugs, values follow Attio's attribute format. Simple values can be strings; complex values are arrays of objects. +Keys are attribute slugs. Most text attributes use array-of-objects format [{"value": "..."}]. Special attributes have their own format. ### COMMON PEOPLE ATTRIBUTES -- name: [{"first_name": "...", "last_name": "..."}] +- name: [{"first_name": "...", "last_name": "...", "full_name": "..."}] (personal-name type, full_name is required) - email_addresses: [{"email_address": "..."}] - phone_numbers: [{"original_phone_number": "...", "country_code": "US"}] -- job_title, description, linkedin, twitter +- job_title: [{"value": "..."}] +- description: [{"value": "..."}] +- linkedin: [{"value": "https://linkedin.com/in/..."}] +- twitter: [{"value": "@handle"}] ### COMMON COMPANY ATTRIBUTES - name: [{"value": "..."}] - domains: [{"domain": "..."}] -- description, primary_location, categories +- description: [{"value": "..."}] +- primary_location: [{"line_1": "...", "locality": "...", "region": "...", "postcode": "...", "country_code": "US"}] ### EXAMPLES -Person: {"name": [{"first_name": "John", "last_name": "Doe"}], "email_addresses": [{"email_address": "john@example.com"}]} +Person: {"name": [{"first_name": "John", "last_name": "Doe", "full_name": "John Doe"}], "email_addresses": [{"email_address": "john@example.com"}], "job_title": [{"value": "Engineer"}]} Company: {"name": [{"value": "Acme Corp"}], "domains": [{"domain": "acme.com"}]}`, placeholder: 'Describe the record values you want to set...', generationType: 'json-object', @@ -184,8 +188,8 @@ Company: {"name": [{"value": "Acme Corp"}], "domains": [{"domain": "acme.com"}]} { id: 'filter', title: 'Filter', - type: 'long-input', - placeholder: '{"name": "John Smith"} (optional)', + type: 'code', + placeholder: '{"name": "John Smith"}', condition: { field: 'operation', value: 'list_records' }, wandConfig: { enabled: true, @@ -215,8 +219,8 @@ Empty (list all): {}`, { id: 'sorts', title: 'Sort', - type: 'long-input', - placeholder: '[{"direction":"asc","attribute":"name"}] (optional)', + type: 'code', + placeholder: '[{"direction":"asc","attribute":"name"}]', condition: { field: 'operation', value: 'list_records' }, wandConfig: { enabled: true, @@ -332,7 +336,7 @@ YYYY-MM-DDTHH:mm:ss.SSSZ id: 'noteMeetingId', title: 'Meeting ID', type: 'short-input', - placeholder: 'Link to a meeting (optional)', + placeholder: 'Link to a meeting', condition: { field: 'operation', value: 'create_note' }, mode: 'advanced', }, @@ -358,7 +362,7 @@ YYYY-MM-DDTHH:mm:ss.SSSZ id: 'taskDeadline', title: 'Deadline', type: 'short-input', - placeholder: '2024-12-01T15:00:00.000Z (optional)', + placeholder: '2024-12-01T15:00:00.000Z', condition: { field: 'operation', value: ['create_task', 'update_task'] }, wandConfig: { enabled: true, @@ -394,8 +398,8 @@ YYYY-MM-DDTHH:mm:ss.SSSZ { id: 'taskLinkedRecords', title: 'Linked Records', - type: 'long-input', - placeholder: '[{"target_object":"people","target_record_id":"..."}] (optional)', + type: 'code', + placeholder: '[{"target_object":"people","target_record_id":"..."}]', condition: { field: 'operation', value: ['create_task', 'update_task'] }, wandConfig: { enabled: true, @@ -420,9 +424,8 @@ Return ONLY the JSON array. No explanations, no markdown, no extra text. { id: 'taskAssignees', title: 'Assignees', - type: 'long-input', - placeholder: - '[{"referenced_actor_type":"workspace-member","referenced_actor_id":"..."}] (optional)', + type: 'code', + placeholder: '[{"referenced_actor_type":"workspace-member","referenced_actor_id":"..."}]', condition: { field: 'operation', value: ['create_task', 'update_task'] }, wandConfig: { enabled: true, @@ -456,21 +459,21 @@ Return ONLY the JSON array. No explanations, no markdown, no extra text. id: 'taskFilterObject', title: 'Linked Object Type', type: 'short-input', - placeholder: 'e.g. people, companies (optional)', + placeholder: 'e.g. people, companies', condition: { field: 'operation', value: 'list_tasks' }, }, { id: 'taskFilterRecordId', title: 'Linked Record ID', type: 'short-input', - placeholder: 'Filter by linked record ID (optional)', + placeholder: 'Filter by linked record ID', condition: { field: 'operation', value: 'list_tasks' }, }, { id: 'taskFilterAssignee', title: 'Assignee', type: 'short-input', - placeholder: 'Filter by assignee email or ID (optional)', + placeholder: 'Filter by assignee email or ID', condition: { field: 'operation', value: 'list_tasks' }, }, { @@ -571,7 +574,7 @@ Return ONLY the JSON array. No explanations, no markdown, no extra text. id: 'listApiSlug', title: 'API Slug', type: 'short-input', - placeholder: 'e.g. my_list (optional, auto-generated)', + placeholder: 'e.g. my_list (auto-generated from name)', condition: { field: 'operation', value: ['create_list', 'update_list'] }, mode: 'advanced', }, @@ -622,8 +625,8 @@ Return ONLY the JSON array. No explanations, no markdown, no extra text. { id: 'entryValues', title: 'Entry Values', - type: 'long-input', - placeholder: '{"attribute_slug": "value"} (optional)', + type: 'code', + placeholder: '{"attribute_slug": "value"}', condition: { field: 'operation', value: ['create_list_entry', 'update_list_entry'], @@ -652,8 +655,8 @@ Keys are list attribute slugs. Values follow Attio attribute format. { id: 'entryFilter', title: 'Filter', - type: 'long-input', - placeholder: '{"attribute": {"$operator": "value"}} (optional)', + type: 'code', + placeholder: '{"attribute": {"$operator": "value"}}', condition: { field: 'operation', value: 'query_list_entries' }, wandConfig: { enabled: true, @@ -679,8 +682,8 @@ Logical: $and, $or, $not { id: 'entrySorts', title: 'Sort', - type: 'long-input', - placeholder: '[{"direction":"asc","attribute":"created_at"}] (optional)', + type: 'code', + placeholder: '[{"direction":"asc","attribute":"created_at"}]', condition: { field: 'operation', value: 'query_list_entries' }, wandConfig: { enabled: true, @@ -819,7 +822,7 @@ YYYY-MM-DDTHH:mm:ss.SSSZ id: 'threadFilterRecordId', title: 'Record ID', type: 'short-input', - placeholder: 'Filter by record ID (optional)', + placeholder: 'Filter by record ID', condition: { field: 'operation', value: 'list_threads' }, }, { @@ -833,7 +836,7 @@ YYYY-MM-DDTHH:mm:ss.SSSZ id: 'threadFilterEntryId', title: 'Entry ID', type: 'short-input', - placeholder: 'Filter by entry ID (optional)', + placeholder: 'Filter by entry ID', condition: { field: 'operation', value: 'list_threads' }, }, { @@ -865,15 +868,15 @@ YYYY-MM-DDTHH:mm:ss.SSSZ type: 'short-input', placeholder: 'https://example.com/webhook', condition: { field: 'operation', value: ['create_webhook', 'update_webhook'] }, - required: { field: 'operation', value: 'create_webhook' }, + required: { field: 'operation', value: ['create_webhook', 'update_webhook'] }, }, { id: 'webhookSubscriptions', title: 'Subscriptions', - type: 'long-input', + type: 'code', placeholder: '[{"event_type":"record.created","filter":{"object_id":"..."}}]', condition: { field: 'operation', value: ['create_webhook', 'update_webhook'] }, - required: { field: 'operation', value: 'create_webhook' }, + required: { field: 'operation', value: ['create_webhook', 'update_webhook'] }, wandConfig: { enabled: true, maintainHistory: true, @@ -911,7 +914,8 @@ workspace-member.created id: 'limit', title: 'Limit', type: 'short-input', - placeholder: 'Max results (optional)', + placeholder: 'Max results', + mode: 'advanced', condition: { field: 'operation', value: [ @@ -925,6 +929,24 @@ workspace-member.created ], }, }, + { + id: 'offset', + title: 'Offset', + type: 'short-input', + placeholder: 'Number of results to skip', + mode: 'advanced', + condition: { + field: 'operation', + value: [ + 'list_records', + 'list_notes', + 'list_tasks', + 'query_list_entries', + 'list_threads', + 'list_webhooks', + ], + }, + }, ...getTrigger('attio_record_created').subBlocks, ...getTrigger('attio_record_updated').subBlocks, ...getTrigger('attio_record_deleted').subBlocks, @@ -1103,6 +1125,7 @@ workspace-member.created // Shared params if (params.limit) cleanParams.limit = Number(params.limit) + if (params.offset) cleanParams.offset = Number(params.offset) return cleanParams }, @@ -1164,6 +1187,7 @@ workspace-member.created webhookTargetUrl: { type: 'string', description: 'Webhook target URL' }, webhookSubscriptions: { type: 'json', description: 'Webhook event subscriptions' }, limit: { type: 'string', description: 'Maximum number of results' }, + offset: { type: 'string', description: 'Number of results to skip for pagination' }, }, outputs: { diff --git a/apps/sim/lib/webhooks/processor.ts b/apps/sim/lib/webhooks/processor.ts index 0602f0b85c..4852ae49fe 100644 --- a/apps/sim/lib/webhooks/processor.ts +++ b/apps/sim/lib/webhooks/processor.ts @@ -13,6 +13,7 @@ import { convertSquareBracketsToTwiML } from '@/lib/webhooks/utils' import { handleSlackChallenge, handleWhatsAppVerification, + validateAttioSignature, validateCalcomSignature, validateCirclebackSignature, validateFirefliesSignature, @@ -597,6 +598,33 @@ export async function verifyProviderAuth( } } + if (foundWebhook.provider === 'attio') { + const secret = providerConfig.webhookSecret as string | undefined + + if (!secret) { + logger.debug( + `[${requestId}] Attio webhook ${foundWebhook.id} has no signing secret, skipping signature verification` + ) + } else { + const signature = request.headers.get('Attio-Signature') + + if (!signature) { + logger.warn(`[${requestId}] Attio webhook missing signature header`) + return new NextResponse('Unauthorized - Missing Attio signature', { status: 401 }) + } + + const isValidSignature = validateAttioSignature(secret, signature, rawBody) + + if (!isValidSignature) { + logger.warn(`[${requestId}] Attio signature verification failed`, { + signatureLength: signature.length, + secretLength: secret.length, + }) + return new NextResponse('Unauthorized - Invalid Attio signature', { status: 401 }) + } + } + } + if (foundWebhook.provider === 'linear') { const secret = providerConfig.webhookSecret as string | undefined diff --git a/apps/sim/lib/webhooks/provider-subscriptions.ts b/apps/sim/lib/webhooks/provider-subscriptions.ts index 10c1205711..7bb100a09e 100644 --- a/apps/sim/lib/webhooks/provider-subscriptions.ts +++ b/apps/sim/lib/webhooks/provider-subscriptions.ts @@ -19,6 +19,7 @@ const calendlyLogger = createLogger('CalendlyWebhook') const grainLogger = createLogger('GrainWebhook') const lemlistLogger = createLogger('LemlistWebhook') const webflowLogger = createLogger('WebflowWebhook') +const attioLogger = createLogger('AttioWebhook') const providerSubscriptionsLogger = createLogger('WebhookProviderSubscriptions') function getProviderConfig(webhook: any): Record { @@ -976,6 +977,196 @@ export async function deleteWebflowWebhook( } } +export async function createAttioWebhookSubscription( + userId: string, + webhookData: any, + requestId: string +): Promise<{ externalId: string; webhookSecret: string } | undefined> { + try { + const { path, providerConfig } = webhookData + const { triggerId, credentialId } = providerConfig || {} + + if (!credentialId) { + attioLogger.warn(`[${requestId}] Missing credentialId for Attio webhook creation.`, { + webhookId: webhookData.id, + }) + throw new Error( + 'Attio account connection required. Please connect your Attio account in the trigger configuration and try again.' + ) + } + + const credentialOwner = await getCredentialOwner(credentialId, requestId) + const accessToken = credentialOwner + ? await refreshAccessTokenIfNeeded( + credentialOwner.accountId, + credentialOwner.userId, + requestId + ) + : null + + if (!accessToken) { + attioLogger.warn( + `[${requestId}] Could not retrieve Attio access token for user ${userId}. Cannot create webhook.` + ) + throw new Error( + 'Attio account connection required. Please connect your Attio account in the trigger configuration and try again.' + ) + } + + const notificationUrl = `${getBaseUrl()}/api/webhooks/trigger/${path}` + + const { TRIGGER_EVENT_MAP } = await import('@/triggers/attio/utils') + + let subscriptions: Array<{ event_type: string }> = [] + if (triggerId === 'attio_webhook') { + const allEvents = new Set() + for (const events of Object.values(TRIGGER_EVENT_MAP)) { + for (const event of events) { + allEvents.add(event) + } + } + subscriptions = Array.from(allEvents).map((event_type) => ({ event_type })) + } else { + const events = TRIGGER_EVENT_MAP[triggerId] + if (!events || events.length === 0) { + attioLogger.warn(`[${requestId}] No event types mapped for trigger ${triggerId}`, { + webhookId: webhookData.id, + }) + throw new Error(`Unknown Attio trigger type: ${triggerId}`) + } + subscriptions = events.map((event_type) => ({ event_type })) + } + + const requestBody = { + target_url: notificationUrl, + subscriptions, + } + + const attioResponse = await fetch('https://api.attio.com/v2/webhooks', { + method: 'POST', + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(requestBody), + }) + + if (!attioResponse.ok) { + const errorBody = await attioResponse.json().catch(() => ({})) + attioLogger.error( + `[${requestId}] Failed to create webhook in Attio for webhook ${webhookData.id}. Status: ${attioResponse.status}`, + { response: errorBody } + ) + + let userFriendlyMessage = 'Failed to create webhook subscription in Attio' + if (attioResponse.status === 401) { + userFriendlyMessage = 'Attio authentication failed. Please reconnect your Attio account.' + } else if (attioResponse.status === 403) { + userFriendlyMessage = + 'Attio access denied. Please ensure your integration has webhook permissions.' + } + + throw new Error(userFriendlyMessage) + } + + const responseBody = await attioResponse.json() + const data = responseBody.data || responseBody + const webhookId = data.id?.webhook_id || data.webhook_id || data.id + const secret = data.secret + + if (!webhookId) { + attioLogger.error( + `[${requestId}] Attio webhook created but no webhook_id returned for webhook ${webhookData.id}`, + { response: responseBody } + ) + throw new Error('Attio webhook creation succeeded but no webhook ID was returned') + } + + if (!secret) { + attioLogger.warn( + `[${requestId}] Attio webhook created but no secret returned for webhook ${webhookData.id}. Signature verification will be skipped.`, + { response: responseBody } + ) + } + + attioLogger.info( + `[${requestId}] Successfully created webhook in Attio for webhook ${webhookData.id}.`, + { attioWebhookId: webhookId } + ) + + return { externalId: webhookId, webhookSecret: secret || '' } + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error) + attioLogger.error( + `[${requestId}] Exception during Attio webhook creation for webhook ${webhookData.id}.`, + { message } + ) + throw error + } +} + +export async function deleteAttioWebhook( + webhook: any, + _workflow: any, + requestId: string +): Promise { + try { + const config = getProviderConfig(webhook) + const externalId = config.externalId as string | undefined + const credentialId = config.credentialId as string | undefined + + if (!externalId) { + attioLogger.warn( + `[${requestId}] Missing externalId for Attio webhook deletion ${webhook.id}, skipping cleanup` + ) + return + } + + if (!credentialId) { + attioLogger.warn( + `[${requestId}] Missing credentialId for Attio webhook deletion ${webhook.id}, skipping cleanup` + ) + return + } + + const credentialOwner = await getCredentialOwner(credentialId, requestId) + const accessToken = credentialOwner + ? await refreshAccessTokenIfNeeded( + credentialOwner.accountId, + credentialOwner.userId, + requestId + ) + : null + + if (!accessToken) { + attioLogger.warn( + `[${requestId}] Could not retrieve Attio access token. Cannot delete webhook.`, + { webhookId: webhook.id } + ) + return + } + + const attioResponse = await fetch(`https://api.attio.com/v2/webhooks/${externalId}`, { + method: 'DELETE', + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }) + + if (!attioResponse.ok && attioResponse.status !== 404) { + const responseBody = await attioResponse.json().catch(() => ({})) + attioLogger.warn( + `[${requestId}] Failed to delete Attio webhook (non-fatal): ${attioResponse.status}`, + { response: responseBody } + ) + } else { + attioLogger.info(`[${requestId}] Successfully deleted Attio webhook ${externalId}`) + } + } catch (error) { + attioLogger.warn(`[${requestId}] Error deleting Attio webhook (non-fatal)`, error) + } +} + export async function createGrainWebhookSubscription( _request: NextRequest, webhookData: any, @@ -1611,6 +1802,7 @@ type RecreateCheckInput = { /** Providers that create external webhook subscriptions */ const PROVIDERS_WITH_EXTERNAL_SUBSCRIPTIONS = new Set([ 'airtable', + 'attio', 'calendly', 'webflow', 'typeform', @@ -1626,6 +1818,7 @@ const SYSTEM_MANAGED_FIELDS = new Set([ 'externalSubscriptionId', 'eventTypes', 'webhookTag', + 'webhookSecret', 'historyId', 'lastCheckedTimestamp', 'setupCompleted', @@ -1686,6 +1879,16 @@ export async function createExternalWebhookSubscription( updatedProviderConfig = { ...updatedProviderConfig, externalId } externalSubscriptionCreated = true } + } else if (provider === 'attio') { + const result = await createAttioWebhookSubscription(userId, webhookData, requestId) + if (result) { + updatedProviderConfig = { + ...updatedProviderConfig, + externalId: result.externalId, + webhookSecret: result.webhookSecret, + } + externalSubscriptionCreated = true + } } else if (provider === 'calendly') { const externalId = await createCalendlyWebhookSubscription(webhookData, requestId) if (externalId) { @@ -1736,7 +1939,7 @@ export async function createExternalWebhookSubscription( /** * Clean up external webhook subscriptions for a webhook - * Handles Airtable, Teams, Telegram, Typeform, Calendly, Grain, and Lemlist cleanup + * Handles Airtable, Attio, Teams, Telegram, Typeform, Calendly, Grain, and Lemlist cleanup * Don't fail deletion if cleanup fails */ export async function cleanupExternalWebhook( @@ -1746,6 +1949,8 @@ export async function cleanupExternalWebhook( ): Promise { if (webhook.provider === 'airtable') { await deleteAirtableWebhook(webhook, workflow, requestId) + } else if (webhook.provider === 'attio') { + await deleteAttioWebhook(webhook, workflow, requestId) } else if (webhook.provider === 'microsoft-teams') { await deleteTeamsSubscription(webhook, workflow, requestId) } else if (webhook.provider === 'telegram') { diff --git a/apps/sim/lib/webhooks/utils.server.ts b/apps/sim/lib/webhooks/utils.server.ts index f63983577b..7d9abfcef7 100644 --- a/apps/sim/lib/webhooks/utils.server.ts +++ b/apps/sim/lib/webhooks/utils.server.ts @@ -1395,6 +1395,41 @@ export function validateLinearSignature(secret: string, signature: string, body: } } +/** + * Validates an Attio webhook request signature using HMAC SHA-256 + * @param secret - Attio webhook signing secret (plain text) + * @param signature - Attio-Signature header value (hex-encoded HMAC SHA-256 signature) + * @param body - Raw request body string + * @returns Whether the signature is valid + */ +export function validateAttioSignature(secret: string, signature: string, body: string): boolean { + try { + if (!secret || !signature || !body) { + logger.warn('Attio signature validation missing required fields', { + hasSecret: !!secret, + hasSignature: !!signature, + hasBody: !!body, + }) + return false + } + + const computedHash = crypto.createHmac('sha256', secret).update(body, 'utf8').digest('hex') + + logger.debug('Attio signature comparison', { + computedSignature: `${computedHash.substring(0, 10)}...`, + providedSignature: `${signature.substring(0, 10)}...`, + computedLength: computedHash.length, + providedLength: signature.length, + match: computedHash === signature, + }) + + return safeCompare(computedHash, signature) + } catch (error) { + logger.error('Error validating Attio signature:', error) + return false + } +} + /** * Validates a Circleback webhook request signature using HMAC SHA-256 * @param secret - Circleback signing secret (plain text) diff --git a/apps/sim/tools/attio/create_list.ts b/apps/sim/tools/attio/create_list.ts index a41d7c5396..eb2d344722 100644 --- a/apps/sim/tools/attio/create_list.ts +++ b/apps/sim/tools/attio/create_list.ts @@ -65,12 +65,19 @@ export const attioCreateListTool: ToolConfig { + const apiSlug = + params.apiSlug || + params.name + ?.toLowerCase() + .replace(/[^a-z0-9]+/g, '_') + .replace(/^_|_$/g, '') const data: Record = { name: params.name, + api_slug: apiSlug, parent_object: params.parentObject, + workspace_access: params.workspaceAccess ?? null, + workspace_member_access: [] as unknown[], } - if (params.apiSlug) data.api_slug = params.apiSlug - if (params.workspaceAccess) data.workspace_access = params.workspaceAccess if (params.workspaceMemberAccess) { try { data.workspace_member_access = diff --git a/apps/sim/tools/attio/create_list_entry.ts b/apps/sim/tools/attio/create_list_entry.ts index 7c12a38864..381e61e1d7 100644 --- a/apps/sim/tools/attio/create_list_entry.ts +++ b/apps/sim/tools/attio/create_list_entry.ts @@ -60,20 +60,22 @@ export const attioCreateListEntryTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { - const data: Record = { - parent_record_id: params.parentRecordId, - parent_object: params.parentObject, - } + let entryValues: unknown = {} if (params.entryValues) { try { - data.entry_values = + entryValues = typeof params.entryValues === 'string' ? JSON.parse(params.entryValues) : params.entryValues } catch { - data.entry_values = {} + entryValues = {} } } + const data: Record = { + parent_record_id: params.parentRecordId, + parent_object: params.parentObject, + entry_values: entryValues, + } return { data } }, }, diff --git a/apps/sim/tools/attio/create_note.ts b/apps/sim/tools/attio/create_note.ts index 6483fa51e2..3124959a92 100644 --- a/apps/sim/tools/attio/create_note.ts +++ b/apps/sim/tools/attio/create_note.ts @@ -83,7 +83,7 @@ export const attioCreateNoteTool: ToolConfig { const searchParams = new URLSearchParams() - if (params.limit !== undefined) searchParams.set('limit', String(params.limit)) - if (params.offset !== undefined) searchParams.set('offset', String(params.offset)) + if (params.limit != null) searchParams.set('limit', String(params.limit)) + if (params.offset != null) searchParams.set('offset', String(params.offset)) const qs = searchParams.toString() return `https://api.attio.com/v2/webhooks${qs ? `?${qs}` : ''}` }, diff --git a/apps/sim/tools/attio/query_list_entries.ts b/apps/sim/tools/attio/query_list_entries.ts index a876b6bf18..d234c2aec3 100644 --- a/apps/sim/tools/attio/query_list_entries.ts +++ b/apps/sim/tools/attio/query_list_entries.ts @@ -83,8 +83,8 @@ export const attioQueryListEntriesTool: ToolConfig< body.sorts = [] } } - if (params.limit !== undefined) body.limit = params.limit - if (params.offset !== undefined) body.offset = params.offset + if (params.limit != null) body.limit = params.limit + if (params.offset != null) body.offset = params.offset return body }, }, diff --git a/apps/sim/tools/attio/update_list.ts b/apps/sim/tools/attio/update_list.ts index 798949bae4..e54176a128 100644 --- a/apps/sim/tools/attio/update_list.ts +++ b/apps/sim/tools/attio/update_list.ts @@ -66,10 +66,10 @@ export const attioUpdateListTool: ToolConfig { const data: Record = {} - if (params.name !== undefined) data.name = params.name - if (params.apiSlug !== undefined) data.api_slug = params.apiSlug - if (params.workspaceAccess !== undefined) data.workspace_access = params.workspaceAccess - if (params.workspaceMemberAccess !== undefined) { + if (params.name != null) data.name = params.name + if (params.apiSlug != null) data.api_slug = params.apiSlug + if (params.workspaceAccess != null) data.workspace_access = params.workspaceAccess + if (params.workspaceMemberAccess != null) { try { data.workspace_member_access = typeof params.workspaceMemberAccess === 'string' diff --git a/apps/sim/tools/attio/update_object.ts b/apps/sim/tools/attio/update_object.ts index cd220e508d..1b8c0024a9 100644 --- a/apps/sim/tools/attio/update_object.ts +++ b/apps/sim/tools/attio/update_object.ts @@ -59,9 +59,9 @@ export const attioUpdateObjectTool: ToolConfig { const data: Record = {} - if (params.apiSlug !== undefined) data.api_slug = params.apiSlug - if (params.singularNoun !== undefined) data.singular_noun = params.singularNoun - if (params.pluralNoun !== undefined) data.plural_noun = params.pluralNoun + if (params.apiSlug != null) data.api_slug = params.apiSlug + if (params.singularNoun != null) data.singular_noun = params.singularNoun + if (params.pluralNoun != null) data.plural_noun = params.pluralNoun return { data } }, }, diff --git a/apps/sim/tools/attio/update_task.ts b/apps/sim/tools/attio/update_task.ts index b22e9e014a..9b1e4e21e8 100644 --- a/apps/sim/tools/attio/update_task.ts +++ b/apps/sim/tools/attio/update_task.ts @@ -64,8 +64,8 @@ export const attioUpdateTaskTool: ToolConfig { const data: Record = {} - if (params.deadlineAt !== undefined) data.deadline_at = params.deadlineAt || null - if (params.isCompleted !== undefined) data.is_completed = params.isCompleted + if (params.deadlineAt != null) data.deadline_at = params.deadlineAt || null + if (params.isCompleted != null) data.is_completed = params.isCompleted if (params.linkedRecords) { try { data.linked_records = diff --git a/apps/sim/tools/attio/update_webhook.ts b/apps/sim/tools/attio/update_webhook.ts index d40e478473..662691c4fd 100644 --- a/apps/sim/tools/attio/update_webhook.ts +++ b/apps/sim/tools/attio/update_webhook.ts @@ -34,15 +34,15 @@ export const attioUpdateWebhookTool: ToolConfig< }, targetUrl: { type: 'string', - required: false, + required: true, visibility: 'user-or-llm', - description: 'New HTTPS target URL', + description: 'HTTPS target URL for webhook delivery', }, subscriptions: { type: 'string', - required: false, + required: true, visibility: 'user-or-llm', - description: 'New JSON array of subscriptions', + description: 'JSON array of subscriptions, e.g. [{"event_type":"note.created"}]', }, }, @@ -54,18 +54,21 @@ export const attioUpdateWebhookTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { - const data: Record = {} - if (params.targetUrl !== undefined) data.target_url = params.targetUrl + let subscriptions: unknown = [] if (params.subscriptions) { try { - data.subscriptions = + subscriptions = typeof params.subscriptions === 'string' ? JSON.parse(params.subscriptions) : params.subscriptions } catch { - data.subscriptions = [] + subscriptions = [] } } + const data: Record = { + target_url: params.targetUrl, + subscriptions, + } return { data } }, }, diff --git a/apps/sim/triggers/attio/comment_created.ts b/apps/sim/triggers/attio/comment_created.ts index 721d1b5d51..7fdb6c0811 100644 --- a/apps/sim/triggers/attio/comment_created.ts +++ b/apps/sim/triggers/attio/comment_created.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildCommentOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildCommentOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioCommentCreatedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_comment_created', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('comment.created'), - extraFields: buildAttioExtraFields('attio_comment_created'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_comment_created'), outputs: buildCommentOutputs(), diff --git a/apps/sim/triggers/attio/comment_deleted.ts b/apps/sim/triggers/attio/comment_deleted.ts index 8f3830de28..b784486fe8 100644 --- a/apps/sim/triggers/attio/comment_deleted.ts +++ b/apps/sim/triggers/attio/comment_deleted.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildCommentOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildCommentOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioCommentDeletedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_comment_deleted', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('comment.deleted'), - extraFields: buildAttioExtraFields('attio_comment_deleted'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_comment_deleted'), outputs: buildCommentOutputs(), diff --git a/apps/sim/triggers/attio/comment_resolved.ts b/apps/sim/triggers/attio/comment_resolved.ts index e0b1cb7d96..5bda970782 100644 --- a/apps/sim/triggers/attio/comment_resolved.ts +++ b/apps/sim/triggers/attio/comment_resolved.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildCommentOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildCommentOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioCommentResolvedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_comment_resolved', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('comment.resolved'), - extraFields: buildAttioExtraFields('attio_comment_resolved'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_comment_resolved'), outputs: buildCommentOutputs(), diff --git a/apps/sim/triggers/attio/comment_unresolved.ts b/apps/sim/triggers/attio/comment_unresolved.ts index 434cad8814..af0e389969 100644 --- a/apps/sim/triggers/attio/comment_unresolved.ts +++ b/apps/sim/triggers/attio/comment_unresolved.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildCommentOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildCommentOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioCommentUnresolvedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_comment_unresolved', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('comment.unresolved'), - extraFields: buildAttioExtraFields('attio_comment_unresolved'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_comment_unresolved'), outputs: buildCommentOutputs(), diff --git a/apps/sim/triggers/attio/list_entry_created.ts b/apps/sim/triggers/attio/list_entry_created.ts index 8ef9076662..36acba6b03 100644 --- a/apps/sim/triggers/attio/list_entry_created.ts +++ b/apps/sim/triggers/attio/list_entry_created.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildListEntryOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildListEntryOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioListEntryCreatedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_list_entry_created', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('list-entry.created'), - extraFields: buildAttioExtraFields('attio_list_entry_created'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_list_entry_created'), outputs: buildListEntryOutputs(), diff --git a/apps/sim/triggers/attio/list_entry_deleted.ts b/apps/sim/triggers/attio/list_entry_deleted.ts index 72f5911987..be752ce6e4 100644 --- a/apps/sim/triggers/attio/list_entry_deleted.ts +++ b/apps/sim/triggers/attio/list_entry_deleted.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildListEntryOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildListEntryOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioListEntryDeletedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_list_entry_deleted', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('list-entry.deleted'), - extraFields: buildAttioExtraFields('attio_list_entry_deleted'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_list_entry_deleted'), outputs: buildListEntryOutputs(), diff --git a/apps/sim/triggers/attio/list_entry_updated.ts b/apps/sim/triggers/attio/list_entry_updated.ts index 209d553e78..d03bc1142b 100644 --- a/apps/sim/triggers/attio/list_entry_updated.ts +++ b/apps/sim/triggers/attio/list_entry_updated.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildListEntryUpdatedOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildListEntryUpdatedOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioListEntryUpdatedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_list_entry_updated', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('list-entry.updated'), - extraFields: buildAttioExtraFields('attio_list_entry_updated'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_list_entry_updated'), outputs: buildListEntryUpdatedOutputs(), diff --git a/apps/sim/triggers/attio/note_created.ts b/apps/sim/triggers/attio/note_created.ts index bb7cfb3b90..d4a5845d9c 100644 --- a/apps/sim/triggers/attio/note_created.ts +++ b/apps/sim/triggers/attio/note_created.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildNoteOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildNoteOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioNoteCreatedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_note_created', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('note.created'), - extraFields: buildAttioExtraFields('attio_note_created'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_note_created'), outputs: buildNoteOutputs(), diff --git a/apps/sim/triggers/attio/note_deleted.ts b/apps/sim/triggers/attio/note_deleted.ts index df453adcb8..76801ec1ac 100644 --- a/apps/sim/triggers/attio/note_deleted.ts +++ b/apps/sim/triggers/attio/note_deleted.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildNoteOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildNoteOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioNoteDeletedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_note_deleted', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('note.deleted'), - extraFields: buildAttioExtraFields('attio_note_deleted'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_note_deleted'), outputs: buildNoteOutputs(), diff --git a/apps/sim/triggers/attio/note_updated.ts b/apps/sim/triggers/attio/note_updated.ts index 886cbd59e8..cf4ee6b9a9 100644 --- a/apps/sim/triggers/attio/note_updated.ts +++ b/apps/sim/triggers/attio/note_updated.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildNoteOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildNoteOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioNoteUpdatedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_note_updated', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('note.updated'), - extraFields: buildAttioExtraFields('attio_note_updated'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_note_updated'), outputs: buildNoteOutputs(), diff --git a/apps/sim/triggers/attio/record_created.ts b/apps/sim/triggers/attio/record_created.ts index c1b84c5e8f..c27553d26a 100644 --- a/apps/sim/triggers/attio/record_created.ts +++ b/apps/sim/triggers/attio/record_created.ts @@ -1,9 +1,7 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' import { - attioSetupInstructions, attioTriggerOptions, - buildAttioExtraFields, + buildAttioTriggerSubBlocks, buildRecordOutputs, } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' @@ -22,13 +20,18 @@ export const attioRecordCreatedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_record_created', - triggerOptions: attioTriggerOptions, - includeDropdown: true, - setupInstructions: attioSetupInstructions('record.created'), - extraFields: buildAttioExtraFields('attio_record_created'), - }), + subBlocks: [ + { + id: 'selectedTriggerId', + title: 'Trigger Type', + type: 'dropdown', + mode: 'trigger', + options: attioTriggerOptions, + value: () => 'attio_record_created', + required: true, + }, + ...buildAttioTriggerSubBlocks('attio_record_created'), + ], outputs: buildRecordOutputs(), diff --git a/apps/sim/triggers/attio/record_deleted.ts b/apps/sim/triggers/attio/record_deleted.ts index f4de4ea537..f71256fd97 100644 --- a/apps/sim/triggers/attio/record_deleted.ts +++ b/apps/sim/triggers/attio/record_deleted.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildRecordOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildRecordOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioRecordDeletedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_record_deleted', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('record.deleted'), - extraFields: buildAttioExtraFields('attio_record_deleted'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_record_deleted'), outputs: buildRecordOutputs(), diff --git a/apps/sim/triggers/attio/record_merged.ts b/apps/sim/triggers/attio/record_merged.ts index d611993f41..ca8217d9ce 100644 --- a/apps/sim/triggers/attio/record_merged.ts +++ b/apps/sim/triggers/attio/record_merged.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildRecordMergedEventOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildRecordMergedEventOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioRecordMergedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_record_merged', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('record.merged'), - extraFields: buildAttioExtraFields('attio_record_merged'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_record_merged'), outputs: buildRecordMergedEventOutputs(), diff --git a/apps/sim/triggers/attio/record_updated.ts b/apps/sim/triggers/attio/record_updated.ts index 12e693d5ba..c64970f86a 100644 --- a/apps/sim/triggers/attio/record_updated.ts +++ b/apps/sim/triggers/attio/record_updated.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildRecordUpdatedOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildRecordUpdatedOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioRecordUpdatedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_record_updated', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('record.updated'), - extraFields: buildAttioExtraFields('attio_record_updated'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_record_updated'), outputs: buildRecordUpdatedOutputs(), diff --git a/apps/sim/triggers/attio/task_created.ts b/apps/sim/triggers/attio/task_created.ts index a143802886..e88b7f1680 100644 --- a/apps/sim/triggers/attio/task_created.ts +++ b/apps/sim/triggers/attio/task_created.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildTaskOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildTaskOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioTaskCreatedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_task_created', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('task.created'), - extraFields: buildAttioExtraFields('attio_task_created'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_task_created'), outputs: buildTaskOutputs(), diff --git a/apps/sim/triggers/attio/task_deleted.ts b/apps/sim/triggers/attio/task_deleted.ts index 4b19ef594a..a58cac1953 100644 --- a/apps/sim/triggers/attio/task_deleted.ts +++ b/apps/sim/triggers/attio/task_deleted.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildTaskOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildTaskOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioTaskDeletedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_task_deleted', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('task.deleted'), - extraFields: buildAttioExtraFields('attio_task_deleted'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_task_deleted'), outputs: buildTaskOutputs(), diff --git a/apps/sim/triggers/attio/task_updated.ts b/apps/sim/triggers/attio/task_updated.ts index d056dd4e4c..ddcd0c1012 100644 --- a/apps/sim/triggers/attio/task_updated.ts +++ b/apps/sim/triggers/attio/task_updated.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildTaskOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildTaskOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioTaskUpdatedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_task_updated', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('task.updated'), - extraFields: buildAttioExtraFields('attio_task_updated'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_task_updated'), outputs: buildTaskOutputs(), diff --git a/apps/sim/triggers/attio/utils.ts b/apps/sim/triggers/attio/utils.ts index 3cfa8fc126..6c60759b42 100644 --- a/apps/sim/triggers/attio/utils.ts +++ b/apps/sim/triggers/attio/utils.ts @@ -22,15 +22,11 @@ export const attioTriggerOptions = [ { label: 'Generic Webhook (All Events)', id: 'attio_webhook' }, ] -export function attioSetupInstructions(eventType: string): string { +export function attioSetupInstructions(): string { const instructions = [ - 'Note: You need access to the Attio developer settings to create webhooks. See the Attio webhook documentation for details.', - 'In Attio, navigate to Settings > Developers and select your integration.', - 'Go to the Webhooks tab and click "Create Webhook".', - 'Paste the Webhook URL from above into the target URL field.', - `Add a subscription with the event type ${eventType}. You can optionally add filters to scope the events.`, - 'Save the webhook. Copy the signing secret shown and paste it in the field above for signature verification.', - 'The webhook is now active. Attio will send events to the URL you configured.', + 'Note: Webhooks are automatically created in Attio when you deploy this workflow, and deleted when you undeploy. See the Attio webhook documentation for details.', + 'Connect your Attio account using the credential selector above.', + 'Deploy the workflow — a webhook will be created automatically in your Attio workspace.', ] return instructions @@ -41,17 +37,34 @@ export function attioSetupInstructions(eventType: string): string { .join('') } -export function buildAttioExtraFields(triggerId: string): SubBlockConfig[] { +/** + * Builds subBlocks for an Attio trigger with OAuth credentials and automatic webhook lifecycle. + * Used by both the primary trigger (with dropdown) and secondary triggers. + */ +export function buildAttioTriggerSubBlocks(triggerId: string): SubBlockConfig[] { return [ { - id: 'webhookSecret', - title: 'Webhook Secret', - type: 'short-input', - placeholder: 'Enter the webhook signing secret from Attio', - description: - 'The signing secret from Attio used to verify webhook deliveries via HMAC-SHA256 signature', - password: true, - required: false, + id: 'triggerCredentials', + title: 'Attio Account', + type: 'oauth-input', + serviceId: 'attio', + mode: 'trigger', + required: true, + condition: { field: 'selectedTriggerId', value: triggerId }, + }, + { + id: 'triggerSave', + title: 'Save', + type: 'trigger-save', + mode: 'trigger', + condition: { field: 'selectedTriggerId', value: triggerId }, + }, + { + id: 'triggerInstructions', + title: 'Setup Instructions', + hideFromPreview: true, + type: 'text', + defaultValue: attioSetupInstructions(), mode: 'trigger', condition: { field: 'selectedTriggerId', value: triggerId }, }, @@ -257,7 +270,7 @@ export function buildGenericWebhookOutputs(): Record { /** * Maps trigger IDs to the exact Attio event type strings. */ -const TRIGGER_EVENT_MAP: Record = { +export const TRIGGER_EVENT_MAP: Record = { attio_record_created: ['record.created'], attio_record_updated: ['record.updated'], attio_record_deleted: ['record.deleted'], diff --git a/apps/sim/triggers/attio/webhook.ts b/apps/sim/triggers/attio/webhook.ts index 87f615a18e..9bbcd2f119 100644 --- a/apps/sim/triggers/attio/webhook.ts +++ b/apps/sim/triggers/attio/webhook.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildGenericWebhookOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildGenericWebhookOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioWebhookTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_webhook', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('All Events'), - extraFields: buildAttioExtraFields('attio_webhook'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_webhook'), outputs: buildGenericWebhookOutputs(),