Compare commits

..

29 Commits

Author SHA1 Message Date
Vikhyath Mondreti
f9cfca92bf v0.5.31: add zod as direct dep 2025-12-15 20:40:02 -08:00
Waleed
25afacb25e v0.5.30: vllm fixes, permissions fixes, isolated vms for code execution, tool fixes 2025-12-15 19:38:01 -08:00
Gaurav Chadha
fcf52ac4d5 fix(landing): prevent url encoding for spaces for footer links (#2376) 2025-12-15 10:59:12 -08:00
Shivam
842200bcf2 fix(docs): clarify working directory for drizzle migration (#2375) 2025-12-15 10:58:27 -08:00
Waleed
a0fb889644 v0.5.29: chat voice mode, opengraph for docs, option to disable auth 2025-12-13 19:50:06 -08:00
Waleed
f526c36fc0 v0.5.28: tool fixes, sqs, spotify, nextjs update, component playground 2025-12-12 21:05:57 -08:00
Waleed
e24f31cbce v0.5.27: sidebar updates, ssrf patches, gpt-5.2, stagehand fixes 2025-12-11 14:45:25 -08:00
Waleed
3fbd57caf1 v0.5.26: tool fixes, templates and knowledgebase fixes, deployment versions in logs 2025-12-11 00:52:13 -08:00
Vikhyath Mondreti
b5da61377c v0.5.25: minor ui improvements, copilot billing fix 2025-12-10 18:32:27 -08:00
Waleed
18b7032494 v0.5.24: agent tool and UX improvements, redis service overhaul (#2291)
* feat(folders): add the ability to create a folder within a folder in popover (#2287)

* fix(agent): filter out empty params to ensure LLM can set tool params at runtime (#2288)

* fix(mcp): added backfill effect to add missing descriptions for mcp tools (#2290)

* fix(redis): cleanup access pattern across callsites (#2289)

* fix(redis): cleanup access pattern across callsites

* swap redis command to be non blocking

* improvement(log-details): polling, trace spans (#2292)

---------

Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Emir Karabeg <78010029+emir-karabeg@users.noreply.github.com>
2025-12-10 13:09:21 -08:00
Waleed
b7bbef8620 v0.5.23: kb, logs, general ui improvements, token bucket rate limits, docs, mcp, autolayout improvements (#2286)
* fix(mcp): prevent redundant MCP server discovery calls at runtime, use cached tool schema instead (#2273)

* fix(mcp): prevent redundant MCP server discovery calls at runtime, use cached tool schema instead

* added backfill, added loading state for tools in settings > mcp

* fix tool inp

* feat(rate-limiter): token bucket algorithm  (#2270)

* fix(ratelimit): make deployed chat rate limited

* improvement(rate-limiter): use token bucket algo

* update docs

* fix

* fix type

* fix db rate limiter

* address greptile comments

* feat(i18n): update translations (#2275)

Co-authored-by: icecrasher321 <icecrasher321@users.noreply.github.com>

* fix(tools): updated kalshi and polymarket tools to accurately reflect outputs (#2274)

* feat(i18n): update translations (#2276)

Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>

* fix(autolayout): align by handle (#2277)

* fix(autolayout): align by handle

* use shared constants everywhere

* cleanup

* fix(copilot): fix custom tools (#2278)

* Fix title custom tool

* Checkpoitn (broken)

* Fix custom tool flash

* Edit workflow returns null fix

* Works

* Fix lint

* fix(ime): prevent form submission during IME composition steps (#2279)

* fix(ui): prevent form submission during IME composition steps

* chore(gitignore): add IntelliJ IDE files to .gitignore

---------

Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>

* feat(ui): logs, kb, emcn (#2207)

* feat(kb): emcn alignment; sidebar: popover primary; settings-modal: expand

* feat: EMCN breadcrumb; improvement(KB): UI

* fix: hydration error

* improvement(KB): UI

* feat: emcn modal sizing, KB tags; refactor: deleted old sidebar

* feat(logs): UI

* fix: add documents modal name

* feat: logs, emcn, cursorrules; refactor: logs

* feat: dashboard

* feat: notifications; improvement: logs details

* fixed random rectangle on canvas

* fixed the name of the file to align

* fix build

---------

Co-authored-by: waleed <walif6@gmail.com>

* fix(creds): glitch allowing multiple credentials in an integration (#2282)

* improvement: custom tools modal, logs-details (#2283)

* fix(docs): fix copy page button and header hook (#2284)

* improvement(chat): add the ability to download files from the deployed chat (#2280)

* added teams download and chat download file

* Removed comments

* removed comments

* component structure and download all

* removed comments

* cleanup code

* fix empty files case

* small fix

* fix(container): resize heuristic improvement (#2285)

* estimate block height for resize based on subblocks

* fix hydration error

* make more conservative

---------

Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: icecrasher321 <icecrasher321@users.noreply.github.com>
Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>
Co-authored-by: Siddharth Ganesan <33737564+Sg312@users.noreply.github.com>
Co-authored-by: mosa <mosaxiv@gmail.com>
Co-authored-by: Emir Karabeg <78010029+emir-karabeg@users.noreply.github.com>
Co-authored-by: Adam Gough <77861281+aadamgough@users.noreply.github.com>
2025-12-10 00:57:58 -08:00
Waleed
52edbea659 v0.5.22: rss feed trigger, sftp tool, billing fixes, 413 surfacing, copilot improvements 2025-12-09 10:27:36 -08:00
Vikhyath Mondreti
d480057fd3 fix(migration): migration got removed by force push (#2253) 2025-12-08 14:08:12 -08:00
Waleed
c27c233da0 v0.5.21: google groups, virtualized code viewer, ui, autolayout, docs improvements 2025-12-08 13:10:50 -08:00
Waleed
ebef5f3a27 v0.5.20: google slides, ui fixes, subflow resizing improvements 2025-12-06 15:36:09 -08:00
Vikhyath Mondreti
12c4c2d44f v0.5.19: copilot fix 2025-12-05 15:27:31 -08:00
Vikhyath Mondreti
929a352edb fix(build): added trigger.dev sdk mock to tests (#2216) 2025-12-05 14:26:50 -08:00
Vikhyath Mondreti
6cd078b0fe v0.5.18: ui fixes, nextjs16, workspace notifications, admin APIs, loading improvements, new slack tools 2025-12-05 14:03:09 -08:00
Waleed
31874939ee v0.5.17: modals, billing fixes, bun update, zoom, dropbox, kalshi, polymarket, datadog, ahrefs, gitlab, shopify, ssh, wordpress integrations 2025-12-04 13:29:46 -08:00
Waleed
e157ce5fbc v0.5.16: MCP fixes, code refactors, jira fixes, new mistral models 2025-12-02 22:02:11 -08:00
Vikhyath Mondreti
774e5d585c v0.5.15: add tools, revert subblock prop change 2025-12-01 13:52:12 -08:00
Vikhyath Mondreti
54cc93743f v0.5.14: fix issue with teams, google selectors + cleanup code 2025-12-01 12:39:39 -08:00
Waleed
8c32ad4c0d v0.5.13: polling fixes, generic agent search tool, status page, smtp, sendgrid, linkedin, more tools (#2148)
* feat(tools): added smtp, sendgrid, mailgun, linkedin, fixed permissions in context menu (#2133)

* feat(tools): added twilio sendgrid integration

* feat(tools): added smtp, sendgrid, mailgun, fixed permissions in context menu

* added top level mocks for sporadically failing tests

* incr type safety

* fix(team-plans): track departed member usage so value not lost (#2118)

* fix(team-plans): track departed member usage so value not lost

* reset usage to 0 when they leave team

* prep merge with stagig

* regen migrations

* fix org invite + ws selection'

---------

Co-authored-by: Waleed <walif6@gmail.com>

* feat(i18n): update translations (#2134)

Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>

* feat(creators): add verification for creators (#2135)

* feat(tools): added apify block/tools  (#2136)

* feat(tools): added apify

* cleanup

* feat(i18n): update translations (#2137)

Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>

* feat(env): added more optional env var examples (#2138)

* feat(statuspage): added statuspage, updated list of tools in footer, renamed routes (#2139)

* feat(statuspage): added statuspage, updated list of tools in footer, renamed routes

* ack PR comments

* feat(tools): add generic search tool (#2140)

* feat(i18n): update translations (#2141)

* fix(sdks): bump sdk versions (#2142)

* fix(webhooks): count test webhooks towards usage limit (#2143)

* fix(bill): add requestId to webhook processing (#2144)

* improvement(subflow): remove all associated edges when moving a block into a subflow (#2145)

* improvement(subflow): remove all associated edges when moving a block into a subflow

* ack PR comments

* fix(polling): mark webhook failed on webhook trigger errors (#2146)

* fix(deps): declare core transient deps explicitly (#2147)

* fix(deps): declare core transient deps explicitly

* ack PR comments

---------

Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>
2025-12-01 10:15:36 -08:00
Waleed
1d08796853 v0.5.12: memory optimizations, sentry, incidentio, posthog, zendesk, pylon, intercom, mailchimp, loading optimizations (#2132)
* fix(memory-util): fixed unbounded array of gmail/outlook pollers causing high memory util, added missing db indexes/removed unused ones, auto-disable schedules/webhooks after 10 consecutive failures (#2115)

* fix(memory-util): fixed unbounded array of gmail/outlook pollers causing high memory util, added missing db indexes/removed unused ones, auto-disable schedules/webhooks after 10 consecutive failures

* ack PR comments

* ack

* improvement(teams-plan): seats increase simplification + not triggering checkout session (#2117)

* improvement(teams-plan): seats increase simplification + not triggering checkout session

* cleanup via helper

* feat(tools): added sentry, incidentio, and posthog tools (#2116)

* feat(tools): added sentry, incidentio, and posthog tools

* update docs

* fixed docs to use native fumadocs for llms.txt and copy markdown, fixed tool issues

* cleanup

* enhance error extractor, fixed posthog tools

* docs enhancements, cleanup

* added more incident io ops, remove zustand/shallow in favor of zustand/react/shallow

* fix type errors

* remove unnecessary comments

* added vllm to docs

* feat(i18n): update translations (#2120)

* feat(i18n): update translations

* fix build

---------

Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>

* improvement(workflow-execution): perf improvements to passing workflow state + decrypted env vars (#2119)

* improvement(execution): load workflow state once instead of 2-3 times

* decrypt only in get helper

* remove comments

* remove comments

* feat(models): host google gemini models (#2122)

* feat(models): host google gemini models

* remove unused primary key

* feat(i18n): update translations (#2123)

Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>

* feat(tools): added zendesk, pylon, intercom, & mailchimp (#2126)

* feat(tools): added zendesk, pylon, intercom, & mailchimp

* finish zendesk and pylon

* updated docs

* feat(i18n): update translations (#2129)

* feat(i18n): update translations

* fixed build

---------

Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>

* fix(permissions): add client-side permissions validation to prevent unauthorized actions, upgraded custom tool modal (#2130)

* fix(permissions): add client-side permissions validation to prevent unauthorized actions, upgraded custom tool modal

* fix failing test

* fix test

* cleanup

* fix(custom-tools): add composite index on custom tool names & workspace id (#2131)

---------

Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>
2025-11-28 16:08:06 -08:00
Waleed
ebcd243942 v0.5.11: stt, videogen, vllm, billing fixes, new models 2025-11-25 01:14:12 -08:00
Waleed
b7e814b721 v0.5.10: copilot upgrade, preprocessor, logs search, UI, code hygiene 2025-11-21 12:04:34 -08:00
Waleed
842ef27ed9 v0.5.9: add backwards compatibility for agent messages array 2025-11-20 11:19:42 -08:00
Vikhyath Mondreti
31c34b2ea3 v0.5.8: notifications, billing, ui changes, store loading state machine 2025-11-20 01:32:32 -08:00
Vikhyath Mondreti
8f0ef58056 v0.5.7: combobox selectors, usage indicator, workflow loading race condition, other improvements 2025-11-17 21:25:51 -08:00
92 changed files with 1623 additions and 1525 deletions

View File

@@ -188,6 +188,7 @@ DATABASE_URL="postgresql://postgres:your_password@localhost:5432/simstudio"
Then run the migrations:
```bash
cd apps/sim # Required so drizzle picks correct .env file
bunx drizzle-kit migrate --config=./drizzle.config.ts
```

View File

@@ -109,7 +109,7 @@ export default function Footer({ fullWidth = false }: FooterProps) {
{FOOTER_BLOCKS.map((block) => (
<Link
key={block}
href={`https://docs.sim.ai/blocks/${block.toLowerCase().replace(' ', '-')}`}
href={`https://docs.sim.ai/blocks/${block.toLowerCase().replaceAll(' ', '-')}`}
target='_blank'
rel='noopener noreferrer'
className='text-[14px] text-muted-foreground transition-colors hover:text-foreground'

View File

@@ -1,86 +0,0 @@
import { Button } from '@/components/emcn'
import { Alert, AlertDescription } from '@/components/ui/alert'
import type { SaveStatus } from '@/hooks/use-auto-save'
interface SaveStatusIndicatorProps {
/** Current save status */
status: SaveStatus
/** Error message to display */
errorMessage: string | null
/** Text to show while saving (e.g., "Saving schedule...") */
savingText?: string
/** Text to show while loading (e.g., "Loading schedule...") */
loadingText?: string
/** Whether to show loading indicator */
isLoading?: boolean
/** Callback when retry button is clicked */
onRetry?: () => void
/** Whether retry is disabled (e.g., during saving) */
retryDisabled?: boolean
/** Number of retry attempts made */
retryCount?: number
/** Maximum retry attempts allowed */
maxRetries?: number
}
/**
* Shared component for displaying save status indicators.
* Shows saving spinner, error alerts with retry, and loading indicators.
*/
export function SaveStatusIndicator({
status,
errorMessage,
savingText = 'Saving...',
loadingText = 'Loading...',
isLoading = false,
onRetry,
retryDisabled = false,
retryCount = 0,
maxRetries = 3,
}: SaveStatusIndicatorProps) {
const maxRetriesReached = retryCount >= maxRetries
return (
<>
{/* Saving indicator */}
{status === 'saving' && (
<div className='flex items-center gap-2 text-muted-foreground text-sm'>
<div className='h-4 w-4 animate-spin rounded-full border-[1.5px] border-current border-t-transparent' />
{savingText}
</div>
)}
{/* Error message with retry */}
{errorMessage && (
<Alert variant='destructive'>
<AlertDescription className='flex items-center justify-between'>
<span>
{errorMessage}
{maxRetriesReached && (
<span className='ml-1 text-xs opacity-75'>(Max retries reached)</span>
)}
</span>
{onRetry && (
<Button
variant='ghost'
onClick={onRetry}
disabled={retryDisabled || status === 'saving'}
className='ml-2 h-6 px-2 text-xs'
>
Retry
</Button>
)}
</AlertDescription>
</Alert>
)}
{/* Loading indicator */}
{isLoading && status !== 'saving' && (
<div className='flex items-center gap-2 text-muted-foreground text-sm'>
<div className='h-4 w-4 animate-spin rounded-full border-[1.5px] border-current border-t-transparent' />
{loadingText}
</div>
)}
</>
)
}

View File

@@ -1,9 +1,11 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useParams } from 'next/navigation'
import { Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from '@/components/emcn'
import { Trash } from '@/components/emcn/icons/trash'
import { Alert, AlertDescription } from '@/components/ui/alert'
import { cn } from '@/lib/core/utils/cn'
import { createLogger } from '@/lib/logs/console/logger'
import { parseCronToHumanReadable } from '@/lib/workflows/schedules/utils'
import { SaveStatusIndicator } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/save-status-indicator/save-status-indicator'
import { useAutoSave } from '@/hooks/use-auto-save'
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
import { useScheduleManagement } from '@/hooks/use-schedule-management'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
@@ -16,9 +18,16 @@ interface ScheduleSaveProps {
disabled?: boolean
}
type SaveStatus = 'idle' | 'saving' | 'saved' | 'error'
export function ScheduleSave({ blockId, isPreview = false, disabled = false }: ScheduleSaveProps) {
const params = useParams()
const workflowId = params.workflowId as string
const [saveStatus, setSaveStatus] = useState<SaveStatus>('idle')
const [errorMessage, setErrorMessage] = useState<string | null>(null)
const [deleteStatus, setDeleteStatus] = useState<'idle' | 'deleting'>('idle')
const [showDeleteDialog, setShowDeleteDialog] = useState(false)
const [scheduleStatus, setScheduleStatus] = useState<'active' | 'disabled' | null>(null)
const [nextRunAt, setNextRunAt] = useState<Date | null>(null)
const [lastRanAt, setLastRanAt] = useState<Date | null>(null)
const [failedCount, setFailedCount] = useState<number>(0)
@@ -27,7 +36,7 @@ export function ScheduleSave({ blockId, isPreview = false, disabled = false }: S
const { collaborativeSetSubblockValue } = useCollaborativeWorkflow()
const { scheduleId, saveConfig, isSaving } = useScheduleManagement({
const { scheduleId, saveConfig, deleteConfig, isSaving } = useScheduleManagement({
blockId,
isPreview,
})
@@ -47,8 +56,13 @@ export function ScheduleSave({ blockId, isPreview = false, disabled = false }: S
)
const scheduleTimezone = useSubBlockStore((state) => state.getValue(blockId, 'timezone'))
const validateRequiredFields = useCallback((): boolean => {
if (!scheduleType) return false
const validateRequiredFields = useCallback((): { valid: boolean; missingFields: string[] } => {
const missingFields: string[] = []
if (!scheduleType) {
missingFields.push('Frequency')
return { valid: false, missingFields }
}
switch (scheduleType) {
case 'minutes': {
@@ -59,7 +73,7 @@ export function ScheduleSave({ blockId, isPreview = false, disabled = false }: S
minutesNum < 1 ||
minutesNum > 1440
) {
return false
missingFields.push('Minutes Interval (must be 1-1440)')
}
break
}
@@ -73,39 +87,48 @@ export function ScheduleSave({ blockId, isPreview = false, disabled = false }: S
hourlyNum < 0 ||
hourlyNum > 59
) {
return false
missingFields.push('Minute (must be 0-59)')
}
break
}
case 'daily':
if (!scheduleDailyTime) return false
if (!scheduleDailyTime) {
missingFields.push('Time')
}
break
case 'weekly':
if (!scheduleWeeklyDay || !scheduleWeeklyTime) return false
if (!scheduleWeeklyDay) {
missingFields.push('Day of Week')
}
if (!scheduleWeeklyTime) {
missingFields.push('Time')
}
break
case 'monthly': {
const monthlyNum = Number(scheduleMonthlyDay)
if (
!scheduleMonthlyDay ||
Number.isNaN(monthlyNum) ||
monthlyNum < 1 ||
monthlyNum > 31 ||
!scheduleMonthlyTime
) {
return false
if (!scheduleMonthlyDay || Number.isNaN(monthlyNum) || monthlyNum < 1 || monthlyNum > 31) {
missingFields.push('Day of Month (must be 1-31)')
}
if (!scheduleMonthlyTime) {
missingFields.push('Time')
}
break
}
case 'custom':
if (!scheduleCronExpression) return false
if (!scheduleCronExpression) {
missingFields.push('Cron Expression')
}
break
}
if (!scheduleTimezone && scheduleType !== 'minutes' && scheduleType !== 'hourly') {
return false
missingFields.push('Timezone')
}
return true
return {
valid: missingFields.length === 0,
missingFields,
}
}, [
scheduleType,
scheduleMinutesInterval,
@@ -137,7 +160,7 @@ export function ScheduleSave({ blockId, isPreview = false, disabled = false }: S
const subscribedSubBlockValues = useSubBlockStore(
useCallback(
(state) => {
const values: Record<string, unknown> = {}
const values: Record<string, any> = {}
requiredSubBlockIds.forEach((subBlockId) => {
const value = state.getValue(blockId, subBlockId)
if (value !== null && value !== undefined && value !== '') {
@@ -150,57 +173,52 @@ export function ScheduleSave({ blockId, isPreview = false, disabled = false }: S
)
)
const configFingerprint = useMemo(() => {
return JSON.stringify(subscribedSubBlockValues)
}, [subscribedSubBlockValues])
const handleSaveSuccess = useCallback(
async (result: { success: boolean; nextRunAt?: string; cronExpression?: string }) => {
const scheduleIdValue = useSubBlockStore.getState().getValue(blockId, 'scheduleId')
collaborativeSetSubblockValue(blockId, 'scheduleId', scheduleIdValue)
if (result.nextRunAt) {
setNextRunAt(new Date(result.nextRunAt))
}
await fetchScheduleStatus()
if (result.cronExpression) {
setSavedCronExpression(result.cronExpression)
}
},
[blockId, collaborativeSetSubblockValue]
)
const {
saveStatus,
errorMessage,
retryCount,
maxRetries,
triggerSave,
onConfigChange,
markInitialLoadComplete,
} = useAutoSave({
disabled: isPreview || disabled,
isExternallySaving: isSaving,
validate: validateRequiredFields,
onSave: saveConfig,
onSaveSuccess: handleSaveSuccess,
loggerName: 'ScheduleSave',
})
const previousValuesRef = useRef<Record<string, any>>({})
const validationTimeoutRef = useRef<NodeJS.Timeout | null>(null)
useEffect(() => {
onConfigChange(configFingerprint)
}, [configFingerprint, onConfigChange])
if (saveStatus !== 'error') {
previousValuesRef.current = subscribedSubBlockValues
return
}
useEffect(() => {
if (!isLoadingStatus && scheduleId) {
return markInitialLoadComplete(configFingerprint)
const hasChanges = Object.keys(subscribedSubBlockValues).some(
(key) =>
previousValuesRef.current[key] !== (subscribedSubBlockValues as Record<string, any>)[key]
)
if (!hasChanges) {
return
}
if (!scheduleId && !isLoadingStatus) {
return markInitialLoadComplete(configFingerprint)
if (validationTimeoutRef.current) {
clearTimeout(validationTimeoutRef.current)
}
}, [isLoadingStatus, scheduleId, configFingerprint, markInitialLoadComplete])
validationTimeoutRef.current = setTimeout(() => {
const validation = validateRequiredFields()
if (validation.valid) {
setErrorMessage(null)
setSaveStatus('idle')
logger.debug('Error cleared after validation passed', { blockId })
} else {
setErrorMessage(`Missing required fields: ${validation.missingFields.join(', ')}`)
logger.debug('Error message updated', {
blockId,
missingFields: validation.missingFields,
})
}
previousValuesRef.current = subscribedSubBlockValues
}, 300)
return () => {
if (validationTimeoutRef.current) {
clearTimeout(validationTimeoutRef.current)
}
}
}, [blockId, subscribedSubBlockValues, saveStatus, validateRequiredFields])
const fetchScheduleStatus = useCallback(async () => {
if (!scheduleId || isPreview) return
@@ -213,6 +231,7 @@ export function ScheduleSave({ blockId, isPreview = false, disabled = false }: S
if (response.ok) {
const data = await response.json()
if (data.schedule) {
setScheduleStatus(data.schedule.status)
setNextRunAt(data.schedule.nextRunAt ? new Date(data.schedule.nextRunAt) : null)
setLastRanAt(data.schedule.lastRanAt ? new Date(data.schedule.lastRanAt) : null)
setFailedCount(data.schedule.failedCount || 0)
@@ -232,82 +251,249 @@ export function ScheduleSave({ blockId, isPreview = false, disabled = false }: S
}
}, [scheduleId, isPreview, fetchScheduleStatus])
if (isPreview) {
return null
const handleSave = async () => {
if (isPreview || disabled) return
setSaveStatus('saving')
setErrorMessage(null)
try {
const validation = validateRequiredFields()
if (!validation.valid) {
setErrorMessage(`Missing required fields: ${validation.missingFields.join(', ')}`)
setSaveStatus('error')
return
}
const result = await saveConfig()
if (!result.success) {
throw new Error('Save config returned false')
}
setSaveStatus('saved')
setErrorMessage(null)
const scheduleIdValue = useSubBlockStore.getState().getValue(blockId, 'scheduleId')
collaborativeSetSubblockValue(blockId, 'scheduleId', scheduleIdValue)
if (result.nextRunAt) {
setNextRunAt(new Date(result.nextRunAt))
setScheduleStatus('active')
}
// Fetch additional status info, then apply cron from save result to prevent stale data
await fetchScheduleStatus()
if (result.cronExpression) {
setSavedCronExpression(result.cronExpression)
}
setTimeout(() => {
setSaveStatus('idle')
}, 2000)
logger.info('Schedule configuration saved successfully', {
blockId,
hasScheduleId: !!scheduleId,
})
} catch (error: any) {
setSaveStatus('error')
setErrorMessage(error.message || 'An error occurred while saving.')
logger.error('Error saving schedule config', { error })
}
}
const hasScheduleInfo = scheduleId || isLoadingStatus || saveStatus === 'saving' || errorMessage
const handleDelete = async () => {
if (isPreview || disabled) return
if (!hasScheduleInfo) {
return null
setShowDeleteDialog(false)
setDeleteStatus('deleting')
try {
const success = await deleteConfig()
if (!success) {
throw new Error('Failed to delete schedule')
}
setScheduleStatus(null)
setNextRunAt(null)
setLastRanAt(null)
setFailedCount(0)
collaborativeSetSubblockValue(blockId, 'scheduleId', null)
logger.info('Schedule deleted successfully', { blockId })
} catch (error: any) {
setErrorMessage(error.message || 'An error occurred while deleting.')
logger.error('Error deleting schedule', { error })
} finally {
setDeleteStatus('idle')
}
}
const handleDeleteConfirm = () => {
handleDelete()
}
const handleToggleStatus = async () => {
if (!scheduleId || isPreview || disabled) return
try {
const action = scheduleStatus === 'active' ? 'disable' : 'reactivate'
const response = await fetch(`/api/schedules/${scheduleId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action }),
})
if (response.ok) {
await fetchScheduleStatus()
logger.info(`Schedule ${action}d successfully`, { scheduleId })
} else {
throw new Error(`Failed to ${action} schedule`)
}
} catch (error: any) {
setErrorMessage(
error.message ||
`An error occurred while ${scheduleStatus === 'active' ? 'disabling' : 'reactivating'} the schedule.`
)
logger.error('Error toggling schedule status', { error })
}
}
return (
<div className='space-y-1 pb-4'>
<SaveStatusIndicator
status={saveStatus}
errorMessage={errorMessage}
savingText='Saving schedule...'
loadingText='Loading schedule...'
isLoading={isLoadingStatus}
onRetry={triggerSave}
retryDisabled={isSaving}
retryCount={retryCount}
maxRetries={maxRetries}
/>
{/* Schedule status info */}
{scheduleId && !isLoadingStatus && saveStatus !== 'saving' && (
<>
{failedCount > 0 && (
<p className='text-destructive text-sm'>
{failedCount} failed run{failedCount !== 1 ? 's' : ''}
</p>
<div className='mt-2'>
<div className='flex gap-2'>
<Button
variant='default'
onClick={handleSave}
disabled={disabled || isPreview || isSaving || saveStatus === 'saving' || isLoadingStatus}
className={cn(
'h-9 flex-1 rounded-[8px] transition-all duration-200',
saveStatus === 'saved' && 'bg-green-600 hover:bg-green-700',
saveStatus === 'error' && 'bg-red-600 hover:bg-red-700'
)}
{savedCronExpression && (
<p className='text-muted-foreground text-sm'>
Runs{' '}
{parseCronToHumanReadable(
savedCronExpression,
scheduleTimezone || 'UTC'
).toLowerCase()}
</p>
>
{saveStatus === 'saving' && (
<>
<div className='mr-2 h-4 w-4 animate-spin rounded-full border-[1.5px] border-current border-t-transparent' />
Saving...
</>
)}
{saveStatus === 'saved' && 'Saved'}
{saveStatus === 'idle' && (scheduleId ? 'Update Schedule' : 'Save Schedule')}
{saveStatus === 'error' && 'Error'}
</Button>
{nextRunAt && (
<p className='text-sm'>
<span className='font-medium'>Next run:</span>{' '}
{nextRunAt.toLocaleString('en-US', {
timeZone: scheduleTimezone || 'UTC',
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
hour12: true,
})}{' '}
{scheduleTimezone || 'UTC'}
</p>
)}
{scheduleId && (
<Button
variant='default'
onClick={() => setShowDeleteDialog(true)}
disabled={disabled || isPreview || deleteStatus === 'deleting' || isSaving}
className='h-9 rounded-[8px] px-3'
>
{deleteStatus === 'deleting' ? (
<div className='h-4 w-4 animate-spin rounded-full border-[1.5px] border-current border-t-transparent' />
) : (
<Trash className='h-[14px] w-[14px]' />
)}
</Button>
)}
</div>
{lastRanAt && (
<p className='text-muted-foreground text-sm'>
<span className='font-medium'>Last ran:</span>{' '}
{lastRanAt.toLocaleString('en-US', {
timeZone: scheduleTimezone || 'UTC',
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
hour12: true,
})}{' '}
{scheduleTimezone || 'UTC'}
</p>
)}
</>
{errorMessage && (
<Alert variant='destructive' className='mt-2'>
<AlertDescription>{errorMessage}</AlertDescription>
</Alert>
)}
{scheduleId && (scheduleStatus || isLoadingStatus || nextRunAt) && (
<div className='mt-2 space-y-1'>
{isLoadingStatus ? (
<div className='flex items-center gap-2 text-muted-foreground text-sm'>
<div className='h-4 w-4 animate-spin rounded-full border-[1.5px] border-current border-t-transparent' />
Loading schedule status...
</div>
) : (
<>
{failedCount > 0 && (
<div className='flex items-center gap-2'>
<span className='text-destructive text-sm'>
{failedCount} failed run{failedCount !== 1 ? 's' : ''}
</span>
</div>
)}
{savedCronExpression && (
<p className='text-muted-foreground text-sm'>
Runs{' '}
{parseCronToHumanReadable(
savedCronExpression,
scheduleTimezone || 'UTC'
).toLowerCase()}
</p>
)}
{nextRunAt && (
<p className='text-sm'>
<span className='font-medium'>Next run:</span>{' '}
{nextRunAt.toLocaleString('en-US', {
timeZone: scheduleTimezone || 'UTC',
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
hour12: true,
})}{' '}
{scheduleTimezone || 'UTC'}
</p>
)}
{lastRanAt && (
<p className='text-muted-foreground text-sm'>
<span className='font-medium'>Last ran:</span>{' '}
{lastRanAt.toLocaleString('en-US', {
timeZone: scheduleTimezone || 'UTC',
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
hour12: true,
})}{' '}
{scheduleTimezone || 'UTC'}
</p>
)}
</>
)}
</div>
)}
<Modal open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
<ModalContent size='sm'>
<ModalHeader>Delete Schedule</ModalHeader>
<ModalBody>
<p className='text-[12px] text-[var(--text-tertiary)]'>
Are you sure you want to delete this schedule configuration? This will stop the
workflow from running automatically.{' '}
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
</p>
</ModalBody>
<ModalFooter>
<Button variant='active' onClick={() => setShowDeleteDialog(false)}>
Cancel
</Button>
<Button
variant='primary'
onClick={handleDeleteConfirm}
className='!bg-[var(--text-error)] !text-white hover:!bg-[var(--text-error)]/90'
>
Delete
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</div>
)
}

View File

@@ -91,7 +91,8 @@ export function FieldFormat({
placeholder = 'fieldName',
showType = true,
showValue = false,
valuePlaceholder = 'Enter default value',
valuePlaceholder = 'Enter test value',
config,
}: FieldFormatProps) {
const [storeValue, setStoreValue] = useSubBlockValue<Field[]>(blockId, subBlockId)
const valueInputRefs = useRef<Record<string, HTMLInputElement | HTMLTextAreaElement>>({})
@@ -453,6 +454,7 @@ export function FieldFormat({
)
}
// Export specific components for backward compatibility
export function InputFormat(props: Omit<FieldFormatProps, 'title' | 'placeholder'>) {
return <FieldFormat {...props} title='Input' placeholder='firstName' />
}

View File

@@ -1,15 +1,23 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { Button } from '@/components/emcn/components'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import {
Button,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
} from '@/components/emcn/components'
import { Trash } from '@/components/emcn/icons/trash'
import { Alert, AlertDescription } from '@/components/ui/alert'
import { cn } from '@/lib/core/utils/cn'
import { createLogger } from '@/lib/logs/console/logger'
import { SaveStatusIndicator } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/save-status-indicator/save-status-indicator'
import { ShortInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/short-input/short-input'
import { useAutoSave } from '@/hooks/use-auto-save'
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
import { getTriggerConfigAggregation } from '@/hooks/use-trigger-config-aggregation'
import { useTriggerConfigAggregation } from '@/hooks/use-trigger-config-aggregation'
import { useWebhookManagement } from '@/hooks/use-webhook-management'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import { getTrigger, isTriggerValid } from '@/triggers'
import { SYSTEM_SUBBLOCK_IDS } from '@/triggers/constants'
import { ShortInput } from '../short-input/short-input'
const logger = createLogger('TriggerSave')
@@ -21,6 +29,8 @@ interface TriggerSaveProps {
disabled?: boolean
}
type SaveStatus = 'idle' | 'saving' | 'saved' | 'error'
export function TriggerSave({
blockId,
subBlockId,
@@ -28,8 +38,11 @@ export function TriggerSave({
isPreview = false,
disabled = false,
}: TriggerSaveProps) {
const [saveStatus, setSaveStatus] = useState<SaveStatus>('idle')
const [errorMessage, setErrorMessage] = useState<string | null>(null)
const [deleteStatus, setDeleteStatus] = useState<'idle' | 'deleting'>('idle')
const [showDeleteDialog, setShowDeleteDialog] = useState(false)
const [isGeneratingTestUrl, setIsGeneratingTestUrl] = useState(false)
const [testUrlError, setTestUrlError] = useState<string | null>(null)
const storedTestUrl = useSubBlockStore((state) => state.getValue(blockId, 'testUrl'))
const storedTestUrlExpiresAt = useSubBlockStore((state) =>
@@ -57,12 +70,13 @@ export function TriggerSave({
const { collaborativeSetSubblockValue } = useCollaborativeWorkflow()
const { webhookId, saveConfig, isLoading } = useWebhookManagement({
const { webhookId, saveConfig, deleteConfig, isLoading } = useWebhookManagement({
blockId,
triggerId: effectiveTriggerId,
isPreview,
})
const triggerConfig = useSubBlockStore((state) => state.getValue(blockId, 'triggerConfig'))
const triggerCredentials = useSubBlockStore((state) =>
state.getValue(blockId, 'triggerCredentials')
)
@@ -73,26 +87,40 @@ export function TriggerSave({
const hasWebhookUrlDisplay =
triggerDef?.subBlocks.some((sb) => sb.id === 'webhookUrlDisplay') ?? false
const validateRequiredFields = useCallback((): boolean => {
if (!triggerDef) return true
const aggregatedConfig = getTriggerConfigAggregation(blockId, effectiveTriggerId)
const requiredSubBlocks = triggerDef.subBlocks.filter(
(sb) => sb.required && sb.mode === 'trigger' && !SYSTEM_SUBBLOCK_IDS.includes(sb.id)
)
for (const subBlock of requiredSubBlocks) {
if (subBlock.id === 'triggerCredentials') {
if (!triggerCredentials) return false
} else {
const value = aggregatedConfig?.[subBlock.id]
if (value === undefined || value === null || value === '') return false
const validateRequiredFields = useCallback(
(
configToCheck: Record<string, any> | null | undefined
): { valid: boolean; missingFields: string[] } => {
if (!triggerDef) {
return { valid: true, missingFields: [] }
}
}
return true
}, [triggerDef, triggerCredentials, blockId, effectiveTriggerId])
const missingFields: string[] = []
triggerDef.subBlocks
.filter(
(sb) => sb.required && sb.mode === 'trigger' && !SYSTEM_SUBBLOCK_IDS.includes(sb.id)
)
.forEach((subBlock) => {
if (subBlock.id === 'triggerCredentials') {
if (!triggerCredentials) {
missingFields.push(subBlock.title || 'Credentials')
}
} else {
const value = configToCheck?.[subBlock.id]
if (value === undefined || value === null || value === '') {
missingFields.push(subBlock.title || subBlock.id)
}
}
})
return {
valid: missingFields.length === 0,
missingFields,
}
},
[triggerDef, triggerCredentials]
)
const requiredSubBlockIds = useMemo(() => {
if (!triggerDef) return []
@@ -105,11 +133,11 @@ export function TriggerSave({
useCallback(
(state) => {
if (!triggerDef) return {}
const values: Record<string, unknown> = {}
requiredSubBlockIds.forEach((id) => {
const value = state.getValue(blockId, id)
const values: Record<string, any> = {}
requiredSubBlockIds.forEach((subBlockId) => {
const value = state.getValue(blockId, subBlockId)
if (value !== null && value !== undefined && value !== '') {
values[id] = value
values[subBlockId] = value
}
})
return values
@@ -118,9 +146,69 @@ export function TriggerSave({
)
)
const configFingerprint = useMemo(() => {
return JSON.stringify({ ...subscribedSubBlockValues, triggerCredentials })
}, [subscribedSubBlockValues, triggerCredentials])
const previousValuesRef = useRef<Record<string, any>>({})
const validationTimeoutRef = useRef<NodeJS.Timeout | null>(null)
useEffect(() => {
if (saveStatus !== 'error' || !triggerDef) {
previousValuesRef.current = subscribedSubBlockValues
return
}
const hasChanges = Object.keys(subscribedSubBlockValues).some(
(key) =>
previousValuesRef.current[key] !== (subscribedSubBlockValues as Record<string, any>)[key]
)
if (!hasChanges) {
return
}
if (validationTimeoutRef.current) {
clearTimeout(validationTimeoutRef.current)
}
validationTimeoutRef.current = setTimeout(() => {
const aggregatedConfig = useTriggerConfigAggregation(blockId, effectiveTriggerId)
if (aggregatedConfig) {
useSubBlockStore.getState().setValue(blockId, 'triggerConfig', aggregatedConfig)
}
const validation = validateRequiredFields(aggregatedConfig)
if (validation.valid) {
setErrorMessage(null)
setSaveStatus('idle')
logger.debug('Error cleared after validation passed', {
blockId,
triggerId: effectiveTriggerId,
})
} else {
setErrorMessage(`Missing required fields: ${validation.missingFields.join(', ')}`)
logger.debug('Error message updated', {
blockId,
triggerId: effectiveTriggerId,
missingFields: validation.missingFields,
})
}
previousValuesRef.current = subscribedSubBlockValues
}, 300)
return () => {
if (validationTimeoutRef.current) {
clearTimeout(validationTimeoutRef.current)
}
}
}, [
blockId,
effectiveTriggerId,
triggerDef,
subscribedSubBlockValues,
saveStatus,
validateRequiredFields,
])
useEffect(() => {
if (isTestUrlExpired && storedTestUrl) {
@@ -129,63 +217,69 @@ export function TriggerSave({
}
}, [blockId, isTestUrlExpired, storedTestUrl])
const handleSave = useCallback(async () => {
const aggregatedConfig = getTriggerConfigAggregation(blockId, effectiveTriggerId)
const handleSave = async () => {
if (isPreview || disabled) return
if (aggregatedConfig) {
useSubBlockStore.getState().setValue(blockId, 'triggerConfig', aggregatedConfig)
setSaveStatus('saving')
setErrorMessage(null)
try {
const aggregatedConfig = useTriggerConfigAggregation(blockId, effectiveTriggerId)
if (aggregatedConfig) {
useSubBlockStore.getState().setValue(blockId, 'triggerConfig', aggregatedConfig)
logger.debug('Stored aggregated trigger config', {
blockId,
triggerId: effectiveTriggerId,
aggregatedConfig,
})
}
const validation = validateRequiredFields(aggregatedConfig)
if (!validation.valid) {
setErrorMessage(`Missing required fields: ${validation.missingFields.join(', ')}`)
setSaveStatus('error')
return
}
const success = await saveConfig()
if (!success) {
throw new Error('Save config returned false')
}
setSaveStatus('saved')
setErrorMessage(null)
const savedWebhookId = useSubBlockStore.getState().getValue(blockId, 'webhookId')
const savedTriggerPath = useSubBlockStore.getState().getValue(blockId, 'triggerPath')
const savedTriggerId = useSubBlockStore.getState().getValue(blockId, 'triggerId')
const savedTriggerConfig = useSubBlockStore.getState().getValue(blockId, 'triggerConfig')
collaborativeSetSubblockValue(blockId, 'webhookId', savedWebhookId)
collaborativeSetSubblockValue(blockId, 'triggerPath', savedTriggerPath)
collaborativeSetSubblockValue(blockId, 'triggerId', savedTriggerId)
collaborativeSetSubblockValue(blockId, 'triggerConfig', savedTriggerConfig)
setTimeout(() => {
setSaveStatus('idle')
}, 2000)
logger.info('Trigger configuration saved successfully', {
blockId,
triggerId: effectiveTriggerId,
hasWebhookId: !!webhookId,
})
} catch (error: any) {
setSaveStatus('error')
setErrorMessage(error.message || 'An error occurred while saving.')
logger.error('Error saving trigger configuration', { error })
}
return saveConfig()
}, [blockId, effectiveTriggerId, saveConfig])
const handleSaveSuccess = useCallback(() => {
const savedWebhookId = useSubBlockStore.getState().getValue(blockId, 'webhookId')
const savedTriggerPath = useSubBlockStore.getState().getValue(blockId, 'triggerPath')
const savedTriggerId = useSubBlockStore.getState().getValue(blockId, 'triggerId')
const savedTriggerConfig = useSubBlockStore.getState().getValue(blockId, 'triggerConfig')
collaborativeSetSubblockValue(blockId, 'webhookId', savedWebhookId)
collaborativeSetSubblockValue(blockId, 'triggerPath', savedTriggerPath)
collaborativeSetSubblockValue(blockId, 'triggerId', savedTriggerId)
collaborativeSetSubblockValue(blockId, 'triggerConfig', savedTriggerConfig)
}, [blockId, collaborativeSetSubblockValue])
const {
saveStatus,
errorMessage,
retryCount,
maxRetries,
triggerSave,
onConfigChange,
markInitialLoadComplete,
} = useAutoSave({
disabled: isPreview || disabled || !triggerDef,
isExternallySaving: isLoading,
validate: validateRequiredFields,
onSave: handleSave,
onSaveSuccess: handleSaveSuccess,
loggerName: 'TriggerSave',
})
useEffect(() => {
onConfigChange(configFingerprint)
}, [configFingerprint, onConfigChange])
useEffect(() => {
if (!isLoading && webhookId) {
return markInitialLoadComplete(configFingerprint)
}
if (!webhookId && !isLoading) {
return markInitialLoadComplete(configFingerprint)
}
}, [isLoading, webhookId, configFingerprint, markInitialLoadComplete])
}
const generateTestUrl = async () => {
if (!webhookId) return
try {
setIsGeneratingTestUrl(true)
setTestUrlError(null)
const res = await fetch(`/api/webhooks/${webhookId}/test-url`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -202,7 +296,7 @@ export function TriggerSave({
collaborativeSetSubblockValue(blockId, 'testUrlExpiresAt', json.expiresAt)
} catch (e) {
logger.error('Failed to generate test webhook URL', { error: e })
setTestUrlError(
setErrorMessage(
e instanceof Error ? e.message : 'Failed to generate test URL. Please try again.'
)
} finally {
@@ -210,49 +304,114 @@ export function TriggerSave({
}
}
const handleDeleteClick = () => {
if (isPreview || disabled || !webhookId) return
setShowDeleteDialog(true)
}
const handleDeleteConfirm = async () => {
setShowDeleteDialog(false)
setDeleteStatus('deleting')
setErrorMessage(null)
try {
const success = await deleteConfig()
if (success) {
setDeleteStatus('idle')
setSaveStatus('idle')
setErrorMessage(null)
useSubBlockStore.getState().setValue(blockId, 'testUrl', null)
useSubBlockStore.getState().setValue(blockId, 'testUrlExpiresAt', null)
collaborativeSetSubblockValue(blockId, 'triggerPath', '')
collaborativeSetSubblockValue(blockId, 'webhookId', null)
collaborativeSetSubblockValue(blockId, 'triggerConfig', null)
collaborativeSetSubblockValue(blockId, 'testUrl', null)
collaborativeSetSubblockValue(blockId, 'testUrlExpiresAt', null)
logger.info('Trigger configuration deleted successfully', {
blockId,
triggerId: effectiveTriggerId,
})
} else {
setDeleteStatus('idle')
setErrorMessage('Failed to delete trigger configuration.')
logger.error('Failed to delete trigger configuration')
}
} catch (error: any) {
setDeleteStatus('idle')
setErrorMessage(error.message || 'An error occurred while deleting.')
logger.error('Error deleting trigger configuration', { error })
}
}
if (isPreview) {
return null
}
const isProcessing = saveStatus === 'saving' || isLoading
const displayError = errorMessage || testUrlError
const hasStatusIndicator = isLoading || saveStatus === 'saving' || displayError
const hasTestUrlSection =
webhookId && hasWebhookUrlDisplay && !isLoading && saveStatus !== 'saving'
if (!hasStatusIndicator && !hasTestUrlSection) {
return null
}
const isProcessing = saveStatus === 'saving' || deleteStatus === 'deleting' || isLoading
return (
<div id={`${blockId}-${subBlockId}`} className='space-y-2 pb-4'>
<SaveStatusIndicator
status={saveStatus}
errorMessage={displayError}
savingText='Saving trigger...'
loadingText='Loading trigger...'
isLoading={isLoading}
onRetry={testUrlError ? () => setTestUrlError(null) : triggerSave}
retryDisabled={isProcessing}
retryCount={retryCount}
maxRetries={maxRetries}
/>
<div id={`${blockId}-${subBlockId}`}>
<div className='flex gap-2'>
<Button
variant='default'
onClick={handleSave}
disabled={disabled || isProcessing}
className={cn(
'h-[32px] flex-1 rounded-[8px] px-[12px] transition-all duration-200',
saveStatus === 'saved' && 'bg-green-600 hover:bg-green-700',
saveStatus === 'error' && 'bg-red-600 hover:bg-red-700'
)}
>
{saveStatus === 'saving' && (
<>
<div className='mr-2 h-4 w-4 animate-spin rounded-full border-[1.5px] border-current border-t-transparent' />
Saving...
</>
)}
{saveStatus === 'saved' && 'Saved'}
{saveStatus === 'error' && 'Error'}
{saveStatus === 'idle' && (webhookId ? 'Update Configuration' : 'Save Configuration')}
</Button>
{/* Test webhook URL section */}
{webhookId && hasWebhookUrlDisplay && !isLoading && saveStatus !== 'saving' && (
<div className='space-y-1'>
{webhookId && (
<Button
variant='default'
onClick={handleDeleteClick}
disabled={disabled || isProcessing}
className='h-[32px] rounded-[8px] px-[12px]'
>
{deleteStatus === 'deleting' ? (
<div className='h-4 w-4 animate-spin rounded-full border-[1.5px] border-current border-t-transparent' />
) : (
<Trash className='h-[14px] w-[14px]' />
)}
</Button>
)}
</div>
{errorMessage && (
<Alert variant='destructive' className='mt-2'>
<AlertDescription>{errorMessage}</AlertDescription>
</Alert>
)}
{webhookId && hasWebhookUrlDisplay && (
<div className='mt-2 space-y-1'>
<div className='flex items-center justify-between'>
<span className='font-medium text-sm'>Test Webhook URL</span>
<Button
variant='ghost'
variant='outline'
onClick={generateTestUrl}
disabled={isGeneratingTestUrl || isProcessing}
className='h-6 px-2 py-1 text-[11px]'
className='h-[32px] rounded-[8px] px-[12px]'
>
{isGeneratingTestUrl ? (
<>
<div className='mr-1.5 h-2.5 w-2.5 animate-spin rounded-full border-[1.5px] border-current border-t-transparent' />
<div className='mr-2 h-3 w-3 animate-spin rounded-full border-[1.5px] border-current border-t-transparent' />
Generating
</>
) : testUrl ? (
@@ -291,6 +450,31 @@ export function TriggerSave({
)}
</div>
)}
<Modal open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
<ModalContent size='sm'>
<ModalHeader>Delete Trigger</ModalHeader>
<ModalBody>
<p className='text-[12px] text-[var(--text-tertiary)]'>
Are you sure you want to delete this trigger configuration? This will remove the
webhook and stop all incoming triggers.{' '}
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
</p>
</ModalBody>
<ModalFooter>
<Button variant='active' onClick={() => setShowDeleteDialog(false)}>
Cancel
</Button>
<Button
variant='primary'
onClick={handleDeleteConfirm}
className='!bg-[var(--text-error)] !text-white hover:!bg-[var(--text-error)]/90'
>
Delete
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</div>
)
}

View File

@@ -293,6 +293,7 @@ function SubBlockComponent({
setIsValidJson(isValid)
}
// Check if wand is enabled for this sub-block
const isWandEnabled = config.wandConfig?.enabled ?? false
/**
@@ -815,13 +816,6 @@ function SubBlockComponent({
}
}
// Render without wrapper for components that may return null
const noWrapper =
config.noWrapper || config.type === 'trigger-save' || config.type === 'schedule-save'
if (noWrapper) {
return renderInput()
}
return (
<div onMouseDown={handleMouseDown} className='flex flex-col gap-[10px]'>
{renderLabel(

View File

@@ -336,26 +336,6 @@ export function Editor() {
subBlockState
)
const isNoWrapper =
subBlock.noWrapper ||
subBlock.type === 'trigger-save' ||
subBlock.type === 'schedule-save'
if (isNoWrapper) {
return (
<SubBlock
key={stableKey}
blockId={currentBlockId}
config={subBlock}
isPreview={false}
subBlockValues={subBlockState}
disabled={!userPermissions.canEdit}
fieldDiffStatus={undefined}
allowExpandInPreview={false}
/>
)
}
return (
<div key={stableKey}>
<SubBlock

View File

@@ -1,8 +1,6 @@
import { useCallback, useEffect, useState } from 'react'
import { createLogger } from '@/lib/logs/console/logger'
import { parseCronToHumanReadable } from '@/lib/workflows/schedules/utils'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import type { ScheduleInfo } from '../types'
const logger = createLogger('useScheduleInfo')
@@ -34,20 +32,9 @@ export function useScheduleInfo(
blockType: string,
workflowId: string
): UseScheduleInfoReturn {
const activeWorkflowId = useWorkflowRegistry((state) => state.activeWorkflowId)
const [isLoading, setIsLoading] = useState(false)
const [scheduleInfo, setScheduleInfo] = useState<ScheduleInfo | null>(null)
const scheduleIdFromStore = useSubBlockStore(
useCallback(
(state) => {
if (!activeWorkflowId) return null
return state.workflowValues[activeWorkflowId]?.[blockId]?.scheduleId as string | null
},
[activeWorkflowId, blockId]
)
)
const fetchScheduleInfo = useCallback(
async (wfId: string) => {
if (!wfId) return
@@ -156,10 +143,22 @@ export function useScheduleInfo(
setIsLoading(false)
}
const handleScheduleUpdate = (event: CustomEvent) => {
if (event.detail?.workflowId === workflowId && event.detail?.blockId === blockId) {
logger.debug('Schedule update event received, refetching schedule info')
if (blockType === 'schedule') {
fetchScheduleInfo(workflowId)
}
}
}
window.addEventListener('schedule-updated', handleScheduleUpdate as EventListener)
return () => {
setIsLoading(false)
window.removeEventListener('schedule-updated', handleScheduleUpdate as EventListener)
}
}, [blockType, workflowId, blockId, scheduleIdFromStore, fetchScheduleInfo])
}, [blockType, workflowId, blockId, fetchScheduleInfo])
return {
scheduleInfo,

View File

@@ -44,11 +44,7 @@ export function useWebhookInfo(blockId: string, workflowId: string): UseWebhookI
useCallback(
(state) => {
const blockValues = state.workflowValues[activeWorkflowId || '']?.[blockId]
// Check for webhookId (set by trigger auto-save) or webhookProvider+webhookPath (legacy)
return !!(
blockValues?.webhookId ||
(blockValues?.webhookProvider && blockValues?.webhookPath)
)
return !!(blockValues?.webhookProvider && blockValues?.webhookPath)
},
[activeWorkflowId, blockId]
)
@@ -76,16 +72,6 @@ export function useWebhookInfo(blockId: string, workflowId: string): UseWebhookI
)
)
const webhookIdFromStore = useSubBlockStore(
useCallback(
(state) => {
if (!activeWorkflowId) return null
return state.workflowValues[activeWorkflowId]?.[blockId]?.webhookId as string | null
},
[activeWorkflowId, blockId]
)
)
const fetchWebhookStatus = useCallback(async () => {
if (!workflowId || !blockId || !isWebhookConfigured) {
setWebhookStatus({ isDisabled: false, webhookId: undefined })
@@ -128,7 +114,7 @@ export function useWebhookInfo(blockId: string, workflowId: string): UseWebhookI
useEffect(() => {
fetchWebhookStatus()
}, [fetchWebhookStatus, webhookIdFromStore])
}, [fetchWebhookStatus])
const reactivateWebhook = useCallback(
async (webhookId: string) => {

View File

@@ -550,6 +550,9 @@ export const WorkflowBlock = memo(function WorkflowBlock({
const currentStoreBlock = currentWorkflow.getBlockById(id)
const isStarterBlock = type === 'starter'
const isWebhookTriggerBlock = type === 'webhook' || type === 'generic_webhook'
/**
* Subscribe to this block's subblock values to track changes for conditional rendering
* of subblocks based on their conditions.
@@ -805,7 +808,7 @@ export const WorkflowBlock = memo(function WorkflowBlock({
updateNodeInternals(id)
}, [horizontalHandles, id, updateNodeInternals])
const showWebhookIndicator = displayTriggerMode && isWebhookConfigured
const showWebhookIndicator = (isStarterBlock || isWebhookTriggerBlock) && isWebhookConfigured
const shouldShowScheduleBadge =
type === 'schedule' && !isLoadingScheduleInfo && scheduleInfo !== null
const userPermissions = useUserPermissionsContext()
@@ -906,30 +909,28 @@ export const WorkflowBlock = memo(function WorkflowBlock({
)}
{!isEnabled && <Badge>disabled</Badge>}
{type === 'schedule' && shouldShowScheduleBadge && (
{type === 'schedule' && shouldShowScheduleBadge && scheduleInfo?.isDisabled && (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Badge
variant='outline'
className={scheduleInfo?.isDisabled ? 'cursor-pointer' : ''}
className='cursor-pointer'
style={{
borderColor: scheduleInfo?.isDisabled ? 'var(--warning)' : 'var(--success)',
color: scheduleInfo?.isDisabled ? 'var(--warning)' : 'var(--success)',
borderColor: 'var(--warning)',
color: 'var(--warning)',
}}
onClick={(e) => {
e.stopPropagation()
if (scheduleInfo?.isDisabled && scheduleInfo?.id) {
if (scheduleInfo?.id) {
reactivateSchedule(scheduleInfo.id)
}
}}
>
{scheduleInfo?.isDisabled ? 'disabled' : 'active'}
disabled
</Badge>
</Tooltip.Trigger>
<Tooltip.Content>
{scheduleInfo?.isDisabled
? 'Click to reactivate'
: scheduleInfo?.scheduleTiming || 'Schedule is active'}
<span className='text-sm'>Click to reactivate</span>
</Tooltip.Content>
</Tooltip.Root>
)}
@@ -939,27 +940,47 @@ export const WorkflowBlock = memo(function WorkflowBlock({
<Tooltip.Trigger asChild>
<Badge
variant='outline'
className={isWebhookDisabled && webhookId ? 'cursor-pointer' : ''}
style={{
borderColor: isWebhookDisabled ? 'var(--warning)' : 'var(--success)',
color: isWebhookDisabled ? 'var(--warning)' : 'var(--success)',
}}
className='bg-[var(--brand-tertiary)] text-[var(--brand-tertiary)]'
>
<div className='relative flex items-center justify-center'>
<div className='197, 94, 0.2)] absolute h-3 w-3 rounded-full bg-[rgba(34,' />
<div className='relative h-2 w-2 rounded-full bg-[var(--brand-tertiary)]' />
</div>
Webhook
</Badge>
</Tooltip.Trigger>
<Tooltip.Content side='top' className='max-w-[300px] p-4'>
{webhookProvider && webhookPath ? (
<>
<p className='text-sm'>{getProviderName(webhookProvider)} Webhook</p>
<p className='mt-1 text-muted-foreground text-xs'>Path: {webhookPath}</p>
</>
) : (
<p className='text-muted-foreground text-sm'>
This workflow is triggered by a webhook.
</p>
)}
</Tooltip.Content>
</Tooltip.Root>
)}
{isWebhookConfigured && isWebhookDisabled && webhookId && (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Badge
variant='outline'
className='cursor-pointer'
style={{ borderColor: 'var(--warning)', color: 'var(--warning)' }}
onClick={(e) => {
e.stopPropagation()
if (isWebhookDisabled && webhookId) {
reactivateWebhook(webhookId)
}
reactivateWebhook(webhookId)
}}
>
{isWebhookDisabled ? 'disabled' : 'active'}
disabled
</Badge>
</Tooltip.Trigger>
<Tooltip.Content>
{isWebhookDisabled
? 'Click to reactivate'
: webhookProvider
? `${getProviderName(webhookProvider)} Webhook`
: 'Trigger is active'}
<span className='text-sm'>Click to reactivate</span>
</Tooltip.Content>
</Tooltip.Root>
)}

View File

@@ -155,15 +155,6 @@ export const ScheduleBlock: BlockConfig = {
condition: { field: 'scheduleType', value: ['minutes', 'hourly'], not: true },
},
{
id: 'inputFormat',
title: 'Input Format',
type: 'input-format',
description:
'Define input parameters that will be available when the schedule triggers. Use Value to set default values for scheduled executions.',
mode: 'trigger',
},
{
id: 'scheduleSave',
type: 'schedule-save',

View File

@@ -215,7 +215,6 @@ export interface SubBlockConfig {
connectionDroppable?: boolean
hidden?: boolean
hideFromPreview?: boolean // Hide this subblock from the workflow block preview
noWrapper?: boolean // Render the input directly without wrapper div
requiresFeature?: string // Environment variable name that must be truthy for this subblock to be visible
description?: string
value?: (params: Record<string, any>) => string

View File

@@ -1,236 +0,0 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import { createLogger } from '@/lib/logs/console/logger'
const logger = createLogger('useAutoSave')
/** Auto-save debounce delay in milliseconds */
const AUTO_SAVE_DEBOUNCE_MS = 1500
/** Delay before enabling auto-save after initial load */
const INITIAL_LOAD_DELAY_MS = 500
/** Default maximum retry attempts */
const DEFAULT_MAX_RETRIES = 3
/** Delay before resetting save status to idle after successful save */
const SAVED_STATUS_DISPLAY_MS = 2000
export type SaveStatus = 'idle' | 'saving' | 'saved' | 'error'
export interface SaveConfigResult {
success: boolean
}
interface UseAutoSaveOptions<T extends SaveConfigResult = SaveConfigResult> {
/** Whether auto-save is disabled (e.g., in preview mode) */
disabled?: boolean
/** Whether a save operation is already in progress externally */
isExternallySaving?: boolean
/** Maximum retry attempts (default: 3) */
maxRetries?: number
/** Validate config before saving, return true if valid */
validate: () => boolean
/** Perform the save operation */
onSave: () => Promise<T>
/** Optional callback after successful save */
onSaveSuccess?: (result: T) => void
/** Optional callback after failed save */
onSaveError?: (error: Error) => void
/** Logger name for debugging */
loggerName?: string
}
interface UseAutoSaveReturn {
/** Current save status */
saveStatus: SaveStatus
/** Error message if save failed */
errorMessage: string | null
/** Current retry count */
retryCount: number
/** Maximum retries allowed */
maxRetries: number
/** Whether max retries has been reached */
maxRetriesReached: boolean
/** Trigger an immediate save attempt (for retry button) */
triggerSave: () => Promise<void>
/** Call this when config changes to trigger debounced save */
onConfigChange: (configFingerprint: string) => void
/** Call this when initial load completes to enable auto-save */
markInitialLoadComplete: (currentFingerprint: string) => void
}
/**
* Shared hook for auto-saving configuration with debouncing, retry limits, and status management.
*
* @example
* ```tsx
* const { saveStatus, errorMessage, triggerSave, onConfigChange, markInitialLoadComplete } = useAutoSave({
* disabled: isPreview,
* isExternallySaving: isSaving,
* validate: () => validateRequiredFields(),
* onSave: async () => saveConfig(),
* onSaveSuccess: (result) => { ... },
* })
*
* // When config fingerprint changes
* useEffect(() => {
* onConfigChange(configFingerprint)
* }, [configFingerprint, onConfigChange])
*
* // When initial data loads
* useEffect(() => {
* if (!isLoading && dataId) {
* markInitialLoadComplete(configFingerprint)
* }
* }, [isLoading, dataId, configFingerprint, markInitialLoadComplete])
* ```
*/
export function useAutoSave<T extends SaveConfigResult = SaveConfigResult>({
disabled = false,
isExternallySaving = false,
maxRetries = DEFAULT_MAX_RETRIES,
validate,
onSave,
onSaveSuccess,
onSaveError,
loggerName,
}: UseAutoSaveOptions<T>): UseAutoSaveReturn {
const [saveStatus, setSaveStatus] = useState<SaveStatus>('idle')
const [errorMessage, setErrorMessage] = useState<string | null>(null)
const [retryCount, setRetryCount] = useState(0)
const autoSaveTimeoutRef = useRef<NodeJS.Timeout | null>(null)
const lastSavedConfigRef = useRef<string | null>(null)
const isInitialLoadRef = useRef(true)
const currentFingerprintRef = useRef<string | null>(null)
// Clear any pending timeout on unmount
useEffect(() => {
return () => {
if (autoSaveTimeoutRef.current) {
clearTimeout(autoSaveTimeoutRef.current)
}
}
}, [])
const performSave = useCallback(async () => {
if (disabled || isExternallySaving) return
// Final validation check before saving
if (!validate()) {
setSaveStatus('idle')
return
}
setSaveStatus('saving')
setErrorMessage(null)
try {
const result = await onSave()
if (!result.success) {
throw new Error('Save operation returned unsuccessful result')
}
// Update last saved config to current
lastSavedConfigRef.current = currentFingerprintRef.current
setSaveStatus('saved')
setErrorMessage(null)
setRetryCount(0) // Reset retry count on success
if (onSaveSuccess) {
onSaveSuccess(result)
}
// Reset to idle after display duration
setTimeout(() => {
setSaveStatus('idle')
}, SAVED_STATUS_DISPLAY_MS)
if (loggerName) {
logger.info(`${loggerName}: Auto-save completed successfully`)
}
} catch (error: unknown) {
setSaveStatus('error')
const message = error instanceof Error ? error.message : 'An error occurred while saving.'
setErrorMessage(message)
setRetryCount((prev) => prev + 1)
if (onSaveError && error instanceof Error) {
onSaveError(error)
}
if (loggerName) {
logger.error(`${loggerName}: Auto-save failed`, { error })
}
}
}, [disabled, isExternallySaving, validate, onSave, onSaveSuccess, onSaveError, loggerName])
const onConfigChange = useCallback(
(configFingerprint: string) => {
currentFingerprintRef.current = configFingerprint
if (disabled) return
// Clear any existing timeout
if (autoSaveTimeoutRef.current) {
clearTimeout(autoSaveTimeoutRef.current)
}
// Skip if initial load hasn't completed
if (isInitialLoadRef.current) return
// Skip if already saving
if (saveStatus === 'saving' || isExternallySaving) return
// Clear error if validation now passes
if (saveStatus === 'error' && validate()) {
setErrorMessage(null)
setSaveStatus('idle')
setRetryCount(0) // Reset retry count when config changes
}
// Skip if config hasn't changed
if (configFingerprint === lastSavedConfigRef.current) return
// Skip if validation fails
if (!validate()) return
// Schedule debounced save
autoSaveTimeoutRef.current = setTimeout(() => {
if (loggerName) {
logger.debug(`${loggerName}: Triggering debounced auto-save`)
}
performSave()
}, AUTO_SAVE_DEBOUNCE_MS)
},
[disabled, saveStatus, isExternallySaving, validate, performSave, loggerName]
)
const markInitialLoadComplete = useCallback((currentFingerprint: string) => {
// Delay before enabling auto-save to prevent immediate trigger
const timer = setTimeout(() => {
isInitialLoadRef.current = false
lastSavedConfigRef.current = currentFingerprint
currentFingerprintRef.current = currentFingerprint
}, INITIAL_LOAD_DELAY_MS)
return () => clearTimeout(timer)
}, [])
const triggerSave = useCallback(async () => {
// Allow retry even if max retries reached (manual trigger)
await performSave()
}, [performSave])
return {
saveStatus,
errorMessage,
retryCount,
maxRetries,
maxRetriesReached: retryCount >= maxRetries,
triggerSave,
onConfigChange,
markInitialLoadComplete,
}
}

View File

@@ -23,10 +23,15 @@ interface ScheduleManagementState {
isLoading: boolean
isSaving: boolean
saveConfig: () => Promise<SaveConfigResult>
deleteConfig: () => Promise<boolean>
}
/**
* Hook to manage schedule lifecycle for schedule blocks
* Handles:
* - Loading existing schedules from the API
* - Saving schedule configurations
* - Deleting schedule configurations
*/
export function useScheduleManagement({
blockId,
@@ -40,6 +45,8 @@ export function useScheduleManagement({
)
const isLoading = useSubBlockStore((state) => state.loadingSchedules.has(blockId))
const isChecked = useSubBlockStore((state) => state.checkedSchedules.has(blockId))
const [isSaving, setIsSaving] = useState(false)
useEffect(() => {
@@ -176,10 +183,49 @@ export function useScheduleManagement({
}
}
const deleteConfig = async (): Promise<boolean> => {
if (isPreview || !scheduleId) {
return false
}
try {
setIsSaving(true)
const response = await fetch(`/api/schedules/${scheduleId}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
workspaceId: params.workspaceId as string,
}),
})
if (!response.ok) {
logger.error('Failed to delete schedule')
return false
}
useSubBlockStore.getState().setValue(blockId, 'scheduleId', null)
useSubBlockStore.setState((state) => {
const newSet = new Set(state.checkedSchedules)
newSet.delete(blockId)
return { checkedSchedules: newSet }
})
logger.info('Schedule deleted successfully')
return true
} catch (error) {
logger.error('Error deleting schedule:', error)
return false
} finally {
setIsSaving(false)
}
}
return {
scheduleId,
isLoading,
isSaving,
saveConfig,
deleteConfig,
}
}

View File

@@ -3,7 +3,7 @@ import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import { getTrigger, isTriggerValid } from '@/triggers'
import { SYSTEM_SUBBLOCK_IDS } from '@/triggers/constants'
const logger = createLogger('getTriggerConfigAggregation')
const logger = createLogger('useTriggerConfigAggregation')
/**
* Maps old trigger config field names to new subblock IDs for backward compatibility.
@@ -34,7 +34,7 @@ function mapOldFieldNameToNewSubBlockId(oldFieldName: string): string {
* @returns The aggregated config object, or null if no valid config
*/
export function getTriggerConfigAggregation(
export function useTriggerConfigAggregation(
blockId: string,
triggerId: string | undefined
): Record<string, any> | null {

View File

@@ -16,18 +16,14 @@ interface UseWebhookManagementProps {
isPreview?: boolean
}
interface SaveConfigResult {
success: boolean
webhookId?: string
}
interface WebhookManagementState {
webhookUrl: string
webhookPath: string
webhookId: string | null
isLoading: boolean
isSaving: boolean
saveConfig: () => Promise<SaveConfigResult>
saveConfig: () => Promise<boolean>
deleteConfig: () => Promise<boolean>
}
/**
@@ -85,6 +81,10 @@ function resolveEffectiveTriggerId(
/**
* Hook to manage webhook lifecycle for trigger blocks
* Handles:
* - Pre-generating webhook URLs based on blockId (without creating webhook)
* - Loading existing webhooks from the API
* - Saving and deleting webhook configurations
*/
export function useWebhookManagement({
blockId,
@@ -103,6 +103,7 @@ export function useWebhookManagement({
useCallback((state) => state.getValue(blockId, 'triggerPath') as string | null, [blockId])
)
const isLoading = useSubBlockStore((state) => state.loadingWebhooks.has(blockId))
const isChecked = useSubBlockStore((state) => state.checkedWebhooks.has(blockId))
const webhookUrl = useMemo(() => {
if (!webhookPath) {
@@ -210,9 +211,9 @@ export function useWebhookManagement({
const createWebhook = async (
effectiveTriggerId: string | undefined,
selectedCredentialId: string | null
): Promise<SaveConfigResult> => {
): Promise<boolean> => {
if (!triggerDef || !effectiveTriggerId) {
return { success: false }
return false
}
const triggerConfig = useSubBlockStore.getState().getValue(blockId, 'triggerConfig')
@@ -265,14 +266,14 @@ export function useWebhookManagement({
blockId,
})
return { success: true, webhookId: savedWebhookId }
return true
}
const updateWebhook = async (
webhookIdToUpdate: string,
effectiveTriggerId: string | undefined,
selectedCredentialId: string | null
): Promise<SaveConfigResult> => {
): Promise<boolean> => {
const triggerConfig = useSubBlockStore.getState().getValue(blockId, 'triggerConfig')
const response = await fetch(`/api/webhooks/${webhookIdToUpdate}`, {
@@ -309,12 +310,12 @@ export function useWebhookManagement({
}
logger.info('Trigger config saved successfully', { blockId, webhookId: webhookIdToUpdate })
return { success: true, webhookId: webhookIdToUpdate }
return true
}
const saveConfig = async (): Promise<SaveConfigResult> => {
const saveConfig = async (): Promise<boolean> => {
if (isPreview || !triggerDef) {
return { success: false }
return false
}
const effectiveTriggerId = resolveEffectiveTriggerId(blockId, triggerId)
@@ -338,6 +339,41 @@ export function useWebhookManagement({
}
}
const deleteConfig = async (): Promise<boolean> => {
if (isPreview || !webhookId) {
return false
}
try {
setIsSaving(true)
const response = await fetch(`/api/webhooks/${webhookId}`, {
method: 'DELETE',
})
if (!response.ok) {
logger.error('Failed to delete webhook')
return false
}
useSubBlockStore.getState().setValue(blockId, 'triggerPath', '')
useSubBlockStore.getState().setValue(blockId, 'webhookId', null)
useSubBlockStore.setState((state) => {
const newSet = new Set(state.checkedWebhooks)
newSet.delete(blockId)
return { checkedWebhooks: newSet }
})
logger.info('Webhook deleted successfully')
return true
} catch (error) {
logger.error('Error deleting webhook:', error)
return false
} finally {
setIsSaving(false)
}
}
return {
webhookUrl,
webhookPath: webhookPath || blockId,
@@ -345,5 +381,6 @@ export function useWebhookManagement({
isLoading,
isSaving,
saveConfig,
deleteConfig,
}
}

View File

@@ -402,7 +402,8 @@ export class Serializer {
// Second pass: filter by mode and conditions
Object.entries(block.subBlocks).forEach(([id, subBlock]) => {
const matchingConfigs = blockConfig.subBlocks.filter((config) => config.id === id)
// Find the corresponding subblock config to check its mode and condition
const subBlockConfig = blockConfig.subBlocks.find((config) => config.id === id)
// Include field if it matches current mode OR if it's the starter inputFormat with values
const hasStarterInputFormatValues =
@@ -416,17 +417,13 @@ export class Serializer {
const isLegacyAgentField =
isAgentBlock && ['systemPrompt', 'userPrompt', 'memories'].includes(id)
const anyConditionMet =
matchingConfigs.length === 0
? true
: matchingConfigs.some(
(config) =>
shouldIncludeField(config, isAdvancedMode) &&
evaluateCondition(config.condition, allValues)
)
// Check if field's condition is met (conditionally-hidden fields should be excluded)
const conditionMet = subBlockConfig
? evaluateCondition(subBlockConfig.condition, allValues)
: true
if (
(matchingConfigs.length > 0 && anyConditionMet) ||
(subBlockConfig && shouldIncludeField(subBlockConfig, isAdvancedMode) && conditionMet) ||
hasStarterInputFormatValues ||
isLegacyAgentField
) {
@@ -543,26 +540,26 @@ export class Serializer {
// Iterate through the tool's parameters, not the block's subBlocks
Object.entries(currentTool.params || {}).forEach(([paramId, paramConfig]) => {
if (paramConfig.required && paramConfig.visibility === 'user-only') {
const matchingConfigs = blockConfig.subBlocks?.filter((sb: any) => sb.id === paramId) || []
const subBlockConfig = blockConfig.subBlocks?.find((sb: any) => sb.id === paramId)
let shouldValidateParam = true
if (matchingConfigs.length > 0) {
if (subBlockConfig) {
const isAdvancedMode = block.advancedMode ?? false
const includedByMode = shouldIncludeField(subBlockConfig, isAdvancedMode)
shouldValidateParam = matchingConfigs.some((subBlockConfig: any) => {
const includedByMode = shouldIncludeField(subBlockConfig, isAdvancedMode)
// Check visibility condition
const includedByCondition = evaluateCondition(subBlockConfig.condition, params)
const includedByCondition = evaluateCondition(subBlockConfig.condition, params)
// Check if field is required based on its required condition (if it's a condition object)
const isRequired = (() => {
if (!subBlockConfig.required) return false
if (typeof subBlockConfig.required === 'boolean') return subBlockConfig.required
// If required is a condition object, evaluate it
return evaluateCondition(subBlockConfig.required, params)
})()
const isRequired = (() => {
if (!subBlockConfig.required) return false
if (typeof subBlockConfig.required === 'boolean') return subBlockConfig.required
return evaluateCondition(subBlockConfig.required, params)
})()
return includedByMode && includedByCondition && isRequired
})
shouldValidateParam = includedByMode && includedByCondition && isRequired
}
if (!shouldValidateParam) {
@@ -571,12 +568,7 @@ export class Serializer {
const fieldValue = params[paramId]
if (fieldValue === undefined || fieldValue === null || fieldValue === '') {
const activeConfig = matchingConfigs.find(
(config: any) =>
shouldIncludeField(config, block.advancedMode ?? false) &&
evaluateCondition(config.condition, params)
)
const displayName = activeConfig?.title || paramId
const displayName = subBlockConfig?.title || paramId
missingFields.push(displayName)
}
}
@@ -604,6 +596,8 @@ export class Serializer {
const accessibleIds = new Set<string>(ancestorIds)
accessibleIds.add(blockId)
// Only add starter block if it's actually upstream (already in ancestorIds)
// Don't add it just because it exists on the canvas
if (starterBlock && ancestorIds.includes(starterBlock.id)) {
accessibleIds.add(starterBlock.id)
}

View File

@@ -47,14 +47,6 @@ export const airtableWebhookTrigger: TriggerConfig = {
defaultValue: false,
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'airtable_webhook',
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -75,6 +67,14 @@ export const airtableWebhookTrigger: TriggerConfig = {
.join(''),
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'airtable_webhook',
},
],
outputs: {

View File

@@ -38,18 +38,6 @@ export const calendlyInviteeCanceledTrigger: TriggerConfig = {
value: 'calendly_invitee_canceled',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'calendly_invitee_canceled',
condition: {
field: 'selectedTriggerId',
value: 'calendly_invitee_canceled',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -59,7 +47,7 @@ export const calendlyInviteeCanceledTrigger: TriggerConfig = {
'<strong>Note:</strong> This trigger requires a paid Calendly subscription (Professional, Teams, or Enterprise plan).',
'Get your Personal Access Token from <strong>Settings > Integrations > API & Webhooks</strong> in your Calendly account.',
'Use the "Get Current User" operation in a Calendly block to retrieve your Organization URI.',
'The webhook will be automatically created in Calendly once you complete the configuration above.',
'The webhook will be automatically created in Calendly when you save this trigger.',
'This webhook triggers when an invitee cancels an event. The payload includes cancellation details and reason.',
]
.map(
@@ -73,6 +61,18 @@ export const calendlyInviteeCanceledTrigger: TriggerConfig = {
value: 'calendly_invitee_canceled',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'calendly_invitee_canceled',
condition: {
field: 'selectedTriggerId',
value: 'calendly_invitee_canceled',
},
},
],
outputs: buildInviteeOutputs(),

View File

@@ -47,18 +47,6 @@ export const calendlyInviteeCreatedTrigger: TriggerConfig = {
value: 'calendly_invitee_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'calendly_invitee_created',
condition: {
field: 'selectedTriggerId',
value: 'calendly_invitee_created',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -68,7 +56,7 @@ export const calendlyInviteeCreatedTrigger: TriggerConfig = {
'<strong>Note:</strong> This trigger requires a paid Calendly subscription (Professional, Teams, or Enterprise plan).',
'Get your Personal Access Token from <strong>Settings > Integrations > API & Webhooks</strong> in your Calendly account.',
'Use the "Get Current User" operation in a Calendly block to retrieve your Organization URI.',
'The webhook will be automatically created in Calendly once you complete the configuration above.',
'The webhook will be automatically created in Calendly when you save this trigger.',
'This webhook triggers when an invitee schedules a new event. Rescheduling triggers both cancellation and creation events.',
]
.map(
@@ -82,6 +70,18 @@ export const calendlyInviteeCreatedTrigger: TriggerConfig = {
value: 'calendly_invitee_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'calendly_invitee_created',
condition: {
field: 'selectedTriggerId',
value: 'calendly_invitee_created',
},
},
],
outputs: buildInviteeOutputs(),

View File

@@ -38,18 +38,6 @@ export const calendlyRoutingFormSubmittedTrigger: TriggerConfig = {
value: 'calendly_routing_form_submitted',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'calendly_routing_form_submitted',
condition: {
field: 'selectedTriggerId',
value: 'calendly_routing_form_submitted',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -59,7 +47,7 @@ export const calendlyRoutingFormSubmittedTrigger: TriggerConfig = {
'<strong>Note:</strong> This trigger requires a paid Calendly subscription (Professional, Teams, or Enterprise plan).',
'Get your Personal Access Token from <strong>Settings > Integrations > API & Webhooks</strong> in your Calendly account.',
'Use the "Get Current User" operation in a Calendly block to retrieve your Organization URI.',
'The webhook will be automatically created in Calendly once you complete the configuration above.',
'The webhook will be automatically created in Calendly when you save this trigger.',
'This webhook triggers when someone submits a routing form, regardless of whether they book an event.',
]
.map(
@@ -73,6 +61,18 @@ export const calendlyRoutingFormSubmittedTrigger: TriggerConfig = {
value: 'calendly_routing_form_submitted',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'calendly_routing_form_submitted',
condition: {
field: 'selectedTriggerId',
value: 'calendly_routing_form_submitted',
},
},
],
outputs: buildRoutingFormOutputs(),

View File

@@ -37,18 +37,6 @@ export const calendlyWebhookTrigger: TriggerConfig = {
value: 'calendly_webhook',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'calendly_webhook',
condition: {
field: 'selectedTriggerId',
value: 'calendly_webhook',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -58,7 +46,7 @@ export const calendlyWebhookTrigger: TriggerConfig = {
'<strong>Note:</strong> This trigger requires a paid Calendly subscription (Professional, Teams, or Enterprise plan).',
'Get your Personal Access Token from <strong>Settings > Integrations > API & Webhooks</strong> in your Calendly account.',
'Use the "Get Current User" operation in a Calendly block to retrieve your Organization URI.',
'The webhook will be automatically created in Calendly once you complete the configuration above.',
'The webhook will be automatically created in Calendly when you save this trigger.',
'This webhook subscribes to all Calendly events (invitee created, invitee canceled, and routing form submitted). Use the <code>event</code> field in the payload to determine the event type.',
]
.map(
@@ -72,6 +60,18 @@ export const calendlyWebhookTrigger: TriggerConfig = {
value: 'calendly_webhook',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'calendly_webhook',
condition: {
field: 'selectedTriggerId',
value: 'calendly_webhook',
},
},
],
outputs: {

View File

@@ -56,14 +56,6 @@ export const genericWebhookTrigger: TriggerConfig = {
'Define the expected JSON input schema for this webhook (optional). Use type "files" for file uploads.',
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'generic_webhook',
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -84,6 +76,14 @@ export const genericWebhookTrigger: TriggerConfig = {
.join(''),
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'generic_webhook',
},
],
outputs: {},

View File

@@ -75,18 +75,6 @@ export const githubIssueClosedTrigger: TriggerConfig = {
value: 'github_issue_closed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_issue_closed',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_closed',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -113,6 +101,18 @@ export const githubIssueClosedTrigger: TriggerConfig = {
value: 'github_issue_closed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_issue_closed',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_closed',
},
},
],
outputs: {

View File

@@ -75,18 +75,6 @@ export const githubIssueCommentTrigger: TriggerConfig = {
value: 'github_issue_comment',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_issue_comment',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_comment',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -114,6 +102,18 @@ export const githubIssueCommentTrigger: TriggerConfig = {
value: 'github_issue_comment',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_issue_comment',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_comment',
},
},
],
outputs: {

View File

@@ -96,18 +96,6 @@ export const githubIssueOpenedTrigger: TriggerConfig = {
value: 'github_issue_opened',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_issue_opened',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_opened',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -134,6 +122,18 @@ export const githubIssueOpenedTrigger: TriggerConfig = {
value: 'github_issue_opened',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_issue_opened',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_opened',
},
},
],
outputs: {

View File

@@ -76,18 +76,6 @@ export const githubPRClosedTrigger: TriggerConfig = {
value: 'github_pr_closed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_pr_closed',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_closed',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -114,6 +102,18 @@ export const githubPRClosedTrigger: TriggerConfig = {
value: 'github_pr_closed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_pr_closed',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_closed',
},
},
],
outputs: {

View File

@@ -75,18 +75,6 @@ export const githubPRCommentTrigger: TriggerConfig = {
value: 'github_pr_comment',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_pr_comment',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_comment',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -114,6 +102,18 @@ export const githubPRCommentTrigger: TriggerConfig = {
value: 'github_pr_comment',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_pr_comment',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_comment',
},
},
],
outputs: {

View File

@@ -75,18 +75,6 @@ export const githubPRMergedTrigger: TriggerConfig = {
value: 'github_pr_merged',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_pr_merged',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_merged',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -113,6 +101,18 @@ export const githubPRMergedTrigger: TriggerConfig = {
value: 'github_pr_merged',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_pr_merged',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_merged',
},
},
],
outputs: {

View File

@@ -75,18 +75,6 @@ export const githubPROpenedTrigger: TriggerConfig = {
value: 'github_pr_opened',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_pr_opened',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_opened',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -113,6 +101,18 @@ export const githubPROpenedTrigger: TriggerConfig = {
value: 'github_pr_opened',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_pr_opened',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_opened',
},
},
],
outputs: {

View File

@@ -76,18 +76,6 @@ export const githubPRReviewedTrigger: TriggerConfig = {
value: 'github_pr_reviewed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_pr_reviewed',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_reviewed',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -114,6 +102,18 @@ export const githubPRReviewedTrigger: TriggerConfig = {
value: 'github_pr_reviewed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_pr_reviewed',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_reviewed',
},
},
],
outputs: {

View File

@@ -75,18 +75,6 @@ export const githubPushTrigger: TriggerConfig = {
value: 'github_push',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_push',
condition: {
field: 'selectedTriggerId',
value: 'github_push',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -113,6 +101,18 @@ export const githubPushTrigger: TriggerConfig = {
value: 'github_push',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_push',
condition: {
field: 'selectedTriggerId',
value: 'github_push',
},
},
],
outputs: {

View File

@@ -75,18 +75,6 @@ export const githubReleasePublishedTrigger: TriggerConfig = {
value: 'github_release_published',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_release_published',
condition: {
field: 'selectedTriggerId',
value: 'github_release_published',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -113,6 +101,18 @@ export const githubReleasePublishedTrigger: TriggerConfig = {
value: 'github_release_published',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_release_published',
condition: {
field: 'selectedTriggerId',
value: 'github_release_published',
},
},
],
outputs: {

View File

@@ -72,18 +72,6 @@ export const githubWebhookTrigger: TriggerConfig = {
value: 'github_webhook',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_webhook',
condition: {
field: 'selectedTriggerId',
value: 'github_webhook',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -110,6 +98,18 @@ export const githubWebhookTrigger: TriggerConfig = {
value: 'github_webhook',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_webhook',
condition: {
field: 'selectedTriggerId',
value: 'github_webhook',
},
},
],
outputs: {

View File

@@ -76,18 +76,6 @@ export const githubWorkflowRunTrigger: TriggerConfig = {
value: 'github_workflow_run',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_workflow_run',
condition: {
field: 'selectedTriggerId',
value: 'github_workflow_run',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -114,6 +102,18 @@ export const githubWorkflowRunTrigger: TriggerConfig = {
value: 'github_workflow_run',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'github_workflow_run',
condition: {
field: 'selectedTriggerId',
value: 'github_workflow_run',
},
},
],
outputs: {

View File

@@ -104,14 +104,6 @@ export const gmailPollingTrigger: TriggerConfig = {
required: false,
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'gmail_poller',
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -129,6 +121,14 @@ export const gmailPollingTrigger: TriggerConfig = {
.join(''),
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'gmail_poller',
},
],
outputs: {

View File

@@ -59,14 +59,6 @@ export const googleFormsWebhookTrigger: TriggerConfig = {
defaultValue: true,
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'google_forms_webhook',
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -94,12 +86,12 @@ export const googleFormsWebhookTrigger: TriggerConfig = {
const script = `function onFormSubmit(e) {
const WEBHOOK_URL = "{{WEBHOOK_URL}}";
const SHARED_SECRET = "{{SHARED_SECRET}}";
try {
const form = FormApp.getActiveForm();
const formResponse = e.response;
const itemResponses = formResponse.getItemResponses();
// Build answers object
const answers = {};
for (var i = 0; i < itemResponses.length; i++) {
@@ -108,7 +100,7 @@ export const googleFormsWebhookTrigger: TriggerConfig = {
const answer = itemResponse.getResponse();
answers[question] = answer;
}
// Build payload
const payload = {
provider: "google_forms",
@@ -118,7 +110,7 @@ export const googleFormsWebhookTrigger: TriggerConfig = {
lastSubmittedTime: formResponse.getTimestamp().toISOString(),
answers: answers
};
// Send to webhook
const options = {
method: "post",
@@ -129,9 +121,9 @@ export const googleFormsWebhookTrigger: TriggerConfig = {
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(WEBHOOK_URL, options);
if (response.getResponseCode() !== 200) {
Logger.log("Webhook failed: " + response.getContentText());
} else {
@@ -153,6 +145,14 @@ export const googleFormsWebhookTrigger: TriggerConfig = {
description: 'Copy this code and paste it into your Google Forms Apps Script editor',
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'google_forms_webhook',
},
],
outputs: {

View File

@@ -93,17 +93,6 @@ export const hubspotCompanyCreatedTrigger: TriggerConfig = {
value: 'hubspot_company_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_company_created',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_company_created',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -167,6 +156,17 @@ export const hubspotCompanyCreatedTrigger: TriggerConfig = {
value: 'hubspot_company_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_company_created',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_company_created',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',

View File

@@ -93,17 +93,6 @@ export const hubspotCompanyDeletedTrigger: TriggerConfig = {
value: 'hubspot_company_deleted',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_company_deleted',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_company_deleted',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -167,6 +156,17 @@ export const hubspotCompanyDeletedTrigger: TriggerConfig = {
value: 'hubspot_company_deleted',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_company_deleted',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_company_deleted',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',

View File

@@ -107,17 +107,6 @@ export const hubspotCompanyPropertyChangedTrigger: TriggerConfig = {
value: 'hubspot_company_property_changed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_company_property_changed',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_company_property_changed',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -181,6 +170,17 @@ export const hubspotCompanyPropertyChangedTrigger: TriggerConfig = {
value: 'hubspot_company_property_changed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_company_property_changed',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_company_property_changed',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',

View File

@@ -93,17 +93,6 @@ export const hubspotContactCreatedTrigger: TriggerConfig = {
value: 'hubspot_contact_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_contact_created',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_contact_created',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -167,6 +156,17 @@ export const hubspotContactCreatedTrigger: TriggerConfig = {
value: 'hubspot_contact_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_contact_created',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_contact_created',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',

View File

@@ -93,17 +93,6 @@ export const hubspotContactDeletedTrigger: TriggerConfig = {
value: 'hubspot_contact_deleted',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_contact_deleted',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_contact_deleted',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -167,6 +156,17 @@ export const hubspotContactDeletedTrigger: TriggerConfig = {
value: 'hubspot_contact_deleted',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_contact_deleted',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_contact_deleted',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',

View File

@@ -94,17 +94,6 @@ export const hubspotContactPrivacyDeletedTrigger: TriggerConfig = {
value: 'hubspot_contact_privacy_deleted',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_contact_privacy_deleted',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_contact_privacy_deleted',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -168,6 +157,17 @@ export const hubspotContactPrivacyDeletedTrigger: TriggerConfig = {
value: 'hubspot_contact_privacy_deleted',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_contact_privacy_deleted',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_contact_privacy_deleted',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',

View File

@@ -107,17 +107,6 @@ export const hubspotContactPropertyChangedTrigger: TriggerConfig = {
value: 'hubspot_contact_property_changed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_contact_property_changed',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_contact_property_changed',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -181,6 +170,17 @@ export const hubspotContactPropertyChangedTrigger: TriggerConfig = {
value: 'hubspot_contact_property_changed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_contact_property_changed',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_contact_property_changed',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',

View File

@@ -93,17 +93,6 @@ export const hubspotConversationCreationTrigger: TriggerConfig = {
value: 'hubspot_conversation_creation',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_conversation_creation',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_conversation_creation',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -167,6 +156,17 @@ export const hubspotConversationCreationTrigger: TriggerConfig = {
value: 'hubspot_conversation_creation',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_conversation_creation',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_conversation_creation',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',

View File

@@ -93,17 +93,6 @@ export const hubspotConversationDeletionTrigger: TriggerConfig = {
value: 'hubspot_conversation_deletion',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_conversation_deletion',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_conversation_deletion',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -167,6 +156,17 @@ export const hubspotConversationDeletionTrigger: TriggerConfig = {
value: 'hubspot_conversation_deletion',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_conversation_deletion',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_conversation_deletion',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',

View File

@@ -93,17 +93,6 @@ export const hubspotConversationNewMessageTrigger: TriggerConfig = {
value: 'hubspot_conversation_new_message',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_conversation_new_message',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_conversation_new_message',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -167,6 +156,17 @@ export const hubspotConversationNewMessageTrigger: TriggerConfig = {
value: 'hubspot_conversation_new_message',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_conversation_new_message',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_conversation_new_message',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',

View File

@@ -94,17 +94,6 @@ export const hubspotConversationPrivacyDeletionTrigger: TriggerConfig = {
value: 'hubspot_conversation_privacy_deletion',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_conversation_privacy_deletion',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_conversation_privacy_deletion',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -168,6 +157,17 @@ export const hubspotConversationPrivacyDeletionTrigger: TriggerConfig = {
value: 'hubspot_conversation_privacy_deletion',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_conversation_privacy_deletion',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_conversation_privacy_deletion',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',

View File

@@ -107,17 +107,6 @@ export const hubspotConversationPropertyChangedTrigger: TriggerConfig = {
value: 'hubspot_conversation_property_changed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_conversation_property_changed',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_conversation_property_changed',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -181,6 +170,17 @@ export const hubspotConversationPropertyChangedTrigger: TriggerConfig = {
value: 'hubspot_conversation_property_changed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_conversation_property_changed',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_conversation_property_changed',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',

View File

@@ -93,17 +93,6 @@ export const hubspotDealCreatedTrigger: TriggerConfig = {
value: 'hubspot_deal_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_deal_created',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_deal_created',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -167,6 +156,17 @@ export const hubspotDealCreatedTrigger: TriggerConfig = {
value: 'hubspot_deal_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_deal_created',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_deal_created',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',

View File

@@ -93,17 +93,6 @@ export const hubspotDealDeletedTrigger: TriggerConfig = {
value: 'hubspot_deal_deleted',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_deal_deleted',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_deal_deleted',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -167,6 +156,17 @@ export const hubspotDealDeletedTrigger: TriggerConfig = {
value: 'hubspot_deal_deleted',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_deal_deleted',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_deal_deleted',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',

View File

@@ -107,17 +107,6 @@ export const hubspotDealPropertyChangedTrigger: TriggerConfig = {
value: 'hubspot_deal_property_changed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_deal_property_changed',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_deal_property_changed',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -181,6 +170,17 @@ export const hubspotDealPropertyChangedTrigger: TriggerConfig = {
value: 'hubspot_deal_property_changed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_deal_property_changed',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_deal_property_changed',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',

View File

@@ -93,17 +93,6 @@ export const hubspotTicketCreatedTrigger: TriggerConfig = {
value: 'hubspot_ticket_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_ticket_created',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_ticket_created',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -167,6 +156,17 @@ export const hubspotTicketCreatedTrigger: TriggerConfig = {
value: 'hubspot_ticket_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_ticket_created',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_ticket_created',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',

View File

@@ -93,17 +93,6 @@ export const hubspotTicketDeletedTrigger: TriggerConfig = {
value: 'hubspot_ticket_deleted',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_ticket_deleted',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_ticket_deleted',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -167,6 +156,17 @@ export const hubspotTicketDeletedTrigger: TriggerConfig = {
value: 'hubspot_ticket_deleted',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_ticket_deleted',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_ticket_deleted',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',

View File

@@ -107,17 +107,6 @@ export const hubspotTicketPropertyChangedTrigger: TriggerConfig = {
value: 'hubspot_ticket_property_changed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_ticket_property_changed',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_ticket_property_changed',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -181,6 +170,17 @@ export const hubspotTicketPropertyChangedTrigger: TriggerConfig = {
value: 'hubspot_ticket_property_changed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'hubspot_ticket_property_changed',
condition: {
field: 'selectedTriggerId',
value: 'hubspot_ticket_property_changed',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',

View File

@@ -82,7 +82,7 @@ export function hubspotSetupInstructions(eventType: string, additionalNotes?: st
'<strong>Step 3: Configure OAuth Settings</strong><br/>After creating your app via CLI, configure it to add the OAuth Redirect URL: <code>https://www.sim.ai/api/auth/oauth2/callback/hubspot</code>. Then retrieve your <strong>Client ID</strong> and <strong>Client Secret</strong> from your app configuration and enter them in the fields above.',
"<strong>Step 4: Get App ID and Developer API Key</strong><br/>In your HubSpot developer account, find your <strong>App ID</strong> (shown below your app name) and your <strong>Developer API Key</strong> (in app settings). You'll need both for the next steps.",
'<strong>Step 5: Set Required Scopes</strong><br/>Configure your app to include the required OAuth scope: <code>crm.objects.contacts.read</code>',
'<strong>Step 6: Save Configuration in Sim</strong><br/>Your unique webhook URL will be generated automatically once you complete the configuration above.',
'<strong>Step 6: Save Configuration in Sim</strong><br/>Click the <strong>"Save Configuration"</strong> button below. This will generate your unique webhook URL.',
'<strong>Step 7: Configure Webhook in HubSpot via API</strong><br/>After saving above, copy the <strong>Webhook URL</strong> and run the two curl commands below (replace <code>{YOUR_APP_ID}</code>, <code>{YOUR_DEVELOPER_API_KEY}</code>, and <code>{YOUR_WEBHOOK_URL_FROM_ABOVE}</code> with your actual values).',
"<strong>Step 8: Test Your Webhook</strong><br/>Create or modify a contact in HubSpot to trigger the webhook. Check your workflow execution logs in Sim to verify it's working.",
]

View File

@@ -56,18 +56,6 @@ export const jiraIssueCommentedTrigger: TriggerConfig = {
value: 'jira_issue_commented',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'jira_issue_commented',
condition: {
field: 'selectedTriggerId',
value: 'jira_issue_commented',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -80,6 +68,18 @@ export const jiraIssueCommentedTrigger: TriggerConfig = {
value: 'jira_issue_commented',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'jira_issue_commented',
condition: {
field: 'selectedTriggerId',
value: 'jira_issue_commented',
},
},
],
outputs: buildCommentOutputs(),

View File

@@ -65,18 +65,6 @@ export const jiraIssueCreatedTrigger: TriggerConfig = {
value: 'jira_issue_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'jira_issue_created',
condition: {
field: 'selectedTriggerId',
value: 'jira_issue_created',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -89,6 +77,18 @@ export const jiraIssueCreatedTrigger: TriggerConfig = {
value: 'jira_issue_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'jira_issue_created',
condition: {
field: 'selectedTriggerId',
value: 'jira_issue_created',
},
},
],
outputs: buildIssueOutputs(),

View File

@@ -56,18 +56,6 @@ export const jiraIssueDeletedTrigger: TriggerConfig = {
value: 'jira_issue_deleted',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'jira_issue_deleted',
condition: {
field: 'selectedTriggerId',
value: 'jira_issue_deleted',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -80,6 +68,18 @@ export const jiraIssueDeletedTrigger: TriggerConfig = {
value: 'jira_issue_deleted',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'jira_issue_deleted',
condition: {
field: 'selectedTriggerId',
value: 'jira_issue_deleted',
},
},
],
outputs: buildIssueOutputs(),

View File

@@ -70,18 +70,6 @@ export const jiraIssueUpdatedTrigger: TriggerConfig = {
value: 'jira_issue_updated',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'jira_issue_updated',
condition: {
field: 'selectedTriggerId',
value: 'jira_issue_updated',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -94,6 +82,18 @@ export const jiraIssueUpdatedTrigger: TriggerConfig = {
value: 'jira_issue_updated',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'jira_issue_updated',
condition: {
field: 'selectedTriggerId',
value: 'jira_issue_updated',
},
},
],
outputs: buildIssueUpdatedOutputs(),

View File

@@ -43,18 +43,6 @@ export const jiraWebhookTrigger: TriggerConfig = {
value: 'jira_webhook',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'jira_webhook',
condition: {
field: 'selectedTriggerId',
value: 'jira_webhook',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -67,6 +55,18 @@ export const jiraWebhookTrigger: TriggerConfig = {
value: 'jira_webhook',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'jira_webhook',
condition: {
field: 'selectedTriggerId',
value: 'jira_webhook',
},
},
],
outputs: {

View File

@@ -56,18 +56,6 @@ export const jiraWorklogCreatedTrigger: TriggerConfig = {
value: 'jira_worklog_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'jira_worklog_created',
condition: {
field: 'selectedTriggerId',
value: 'jira_worklog_created',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -80,6 +68,18 @@ export const jiraWorklogCreatedTrigger: TriggerConfig = {
value: 'jira_worklog_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'jira_worklog_created',
condition: {
field: 'selectedTriggerId',
value: 'jira_worklog_created',
},
},
],
outputs: buildWorklogOutputs(),

View File

@@ -39,18 +39,6 @@ export const linearCommentCreatedTrigger: TriggerConfig = {
value: 'linear_comment_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_comment_created',
condition: {
field: 'selectedTriggerId',
value: 'linear_comment_created',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -63,6 +51,18 @@ export const linearCommentCreatedTrigger: TriggerConfig = {
value: 'linear_comment_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_comment_created',
condition: {
field: 'selectedTriggerId',
value: 'linear_comment_created',
},
},
],
outputs: buildCommentOutputs(),

View File

@@ -39,18 +39,6 @@ export const linearCommentUpdatedTrigger: TriggerConfig = {
value: 'linear_comment_updated',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_comment_updated',
condition: {
field: 'selectedTriggerId',
value: 'linear_comment_updated',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -63,6 +51,18 @@ export const linearCommentUpdatedTrigger: TriggerConfig = {
value: 'linear_comment_updated',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_comment_updated',
condition: {
field: 'selectedTriggerId',
value: 'linear_comment_updated',
},
},
],
outputs: buildCommentOutputs(),

View File

@@ -39,18 +39,6 @@ export const linearCustomerRequestCreatedTrigger: TriggerConfig = {
value: 'linear_customer_request_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_customer_request_created',
condition: {
field: 'selectedTriggerId',
value: 'linear_customer_request_created',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -63,6 +51,18 @@ export const linearCustomerRequestCreatedTrigger: TriggerConfig = {
value: 'linear_customer_request_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_customer_request_created',
condition: {
field: 'selectedTriggerId',
value: 'linear_customer_request_created',
},
},
],
outputs: buildCustomerRequestOutputs(),

View File

@@ -39,18 +39,6 @@ export const linearCustomerRequestUpdatedTrigger: TriggerConfig = {
value: 'linear_customer_request_updated',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_customer_request_updated',
condition: {
field: 'selectedTriggerId',
value: 'linear_customer_request_updated',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -63,6 +51,18 @@ export const linearCustomerRequestUpdatedTrigger: TriggerConfig = {
value: 'linear_customer_request_updated',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_customer_request_updated',
condition: {
field: 'selectedTriggerId',
value: 'linear_customer_request_updated',
},
},
],
outputs: buildCustomerRequestOutputs(),

View File

@@ -39,18 +39,6 @@ export const linearCycleCreatedTrigger: TriggerConfig = {
value: 'linear_cycle_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_cycle_created',
condition: {
field: 'selectedTriggerId',
value: 'linear_cycle_created',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -63,6 +51,18 @@ export const linearCycleCreatedTrigger: TriggerConfig = {
value: 'linear_cycle_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_cycle_created',
condition: {
field: 'selectedTriggerId',
value: 'linear_cycle_created',
},
},
],
outputs: buildCycleOutputs(),

View File

@@ -39,18 +39,6 @@ export const linearCycleUpdatedTrigger: TriggerConfig = {
value: 'linear_cycle_updated',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_cycle_updated',
condition: {
field: 'selectedTriggerId',
value: 'linear_cycle_updated',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -63,6 +51,18 @@ export const linearCycleUpdatedTrigger: TriggerConfig = {
value: 'linear_cycle_updated',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_cycle_updated',
condition: {
field: 'selectedTriggerId',
value: 'linear_cycle_updated',
},
},
],
outputs: buildCycleOutputs(),

View File

@@ -52,18 +52,6 @@ export const linearIssueCreatedTrigger: TriggerConfig = {
value: 'linear_issue_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_issue_created',
condition: {
field: 'selectedTriggerId',
value: 'linear_issue_created',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -76,6 +64,18 @@ export const linearIssueCreatedTrigger: TriggerConfig = {
value: 'linear_issue_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_issue_created',
condition: {
field: 'selectedTriggerId',
value: 'linear_issue_created',
},
},
],
outputs: buildIssueOutputs(),

View File

@@ -39,18 +39,6 @@ export const linearIssueRemovedTrigger: TriggerConfig = {
value: 'linear_issue_removed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_issue_removed',
condition: {
field: 'selectedTriggerId',
value: 'linear_issue_removed',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -63,6 +51,18 @@ export const linearIssueRemovedTrigger: TriggerConfig = {
value: 'linear_issue_removed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_issue_removed',
condition: {
field: 'selectedTriggerId',
value: 'linear_issue_removed',
},
},
],
outputs: buildIssueOutputs(),

View File

@@ -39,18 +39,6 @@ export const linearIssueUpdatedTrigger: TriggerConfig = {
value: 'linear_issue_updated',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_issue_updated',
condition: {
field: 'selectedTriggerId',
value: 'linear_issue_updated',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -63,6 +51,18 @@ export const linearIssueUpdatedTrigger: TriggerConfig = {
value: 'linear_issue_updated',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_issue_updated',
condition: {
field: 'selectedTriggerId',
value: 'linear_issue_updated',
},
},
],
outputs: buildIssueOutputs(),

View File

@@ -39,18 +39,6 @@ export const linearLabelCreatedTrigger: TriggerConfig = {
value: 'linear_label_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_label_created',
condition: {
field: 'selectedTriggerId',
value: 'linear_label_created',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -63,6 +51,18 @@ export const linearLabelCreatedTrigger: TriggerConfig = {
value: 'linear_label_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_label_created',
condition: {
field: 'selectedTriggerId',
value: 'linear_label_created',
},
},
],
outputs: buildLabelOutputs(),

View File

@@ -39,18 +39,6 @@ export const linearLabelUpdatedTrigger: TriggerConfig = {
value: 'linear_label_updated',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_label_updated',
condition: {
field: 'selectedTriggerId',
value: 'linear_label_updated',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -63,6 +51,18 @@ export const linearLabelUpdatedTrigger: TriggerConfig = {
value: 'linear_label_updated',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_label_updated',
condition: {
field: 'selectedTriggerId',
value: 'linear_label_updated',
},
},
],
outputs: buildLabelOutputs(),

View File

@@ -39,18 +39,6 @@ export const linearProjectCreatedTrigger: TriggerConfig = {
value: 'linear_project_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_project_created',
condition: {
field: 'selectedTriggerId',
value: 'linear_project_created',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -63,6 +51,18 @@ export const linearProjectCreatedTrigger: TriggerConfig = {
value: 'linear_project_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_project_created',
condition: {
field: 'selectedTriggerId',
value: 'linear_project_created',
},
},
],
outputs: buildProjectOutputs(),

View File

@@ -39,18 +39,6 @@ export const linearProjectUpdateCreatedTrigger: TriggerConfig = {
value: 'linear_project_update_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_project_update_created',
condition: {
field: 'selectedTriggerId',
value: 'linear_project_update_created',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -63,6 +51,18 @@ export const linearProjectUpdateCreatedTrigger: TriggerConfig = {
value: 'linear_project_update_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_project_update_created',
condition: {
field: 'selectedTriggerId',
value: 'linear_project_update_created',
},
},
],
outputs: buildProjectUpdateOutputs(),

View File

@@ -39,18 +39,6 @@ export const linearProjectUpdatedTrigger: TriggerConfig = {
value: 'linear_project_updated',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_project_updated',
condition: {
field: 'selectedTriggerId',
value: 'linear_project_updated',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -63,6 +51,18 @@ export const linearProjectUpdatedTrigger: TriggerConfig = {
value: 'linear_project_updated',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_project_updated',
condition: {
field: 'selectedTriggerId',
value: 'linear_project_updated',
},
},
],
outputs: buildProjectOutputs(),

View File

@@ -39,18 +39,6 @@ export const linearWebhookTrigger: TriggerConfig = {
value: 'linear_webhook',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_webhook',
condition: {
field: 'selectedTriggerId',
value: 'linear_webhook',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -66,6 +54,18 @@ export const linearWebhookTrigger: TriggerConfig = {
value: 'linear_webhook',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'linear_webhook',
condition: {
field: 'selectedTriggerId',
value: 'linear_webhook',
},
},
],
outputs: {

View File

@@ -72,18 +72,6 @@ export const microsoftTeamsChatSubscriptionTrigger: TriggerConfig = {
value: 'microsoftteams_chat_subscription',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'microsoftteams_chat_subscription',
condition: {
field: 'selectedTriggerId',
value: 'microsoftteams_chat_subscription',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -105,6 +93,18 @@ export const microsoftTeamsChatSubscriptionTrigger: TriggerConfig = {
value: 'microsoftteams_chat_subscription',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'microsoftteams_chat_subscription',
condition: {
field: 'selectedTriggerId',
value: 'microsoftteams_chat_subscription',
},
},
],
outputs: {

View File

@@ -51,18 +51,6 @@ export const microsoftTeamsWebhookTrigger: TriggerConfig = {
value: 'microsoftteams_webhook',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'microsoftteams_webhook',
condition: {
field: 'selectedTriggerId',
value: 'microsoftteams_webhook',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -88,6 +76,18 @@ export const microsoftTeamsWebhookTrigger: TriggerConfig = {
value: 'microsoftteams_webhook',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'microsoftteams_webhook',
condition: {
field: 'selectedTriggerId',
value: 'microsoftteams_webhook',
},
},
],
outputs: {

View File

@@ -93,14 +93,6 @@ export const outlookPollingTrigger: TriggerConfig = {
required: false,
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'outlook_poller',
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -118,6 +110,14 @@ export const outlookPollingTrigger: TriggerConfig = {
.join(''),
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'outlook_poller',
},
],
outputs: {

View File

@@ -19,14 +19,6 @@ export const rssPollingTrigger: TriggerConfig = {
required: true,
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'rss_poller',
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -44,6 +36,14 @@ export const rssPollingTrigger: TriggerConfig = {
.join(''),
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'rss_poller',
},
],
outputs: {

View File

@@ -30,14 +30,6 @@ export const slackWebhookTrigger: TriggerConfig = {
required: true,
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'slack_webhook',
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -49,7 +41,7 @@ export const slackWebhookTrigger: TriggerConfig = {
'Go to "OAuth & Permissions" and add bot token scopes:<br><ul class="mt-1 ml-5 list-disc"><li><code>app_mentions:read</code> - For viewing messages that tag your bot with an @</li><li><code>chat:write</code> - To send messages to channels your bot is a part of</li></ul>',
'Go to "Event Subscriptions":<br><ul class="mt-1 ml-5 list-disc"><li>Enable events</li><li>Under "Subscribe to Bot Events", add <code>app_mention</code> to listen to messages that mention your bot</li><li>Paste the Webhook URL above into the "Request URL" field</li></ul>',
'Go to "Install App" in the left sidebar and install the app into your desired Slack workspace and channel.',
'Save your changes in Slack. Your trigger will configure automatically once you complete the fields above.',
'Save changes in both Slack and here.',
]
.map(
(instruction, index) =>
@@ -59,6 +51,14 @@ export const slackWebhookTrigger: TriggerConfig = {
hideFromPreview: true,
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'slack_webhook',
},
],
outputs: {

View File

@@ -165,14 +165,6 @@ export const stripeWebhookTrigger: TriggerConfig = {
password: true,
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'stripe_webhook',
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -186,7 +178,7 @@ export const stripeWebhookTrigger: TriggerConfig = {
'Click "Create Destination" to save',
'After creating the endpoint, click "Reveal" next to "Signing secret" and copy it',
'Paste the signing secret into the <strong>Webhook Signing Secret</strong> field above',
'Your webhook trigger will activate automatically once you complete the configuration above',
'Click "Save" to activate your webhook trigger',
]
.map(
(instruction, index) =>
@@ -195,6 +187,14 @@ export const stripeWebhookTrigger: TriggerConfig = {
.join(''),
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'stripe_webhook',
},
],
outputs: {

View File

@@ -30,14 +30,6 @@ export const telegramWebhookTrigger: TriggerConfig = {
required: true,
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'telegram_webhook',
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -46,7 +38,7 @@ export const telegramWebhookTrigger: TriggerConfig = {
defaultValue: [
'Message "/newbot" to <a href="https://t.me/BotFather" target="_blank" rel="noopener noreferrer" class="text-muted-foreground underline transition-colors hover:text-muted-foreground/80">@BotFather</a> in Telegram to create a bot and copy its token.',
'Enter your Bot Token above.',
'Once configured, any message sent to your bot will trigger the workflow.',
'Save settings and any message sent to your bot will trigger the workflow.',
]
.map(
(instruction, index) =>
@@ -55,6 +47,14 @@ export const telegramWebhookTrigger: TriggerConfig = {
.join(''),
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'telegram_webhook',
},
],
outputs: {

View File

@@ -49,14 +49,6 @@ export const twilioVoiceWebhookTrigger: TriggerConfig = {
required: false,
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'twilio_voice_webhook',
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -77,6 +69,14 @@ export const twilioVoiceWebhookTrigger: TriggerConfig = {
.join('\n\n'),
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'twilio_voice_webhook',
},
],
outputs: {

View File

@@ -61,14 +61,6 @@ export const typeformWebhookTrigger: TriggerConfig = {
defaultValue: false,
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'typeform_webhook',
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -79,7 +71,7 @@ export const typeformWebhookTrigger: TriggerConfig = {
'Find your Form ID in the URL when editing your form (e.g., <code>https://admin.typeform.com/form/ABC123/create</code> → Form ID is <code>ABC123</code>)',
'Fill in the form above with your Form ID and Personal Access Token',
'Optionally add a Webhook Secret for enhanced security - Sim will verify all incoming webhooks match this secret',
'Sim will automatically register the webhook with Typeform when you complete the configuration above',
'Click "Save" below - Sim will automatically register the webhook with Typeform',
'<strong>Note:</strong> Requires a Typeform PRO or PRO+ account to use webhooks',
]
.map(
@@ -89,6 +81,14 @@ export const typeformWebhookTrigger: TriggerConfig = {
.join(''),
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'typeform_webhook',
},
],
outputs: {

View File

@@ -53,18 +53,6 @@ export const webflowCollectionItemChangedTrigger: TriggerConfig = {
value: 'webflow_collection_item_changed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'webflow_collection_item_changed',
condition: {
field: 'selectedTriggerId',
value: 'webflow_collection_item_changed',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -89,6 +77,18 @@ export const webflowCollectionItemChangedTrigger: TriggerConfig = {
value: 'webflow_collection_item_changed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'webflow_collection_item_changed',
condition: {
field: 'selectedTriggerId',
value: 'webflow_collection_item_changed',
},
},
],
outputs: {

View File

@@ -66,18 +66,6 @@ export const webflowCollectionItemCreatedTrigger: TriggerConfig = {
value: 'webflow_collection_item_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'webflow_collection_item_created',
condition: {
field: 'selectedTriggerId',
value: 'webflow_collection_item_created',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -102,6 +90,18 @@ export const webflowCollectionItemCreatedTrigger: TriggerConfig = {
value: 'webflow_collection_item_created',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'webflow_collection_item_created',
condition: {
field: 'selectedTriggerId',
value: 'webflow_collection_item_created',
},
},
],
outputs: {

View File

@@ -53,18 +53,6 @@ export const webflowCollectionItemDeletedTrigger: TriggerConfig = {
value: 'webflow_collection_item_deleted',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'webflow_collection_item_deleted',
condition: {
field: 'selectedTriggerId',
value: 'webflow_collection_item_deleted',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -90,6 +78,18 @@ export const webflowCollectionItemDeletedTrigger: TriggerConfig = {
value: 'webflow_collection_item_deleted',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'webflow_collection_item_deleted',
condition: {
field: 'selectedTriggerId',
value: 'webflow_collection_item_deleted',
},
},
],
outputs: {

View File

@@ -40,14 +40,6 @@ export const webflowFormSubmissionTrigger: TriggerConfig = {
required: false,
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'webflow_form_submission',
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -69,6 +61,14 @@ export const webflowFormSubmissionTrigger: TriggerConfig = {
.join(''),
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'webflow_form_submission',
},
],
outputs: {

View File

@@ -31,14 +31,6 @@ export const whatsappWebhookTrigger: TriggerConfig = {
required: true,
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'whatsapp_webhook',
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
@@ -61,6 +53,14 @@ export const whatsappWebhookTrigger: TriggerConfig = {
.join(''),
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'whatsapp_webhook',
},
],
outputs: {