Files
supabase/apps/studio/components/interfaces/Database/CronJobs/CronJobCard.tsx
T
Ivan VasilovandJoshen Lim fd3309c886 feat: UI for pg-cron (#29291)
* Add crons option in the database menu.

* Add react-hook-form to the studio package.json.

* Refactor the functionSelector to be used by other features.

* Add the bulk of the functionality for the cron UI. Some of the code is copy-pasted from functions feature, needs to be cleaned before merging.

* Tons of changes, the Create Cron sheet works now.

* Added some more functionality for the cronjob feature.

* Convert the Cron table to a listing with cards.

* Add click-to-copy in the Auth Hooks feature.

* Remove extra prop.

* Fix type errors.

* Fix some random issues. Fix the tests.

* Fix the tests.

* Add a style for disabled radio button item.

* Make the default SQL snippet. Handle the case pg_net is not installed.

* Fix the heading and save button when creating a new cron job.

* Change the name of the custom label in the schedule dropdown.

* Minor fixes.

* Rename all mentions of cronjobs to cron jobs.

* Always show the cron jobs link.

* Rename the link from crons to cron-jobs.

* Fix the disabled state for the stacked radio group.

* More minor fixes.

* More small fixes.

* Fix the tests.

* Minor UI tweaks

* More minor tweaks

* Add padding bottom

* Add feature flag for the cron ui.

* Fix the SQL function option.

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2024-10-03 15:11:17 -02:30

147 lines
5.8 KiB
TypeScript

import { toString as CronToString } from 'cronstrue'
import { Clock, Loader2, MoreVertical } from 'lucide-react'
import Link from 'next/link'
import { useParams } from 'common'
import { SQLCodeBlock } from 'components/interfaces/Auth/ThirdPartyAuthForm/SqlCodeBlock'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { CronJob } from 'data/database-cron-jobs/database-cron-jobs-query'
import { useDatabaseCronJobToggleMutation } from 'data/database-cron-jobs/database-cron-jobs-toggle-mutation'
import { useState } from 'react'
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
Input,
Label_Shadcn_,
Switch,
} from 'ui'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
interface CronJobCardProps {
job: CronJob
onEditCronJob: (job: CronJob) => void
onDeleteCronJob: (job: CronJob) => void
}
const generateJobDetailsSQL = (jobId: number) => {
return `select * from cron.job_run_details where jobid = '${jobId}' order by start_time desc limit 10`
}
export const CronJobCard = ({ job, onEditCronJob, onDeleteCronJob }: CronJobCardProps) => {
const [toggleConfirmationModalShown, showToggleConfirmationModal] = useState(false)
const { ref } = useParams()
const { project: selectedProject } = useProjectContext()
const { mutate: toggleDatabaseCronJob, isLoading } = useDatabaseCronJobToggleMutation()
// pg_cron can also use "30 seconds" format for schedule. Cronstrue doesn't understand that format so just use the
// original schedule when cronstrue throws
let schedule = job.schedule
try {
const scheduleString = CronToString(job.schedule)
schedule = scheduleString
} catch {}
return (
<>
<div className="bg-surface-100 border-default overflow-hidden border shadow px-5 py-4 flex flex-row rounded-md space-x-4">
<div>
<Clock size={24} className="text-foreground-muted mt-0.5" />
</div>
<div className="flex flex-col flex-0 overflow-y-auto w-full">
<div className="flex flex-row justify-between items-center">
<span className="text-base text-foreground">{job.jobname}</span>
<div className="flex items-center gap-x-2">
{isLoading ? (
<Loader2 size={18} strokeWidth={2} className="animate-spin text-foreground-muted" />
) : (
<Label_Shadcn_
htmlFor={`cron-job-active-${job.jobid}`}
className="text-foreground-light text-xs"
>
{job.active ? 'Active' : 'Inactive'}
</Label_Shadcn_>
)}
<Switch
id={`cron-job-active-${job.jobid}`}
size="large"
disabled={isLoading}
checked={job.active}
onCheckedChange={() => showToggleConfirmationModal(true)}
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button type="default" icon={<MoreVertical />} className="px-1.5" />
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-36">
<DropdownMenuItem asChild>
<Link
href={`/project/${ref}/sql/new?content=${encodeURIComponent(generateJobDetailsSQL(job.jobid))}`}
>
View previous runs
</Link>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => onEditCronJob(job)}>
Edit cron job
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => onDeleteCronJob(job)}>
Delete cron job
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
<div className="text-sm flex flex-row space-x-5 py-4">
<div className="flex flex-col w-full space-y-2">
<div className="grid grid-cols-10 gap-3 items-center">
<span className="text-foreground-light col-span-1">Schedule</span>
<div className="col-span-9">
<Input
title={schedule}
readOnly
className="input-mono [&>div>div>div>input]:text-xs [&>div>div>div>input]:opacity-100 flex-1"
value={schedule}
/>
</div>
</div>
<div className="grid grid-cols-10 gap-3">
<span className="text-foreground-light col-span-1">Command</span>
<div className="col-span-9">
<SQLCodeBlock className="py-2">{[job.command.trim()]}</SQLCodeBlock>
</div>
</div>
</div>
</div>
</div>
</div>
<ConfirmationModal
visible={toggleConfirmationModalShown}
title={job.active ? 'Disable cron job' : 'Enable cron job'}
loading={isLoading}
confirmLabel={job.active ? 'Disable' : 'Enable'}
onCancel={() => showToggleConfirmationModal(false)}
variant={job.active ? 'destructive' : undefined}
onConfirm={() => {
toggleDatabaseCronJob({
projectRef: selectedProject?.ref!,
connectionString: selectedProject?.connectionString,
jobId: job.jobid,
active: !job.active,
})
showToggleConfirmationModal(false)
}}
>
<p className="text-sm text-foreground-light">
<span>{`Are you sure you want to ${job.active ? 'disable' : 'enable'} the`}</span>{' '}
<span className="text-brand">{`${job?.jobname}`}</span>
<span> cron job?</span>
</p>
</ConfirmationModal>
</>
)
}