| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- // @ts-nocheck
- import { useParams } from 'common'
- import { Loader2 } from 'lucide-react'
- import { useRouter } from 'next/router'
- import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs'
- import { MouseEvent, useEffect, useMemo, useState } from 'react'
- import { toast } from 'sonner'
- import { LoadingLine } from 'ui'
- import { formatCronJobColumns } from './CronJobs.utils'
- import { CronJobRunDetailsOverflowNotice } from './CronJobsTab.CleanupNotice'
- import { CronJobsTabDataGrid } from './CronJobsTab.DataGrid'
- import { CronJobsTabHeader } from './CronJobsTab.Header'
- import { useCronJobsData } from './CronJobsTab.useCronJobsData'
- import { DeleteCronJob } from './DeleteCronJob'
- import { CreateCronJobSheet } from '@/components/interfaces/Integrations/CronJobs/CreateCronJobSheet/CreateCronJobSheet'
- import { CronJob } from '@/data/database-cron-jobs/database-cron-jobs-infinite-query'
- import { useSendEventMutation } from '@/data/telemetry/send-event-mutation'
- import { useInfiniteScroll } from '@/hooks/misc/useInfiniteScroll'
- import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
- import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
- import { cleanPointerEventsNoneOnBody } from '@/lib/helpers'
- import { createNavigationHandler } from '@/lib/navigation'
- const EMPTY_CRON_JOB = { jobname: '', schedule: '', active: true, command: '' }
- export const CronjobsTab = () => {
- const router = useRouter()
- const { ref } = useParams()
- const { data: project } = useSelectedProjectQuery()
- const { data: org } = useSelectedOrganizationQuery()
- const [searchQuery, setSearchQuery] = useQueryState('search', parseAsString.withDefault(''))
- const [search, setSearch] = useState(searchQuery)
- const handleSearchSubmit = () => {
- const trimmed = search.trim()
- setSearchQuery(trimmed.length > 0 ? trimmed : null)
- }
- const handleClearSearch = () => {
- setSearch('')
- setSearchQuery(null)
- }
- const { grid, count } = useCronJobsData({
- projectRef: project?.ref,
- connectionString: project?.connectionString,
- searchQuery,
- })
- const [createCronJobSheetShown, setCreateCronJobSheetShown] = useQueryState(
- 'new',
- parseAsBoolean.withDefault(false).withOptions({ clearOnDefault: true })
- )
- const [cronJobIdForEditing, setCronJobForEditing] = useQueryState('edit', parseAsString)
- const cronJobForEditing = grid.rows.find((j) => j.jobid.toString() === cronJobIdForEditing)
- const [, setCronJobForDeletion] = useQueryState('delete', parseAsString)
- const { mutate: sendEvent } = useSendEventMutation()
- const columns = useMemo(
- () =>
- formatCronJobColumns({
- onSelectEdit: (job: CronJob) => {
- sendEvent({
- action: 'cron_job_update_clicked',
- groups: { project: ref ?? 'Unknown', organization: org?.slug ?? 'Unknown' },
- })
- setCronJobForEditing(job.jobid.toString())
- },
- onSelectDelete: (job: CronJob) => {
- sendEvent({
- action: 'cron_job_delete_clicked',
- groups: { project: ref ?? 'Unknown', organization: org?.slug ?? 'Unknown' },
- })
- setCronJobForDeletion(job.jobid.toString())
- },
- }),
- [org?.slug, ref, sendEvent, setCronJobForEditing, setCronJobForDeletion]
- )
- const handleScroll = useInfiniteScroll({
- isLoading: grid.isLoading,
- isFetchingNextPage: grid.isFetchingNextPage,
- hasNextPage: grid.hasNextPage,
- fetchNextPage: grid.fetchNextPage,
- })
- const onOpenCreateJobSheet = () => {
- sendEvent({
- action: 'cron_job_create_clicked',
- groups: { project: project?.ref ?? 'Unknown', organization: org?.slug ?? 'Unknown' },
- })
- setCreateCronJobSheetShown(true)
- }
- // Row click handler
- const handleRowClick = (row: CronJob, event: MouseEvent<HTMLDivElement>) => {
- const { jobid, jobname } = row
- const url = `/project/${ref}/integrations/cron/jobs/${jobid}?child-label=${encodeURIComponent(
- jobname || `Job #${jobid}`
- )}`
- sendEvent({
- action: 'cron_job_history_clicked',
- groups: { project: ref ?? 'Unknown', organization: org?.slug ?? 'Unknown' },
- })
- createNavigationHandler(url, router)(event)
- }
- const onClose = () => {
- setCronJobForEditing(null)
- setCreateCronJobSheetShown(false)
- cleanPointerEventsNoneOnBody(500)
- }
- useEffect(() => {
- if (grid.isSuccess && !!cronJobIdForEditing && !cronJobForEditing) {
- toast('Cron job not found')
- setCronJobForEditing(null)
- }
- }, [cronJobForEditing, cronJobIdForEditing, grid.isSuccess, setCronJobForEditing])
- return (
- <>
- <div className="h-full w-full space-y-4">
- <div className="h-full w-full flex flex-col relative">
- <CronJobsTabHeader
- search={search}
- isRefreshing={grid.isRefetching && !grid.isFetchingNextPage}
- onSearchChange={setSearch}
- onSearchSubmit={handleSearchSubmit}
- onClearSearch={handleClearSearch}
- onRefresh={grid.refetch}
- onCreateJob={onOpenCreateJobSheet}
- />
- <LoadingLine loading={grid.isLoading || grid.isRefetching || grid.isFetchingNextPage} />
- {grid.isMinimal && (
- <CronJobRunDetailsOverflowNotice
- queryCost={grid.queryCost}
- refetchJobs={grid.refetch}
- />
- )}
- <CronJobsTabDataGrid
- columns={columns}
- rows={grid.rows}
- isLoading={grid.isLoading}
- error={grid.error}
- searchQuery={searchQuery}
- onScroll={handleScroll}
- onRowClick={handleRowClick}
- />
- <CronJobsFooter count={count} />
- </div>
- </div>
- <DeleteCronJob />
- <CreateCronJobSheet
- open={!!createCronJobSheetShown || !!cronJobForEditing}
- selectedCronJob={cronJobForEditing ?? EMPTY_CRON_JOB}
- onClose={onClose}
- />
- </>
- )
- }
- // Footer component for displaying job count
- interface CronJobsFooterProps {
- count: {
- value: number | undefined
- isEstimate: boolean
- isLoading: boolean
- }
- }
- const CronJobsFooter = ({ count }: CronJobsFooterProps) => (
- <div className="flex justify-between min-h-9 h-9 overflow-hidden items-center px-6 w-full border-t text-xs text-foreground-light">
- {count.isLoading ? (
- <span className="flex items-center gap-2">
- <Loader2 size={14} className="animate-spin" /> Loading...
- </span>
- ) : (
- `Total: ${count.value ?? 0} jobs${count.isEstimate ? ' (estimate)' : ''}`
- )}
- </div>
- )
|