Files
supabase/apps/studio/components/interfaces/Database/Replication/RetryOptionsDropdown.tsx
Riccardo Busetti 1ea8c656db feat(replication): Add new UI for new table state handling (#37662)
* feat(replication): Add new UI for new table state handling

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix

* Improve

* Fix

* Fix

* Fix TS

* Some UI tweaks

* Tweaaakk

* Fix semantics

* Small optimization

* Some adjustments

* Bit more fixes

* Small UI tweak to simplify UI

* Fixy fix

* gahhh tiny tiny tiny

* Last bit of cleanup

* I swear one final tweak

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2025-08-07 12:16:03 +02:00

121 lines
3.8 KiB
TypeScript

import { ChevronDown, RotateCcw, Undo2 } from 'lucide-react'
import { useState } from 'react'
import { toast } from 'sonner'
import { useParams } from 'common'
import { RollbackType, useRollbackTableMutation } from 'data/replication/rollback-table-mutation'
import { useStartPipelineMutation } from 'data/replication/start-pipeline-mutation'
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from 'ui'
const RETRY_OPTIONS = [
{
type: 'individual' as RollbackType,
icon: <Undo2 className="w-3 h-3 mt-0.5" />,
title: 'Rollback to previous state',
description: 'Restart from the last working state (may not be enough in some cases)',
},
{
type: 'full' as RollbackType,
icon: <RotateCcw className="w-3 h-3 mt-0.5" />,
title: 'Reset from scratch',
description: 'Completely restart the table replication',
},
] as const
interface RetryOptionsDropdownProps {
tableId: number
tableName: string
}
export const RetryOptionsDropdown = ({ tableId, tableName }: RetryOptionsDropdownProps) => {
const { ref: projectRef, pipelineId: _pipelineId } = useParams()
const [isOpen, setIsOpen] = useState(false)
const { mutate: rollbackTable, isLoading: isRollingBack } = useRollbackTableMutation({
onSuccess: (_, vars) => {
const { projectRef, pipelineId } = vars
toast.success(`Table "${tableName}" rolled back successfully`)
startPipeline({ projectRef, pipelineId })
},
onError: (error, vars) => {
const { rollbackType } = vars
toast.error(
`Failed to ${rollbackType === 'full' ? 'reset' : 'rollback'} table: ${error.message}`
)
},
})
const { mutate: startPipeline, isLoading: isRestartingPipeline } = useStartPipelineMutation({
onSuccess: () => {
toast.success('Pipeline restarted successfully')
setIsOpen(false)
},
onError: (error) => {
toast.error(`Failed to restart pipeline: ${error.message}`)
setIsOpen(false)
},
})
const isLoading = isRollingBack || isRestartingPipeline
const handleRollback = async (rollbackType: RollbackType) => {
if (!projectRef) return toast.error('Project ref is required')
if (!_pipelineId) return toast.error('Pipeline ID is required')
const pipelineId = Number(_pipelineId)
rollbackTable({
projectRef,
pipelineId,
tableId,
rollbackType,
})
}
return (
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
<DropdownMenuTrigger asChild>
<Button
size="tiny"
type="default"
loading={isLoading}
disabled={isLoading}
className="w-min"
iconRight={<ChevronDown />}
aria-label={`Rollback ${tableName}`}
>
Rollback
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-72" aria-label="Rollback options">
{RETRY_OPTIONS.map((option) => (
<DropdownMenuItem
key={option.type}
disabled={isLoading}
className="flex flex-col items-start px-3 py-2 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50"
aria-describedby={`rollback-${option.type}-description`}
onClick={() => handleRollback(option.type)}
>
<div className="flex items-start gap-x-2 w-full">
<div className="min-w-4">{option.icon}</div>
<div
id={`rollback-${option.type}-description`}
className="flex flex-col gap-y-1 text-xs"
>
<p>{option.title}</p>
<p className="text-foreground-light">{option.description}</p>
</div>
</div>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
)
}