Files
supabase/apps/studio/components/interfaces/Database/Replication/BatchRestartDialog.tsx
T
Danny WhiteandJoshen Lim 8dd97d75aa refactor(studio): use v2 replication publication APIs (#49844)
## What kind of change does this PR introduce?

Studio data-layer migration.

## What is the current behavior?

Studio loads complete publication details through the original bulk
endpoint and creates publications by executing SQL against the source
database. Publication and source-table data use names where stable table
IDs are available.

## What is the new behavior?

Uses the v2 publication-name, publication-detail, publication mutation,
and source-table endpoints. The existing creation sheet continues to
behave the same, including publishing partition changes through the
parent table by default. Initial-sync selection and Analytics Bucket
associations now consume the selected publication detail. Generated
platform API types and their required nullability updates are included.

The generated Platform contract accounts for roughly 10,000 changed
lines in this PR.

## Dependency

Depends on the v2 source table, table column, and publication endpoints
from
[supabase/platform#37505](https://github.com/supabase/platform/pull/37505),
which are deployed to production.

## To test

1. Open the pipeline creation sheet and select an existing publication.
2. Create a publication with mixed-case schema and table names, then
confirm the table names are shown while stable IDs are submitted.
3. Exercise all four initial-sync policies, including selecting
individual tables.
4. Reopen the publication and table selectors and confirm they refresh
without replacing populated options.
5. Edit and delete a publication.
6. Open an Analytics Bucket associated with a pipeline and confirm its
publication tables resolve correctly.
7. Confirm unlimited WAL retention renders as Unlimited on pipeline
status.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Improved replication publication setup with on-demand table loading,
refresh controls, clearer table labels, and streamlined publication
selection.
- Publication creation and updates now use the latest replication API
and table-based configurations.
- Added clearer handling for tables removed from publications, including
stale-selection warnings.

- **Bug Fixes**
- Prevented table selections from carrying over when switching
publications.
- Improved replication status displays when lag or WAL metrics are
unavailable.
- Updated replication deletion and table management for the latest API
behavior.

- **Tests**
- Expanded coverage for publication creation, table selection, stale
tables, loading states, and replication metrics.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2026-09-08 11:37:59 +10:00

198 lines
6.6 KiB
TypeScript

import { useParams } from 'common'
import { useMemo } from 'react'
import { toast } from 'sonner'
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from 'ui'
import { PipelineStatusName } from './Replication.constants'
import { RestartCostEstimate } from './RestartCostEstimate'
import { getTableCopyTargets } from './TableSyncCopy.utils'
import { ReplicationPipelineTableStatus } from '@/data/replication/pipeline-replication-status-query'
import { useRollbackTablesMutation } from '@/data/replication/rollback-tables-mutation'
import type { TableSyncCopyConfig } from '@/data/replication/types'
interface BatchRestartDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
mode: 'all' | 'errored'
tables: ReplicationPipelineTableStatus[]
sourceId?: number
publicationName?: string
tableSyncCopy?: TableSyncCopyConfig | null
pipelineStatusName?: PipelineStatusName
onRestartStart?: (tableIds: number[]) => void
onRestartComplete?: (tableIds: number[]) => void
}
export const BatchRestartDialog = ({
open,
onOpenChange,
mode,
tables,
sourceId,
publicationName,
tableSyncCopy,
pipelineStatusName,
onRestartStart,
onRestartComplete,
}: BatchRestartDialogProps) => {
const { ref: projectRef, pipelineId: _pipelineId } = useParams()
const pipelineId = Number(_pipelineId)
const affectedTables = useMemo(() => {
if (mode === 'all') {
return tables
} else {
return tables.filter((table) => table.state.name === 'error')
}
}, [mode, tables])
const affectedTableIds = useMemo(() => affectedTables.map((table) => table.id), [affectedTables])
const copiedTables = useMemo(
() => getTableCopyTargets(affectedTables, tableSyncCopy),
[affectedTables, tableSyncCopy]
)
const initialSyncDescription =
copiedTables.length === 0 ? (
<li>
<strong>No table will run an initial sync.</strong> Replication will resume with new changes
only, without syncing existing source rows. There is no additional initial sync charge.
</li>
) : copiedTables.length === affectedTables.length ? (
<li>
<strong>
{copiedTables.length === 1
? 'The table will run its initial sync again.'
: `All ${copiedTables.length} tables will run initial sync again.`}
</strong>{' '}
Existing source rows will be synced again. Data successfully processed during this initial
sync is billed again.
</li>
) : (
<li>
<strong>
{copiedTables.length} of {affectedTables.length} tables will run initial sync again.
</strong>{' '}
Existing source rows for those tables will be synced again and billed again. The remaining
tables will resume replication with new changes only.
</li>
)
const { mutateAsync: rollbackTables, isPending: isResetting } = useRollbackTablesMutation({
onSuccess: (data) => {
const count = data.tables.length
toast.success(
`Restarting replication for ${count} table${count > 1 ? 's' : ''}. Pipeline will restart automatically.`
)
},
onSettled: () => {
onRestartComplete?.(affectedTableIds)
onOpenChange(false)
},
onError: (error) => {
toast.error(`Failed to restart replication: ${error.message}`)
},
})
const handleReset = async () => {
if (!projectRef) return toast.error('Project ref is required')
onRestartStart?.(affectedTableIds)
try {
await rollbackTables({
projectRef,
pipelineId,
target: mode === 'all' ? { type: 'all_tables' } : { type: 'all_errored_tables' },
rollbackType: 'full',
pipelineStatusName,
})
} catch (error) {}
}
const dialogContent =
mode === 'all'
? {
title: 'Restart all tables',
description: (
<div className="space-y-3 text-sm">
<p>
This will restart replication for all {affectedTables.length} table
{affectedTables.length === 1 ? '' : 's'} in this pipeline from scratch:
</p>
<ul className="list-disc list-inside space-y-1.5 pl-2">
{initialSyncDescription}
<li>
<strong>All downstream data will be deleted.</strong> All replicated data will be
removed.
</li>
<li>
<strong>The pipeline will restart automatically.</strong> This is required to
apply this change.
</li>
</ul>
</div>
),
action: 'Restart all tables',
}
: {
title: 'Restart failed tables',
description: (
<div className="space-y-3 text-sm">
<p>
This will restart replication for all{' '}
<strong>{affectedTables.length} currently failed tables</strong> from scratch:
</p>
<ul className="list-disc list-inside space-y-1.5 pl-2">
{initialSyncDescription}
<li>
<strong>Existing downstream data will be deleted.</strong> Replicated data for
these tables will be removed.
</li>
<li>
<strong>Tables that are not failed remain untouched.</strong> The request resets
every table that is failed when it runs.
</li>
<li>
<strong>The pipeline will restart automatically.</strong> This is required to
apply this change.
</li>
</ul>
</div>
),
action: 'Restart failed tables',
}
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{dialogContent.title}</AlertDialogTitle>
<AlertDialogDescription asChild>{dialogContent.description}</AlertDialogDescription>
</AlertDialogHeader>
<RestartCostEstimate
open={open}
projectRef={projectRef}
sourceId={sourceId}
publicationName={publicationName}
tables={copiedTables}
/>
<AlertDialogFooter>
<AlertDialogCancel disabled={isResetting}>Cancel</AlertDialogCancel>
<AlertDialogAction disabled={isResetting} onClick={handleReset} variant="warning">
{isResetting ? 'Restarting replication...' : dialogContent.action}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}