mirror of
https://github.com/supabase/supabase.git
synced 2026-10-09 11:25:06 +03:00
Adds a standalone **Enable cleanup** button to the Cron Jobs page header so users can schedule the daily `delete-job-run-details` cleanup job proactively — previously this was only reachable inside the conditional "table too big" overflow dialog. Addresses [FE-3724](https://linear.app/supabase/issue/FE-3724/enable-pg-cron-cleanup-job-from-ui-and-api) (the UI half; the Management API half needs platform-side work). **Added:** - `Enable cleanup` button in the cron jobs header (left of Refresh), hidden while the existence check loads and whenever a `delete-job-run-details` job already exists - Confirmation dialog with a retention-period select (defaults to 7 days), live SQL preview, and telemetry (`cron_job_cleanup_enable_button_clicked` with `origin` + `retentionInterval`) - Component tests (MSW) for visibility gating and the schedule/cancel flows - E2E regression test for the full schedule → delete → button-reappears cycle **Fixed:** - Name-based `useCronJobQuery` lookup: the `queryFn` dropped the `name` param, and a not-found job returned `undefined` (rejected by react-query v5) — now passes `name` through and returns `CronJob | null` - Cache invalidation gaps: create/delete now invalidate the whole cron-jobs prefix (list, count, job details), so the footer count updates after create/delete and the button reappears after the cleanup job is deleted. The schedule mutation deliberately invalidates only the existence check + count (see inline comment) - Pre-existing e2e leak: the cleanup-workflow test left `delete-job-run-details` scheduled; it now cleans up after itself ## Screenshots | Header button | Dialog | | --- | --- | | <img width="890" height="325" alt="Screenshot 2026-07-22 at 9 44 40 PM" src="https://github.com/user-attachments/assets/966cd640-d8a6-4c8f-92e7-73151bf4de9c" /> | <img width="512" height="461" alt="fe3724-dialog" src="https://github.com/user-attachments/assets/6be1785f-cc7e-4048-a648-9ef260b0949f" /> | ## To test - Go to a project's Integrations → Cron → Jobs with pg_cron enabled and no `delete-job-run-details` job → the `Enable cleanup` button shows next to Refresh - Open the dialog, switch retention intervals → the SQL preview updates; confirm → success toast, the job appears in the grid (`0 12 * * *`), and the button disappears without a reload - Delete the `delete-job-run-details` job from the grid → the button reappears without a reload - Create then delete any other job → the footer `Total: N jobs` count updates both ways without a reload - Regression: with the high-query-cost banner forced (or via the e2e), the overflow dialog's "Schedule cleanup job" step still shows its success state — the dialog must not close mid-flow <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Summary by CodeRabbit * **New Features** * Added an **Enable cleanup** action to the Cron Jobs tab header, including a retention selector and SQL preview. * Enabling schedules the daily cleanup, shows a success toast, updates the grid, and hides the enable button; **Cancel** closes the dialog without scheduling. * **Bug Fixes** * Improved cron job lookup to work by name when needed. * Refreshed related cron job data more reliably after scheduling and deletion. * **Telemetry** * Added an event for cleanup enable button clicks. * **Tests** * Added component and Playwright coverage for enable/cancel/schedule/delete and cleanup banner flows. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
72 lines
2.0 KiB
TypeScript
72 lines
2.0 KiB
TypeScript
import { RefreshCw, Search, X } from 'lucide-react'
|
|
import type { KeyboardEvent, Ref } from 'react'
|
|
import { Button } from 'ui'
|
|
import { Input } from 'ui-patterns/DataInputs/Input'
|
|
|
|
import { EnableCleanupButton } from './CronJobsTab.EnableCleanupButton'
|
|
import { onSearchInputEscape } from '@/lib/keyboard'
|
|
|
|
interface CronJobsTabHeaderProps {
|
|
search: string
|
|
isRefreshing: boolean
|
|
searchInputRef?: Ref<HTMLInputElement>
|
|
onSearchChange: (value: string) => void
|
|
onSearchSubmit: () => void
|
|
onClearSearch: () => void
|
|
onRefresh: () => void
|
|
onCreateJob: () => void
|
|
}
|
|
|
|
export const CronJobsTabHeader = ({
|
|
search,
|
|
isRefreshing,
|
|
searchInputRef,
|
|
onSearchChange,
|
|
onSearchSubmit,
|
|
onClearSearch,
|
|
onRefresh,
|
|
onCreateJob,
|
|
}: CronJobsTabHeaderProps) => {
|
|
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
|
onSearchInputEscape(search, onClearSearch)(event)
|
|
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
|
|
ref={searchInputRef}
|
|
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"
|
|
variant="text"
|
|
icon={<X />}
|
|
onClick={onClearSearch}
|
|
className="p-0 h-5 w-5"
|
|
/>
|
|
),
|
|
]}
|
|
/>
|
|
|
|
<div className="flex items-center gap-x-2">
|
|
<EnableCleanupButton onScheduled={onRefresh} />
|
|
<Button variant="default" icon={<RefreshCw />} loading={isRefreshing} onClick={onRefresh}>
|
|
Refresh
|
|
</Button>
|
|
<Button onClick={onCreateJob}>Create job</Button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|