mirror of
https://github.com/supabase/supabase.git
synced 2026-09-07 02:20:52 +08:00
<!-- ccr-slack-attribution --> _Requested by **Pam Chia** · [Slack thread](https://supabase.slack.com/archives/C076KTY11DF/p1785115156767339?thread_ts=1785115156.767339&cid=C076KTY11DF)_ ## What kind of change does this PR introduce? Bug fix (telemetry). ## What is the current behavior? Clicking the header "Enable cleanup" button fires `cron_job_cleanup_enable_button_clicked` when it merely OPENS the confirmation dialog (`origin: 'header'`), and fires it AGAIN when the dialog is confirmed (`origin: 'dialog'` + `retentionInterval`). So every successful enable logs the event twice, and a naive `count(cron_job_cleanup_enable_button_clicked)` roughly doubles the true number of cleanups enabled. The dual-fire was introduced in #48200. ## What is the new behavior? Opening the dialog fires a new `cron_job_cleanup_dialog_opened` event, and `cron_job_cleanup_enable_button_clicked` fires only on confirm — when cleanup is actually scheduled. Each event now maps 1:1 to a distinct user action. **How:** - Added `cron_job_cleanup_dialog_opened` to the shared telemetry catalog (`packages/common/telemetry-constants.ts`). - Removed the now-redundant `origin` property from `cron_job_cleanup_enable_button_clicked` (the two events encode what `origin` used to); kept `retentionInterval`. - Updated the emit sites in `apps/studio/components/interfaces/Integrations/CronJobs/CronJobsTab.EnableCleanupButton.tsx`: the header open now sends `cron_job_cleanup_dialog_opened`; the dialog confirm sends `cron_job_cleanup_enable_button_clicked` with just `retentionInterval`. ## Additional context `origin` already technically separated the two paths (`count(origin='dialog')` gave the true number), but splitting into two named events removes the footgun of anyone aggregating the raw event. Note for reviewers: I kept the existing event key `cron_job_cleanup_enable_button_clicked` for the confirm path rather than renaming it to something like `cron_job_cleanup_enabled` — happy to rename if preferred, but keeping the key avoids churn on such a new event. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Analytics** * Improved tracking for the cron job cleanup flow by distinguishing when the cleanup confirmation dialog is opened from when cleanup is enabled. * Updated event details to more accurately reflect the cleanup scheduling and confirmation steps. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude <noreply@anthropic.com>
145 lines
4.7 KiB
TypeScript
145 lines
4.7 KiB
TypeScript
import { CRON_CLEANUP_JOB_NAME, getScheduleDeleteCronJobRunDetailsSql } from '@supabase/pg-meta'
|
|
import { useState } from 'react'
|
|
import { toast } from 'sonner'
|
|
import {
|
|
Button,
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogSection,
|
|
DialogSectionSeparator,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from 'ui'
|
|
import { CodeBlock } from 'ui-patterns/CodeBlock'
|
|
|
|
import { CLEANUP_INTERVALS } from './CronJobsTab.constants'
|
|
import { useCronJobQuery } from '@/data/database-cron-jobs/database-cron-job-query'
|
|
import { useScheduleCronJobRunDetailsCleanupMutation } from '@/data/database-cron-jobs/schedule-clean-up-mutation'
|
|
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
|
import { useTrack } from '@/lib/telemetry/track'
|
|
|
|
const DEFAULT_CLEANUP_INTERVAL =
|
|
CLEANUP_INTERVALS.find((option) => option.value === '7 days')?.value ?? CLEANUP_INTERVALS[0].value
|
|
|
|
interface EnableCleanupButtonProps {
|
|
onScheduled: () => void
|
|
}
|
|
|
|
/**
|
|
* One-click action to schedule the daily cleanup job that trims old rows from
|
|
* cron.job_run_details. Hidden once the cleanup job already exists — the job
|
|
* itself then shows up in the jobs grid.
|
|
*/
|
|
export const EnableCleanupButton = ({ onScheduled }: EnableCleanupButtonProps) => {
|
|
const track = useTrack()
|
|
const { data: project } = useSelectedProjectQuery()
|
|
|
|
const [open, setOpen] = useState(false)
|
|
const [cleanupInterval, setCleanupInterval] = useState(DEFAULT_CLEANUP_INTERVAL)
|
|
|
|
const { data: cleanupJob, isSuccess } = useCronJobQuery({
|
|
projectRef: project?.ref,
|
|
connectionString: project?.connectionString,
|
|
name: CRON_CLEANUP_JOB_NAME,
|
|
})
|
|
|
|
const { mutate: scheduleCleanup, isPending: isScheduling } =
|
|
useScheduleCronJobRunDetailsCleanupMutation({
|
|
onSuccess: () => {
|
|
toast.success('Scheduled daily cleanup job.')
|
|
setOpen(false)
|
|
onScheduled()
|
|
},
|
|
})
|
|
|
|
if (!isSuccess || cleanupJob !== null) return null
|
|
|
|
const onConfirm = () => {
|
|
if (!project?.ref) {
|
|
return toast.error('There was an error scheduling the cleanup. Please try again.')
|
|
}
|
|
track('cron_job_cleanup_enable_button_clicked', {
|
|
retentionInterval: cleanupInterval,
|
|
})
|
|
scheduleCleanup({
|
|
projectRef: project.ref,
|
|
connectionString: project.connectionString,
|
|
interval: cleanupInterval,
|
|
})
|
|
}
|
|
|
|
return (
|
|
<Dialog
|
|
open={open}
|
|
onOpenChange={(isOpen) => {
|
|
setOpen(isOpen)
|
|
if (isOpen) track('cron_job_cleanup_dialog_opened')
|
|
}}
|
|
>
|
|
<DialogTrigger asChild>
|
|
<Button variant="default">Enable cleanup</Button>
|
|
</DialogTrigger>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>Enable automatic cleanup</DialogTitle>
|
|
<DialogDescription>
|
|
Schedules a daily job that deletes old cron job run records
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<DialogSectionSeparator />
|
|
<DialogSection className="flex flex-col gap-y-4">
|
|
<p className="text-sm">
|
|
Every cron job run is recorded in the{' '}
|
|
<code className="text-code-inline break-keep!">cron.job_run_details</code> table.
|
|
Without periodic cleanup, the table grows indefinitely and bloats the database.
|
|
</p>
|
|
<div className="flex flex-col gap-y-2 text-sm">
|
|
<p className="text-foreground">Delete run history</p>
|
|
<div className="sm:w-64">
|
|
<Select
|
|
disabled={isScheduling}
|
|
value={cleanupInterval}
|
|
onValueChange={setCleanupInterval}
|
|
>
|
|
<SelectTrigger className="w-full">
|
|
<SelectValue placeholder="Select an interval" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{CLEANUP_INTERVALS.map((option) => (
|
|
<SelectItem key={option.value} value={option.value}>
|
|
{option.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
<CodeBlock
|
|
hideLineNumbers
|
|
language="sql"
|
|
value={getScheduleDeleteCronJobRunDetailsSql(cleanupInterval)}
|
|
className="py-3 px-4 text-xs"
|
|
wrapperClassName="max-w-full"
|
|
/>
|
|
</DialogSection>
|
|
<DialogFooter>
|
|
<Button variant="default" disabled={isScheduling} onClick={() => setOpen(false)}>
|
|
Cancel
|
|
</Button>
|
|
<Button loading={isScheduling} onClick={onConfirm}>
|
|
Enable cleanup
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|