import parser from 'cron-parser' import dayjs from 'dayjs' import { Copy, Edit, Minus, MoreVertical, Play, Trash } from 'lucide-react' import { parseAsString, useQueryState } from 'nuqs' import { useState } from 'react' import { toast } from 'sonner' import { Badge, Button, cn, ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger, copyToClipboard, Dialog, DialogContent, DialogFooter, DialogHeader, DialogSection, DialogSectionSeparator, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, HoverCard, HoverCardContent, HoverCardTrigger, Switch, Tooltip, TooltipContent, TooltipTrigger, } from 'ui' import { TimestampInfo } from 'ui-patterns' import { CodeBlock } from 'ui-patterns/CodeBlock' import { useDatabaseCronJobRunCommandMutation } from '@/data/database-cron-jobs/database-cron-job-run-mutation' import { CronJob } from '@/data/database-cron-jobs/database-cron-jobs-infinite-query' import { useDatabaseCronJobToggleMutation } from '@/data/database-cron-jobs/database-cron-jobs-toggle-mutation' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' const getNextRun = (schedule: string, lastRun?: string) => { // cron-parser can only deal with the traditional cron syntax but technically users can also // use strings like "30 seconds" now, For the latter case, we try our best to parse the next run // (can't guarantee as scope is quite big) if (schedule.includes('*') || schedule.includes('$')) { try { // pg_cron uses '$' for "last day of month", but cron-parser uses 'L' // Convert pg_cron syntax to cron-parser syntax before parsing const normalizedSchedule = schedule.replace(/\$/g, 'L') const interval = parser.parseExpression(normalizedSchedule, { tz: 'UTC' }) return interval.next().getTime() } catch (error) { return undefined } } else { // [Joshen] Only going to attempt to parse if the schedule is as simple as "n second" or "n seconds" // Returned undefined otherwise - we can revisit this perhaps if we get feedback about this const [value, unit] = schedule.toLocaleLowerCase().split(' ') if ( ['second', 'seconds'].includes(unit) && !Number.isNaN(Number(value)) && lastRun !== undefined ) { const parsedLastRun = dayjs(lastRun).add(Number(value), unit as dayjs.ManipulateType) return parsedLastRun.valueOf() } else { return undefined } } } interface CronJobTableCellProps { col: any row: any onSelectEdit: (job: CronJob) => void onSelectDelete: (job: CronJob) => void } export const CronJobTableCell = ({ col, row, onSelectEdit, onSelectDelete, }: CronJobTableCellProps) => { const { data: project } = useSelectedProjectQuery() const [searchQuery] = useQueryState('search', parseAsString.withDefault('')) const [showToggleModal, setShowToggleModal] = useState(false) const value = row?.[col.id] const { jobid, schedule, latest_run, status, active, jobname } = row const formattedValue = col.id === 'jobname' && !jobname ? 'No name provided' : col.id === 'lastest_run' ? !!value ? dayjs(value).valueOf() : undefined : col.id === 'next_run' ? getNextRun(schedule, latest_run) : value const hasValue = col.id === 'next_run' ? !!formattedValue : col.id in row const { mutate: runCronJob, isPending: isRunning } = useDatabaseCronJobRunCommandMutation({ onSuccess: () => { toast.success(`Command from "${jobname}" ran successfully`) }, }) const { mutate: toggleDatabaseCronJob, isPending: isToggling } = useDatabaseCronJobToggleMutation( { onSuccess: (_, vars) => { toast.success(`Successfully ${vars.active ? 'enabled' : 'disabled'} "${jobname}"`) setShowToggleModal(false) }, } ) const onRunCronJob = () => { runCronJob({ projectRef: project?.ref!, connectionString: project?.connectionString, jobId: jobid, }) } const onConfirmToggle = () => { toggleDatabaseCronJob({ projectRef: project?.ref!, connectionString: project?.connectionString, jobId: jobid, active: !active, searchTerm: searchQuery, }) } if (col.id === 'actions') { return (
Job has not been run yet
) : col.id === 'next_run' && !formattedValue ? (Unable to parse next run for job
) : (Command
{formattedValue}
)} {col.id === 'latest_run' && !!status && (