diff --git a/sim/app/api/schedules/route.ts b/sim/app/api/schedules/route.ts index e2798de4bf..4540ade7b0 100644 --- a/sim/app/api/schedules/route.ts +++ b/sim/app/api/schedules/route.ts @@ -8,6 +8,10 @@ import { workflowSchedule } from '@/db/schema' const logger = createLogger('ScheduledAPI') +// Track recent requests to reduce redundant logging +const recentRequests = new Map(); +const LOGGING_THROTTLE_MS = 5000; // 5 seconds between logging for the same workflow + /** * Get schedule information for a workflow */ @@ -15,6 +19,12 @@ export async function GET(req: NextRequest) { const requestId = crypto.randomUUID().slice(0, 8) const url = new URL(req.url) const workflowId = url.searchParams.get('workflowId') + const mode = url.searchParams.get('mode') + + // Skip processing if mode is provided and not 'schedule' + if (mode && mode !== 'schedule') { + return NextResponse.json({ schedule: null }) + } try { const session = await getSession() @@ -27,7 +37,15 @@ export async function GET(req: NextRequest) { return NextResponse.json({ error: 'Missing workflowId parameter' }, { status: 400 }) } - logger.info(`[${requestId}] Getting schedule for workflow ${workflowId}`) + // Check if we should log this request (throttle logging for repeat requests) + const now = Date.now(); + const lastLog = recentRequests.get(workflowId) || 0; + const shouldLog = now - lastLog > LOGGING_THROTTLE_MS; + + if (shouldLog) { + logger.info(`[${requestId}] Getting schedule for workflow ${workflowId}`) + recentRequests.set(workflowId, now); + } // Find the schedule for this workflow const schedule = await db @@ -36,11 +54,15 @@ export async function GET(req: NextRequest) { .where(eq(workflowSchedule.workflowId, workflowId)) .limit(1) + // Set cache control headers to reduce repeated API calls + const headers = new Headers(); + headers.set('Cache-Control', 'max-age=30'); // Cache for 30 seconds + if (schedule.length === 0) { - return NextResponse.json({ schedule: null }) + return NextResponse.json({ schedule: null }, { headers }) } - return NextResponse.json({ schedule: schedule[0] }) + return NextResponse.json({ schedule: schedule[0] }, { headers }) } catch (error) { logger.error(`[${requestId}] Error retrieving workflow schedule`, error) return NextResponse.json({ error: 'Failed to retrieve workflow schedule' }, { status: 500 }) diff --git a/sim/app/api/schedules/schedule/route.ts b/sim/app/api/schedules/schedule/route.ts index a73e4dfcea..88034d5e81 100644 --- a/sim/app/api/schedules/schedule/route.ts +++ b/sim/app/api/schedules/schedule/route.ts @@ -54,17 +54,54 @@ export async function POST(req: NextRequest) { } const startWorkflow = getSubBlockValue(starterBlock, 'startWorkflow') + const scheduleType = getSubBlockValue(starterBlock, 'scheduleType') - // If the workflow is not scheduled, delete any existing schedule - if (startWorkflow !== 'schedule') { - logger.info(`[${requestId}] Removing schedule for workflow ${workflowId}`) + // Check if there's a valid schedule configuration + const hasScheduleConfig = (() => { + const getValue = (id: string): string => { + const value = getSubBlockValue(starterBlock, id); + return value && value.trim() !== '' ? value : ''; + }; + + if (scheduleType === 'minutes' && getValue('minutesInterval')) { + return true; + } + if (scheduleType === 'hourly' && getValue('hourlyMinute') !== '') { + return true; + } + if (scheduleType === 'daily' && getValue('dailyTime')) { + return true; + } + if (scheduleType === 'weekly' && getValue('weeklyDay') && + getValue('weeklyDayTime')) { + return true; + } + if (scheduleType === 'monthly' && getValue('monthlyDay') && + getValue('monthlyTime')) { + return true; + } + if (scheduleType === 'custom' && getValue('cronExpression')) { + return true; + } + return false; + })(); + + // If the workflow is not configured for scheduling, delete any existing schedule + if (startWorkflow !== 'schedule' && !hasScheduleConfig) { + logger.info(`[${requestId}] Removing schedule for workflow ${workflowId} - no valid configuration found`) await db.delete(workflowSchedule).where(eq(workflowSchedule.workflowId, workflowId)) return NextResponse.json({ message: 'Schedule removed' }) } + // If we're here, we either have startWorkflow === 'schedule' or hasScheduleConfig is true + if (startWorkflow !== 'schedule') { + logger.info(`[${requestId}] Setting workflow to scheduled mode based on schedule configuration`) + // The UI should handle this, but as a fallback we'll assume the user intended to schedule + // the workflow even if startWorkflow wasn't set properly + } + // Get schedule configuration from starter block - const scheduleType = getSubBlockValue(starterBlock, 'scheduleType') logger.debug(`[${requestId}] Schedule type for workflow ${workflowId}: ${scheduleType}`) // Calculate cron expression based on schedule type diff --git a/sim/app/w/[id]/components/control-bar/components/marketplace-modal/marketplace-modal.tsx b/sim/app/w/[id]/components/control-bar/components/marketplace-modal/marketplace-modal.tsx index 1d4ba10b29..a4e10ec8e7 100644 --- a/sim/app/w/[id]/components/control-bar/components/marketplace-modal/marketplace-modal.tsx +++ b/sim/app/w/[id]/components/control-bar/components/marketplace-modal/marketplace-modal.tsx @@ -451,6 +451,11 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps) disabled={isUnpublishing} className="gap-2" > + {isUnpublishing ? ( +
+ ) : ( + + )} {isUnpublishing ? 'Unpublishing...' : 'Unpublish'}
diff --git a/sim/app/w/[id]/components/workflow-block/components/action-bar/schedule-status.tsx b/sim/app/w/[id]/components/workflow-block/components/action-bar/schedule-status.tsx deleted file mode 100644 index 4d7760faa5..0000000000 --- a/sim/app/w/[id]/components/workflow-block/components/action-bar/schedule-status.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { useEffect, useState } from 'react' -import { useParams } from 'next/navigation' -import { Calendar } from 'lucide-react' -import { Badge } from '@/components/ui/badge' -import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' -import { createLogger } from '@/lib/logs/console-logger' -import { formatDateTime } from '@/lib/utils' - -const logger = createLogger('ScheduleStatus') - -interface ScheduleStatusProps { - blockId: string -} - -export function ScheduleStatus({ blockId }: ScheduleStatusProps) { - const [scheduleId, setScheduleId] = useState(null) - const [nextRunAt, setNextRunAt] = useState(null) - const [isLoading, setIsLoading] = useState(true) - const params = useParams() - const workflowId = params.id as string - - // Check if schedule exists in the database - useEffect(() => { - const checkSchedule = async () => { - setIsLoading(true) - try { - // Check if there's a schedule for this workflow - const response = await fetch(`/api/schedules?workflowId=${workflowId}`) - if (response.ok) { - const data = await response.json() - if (data.schedule) { - setScheduleId(data.schedule.id) - setNextRunAt(data.schedule.nextRunAt) - } else { - setScheduleId(null) - setNextRunAt(null) - } - } - } catch (error) { - logger.error('Error checking schedule:', { error }) - } finally { - setIsLoading(false) - } - } - - checkSchedule() - }, [workflowId]) - - if (isLoading || !scheduleId || !nextRunAt) { - return null - } - - return ( - - - - - Active - - - -
-
- Schedule Active -
-
Next run: {formatDateTime(new Date(nextRunAt))}
-
-
-
- ) -} diff --git a/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-upload.tsx b/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-upload.tsx index dc797ca544..f1b6683098 100644 --- a/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-upload.tsx +++ b/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-upload.tsx @@ -309,7 +309,6 @@ export function FileUpload({ useSubBlockStore.getState().setValue(blockId, subBlockId, null) } - addNotification('console', `${file.name} was deleted successfully`, activeWorkflowId) useWorkflowStore.getState().triggerUpdate() } catch (error) { addNotification( @@ -387,19 +386,6 @@ export function FileUpload({ } } - // Show a single consolidated notification about the deletions - if (deletionResults.success > 0) { - if (fileCount === 1) { - addNotification('console', `File was deleted successfully`, activeWorkflowId) - } else { - addNotification( - 'console', - `${deletionResults.success} of ${fileCount} files were deleted successfully`, - activeWorkflowId - ) - } - } - // Show error notification if any deletions failed if (deletionResults.failures.length > 0) { if (deletionResults.failures.length === 1) { @@ -427,10 +413,10 @@ export function FileUpload({ return (
-
{file.name}
+
{file.name}
{formatFileSize(file.size)}
- - - - - - -

Max file size: {maxSize}MB

- {multiple &&

You can select multiple files at once

} -
-
)} diff --git a/sim/app/w/[id]/components/workflow-block/components/sub-block/components/schedule/components/schedule-modal.tsx b/sim/app/w/[id]/components/workflow-block/components/sub-block/components/schedule/components/schedule-modal.tsx index c0411a7add..de546a293d 100644 --- a/sim/app/w/[id]/components/workflow-block/components/sub-block/components/schedule/components/schedule-modal.tsx +++ b/sim/app/w/[id]/components/workflow-block/components/sub-block/components/schedule/components/schedule-modal.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react' import { format } from 'date-fns' -import { Calendar, Trash2 } from 'lucide-react' +import { Trash2, X } from 'lucide-react' import { Alert, AlertDescription } from '@/components/ui/alert' import { AlertDialog, @@ -14,13 +14,7 @@ import { } from '@/components/ui/alert-dialog' import { Button } from '@/components/ui/button' import { Calendar as CalendarComponent } from '@/components/ui/calendar' -import { - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog' +import { DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { @@ -31,6 +25,7 @@ import { SelectValue, } from '@/components/ui/select' import { createLogger } from '@/lib/logs/console-logger' +import { cn } from '@/lib/utils' import { UnsavedChangesDialog } from '../../../components/webhook/components/ui/confirmation' import { useSubBlockValue } from '../../../hooks/use-sub-block-value' import { TimeInput } from '../../time-input' @@ -70,6 +65,9 @@ export function ScheduleModal({ const [cronExpression, setCronExpression] = useSubBlockValue(blockId, 'cronExpression') const [timezone, setTimezone] = useSubBlockValue(blockId, 'timezone') + // Get the startWorkflow value at the component level + const [startWorkflow, setStartWorkflow] = useSubBlockValue(blockId, 'startWorkflow') + // UI states const [isSaving, setIsSaving] = useState(false) const [isDeleting, setIsDeleting] = useState(false) @@ -254,6 +252,18 @@ export function ScheduleModal({ return } + // Make sure the block's startWorkflow field is set to 'schedule' + // This is critical to ensure the schedule is actually enabled + logger.debug('Current startWorkflow value:', startWorkflow) + + if (startWorkflow !== 'schedule') { + logger.debug('Setting startWorkflow to schedule') + setStartWorkflow('schedule') + + // Give the state time to update + await new Promise((resolve) => setTimeout(resolve, 100)) + } + const success = await onSave() if (success) { @@ -272,6 +282,7 @@ export function ScheduleModal({ timezone: timezone || 'UTC', cronExpression: cronExpression || '', } + logger.debug('Schedule saved successfully, updating initial values', updatedValues) setInitialValues(updatedValues) setHasChanges(false) onClose() @@ -320,263 +331,291 @@ export function ScheduleModal({ return ( <> - - -
- -
- Schedule Configuration - - Configure when your workflow should run automatically - -
+ + +
+ Schedule Configuration +
- {errorMessage && ( - - {errorMessage} - - )} - -
- {/* Common date and time fields */} -
-
- - - - - - - setScheduleStartAt(date ? date.toISOString() : '')} - initialFocus - /> - - -
- -
- - -
-
- - {/* Frequency selector */} -
- - -
- - {/* Minutes schedule options */} - {scheduleType === 'minutes' && ( -
- - setMinutesInterval(e.target.value)} - placeholder="15" - type="number" - min="1" - /> -
+
+ {errorMessage && ( + + {errorMessage} + )} - {/* Hourly schedule options */} - {scheduleType === 'hourly' && ( -
- - setHourlyMinute(e.target.value)} - placeholder="0" - type="number" - min="0" - max="59" - /> -

- Specify which minute of each hour the workflow should run (0-59) -

-
- )} - - {/* Daily schedule options */} - {scheduleType === 'daily' && ( -
- - -
- )} - - {/* Weekly schedule options */} - {scheduleType === 'weekly' && ( - <> -
-