import type { SubBlockConfig } from '@/blocks/types' import type { TriggerOutput } from '@/triggers/types' /** * Dropdown options for the Ashby trigger type selector. */ export const ashbyTriggerOptions = [ { label: 'Application Submitted', id: 'ashby_application_submit' }, { label: 'Candidate Stage Change', id: 'ashby_candidate_stage_change' }, { label: 'Candidate Hired', id: 'ashby_candidate_hire' }, { label: 'Candidate Deleted', id: 'ashby_candidate_delete' }, { label: 'Job Created', id: 'ashby_job_create' }, { label: 'Offer Created', id: 'ashby_offer_create' }, ] /** * Maps Sim trigger IDs to Ashby webhookType / event action values. * Used by webhook.create body and matchEvent filtering. */ export const ASHBY_TRIGGER_ACTION_MAP: Record = { ashby_application_submit: 'applicationSubmit', ashby_candidate_stage_change: 'candidateStageChange', ashby_candidate_hire: 'candidateHire', ashby_candidate_delete: 'candidateDelete', ashby_job_create: 'jobCreate', ashby_offer_create: 'offerCreate', } /** * Checks if an Ashby webhook event matches the configured trigger. * Ashby sends a ping event on webhook create/edit; this filter rejects * any event whose `action` does not equal the expected webhookType. */ export function isAshbyEventMatch(triggerId: string, action: string): boolean { const expected = ASHBY_TRIGGER_ACTION_MAP[triggerId] if (!expected) return false return expected === action } /** * Generates setup instructions for Ashby webhooks. * Webhooks are automatically created/deleted via the Ashby API. */ export function ashbySetupInstructions(eventType: string): string { const instructions = [ 'Enter your Ashby API Key above. You can find your API key in Ashby at Settings > API Keys.', `The webhook for ${eventType} events will be automatically created in Ashby when you save the trigger.`, 'The webhook will be automatically deleted if you remove this trigger.', ] return instructions .map( (instruction, index) => `
${index + 1}. ${instruction}
` ) .join('') } /** * Builds the complete subBlocks array for an Ashby trigger. * Ashby webhooks are managed via API, so no webhook URL is displayed. * * Structure: [dropdown?] -> apiKey -> instructions */ export function buildAshbySubBlocks(options: { triggerId: string eventType: string includeDropdown?: boolean }): SubBlockConfig[] { const { triggerId, eventType, includeDropdown = false } = options const blocks: SubBlockConfig[] = [] if (includeDropdown) { blocks.push({ id: 'selectedTriggerId', title: 'Trigger Type', type: 'dropdown', mode: 'trigger', options: ashbyTriggerOptions, value: () => triggerId, required: true, }) } blocks.push({ id: 'apiKey', title: 'API Key', type: 'short-input', placeholder: 'Enter your Ashby API key', password: true, required: true, paramVisibility: 'user-only', mode: 'trigger', condition: { field: 'selectedTriggerId', value: triggerId }, }) blocks.push({ id: 'triggerInstructions', title: 'Setup Instructions', hideFromPreview: true, type: 'text', defaultValue: ashbySetupInstructions(eventType), mode: 'trigger', condition: { field: 'selectedTriggerId', value: triggerId }, }) return blocks } /** * Core fields present in all Ashby webhook payloads. */ const coreOutputs = { action: { type: 'string', description: 'The webhook event type (e.g., applicationSubmit, candidateHire)', }, } as const /** * Build outputs for applicationSubmit events. * Payload: { action, data: { application: { id, createdAt, updatedAt, status, * candidate: { id, name }, currentInterviewStage: { id, title }, * job: { id, title } } } } */ export function buildApplicationSubmitOutputs(): Record { return { ...coreOutputs, application: { id: { type: 'string', description: 'Application UUID' }, createdAt: { type: 'string', description: 'Application creation timestamp (ISO 8601)' }, updatedAt: { type: 'string', description: 'Application last update timestamp (ISO 8601)', }, status: { type: 'string', description: 'Application status (Active, Hired, Archived, Lead)', }, candidate: { id: { type: 'string', description: 'Candidate UUID' }, name: { type: 'string', description: 'Candidate name' }, }, currentInterviewStage: { id: { type: 'string', description: 'Current interview stage UUID' }, title: { type: 'string', description: 'Current interview stage title' }, stageType: { type: 'string', description: 'Current interview stage type (e.g., Lead, Applied, Interview, Offer)', }, }, job: { id: { type: 'string', description: 'Job UUID' }, title: { type: 'string', description: 'Job title' }, }, }, } as Record } /** * Build outputs for candidateStageChange events. * Payload matches the application object structure (same as applicationUpdate). * Payload: { action, data: { application: { id, createdAt, updatedAt, status, * candidate: { id, name }, currentInterviewStage: { id, title, type }, * job: { id, title } } } } */ export function buildCandidateStageChangeOutputs(): Record { return { ...coreOutputs, application: { id: { type: 'string', description: 'Application UUID' }, createdAt: { type: 'string', description: 'Application creation timestamp (ISO 8601)' }, updatedAt: { type: 'string', description: 'Application last update timestamp (ISO 8601)', }, status: { type: 'string', description: 'Application status (Active, Hired, Archived, Lead)', }, candidate: { id: { type: 'string', description: 'Candidate UUID' }, name: { type: 'string', description: 'Candidate name' }, }, currentInterviewStage: { id: { type: 'string', description: 'Current interview stage UUID' }, title: { type: 'string', description: 'Current interview stage title' }, stageType: { type: 'string', description: 'Current interview stage type (e.g., Lead, Applied, Interview, Offer)', }, }, job: { id: { type: 'string', description: 'Job UUID' }, title: { type: 'string', description: 'Job title' }, }, }, } as Record } /** * Build outputs for candidateHire events. * Per Ashby docs, candidateHire payloads include application details and most * recent accepted offer information. */ export function buildCandidateHireOutputs(): Record { return { ...coreOutputs, application: { id: { type: 'string', description: 'Application UUID' }, createdAt: { type: 'string', description: 'Application creation timestamp (ISO 8601)' }, updatedAt: { type: 'string', description: 'Application last update timestamp (ISO 8601)', }, status: { type: 'string', description: 'Application status (Hired)' }, candidate: { id: { type: 'string', description: 'Candidate UUID' }, name: { type: 'string', description: 'Candidate name' }, }, currentInterviewStage: { id: { type: 'string', description: 'Current interview stage UUID' }, title: { type: 'string', description: 'Current interview stage title' }, stageType: { type: 'string', description: 'Current interview stage type (e.g., Lead, Applied, Interview, Offer)', }, }, job: { id: { type: 'string', description: 'Job UUID' }, title: { type: 'string', description: 'Job title' }, }, }, offer: { id: { type: 'string', description: 'Accepted offer UUID' }, applicationId: { type: 'string', description: 'Associated application UUID' }, acceptanceStatus: { type: 'string', description: 'Offer acceptance status' }, offerStatus: { type: 'string', description: 'Offer process status' }, decidedAt: { type: 'string', description: 'Offer decision timestamp (ISO 8601)', }, latestVersion: { id: { type: 'string', description: 'Latest offer version UUID' }, }, }, } as Record } /** * Build outputs for candidateDelete events. * Payload: { action, data: { candidate: { id } } } */ export function buildCandidateDeleteOutputs(): Record { return { ...coreOutputs, candidate: { id: { type: 'string', description: 'Deleted candidate UUID' }, }, } as Record } /** * Build outputs for jobCreate events. * Payload: { action, data: { job: { id, title, confidential, status, employmentType } } } */ export function buildJobCreateOutputs(): Record { return { ...coreOutputs, job: { id: { type: 'string', description: 'Job UUID' }, title: { type: 'string', description: 'Job title' }, confidential: { type: 'boolean', description: 'Whether the job is confidential' }, status: { type: 'string', description: 'Job status (Open, Closed, Draft, Archived)' }, employmentType: { type: 'string', description: 'Employment type (FullTime, PartTime, Intern, Contract)', }, }, } as Record } /** * Build outputs for offerCreate events. * Payload: { action, data: { offer: { id, decidedAt, applicationId, acceptanceStatus, * offerStatus, latestVersion: { id } } } } */ export function buildOfferCreateOutputs(): Record { return { ...coreOutputs, offer: { id: { type: 'string', description: 'Offer UUID' }, applicationId: { type: 'string', description: 'Associated application UUID' }, acceptanceStatus: { type: 'string', description: 'Offer acceptance status (Accepted, Declined, Pending, Created, Cancelled)', }, offerStatus: { type: 'string', description: 'Offer process status (WaitingOnApprovalStart, WaitingOnOfferApproval, WaitingOnApprovalDefinition, WaitingOnCandidateResponse, CandidateRejected, CandidateAccepted, OfferCancelled)', }, decidedAt: { type: 'string', description: 'Offer decision timestamp (ISO 8601). Typically null at creation; populated after candidate responds.', }, latestVersion: { id: { type: 'string', description: 'Latest offer version UUID' }, }, }, } as Record }