Files
supabase/apps/studio/components/interfaces/Integrations/CronJobs/CronJobsTab.Header.tsx
T
Charis d34cc65e30 fix(studio): handle large cron.job_run_details table gracefully (#41992)
* fix(studio): check job_run_details size in cron display

When cron.job_run_details grows too large (200k+ rows), loading the
cron jobs overview can timeout and affect other queries by pulling
excessive data into shared buffers.

This change:
- Estimates table size using pg_stat before fetching cron jobs data
- Shows a cleanup notice when the table exceeds the threshold
- Provides batched deletion using ctid ranges to avoid buffer pollution
- Allows scheduling an automated daily cleanup cron job
- Handles timeout errors gracefully with a "suspected overflow" state

The useCronJobsData hook now returns a discriminated union status that
tracks loading, estimate-error, overflow-confirmed, overflow-suspected,
and ready states, allowing the UI to respond appropriately to each case.

* fix(studio): use index when querying cron.job_run_details

cron.job_run_details is only indexed by runid, not by start_time. Change
the query to use the runid index (which gives the same result, since
runid is auto-incrementing).
2026-01-22 11:17:49 -05:00

65 lines
1.6 KiB
TypeScript

import { RefreshCw, Search, X } from 'lucide-react'
import type { KeyboardEvent } from 'react'
import { Button } from 'ui'
import { Input } from 'ui-patterns/DataInputs/Input'
interface CronJobsTabHeaderProps {
search: string
isRefreshing: boolean
onSearchChange: (value: string) => void
onSearchSubmit: () => void
onClearSearch: () => void
onRefresh: () => void
onCreateJob: () => void
}
export const CronJobsTabHeader = ({
search,
isRefreshing,
onSearchChange,
onSearchSubmit,
onClearSearch,
onRefresh,
onCreateJob,
}: CronJobsTabHeaderProps) => {
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter' || event.code === 'NumpadEnter') {
onSearchSubmit()
}
}
return (
<div className="bg-surface-200 py-3 px-10 flex items-center justify-between flex-wrap gap-y-2">
<Input
size="tiny"
className="w-52"
placeholder="Search for a job"
icon={<Search />}
value={search}
onChange={(e) => onSearchChange(e.target.value)}
onKeyDown={handleKeyDown}
actions={[
search && (
<Button
key="clear-search"
size="tiny"
type="text"
icon={<X />}
onClick={onClearSearch}
className="p-0 h-5 w-5"
/>
),
]}
/>
<div className="flex items-center gap-x-2">
<Button type="default" icon={<RefreshCw />} loading={isRefreshing} onClick={onRefresh}>
Refresh
</Button>
<Button onClick={onCreateJob}>Create job</Button>
</div>
</div>
)
}