mirror of
https://github.com/supabase/supabase.git
synced 2026-10-08 02:45:07 +03:00
## What kind of change does this PR introduce? Studio data-layer refactor. ## What is the current behavior? Pipeline creation, editing, and validation build similar destination and pipeline payloads separately. The duplicated mappings rely on type assertions and can drift between actions. ## What is the new behavior? Uses shared typed builders for create, update, and validation payloads across the existing destinations. Update payloads continue to omit blank secrets, while create payloads preserve their current values. This PR does not add table partitioning configuration. ## To test This is a data-layer refactor. No visible behaviour should change. 1. Open **Database > Replication** and click **Start a new pipeline**. 2. Select **BigQuery**, or any other enabled destination. 3. Edit a few non-secret fields and expand **Advanced settings**. 4. Confirm the form remains usable and no runtime errors appear. Create, update, validation, and secret-handling behaviour is covered by the focused tests and CI. Deploy previews and fresh local projects do not have the existing destinations or credentials needed to exercise those paths manually. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved replication destination configuration handling during creation, updates, and validation. * Applied consistent configuration mapping across supported destination types. * Ensured blank secret values are omitted during updates while retained when creating destinations. * Standardized table synchronization defaults when no specific setting is provided. * **Tests** * Added coverage for BigQuery configuration mapping and secret handling. * Updated DuckLake tests for destination updates. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
198 lines
6.6 KiB
TypeScript
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
|
|
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>
|
|
)
|
|
}
|