diff --git a/apps/docs/tailwind.config.js b/apps/docs/tailwind.config.cjs similarity index 100% rename from apps/docs/tailwind.config.js rename to apps/docs/tailwind.config.cjs diff --git a/apps/studio/components/interfaces/Database/Replication/Destinations.tsx b/apps/studio/components/interfaces/Database/Replication/Destinations.tsx index 2a408954c52e3..3335dc78c7be7 100644 --- a/apps/studio/components/interfaces/Database/Replication/Destinations.tsx +++ b/apps/studio/components/interfaces/Database/Replication/Destinations.tsx @@ -133,15 +133,15 @@ export const Destinations = () => { 'flex flex-col px-10 rounded-lg justify-center items-center py-8 mt-4' )} > -

Send data to your first destination

-

+

Send data to your first destination

+

Use destinations to improve performance or run analysis on your data via integrations like BigQuery

diff --git a/apps/studio/components/interfaces/Database/Replication/ErroredTableDetails.tsx b/apps/studio/components/interfaces/Database/Replication/ErroredTableDetails.tsx new file mode 100644 index 0000000000000..c38c6c272cecd --- /dev/null +++ b/apps/studio/components/interfaces/Database/Replication/ErroredTableDetails.tsx @@ -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 + 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 ( +
+ {state.solution &&
{state.solution}
} +
Invalid retry policy configuration
+
+ ) + } + + return ( +
+ {retryPolicy === 'no_retry' ? ( +

+ This error requires manual intervention from our{' '} + + support + + . Alternatively, you may also recreate the pipeline. +

+ ) : retryPolicy === 'manual_retry' ? ( +
+

{state.solution}. You may thereafter rollback the pipeline.

+ +
+ ) : retryPolicy === 'timed_retry' ? ( +
+

+ A retry will be triggered automatically by restarting the pipeline on this table. +

+ +
+ ) : null} +
+ ) +} diff --git a/apps/studio/components/interfaces/Database/Replication/PipelineStatus.tsx b/apps/studio/components/interfaces/Database/Replication/PipelineStatus.tsx index f81a8df536ad1..c33087468ad2a 100644 --- a/apps/studio/components/interfaces/Database/Replication/PipelineStatus.tsx +++ b/apps/studio/components/interfaces/Database/Replication/PipelineStatus.tsx @@ -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 - 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 ( - - -
- - Failed -
-
- - {getPipelineStateMessages(requestStatus, 'failed').message} - -
- ) - } // 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: , + color: 'text-destructive-600', + tooltip: stateMessages.message, + } case PipelineStatusName.STARTING: return { label: 'Starting', @@ -151,24 +121,25 @@ export const PipelineStatus = ({ <> {isLoading && } {isError && ( - + + + + + + Unable to retrieve status: {error?.message} + + )} {isSuccess && ( - <> - {statusConfig.isFailedStatus ? ( -
{renderFailedStatus()}
- ) : ( - - -
- {statusConfig.dot} - {statusConfig.label} -
-
- {statusConfig.tooltip} -
- )} - + + +
+ {statusConfig.dot} + {statusConfig.label} +
+
+ {statusConfig.tooltip} +
)} ) diff --git a/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.tsx b/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.tsx index 29ccd56c33392..888bb679102cc 100644 --- a/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.tsx +++ b/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.tsx @@ -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('') @@ -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} /> - setFilterString(e.target.value)} + actions={ + filterString.length > 0 + ? [ + setFilterString('')} + />, + ] + : undefined + } /> @@ -190,38 +194,6 @@ export const ReplicationPipelineStatus = () => { /> )} - {hasErrors && ( -
-
- -
-

- {errorTables.length} table{errorTables.length > 1 ? 's' : ''} failed -

-

- Some tables encountered replication errors. Check the logs for detailed error - information. -

- -
-
-
- )} - {hasTableData && (
{showDisabledState && ( @@ -254,95 +226,88 @@ export const ReplicationPipelineStatus = () => { Table, Status, Details, - - Actions - , ]} - body={filteredTableStatuses.map((table: TableState, index: number) => { - const statusConfig = getStatusConfig(table.state) - return ( - - -
-

{table.table_name}

- - } - tooltip={{ content: { side: 'bottom', text: 'Open in Table Editor' } }} - > - - -
-
- - {showDisabledState ? ( - Not Available - ) : ( - statusConfig.badge - )} - - - {showDisabledState ? ( -

- Status unavailable while pipeline is {config.badge.toLowerCase()} -

- ) : ( + body={ + <> + {filteredTableStatuses.length === 0 && hasTableData && ( + +
- )} - - - } - 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', - }, - }} - /> - -
- ) - })} + + + ) + })} + + } />
)} - {filteredTableStatuses.length === 0 && hasTableData && ( - - -
-

No results found

-

- Your search for "{filterString}" did not return any results -

-
-
-
- )} - {!isStatusLoading && tableStatuses.length === 0 && (
diff --git a/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.types.ts b/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.types.ts index c5ab523ec1ead..f3c3cf2b8e136 100644 --- a/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.types.ts +++ b/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.types.ts @@ -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 } } diff --git a/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.utils.tsx b/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.utils.tsx index f9ff22a9e9a0a..af4207cc5c545 100644 --- a/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.utils.tsx +++ b/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus.utils.tsx @@ -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: Error, - description: state.message, + description:
{state.reason}
, 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 + } +} diff --git a/apps/studio/components/interfaces/Database/Replication/RetryCountdown.tsx b/apps/studio/components/interfaces/Database/Replication/RetryCountdown.tsx new file mode 100644 index 0000000000000..6b5b1edf65007 --- /dev/null +++ b/apps/studio/components/interfaces/Database/Replication/RetryCountdown.tsx @@ -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({ + 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 ( +
+ {statusMessage}{' '} + {/* [Joshen] It's a bit hard to debug without doing this locally, but we could use CountdownTimerSpan here perhaps */} + {timeDisplay} +
+ ) +} diff --git a/apps/studio/components/interfaces/Database/Replication/RetryOptionsDropdown.tsx b/apps/studio/components/interfaces/Database/Replication/RetryOptionsDropdown.tsx new file mode 100644 index 0000000000000..350bd910bb449 --- /dev/null +++ b/apps/studio/components/interfaces/Database/Replication/RetryOptionsDropdown.tsx @@ -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: , + title: 'Rollback to previous state', + description: 'Restart from the last working state (may not be enough in some cases)', + }, + { + type: 'full' as RollbackType, + icon: , + 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 ( + + + + + + + {RETRY_OPTIONS.map((option) => ( + handleRollback(option.type)} + > +
+
{option.icon}
+
+

{option.title}

+

{option.description}

+
+
+
+ ))} +
+
+ ) +} diff --git a/apps/studio/components/interfaces/Settings/General/Infrastructure/RestartServerButton.tsx b/apps/studio/components/interfaces/Settings/General/Infrastructure/RestartServerButton.tsx index e8c0cc61671d4..5377778738075 100644 --- a/apps/studio/components/interfaces/Settings/General/Infrastructure/RestartServerButton.tsx +++ b/apps/studio/components/interfaces/Settings/General/Infrastructure/RestartServerButton.tsx @@ -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, }, }} > diff --git a/apps/studio/data/replication/rollback-table-mutation.ts b/apps/studio/data/replication/rollback-table-mutation.ts new file mode 100644 index 0000000000000..0dbd02185e843 --- /dev/null +++ b/apps/studio/data/replication/rollback-table-mutation.ts @@ -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 { + 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> + +export const useRollbackTableMutation = ({ + onSuccess, + onError, + ...options +}: Omit< + UseMutationOptions, + 'mutationFn' +> = {}) => { + const queryClient = useQueryClient() + + return useMutation( + (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, + } + ) +} diff --git a/packages/api-types/types/api.d.ts b/packages/api-types/types/api.d.ts index 08089259c9543..810894d44726c 100644 --- a/packages/api-types/types/api.d.ts +++ b/packages/api-types/types/api.d.ts @@ -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,8 +4359,14 @@ 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: { diff --git a/packages/api-types/types/platform.d.ts b/packages/api-types/types/platform.d.ts index f78fd35b01970..0e606ba3d3233 100644 --- a/packages/api-types/types/platform.d.ts +++ b/packages/api-types/types/platform.d.ts @@ -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