mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
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>
This commit is contained in:
1 parent
e6e24aadad
commit
1ea8c656db
12 files changed
+785
-253
No files matched your search
@@ -133,15 +133,15 @@ export const Destinations = () => {
|
||||
'flex flex-col px-10 rounded-lg justify-center items-center py-8 mt-4'
|
||||
)}
|
||||
>
|
||||
<h4 className="text-lg">Send data to your first destination</h4>
|
||||
<p className="prose text-sm text-center mt-2">
|
||||
<h4>Send data to your first destination</h4>
|
||||
<p className="prose text-sm text-center mt-1 max-w-full">
|
||||
Use destinations to improve performance or run analysis on your data via
|
||||
integrations like BigQuery
|
||||
</p>
|
||||
<Button
|
||||
icon={<Plus />}
|
||||
onClick={() => setShowNewDestinationPanel(true)}
|
||||
className="mt-6"
|
||||
className="mt-4"
|
||||
>
|
||||
Add destination
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { useParams } from 'common'
|
||||
import { InlineLink } from 'components/ui/InlineLink'
|
||||
import { TableState } from './ReplicationPipelineStatus.types'
|
||||
import { isValidRetryPolicy } from './ReplicationPipelineStatus.utils'
|
||||
import { RetryCountdown } from './RetryCountdown'
|
||||
import { RetryOptionsDropdown } from './RetryOptionsDropdown'
|
||||
|
||||
interface ErroredTableDetailsProps {
|
||||
state: Extract<TableState['state'], { name: 'error' }>
|
||||
tableName: string
|
||||
tableId: number
|
||||
}
|
||||
|
||||
export const ErroredTableDetails = ({ state, tableName, tableId }: ErroredTableDetailsProps) => {
|
||||
const { ref: projectRef } = useParams()
|
||||
const retryPolicy = state.retry_policy.policy
|
||||
|
||||
if (!isValidRetryPolicy(state.retry_policy)) {
|
||||
return (
|
||||
<div
|
||||
role="region"
|
||||
className="flex flex-col gap-y-3"
|
||||
aria-label={`Error details for table ${tableName}`}
|
||||
>
|
||||
{state.solution && <div className="text-xs text-foreground-light">{state.solution}</div>}
|
||||
<div className="text-xs text-foreground-lighter">Invalid retry policy configuration</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div role="region" aria-label={`Error details for table ${tableName}`}>
|
||||
{retryPolicy === 'no_retry' ? (
|
||||
<p className="text-xs text-foreground-lighter">
|
||||
This error requires manual intervention from our{' '}
|
||||
<InlineLink
|
||||
className="text-foreground-lighter hover:text-foreground"
|
||||
href={`/support?projectRef=${projectRef}&category=dashboard_bug&subject=Database%20replication%20error&error=${state.reason}`}
|
||||
>
|
||||
support
|
||||
</InlineLink>
|
||||
. Alternatively, you may also recreate the pipeline.
|
||||
</p>
|
||||
) : retryPolicy === 'manual_retry' ? (
|
||||
<div className="flex flex-col gap-y-2 text-foreground-lighter">
|
||||
<p className="text-xs">{state.solution}. You may thereafter rollback the pipeline.</p>
|
||||
<RetryOptionsDropdown tableId={tableId} tableName={tableName} />
|
||||
</div>
|
||||
) : retryPolicy === 'timed_retry' ? (
|
||||
<div className="flex flex-col text-foreground-lighter">
|
||||
<p className="text-xs">
|
||||
A retry will be triggered automatically by restarting the pipeline on this table.
|
||||
</p>
|
||||
<RetryCountdown nextRetryTime={state.retry_policy.next_retry} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import { ReplicationPipelineStatusData } from 'data/replication/pipeline-status-query'
|
||||
import { AlertTriangle, Loader2 } from 'lucide-react'
|
||||
import { PipelineStatusRequestStatus } from 'state/replication-pipeline-request-status'
|
||||
import { ResponseError } from 'types'
|
||||
import { cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
|
||||
import { cn, Tooltip, TooltipContent, TooltipTrigger, WarningIcon } from 'ui'
|
||||
import ShimmeringLoader from 'ui-patterns/ShimmeringLoader'
|
||||
import { getPipelineStateMessages, PIPELINE_ERROR_MESSAGES } from './Pipeline.utils'
|
||||
import { getPipelineStateMessages } from './Pipeline.utils'
|
||||
|
||||
export enum PipelineStatusName {
|
||||
FAILED = 'failed',
|
||||
@@ -15,9 +14,6 @@ export enum PipelineStatusName {
|
||||
UNKNOWN = 'unknown',
|
||||
}
|
||||
|
||||
// Type alias for better readability
|
||||
type FailedStatus = Extract<ReplicationPipelineStatusData['status'], { name: 'failed' }>
|
||||
|
||||
interface PipelineStatusProps {
|
||||
pipelineStatus: ReplicationPipelineStatusData['status'] | undefined
|
||||
error: ResponseError | null
|
||||
@@ -35,32 +31,6 @@ export const PipelineStatus = ({
|
||||
isSuccess,
|
||||
requestStatus,
|
||||
}: PipelineStatusProps) => {
|
||||
const isFailedStatus = (
|
||||
status: ReplicationPipelineStatusData['status'] | undefined
|
||||
): status is FailedStatus => {
|
||||
return (
|
||||
status !== null &&
|
||||
status !== undefined &&
|
||||
typeof status === 'object' &&
|
||||
status.name === PipelineStatusName.FAILED
|
||||
)
|
||||
}
|
||||
|
||||
const renderFailedStatus = () => {
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex items-center gap-2 text-sm text-destructive">
|
||||
<AlertTriangle className="w-3 h-3" />
|
||||
<span>Failed</span>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
{getPipelineStateMessages(requestStatus, 'failed').message}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
// Map backend statuses to UX-friendly display
|
||||
const getStatusConfig = () => {
|
||||
const statusName =
|
||||
@@ -89,15 +59,15 @@ export const PipelineStatus = ({
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Failed status object
|
||||
if (isFailedStatus(pipelineStatus)) {
|
||||
return {
|
||||
isFailedStatus: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (pipelineStatus && typeof pipelineStatus === 'object' && 'name' in pipelineStatus) {
|
||||
switch (pipelineStatus.name) {
|
||||
case PipelineStatusName.FAILED:
|
||||
return {
|
||||
label: 'Failed',
|
||||
dot: <AlertTriangle className="w-3 h-3 text-destructive-600" />,
|
||||
color: 'text-destructive-600',
|
||||
tooltip: stateMessages.message,
|
||||
}
|
||||
case PipelineStatusName.STARTING:
|
||||
return {
|
||||
label: 'Starting',
|
||||
@@ -151,24 +121,25 @@ export const PipelineStatus = ({
|
||||
<>
|
||||
{isLoading && <ShimmeringLoader />}
|
||||
{isError && (
|
||||
<AlertError error={error} subject={PIPELINE_ERROR_MESSAGES.RETRIEVE_PIPELINE_STATUS} />
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<WarningIcon />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" className="w-64 text-center">
|
||||
Unable to retrieve status: {error?.message}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
{isSuccess && (
|
||||
<>
|
||||
{statusConfig.isFailedStatus ? (
|
||||
<div className="relative">{renderFailedStatus()}</div>
|
||||
) : (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className={cn('flex items-center gap-2 text-sm w-min', statusConfig.color)}>
|
||||
{statusConfig.dot}
|
||||
<span>{statusConfig.label}</span>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">{statusConfig.tooltip}</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className={cn('flex items-center gap-2 text-sm w-min', statusConfig.color)}>
|
||||
{statusConfig.dot}
|
||||
<span>{statusConfig.label}</span>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">{statusConfig.tooltip}</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
+96
-131
@@ -1,4 +1,4 @@
|
||||
import { Activity, AlertTriangle, ChevronLeft, Copy, ExternalLink, Search } from 'lucide-react'
|
||||
import { Activity, ChevronLeft, ExternalLink, Search, X } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
@@ -16,14 +16,20 @@ import {
|
||||
PipelineStatusRequestStatus,
|
||||
usePipelineRequestStatus,
|
||||
} from 'state/replication-pipeline-request-status'
|
||||
import { Badge, Button, cn, copyToClipboard, Input_Shadcn_ } from 'ui'
|
||||
import { Badge, Button, cn } from 'ui'
|
||||
import { GenericSkeletonLoader } from 'ui-patterns'
|
||||
import { Input } from 'ui-patterns/DataInputs/Input'
|
||||
import { ErroredTableDetails } from './ErroredTableDetails'
|
||||
import { getStatusName, PIPELINE_ERROR_MESSAGES } from './Pipeline.utils'
|
||||
import { PipelineStatus } from './PipelineStatus'
|
||||
import { STATUS_REFRESH_FREQUENCY_MS } from './Replication.constants'
|
||||
import { TableState } from './ReplicationPipelineStatus.types'
|
||||
import { getDisabledStateConfig, getStatusConfig } from './ReplicationPipelineStatus.utils'
|
||||
|
||||
/**
|
||||
* Component for displaying replication pipeline status and table replication details.
|
||||
* Supports both legacy 'error' state and new 'errored' state with retry policies.
|
||||
*/
|
||||
export const ReplicationPipelineStatus = () => {
|
||||
const { ref: projectRef, pipelineId: _pipelineId } = useParams()
|
||||
const [filterString, setFilterString] = useState<string>('')
|
||||
@@ -84,8 +90,6 @@ export const ReplicationPipelineStatus = () => {
|
||||
table.table_name.toLowerCase().includes(filterString.toLowerCase())
|
||||
)
|
||||
|
||||
const errorTables = tableStatuses.filter((table: TableState) => table.state.name === 'error')
|
||||
const hasErrors = errorTables.length > 0
|
||||
const isPipelineRunning = statusName === 'started'
|
||||
const hasTableData = tableStatuses.length > 0
|
||||
const isEnablingDisabling =
|
||||
@@ -93,19 +97,6 @@ export const ReplicationPipelineStatus = () => {
|
||||
requestStatus === PipelineStatusRequestStatus.DisableRequested
|
||||
const showDisabledState = !isPipelineRunning || isEnablingDisabling
|
||||
|
||||
const handleCopyTableStatus = async (tableName: string, state: TableState['state']) => {
|
||||
const statusText = `Table: ${tableName}\nStatus: ${state.name}${
|
||||
'message' in state ? `\nError: ${state.message}` : ''
|
||||
}${'lag' in state ? `\nLag: ${state.lag}ms` : ''}`
|
||||
|
||||
try {
|
||||
await copyToClipboard(statusText)
|
||||
toast.success('Table status copied to clipboard')
|
||||
} catch {
|
||||
toast.error(PIPELINE_ERROR_MESSAGES.COPY_TABLE_STATUS)
|
||||
}
|
||||
}
|
||||
|
||||
const onTogglePipeline = async () => {
|
||||
if (!projectRef) {
|
||||
return console.error('Project ref is required')
|
||||
@@ -158,19 +149,32 @@ export const ReplicationPipelineStatus = () => {
|
||||
className="absolute left-2 top-1/2 transform -translate-y-1/2 text-foreground-lighter"
|
||||
size={14}
|
||||
/>
|
||||
<Input_Shadcn_
|
||||
<Input
|
||||
className="pl-7 h-[26px] text-xs"
|
||||
placeholder="Search for tables"
|
||||
value={filterString}
|
||||
disabled={isPipelineError}
|
||||
onChange={(e) => setFilterString(e.target.value)}
|
||||
actions={
|
||||
filterString.length > 0
|
||||
? [
|
||||
<X
|
||||
key="close"
|
||||
className="mx-2 cursor-pointer text-foreground"
|
||||
size={14}
|
||||
strokeWidth={2}
|
||||
onClick={() => setFilterString('')}
|
||||
/>,
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type={statusName === 'stopped' ? 'primary' : 'default'}
|
||||
onClick={() => onTogglePipeline()}
|
||||
loading={isPipelineError || isStartingPipeline || isStoppingPipeline}
|
||||
disabled={!['failed', 'started', 'stopped'].includes(statusName ?? '')}
|
||||
disabled={!['failed', 'started', 'stopped', 'stopping'].includes(statusName ?? '')}
|
||||
>
|
||||
{statusName === 'stopped' ? 'Enable' : 'Disable'} pipeline
|
||||
</Button>
|
||||
@@ -190,38 +194,6 @@ export const ReplicationPipelineStatus = () => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{hasErrors && (
|
||||
<div className="p-4 border border-destructive-300 bg-destructive-100 rounded-lg">
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertTriangle className="w-5 h-5 text-destructive-600 mt-0.5 shrink-0" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<h4 className="font-medium text-destructive-900 mb-1">
|
||||
{errorTables.length} table{errorTables.length > 1 ? 's' : ''} failed
|
||||
</h4>
|
||||
<p className="text-sm text-destructive-700 mb-3">
|
||||
Some tables encountered replication errors. Check the logs for detailed error
|
||||
information.
|
||||
</p>
|
||||
<Button
|
||||
asChild
|
||||
type="outline"
|
||||
size="tiny"
|
||||
icon={<ExternalLink className="w-3 h-3" />}
|
||||
className="text-destructive-600 border-destructive-300 hover:bg-destructive-50"
|
||||
>
|
||||
<Link
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
href={`/project/${projectRef}/logs/postgres-logs`}
|
||||
>
|
||||
View Logs
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasTableData && (
|
||||
<div className="flex flex-col gap-y-4">
|
||||
{showDisabledState && (
|
||||
@@ -254,95 +226,88 @@ export const ReplicationPipelineStatus = () => {
|
||||
<Table.th key="table">Table</Table.th>,
|
||||
<Table.th key="status">Status</Table.th>,
|
||||
<Table.th key="details">Details</Table.th>,
|
||||
<Table.th key="actions" className="w-16 text-center">
|
||||
Actions
|
||||
</Table.th>,
|
||||
]}
|
||||
body={filteredTableStatuses.map((table: TableState, index: number) => {
|
||||
const statusConfig = getStatusConfig(table.state)
|
||||
return (
|
||||
<Table.tr key={`${table.table_name}-${index}`} className="border-t">
|
||||
<Table.td>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<p>{table.table_name}</p>
|
||||
|
||||
<ButtonTooltip
|
||||
asChild
|
||||
type="text"
|
||||
className="px-1.5"
|
||||
icon={<ExternalLink />}
|
||||
tooltip={{ content: { side: 'bottom', text: 'Open in Table Editor' } }}
|
||||
>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={`/project/${projectRef}/editor/${table.table_id}`}
|
||||
/>
|
||||
</ButtonTooltip>
|
||||
</div>
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
{showDisabledState ? (
|
||||
<Badge variant="default">Not Available</Badge>
|
||||
) : (
|
||||
statusConfig.badge
|
||||
)}
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
{showDisabledState ? (
|
||||
<p className="text-sm text-foreground-lighter">
|
||||
Status unavailable while pipeline is {config.badge.toLowerCase()}
|
||||
</p>
|
||||
) : (
|
||||
body={
|
||||
<>
|
||||
{filteredTableStatuses.length === 0 && hasTableData && (
|
||||
<Table.tr>
|
||||
<Table.td colSpan={3}>
|
||||
<div className="space-y-1">
|
||||
<div className="text-sm text-foreground">{statusConfig.description}</div>
|
||||
{'lag' in table.state && (
|
||||
<div className="text-xs text-foreground-light">
|
||||
Lag: {table.state.lag}ms
|
||||
<p className="text-sm text-foreground">No results found</p>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Your search for "{filterString}" did not return any results
|
||||
</p>
|
||||
</div>
|
||||
</Table.td>
|
||||
</Table.tr>
|
||||
)}
|
||||
{filteredTableStatuses.map((table: TableState, index: number) => {
|
||||
const statusConfig = getStatusConfig(table.state)
|
||||
return (
|
||||
<Table.tr key={`${table.table_name}-${index}`} className="border-t">
|
||||
<Table.td className="align-top">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<p>{table.table_name}</p>
|
||||
|
||||
<ButtonTooltip
|
||||
asChild
|
||||
type="text"
|
||||
className="px-1.5"
|
||||
icon={<ExternalLink />}
|
||||
tooltip={{
|
||||
content: { side: 'bottom', text: 'Open in Table Editor' },
|
||||
}}
|
||||
>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={`/project/${projectRef}/editor/${table.table_id}`}
|
||||
/>
|
||||
</ButtonTooltip>
|
||||
</div>
|
||||
</Table.td>
|
||||
<Table.td className="align-top">
|
||||
{showDisabledState ? (
|
||||
<Badge variant="default">Not Available</Badge>
|
||||
) : (
|
||||
statusConfig.badge
|
||||
)}
|
||||
</Table.td>
|
||||
<Table.td className="align-top">
|
||||
{showDisabledState ? (
|
||||
<p className="text-sm text-foreground-lighter">
|
||||
Status unavailable while pipeline is {config.badge.toLowerCase()}
|
||||
</p>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
<div className="text-sm text-foreground">
|
||||
{statusConfig.description}
|
||||
</div>
|
||||
{'lag' in table.state && (
|
||||
<div className="text-xs text-foreground-light">
|
||||
Lag: {table.state.lag}ms
|
||||
</div>
|
||||
)}
|
||||
{table.state.name === 'error' && (
|
||||
<ErroredTableDetails
|
||||
state={table.state}
|
||||
tableName={table.table_name}
|
||||
tableId={table.table_id}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Table.td>
|
||||
<Table.td className="text-center">
|
||||
<ButtonTooltip
|
||||
type="text"
|
||||
size="tiny"
|
||||
icon={<Copy className="w-3 h-3" />}
|
||||
className="px-1.5"
|
||||
disabled={showDisabledState}
|
||||
onClick={() => handleCopyTableStatus(table.table_name, table.state)}
|
||||
tooltip={{
|
||||
content: {
|
||||
side: 'bottom',
|
||||
text: showDisabledState
|
||||
? `Copy unavailable while pipeline is ${config.badge.toLowerCase()}`
|
||||
: 'Copy status details',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Table.td>
|
||||
</Table.tr>
|
||||
)
|
||||
})}
|
||||
</Table.td>
|
||||
</Table.tr>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{filteredTableStatuses.length === 0 && hasTableData && (
|
||||
<Table.tr>
|
||||
<Table.td colSpan={4}>
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm text-foreground">No results found</p>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Your search for "{filterString}" did not return any results
|
||||
</p>
|
||||
</div>
|
||||
</Table.td>
|
||||
</Table.tr>
|
||||
)}
|
||||
|
||||
{!isStatusLoading && tableStatuses.length === 0 && (
|
||||
<div className="flex flex-col items-center justify-center py-16 px-4">
|
||||
<div className="w-full max-w-sm mx-auto text-center space-y-4">
|
||||
|
||||
+6
-1
@@ -1,3 +1,8 @@
|
||||
export type RetryPolicy =
|
||||
| { policy: 'no_retry' }
|
||||
| { policy: 'manual_retry' }
|
||||
| { policy: 'timed_retry'; next_retry: string }
|
||||
|
||||
export type TableState = {
|
||||
table_id: number
|
||||
table_name: string
|
||||
@@ -6,5 +11,5 @@ export type TableState = {
|
||||
| { name: 'copying_table' }
|
||||
| { name: 'copied_table' }
|
||||
| { name: 'following_wal'; lag: number }
|
||||
| { name: 'error'; message: string }
|
||||
| { name: 'error'; reason: string; solution?: string; retry_policy: RetryPolicy }
|
||||
}
|
||||
+16
-2
@@ -3,7 +3,7 @@ import { Activity, Clock, HelpCircle, Loader2, XCircle } from 'lucide-react'
|
||||
import { PipelineStatusRequestStatus } from 'state/replication-pipeline-request-status'
|
||||
import { Badge } from 'ui'
|
||||
import { getPipelineStateMessages } from './Pipeline.utils'
|
||||
import { TableState } from './ReplicationPipelineStatus.types'
|
||||
import { RetryPolicy, TableState } from './ReplicationPipelineStatus.types'
|
||||
|
||||
export const getStatusConfig = (state: TableState['state']) => {
|
||||
switch (state.name) {
|
||||
@@ -34,7 +34,7 @@ export const getStatusConfig = (state: TableState['state']) => {
|
||||
case 'error':
|
||||
return {
|
||||
badge: <Badge variant="destructive">Error</Badge>,
|
||||
description: state.message,
|
||||
description: <pre className="text-xs font-mono">{state.reason}</pre>,
|
||||
color: 'text-destructive-600',
|
||||
}
|
||||
default:
|
||||
@@ -106,3 +106,17 @@ export const getDisabledStateConfig = ({
|
||||
|
||||
return { title, message, badge, icon, colors }
|
||||
}
|
||||
|
||||
export const isValidRetryPolicy = (policy: any): policy is RetryPolicy => {
|
||||
if (!policy || typeof policy !== 'object' || !policy.policy) return false
|
||||
|
||||
switch (policy.policy) {
|
||||
case 'no_retry':
|
||||
case 'manual_retry':
|
||||
return true
|
||||
case 'timed_retry':
|
||||
return typeof policy.next_retry === 'string'
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
interface RetryCountdownProps {
|
||||
nextRetryTime: string // RFC3339 formatted date
|
||||
}
|
||||
|
||||
interface TimeRemaining {
|
||||
days: number
|
||||
hours: number
|
||||
minutes: number
|
||||
seconds: number
|
||||
isExpired: boolean
|
||||
isInvalid: boolean
|
||||
}
|
||||
|
||||
export const RetryCountdown = ({ nextRetryTime }: RetryCountdownProps) => {
|
||||
const [timeRemaining, setTimeRemaining] = useState<TimeRemaining>({
|
||||
days: 0,
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
seconds: 0,
|
||||
isExpired: false,
|
||||
isInvalid: false,
|
||||
})
|
||||
|
||||
const targetTimestamp = useMemo(() => {
|
||||
try {
|
||||
const date = new Date(nextRetryTime)
|
||||
if (isNaN(date.getTime())) {
|
||||
return null
|
||||
}
|
||||
return date.getTime()
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}, [nextRetryTime])
|
||||
|
||||
const calculateTimeRemaining = useCallback((targetTime: number): TimeRemaining => {
|
||||
const now = Date.now()
|
||||
const difference = targetTime - now
|
||||
|
||||
if (difference <= 0) {
|
||||
return { days: 0, hours: 0, minutes: 0, seconds: 0, isExpired: true, isInvalid: false }
|
||||
}
|
||||
|
||||
const days = Math.floor(difference / (1000 * 60 * 60 * 24))
|
||||
const hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
|
||||
const minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60))
|
||||
const seconds = Math.floor((difference % (1000 * 60)) / 1000)
|
||||
|
||||
return { days, hours, minutes, seconds, isExpired: false, isInvalid: false }
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (targetTimestamp === null) return
|
||||
|
||||
const updateTimer = () => {
|
||||
setTimeRemaining(calculateTimeRemaining(targetTimestamp))
|
||||
}
|
||||
|
||||
updateTimer()
|
||||
const interval = setInterval(updateTimer, 1000)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [targetTimestamp, calculateTimeRemaining])
|
||||
|
||||
const { timeDisplay, statusMessage } = useMemo(() => {
|
||||
if (targetTimestamp === null) {
|
||||
return {
|
||||
timeDisplay: 'Invalid retry time format',
|
||||
statusMessage: '',
|
||||
}
|
||||
}
|
||||
|
||||
const formatTimeUnit = (value: number, unit: string) => {
|
||||
if (value === 0) return null
|
||||
return `${value}${unit.charAt(0)}`
|
||||
}
|
||||
|
||||
let timeDisplay: string
|
||||
let statusMessage: string
|
||||
|
||||
if (timeRemaining.isExpired) {
|
||||
statusMessage = ''
|
||||
timeDisplay = 'Retrying soon...'
|
||||
} else {
|
||||
const parts = [
|
||||
formatTimeUnit(timeRemaining.days, 'day'),
|
||||
formatTimeUnit(timeRemaining.hours, 'hour'),
|
||||
formatTimeUnit(timeRemaining.minutes, 'minute'),
|
||||
formatTimeUnit(timeRemaining.seconds, 'second'),
|
||||
].filter(Boolean)
|
||||
statusMessage = parts.length === 0 ? '' : 'Next retry in:'
|
||||
timeDisplay = parts.length === 0 ? 'Retrying soon...' : parts.join(' ')
|
||||
}
|
||||
|
||||
return { timeDisplay, statusMessage }
|
||||
}, [targetTimestamp, timeRemaining])
|
||||
|
||||
return (
|
||||
<div role="status" aria-live="polite" aria-label={`${statusMessage} ${timeDisplay}`}>
|
||||
<span className="text-xs font-medium">{statusMessage}</span>{' '}
|
||||
{/* [Joshen] It's a bit hard to debug without doing this locally, but we could use CountdownTimerSpan here perhaps */}
|
||||
<span className="text-xs font-mono">{timeDisplay}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
+1
-1
@@ -111,7 +111,7 @@ const RestartServerButton = () => {
|
||||
? 'Unable to restart project as project is not active'
|
||||
: isAwsK8s
|
||||
? 'Project restart is not supported for AWS (Revamped) projects'
|
||||
: '',
|
||||
: undefined,
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { handleError, post } from 'data/fetchers'
|
||||
import type { ResponseError } from 'types'
|
||||
import { replicationKeys } from './keys'
|
||||
|
||||
export type RollbackType = 'individual' | 'full'
|
||||
|
||||
type RollbackTableParams = {
|
||||
projectRef: string
|
||||
pipelineId: number
|
||||
tableId: number
|
||||
rollbackType: RollbackType
|
||||
}
|
||||
|
||||
type RollbackTableResponse = {
|
||||
pipeline_id: number
|
||||
table_id: number
|
||||
new_state: {
|
||||
name: string
|
||||
[key: string]: any
|
||||
}
|
||||
}
|
||||
|
||||
async function rollbackTableState(
|
||||
{ projectRef, pipelineId, tableId, rollbackType }: RollbackTableParams,
|
||||
signal?: AbortSignal
|
||||
): Promise<RollbackTableResponse> {
|
||||
if (!projectRef) throw new Error('Project reference is required')
|
||||
if (!pipelineId) throw new Error('Pipeline ID is required')
|
||||
if (!tableId) throw new Error('Table ID is required')
|
||||
if (!rollbackType) throw new Error('Rollback type is required')
|
||||
|
||||
const { data, error } = await post(
|
||||
'/platform/replication/{ref}/pipelines/{pipeline_id}/rollback-table-state',
|
||||
{
|
||||
params: { path: { ref: projectRef, pipeline_id: pipelineId } },
|
||||
body: { table_id: tableId, rollback_type: rollbackType },
|
||||
signal,
|
||||
}
|
||||
)
|
||||
|
||||
if (error) handleError(error)
|
||||
return data
|
||||
}
|
||||
|
||||
type RollbackTableData = Awaited<ReturnType<typeof rollbackTableState>>
|
||||
|
||||
export const useRollbackTableMutation = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
...options
|
||||
}: Omit<
|
||||
UseMutationOptions<RollbackTableData, ResponseError, RollbackTableParams>,
|
||||
'mutationFn'
|
||||
> = {}) => {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation<RollbackTableData, ResponseError, RollbackTableParams>(
|
||||
(vars) => rollbackTableState(vars),
|
||||
{
|
||||
async onSuccess(data, variables, context) {
|
||||
const { projectRef, pipelineId } = variables
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries(replicationKeys.pipelinesStatus(projectRef, pipelineId)),
|
||||
queryClient.invalidateQueries(
|
||||
replicationKeys.pipelinesReplicationStatus(projectRef, pipelineId)
|
||||
),
|
||||
])
|
||||
|
||||
await onSuccess?.(data, variables, context)
|
||||
},
|
||||
async onError(data, variables, context) {
|
||||
if (onError === undefined) {
|
||||
toast.error(`Failed to rollback table: ${data.message}`)
|
||||
} else {
|
||||
onError(data, variables, context)
|
||||
}
|
||||
},
|
||||
...options,
|
||||
}
|
||||
)
|
||||
}
|
||||
Vendored
+51
-2
@@ -3318,6 +3318,49 @@ export interface components {
|
||||
*/
|
||||
template_url?: string
|
||||
}
|
||||
V1GetUsageApiCountResponse: {
|
||||
error?:
|
||||
| string
|
||||
| {
|
||||
code: number
|
||||
errors: {
|
||||
domain: string
|
||||
location: string
|
||||
locationType: string
|
||||
message: string
|
||||
reason: string
|
||||
}[]
|
||||
message: string
|
||||
status: string
|
||||
}
|
||||
result?: {
|
||||
/** Format: date-time */
|
||||
timestamp: string
|
||||
total_auth_requests: number
|
||||
total_realtime_requests: number
|
||||
total_rest_requests: number
|
||||
total_storage_requests: number
|
||||
}[]
|
||||
}
|
||||
V1GetUsageApiRequestsCountResponse: {
|
||||
error?:
|
||||
| string
|
||||
| {
|
||||
code: number
|
||||
errors: {
|
||||
domain: string
|
||||
location: string
|
||||
locationType: string
|
||||
message: string
|
||||
reason: string
|
||||
}[]
|
||||
message: string
|
||||
status: string
|
||||
}
|
||||
result?: {
|
||||
count: number
|
||||
}[]
|
||||
}
|
||||
V1ListMigrationsResponse: {
|
||||
name?: string
|
||||
version: string
|
||||
@@ -4281,7 +4324,7 @@ export interface operations {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content: {
|
||||
'application/json': components['schemas']['AnalyticsResponse']
|
||||
'application/json': components['schemas']['V1GetUsageApiCountResponse']
|
||||
}
|
||||
}
|
||||
403: {
|
||||
@@ -4316,9 +4359,15 @@ export interface operations {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content: {
|
||||
'application/json': components['schemas']['AnalyticsResponse']
|
||||
'application/json': components['schemas']['V1GetUsageApiRequestsCountResponse']
|
||||
}
|
||||
}
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content?: never
|
||||
}
|
||||
/** @description Failed to get project's usage api requests count */
|
||||
500: {
|
||||
headers: {
|
||||
|
||||
+216
-58
@@ -2504,6 +2504,23 @@ export interface paths {
|
||||
patch?: never
|
||||
trace?: never
|
||||
}
|
||||
'/platform/projects/{ref}/api-keys/temporary': {
|
||||
parameters: {
|
||||
query?: never
|
||||
header?: never
|
||||
path?: never
|
||||
cookie?: never
|
||||
}
|
||||
get?: never
|
||||
put?: never
|
||||
/** Create a temporary API key */
|
||||
post: operations['ApiKeysController_createTemporaryApiKey']
|
||||
delete?: never
|
||||
options?: never
|
||||
head?: never
|
||||
patch?: never
|
||||
trace?: never
|
||||
}
|
||||
'/platform/projects/{ref}/api/graphql': {
|
||||
parameters: {
|
||||
query?: never
|
||||
@@ -3449,10 +3466,10 @@ export interface paths {
|
||||
path?: never
|
||||
cookie?: never
|
||||
}
|
||||
/** Get all replication pipelines. */
|
||||
/** Retrieves all replication pipelines. */
|
||||
get: operations['ReplicationPipelinesController_getPipelines']
|
||||
put?: never
|
||||
/** Create a replication pipeline. */
|
||||
/** Creates a replication pipeline. */
|
||||
post: operations['ReplicationPipelinesController_createPipeline']
|
||||
delete?: never
|
||||
options?: never
|
||||
@@ -3467,12 +3484,12 @@ export interface paths {
|
||||
path?: never
|
||||
cookie?: never
|
||||
}
|
||||
/** Get a replication pipeline by ID. */
|
||||
/** Retrieves a replication pipeline by ID. */
|
||||
get: operations['ReplicationPipelinesController_getPipeline']
|
||||
put?: never
|
||||
/** Update a replication pipeline. */
|
||||
/** Updates a replication pipeline. */
|
||||
post: operations['ReplicationPipelinesController_updatePipeline']
|
||||
/** Delete a replication pipeline. */
|
||||
/** Deletes a replication pipeline. */
|
||||
delete: operations['ReplicationPipelinesController_deletePipeline']
|
||||
options?: never
|
||||
head?: never
|
||||
@@ -3486,7 +3503,7 @@ export interface paths {
|
||||
path?: never
|
||||
cookie?: never
|
||||
}
|
||||
/** Get the status of a replication pipeline. */
|
||||
/** Retrieves the replication status of a pipeline. */
|
||||
get: operations['ReplicationPipelinesController_getPipelineReplicationStatus']
|
||||
put?: never
|
||||
post?: never
|
||||
@@ -3496,6 +3513,23 @@ export interface paths {
|
||||
patch?: never
|
||||
trace?: never
|
||||
}
|
||||
'/platform/replication/{ref}/pipelines/{pipeline_id}/rollback-table-state': {
|
||||
parameters: {
|
||||
query?: never
|
||||
header?: never
|
||||
path?: never
|
||||
cookie?: never
|
||||
}
|
||||
get?: never
|
||||
put?: never
|
||||
/** Rolls back the state of a table in the pipeline. */
|
||||
post: operations['ReplicationPipelinesController_rollbackTableState']
|
||||
delete?: never
|
||||
options?: never
|
||||
head?: never
|
||||
patch?: never
|
||||
trace?: never
|
||||
}
|
||||
'/platform/replication/{ref}/pipelines/{pipeline_id}/start': {
|
||||
parameters: {
|
||||
query?: never
|
||||
@@ -3505,7 +3539,7 @@ export interface paths {
|
||||
}
|
||||
get?: never
|
||||
put?: never
|
||||
/** Start a replication pipeline. */
|
||||
/** Starts a replication pipeline. */
|
||||
post: operations['ReplicationPipelinesController_startPipeline']
|
||||
delete?: never
|
||||
options?: never
|
||||
@@ -3520,7 +3554,7 @@ export interface paths {
|
||||
path?: never
|
||||
cookie?: never
|
||||
}
|
||||
/** Get the status of a replication pipeline. */
|
||||
/** Retrieves the status of a replication pipeline. */
|
||||
get: operations['ReplicationPipelinesController_getPipelineStatus']
|
||||
put?: never
|
||||
post?: never
|
||||
@@ -3539,7 +3573,7 @@ export interface paths {
|
||||
}
|
||||
get?: never
|
||||
put?: never
|
||||
/** Stop a replication pipeline. */
|
||||
/** Stops a replication pipeline. */
|
||||
post: operations['ReplicationPipelinesController_stopPipeline']
|
||||
delete?: never
|
||||
options?: never
|
||||
@@ -7592,9 +7626,25 @@ export interface components {
|
||||
name: 'following_wal'
|
||||
}
|
||||
| {
|
||||
message: string
|
||||
/** @enum {string} */
|
||||
name: 'error'
|
||||
reason: string
|
||||
retry_policy:
|
||||
| {
|
||||
/** @enum {string} */
|
||||
policy: 'no_retry'
|
||||
}
|
||||
| {
|
||||
/** @enum {string} */
|
||||
policy: 'manual_retry'
|
||||
}
|
||||
| {
|
||||
/** @description The time of the next retry (RFC3339 format) */
|
||||
next_retry: string
|
||||
/** @enum {string} */
|
||||
policy: 'timed_retry'
|
||||
}
|
||||
solution?: string
|
||||
}
|
||||
/** @description Table id (internal Postgres OID) */
|
||||
table_id: number
|
||||
@@ -7665,34 +7715,10 @@ export interface components {
|
||||
/** @description Pipeline id */
|
||||
pipeline_id: number
|
||||
/** @description Pipeline status */
|
||||
status:
|
||||
| {
|
||||
/** @enum {string} */
|
||||
name: 'stopped'
|
||||
}
|
||||
| {
|
||||
/** @enum {string} */
|
||||
name: 'starting'
|
||||
}
|
||||
| {
|
||||
/** @enum {string} */
|
||||
name: 'started'
|
||||
}
|
||||
| {
|
||||
/** @enum {string} */
|
||||
name: 'stopping'
|
||||
}
|
||||
| {
|
||||
/** @enum {string} */
|
||||
name: 'unknown'
|
||||
}
|
||||
| {
|
||||
exit_code?: number | null
|
||||
message?: string | null
|
||||
/** @enum {string} */
|
||||
name: 'failed'
|
||||
reason?: string | null
|
||||
}
|
||||
status: {
|
||||
/** @enum {string} */
|
||||
name: 'stopped' | 'starting' | 'started' | 'stopping' | 'unknown' | 'failed'
|
||||
}
|
||||
}
|
||||
ReplicationPublicationsResponse: {
|
||||
/** @description List of publications */
|
||||
@@ -7795,6 +7821,61 @@ export interface components {
|
||||
/** @enum {string} */
|
||||
privilege_type: 'ALL' | 'SELECT' | 'INSERT' | 'UPDATE' | 'REFERENCES'
|
||||
}[]
|
||||
RollbackTableStateBody: {
|
||||
/**
|
||||
* @description Rollback type
|
||||
* @enum {string}
|
||||
*/
|
||||
rollback_type: 'individual' | 'full'
|
||||
/** @description Table id (internal Postgres OID) */
|
||||
table_id: number
|
||||
}
|
||||
RollbackTableStateResponse: {
|
||||
/** @description Table replication state */
|
||||
new_state:
|
||||
| {
|
||||
/** @enum {string} */
|
||||
name: 'queued'
|
||||
}
|
||||
| {
|
||||
/** @enum {string} */
|
||||
name: 'copying_table'
|
||||
}
|
||||
| {
|
||||
/** @enum {string} */
|
||||
name: 'copied_table'
|
||||
}
|
||||
| {
|
||||
lag: number
|
||||
/** @enum {string} */
|
||||
name: 'following_wal'
|
||||
}
|
||||
| {
|
||||
/** @enum {string} */
|
||||
name: 'error'
|
||||
reason: string
|
||||
retry_policy:
|
||||
| {
|
||||
/** @enum {string} */
|
||||
policy: 'no_retry'
|
||||
}
|
||||
| {
|
||||
/** @enum {string} */
|
||||
policy: 'manual_retry'
|
||||
}
|
||||
| {
|
||||
/** @description The time of the next retry (RFC3339 format) */
|
||||
next_retry: string
|
||||
/** @enum {string} */
|
||||
policy: 'timed_retry'
|
||||
}
|
||||
solution?: string
|
||||
}
|
||||
/** @description Pipeline id */
|
||||
pipeline_id: number
|
||||
/** @description Table id (internal Postgres OID) */
|
||||
table_id: number
|
||||
}
|
||||
RunLintByNameResponse: {
|
||||
lints: {
|
||||
cache_key: string
|
||||
@@ -8078,6 +8159,9 @@ export interface components {
|
||||
page_url: string
|
||||
pathname: string
|
||||
}
|
||||
TemporaryApiKeyResponse: {
|
||||
api_key: string
|
||||
}
|
||||
TransferProjectBody: {
|
||||
target_organization_slug: string
|
||||
}
|
||||
@@ -8825,9 +8909,9 @@ export interface components {
|
||||
publication_name: string
|
||||
}
|
||||
/** @description Destination id */
|
||||
destination_id?: number
|
||||
destination_id: number
|
||||
/** @description Source id */
|
||||
source_id?: number
|
||||
source_id: number
|
||||
}
|
||||
UpdateSchemaBody: {
|
||||
name?: string
|
||||
@@ -16463,6 +16547,38 @@ export interface operations {
|
||||
}
|
||||
}
|
||||
}
|
||||
ApiKeysController_createTemporaryApiKey: {
|
||||
parameters: {
|
||||
query: {
|
||||
authorization_exp: string
|
||||
claims: string
|
||||
}
|
||||
header?: never
|
||||
path: {
|
||||
/** @description Project ref */
|
||||
ref: string
|
||||
}
|
||||
cookie?: never
|
||||
}
|
||||
requestBody?: never
|
||||
responses: {
|
||||
/** @description Temporary API key */
|
||||
201: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content: {
|
||||
'application/json': components['schemas']['TemporaryApiKeyResponse']
|
||||
}
|
||||
}
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content?: never
|
||||
}
|
||||
}
|
||||
}
|
||||
ProjectsApiController_projectGraphql: {
|
||||
parameters: {
|
||||
query?: never
|
||||
@@ -19136,7 +19252,7 @@ export interface operations {
|
||||
}
|
||||
requestBody?: never
|
||||
responses: {
|
||||
/** @description Returns all pipelines. */
|
||||
/** @description Returns all replication pipelines. */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
@@ -19151,7 +19267,7 @@ export interface operations {
|
||||
}
|
||||
content?: never
|
||||
}
|
||||
/** @description Failed to get replication pipelines. */
|
||||
/** @description Fails to retrieve replication pipelines. */
|
||||
500: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
@@ -19176,7 +19292,7 @@ export interface operations {
|
||||
}
|
||||
}
|
||||
responses: {
|
||||
/** @description Returns the created replication pipeline ID. */
|
||||
/** @description Returns the ID of the created replication pipeline. */
|
||||
201: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
@@ -19191,7 +19307,7 @@ export interface operations {
|
||||
}
|
||||
content?: never
|
||||
}
|
||||
/** @description Failed to create pipeline. */
|
||||
/** @description Fails to create replication pipeline. */
|
||||
500: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
@@ -19214,7 +19330,7 @@ export interface operations {
|
||||
}
|
||||
requestBody?: never
|
||||
responses: {
|
||||
/** @description Returns the pipeline. */
|
||||
/** @description Returns the details of the specified replication pipeline. */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
@@ -19229,7 +19345,7 @@ export interface operations {
|
||||
}
|
||||
content?: never
|
||||
}
|
||||
/** @description Failed to get pipeline. */
|
||||
/** @description Fails to retrieve replication pipeline. */
|
||||
500: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
@@ -19256,7 +19372,7 @@ export interface operations {
|
||||
}
|
||||
}
|
||||
responses: {
|
||||
/** @description Returned when the pipeline is updated. */
|
||||
/** @description Returns when the replication pipeline is successfully updated. */
|
||||
201: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
@@ -19269,7 +19385,7 @@ export interface operations {
|
||||
}
|
||||
content?: never
|
||||
}
|
||||
/** @description Failed to update pipeline. */
|
||||
/** @description Fails to update replication pipeline. */
|
||||
500: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
@@ -19292,7 +19408,7 @@ export interface operations {
|
||||
}
|
||||
requestBody?: never
|
||||
responses: {
|
||||
/** @description Returned when the pipeline is deleted. */
|
||||
/** @description Returns when the replication pipeline is successfully deleted. */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
@@ -19305,7 +19421,7 @@ export interface operations {
|
||||
}
|
||||
content?: never
|
||||
}
|
||||
/** @description Failed to delete pipeline. */
|
||||
/** @description Fails to delete replication pipeline. */
|
||||
500: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
@@ -19328,7 +19444,7 @@ export interface operations {
|
||||
}
|
||||
requestBody?: never
|
||||
responses: {
|
||||
/** @description Returns the pipeline replication status. */
|
||||
/** @description Returns the replication status of the pipeline. */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
@@ -19343,7 +19459,49 @@ export interface operations {
|
||||
}
|
||||
content?: never
|
||||
}
|
||||
/** @description Failed to get pipeline replication status. */
|
||||
/** @description Fails to retrieve pipeline replication status. */
|
||||
500: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content?: never
|
||||
}
|
||||
}
|
||||
}
|
||||
ReplicationPipelinesController_rollbackTableState: {
|
||||
parameters: {
|
||||
query?: never
|
||||
header?: never
|
||||
path: {
|
||||
/** @description Pipeline id */
|
||||
pipeline_id: number
|
||||
/** @description Project ref */
|
||||
ref: string
|
||||
}
|
||||
cookie?: never
|
||||
}
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': components['schemas']['RollbackTableStateBody']
|
||||
}
|
||||
}
|
||||
responses: {
|
||||
/** @description Returns the table state after the rollback. */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content: {
|
||||
'application/json': components['schemas']['RollbackTableStateResponse']
|
||||
}
|
||||
}
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content?: never
|
||||
}
|
||||
/** @description Fails to roll back table state. */
|
||||
500: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
@@ -19366,7 +19524,7 @@ export interface operations {
|
||||
}
|
||||
requestBody?: never
|
||||
responses: {
|
||||
/** @description Returned when the pipeline is started. */
|
||||
/** @description Returns when the replication pipeline is successfully started. */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
@@ -19379,7 +19537,7 @@ export interface operations {
|
||||
}
|
||||
content?: never
|
||||
}
|
||||
/** @description Failed to start pipeline. */
|
||||
/** @description Fails to start replication pipeline. */
|
||||
500: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
@@ -19402,7 +19560,7 @@ export interface operations {
|
||||
}
|
||||
requestBody?: never
|
||||
responses: {
|
||||
/** @description Returns the pipeline status. */
|
||||
/** @description Returns the current status of the replication pipeline. */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
@@ -19417,7 +19575,7 @@ export interface operations {
|
||||
}
|
||||
content?: never
|
||||
}
|
||||
/** @description Failed to get pipeline status. */
|
||||
/** @description Fails to retrieve pipeline status. */
|
||||
500: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
@@ -19440,7 +19598,7 @@ export interface operations {
|
||||
}
|
||||
requestBody?: never
|
||||
responses: {
|
||||
/** @description Returned when the pipeline is stopped. */
|
||||
/** @description Returns when the replication pipeline is successfully stopped. */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
@@ -19453,7 +19611,7 @@ export interface operations {
|
||||
}
|
||||
content?: never
|
||||
}
|
||||
/** @description Failed to stop pipeline. */
|
||||
/** @description Fails to stop replication pipeline. */
|
||||
500: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
|
||||
Reference in new issue
Block a user