mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 09:55:06 +03:00
## What kind of change does this PR introduce? Studio UI improvement. ## What is the current behavior? The pipeline Overview uses bespoke loading, metrics, table-state and empty-state layouts that shift while data resolves and repeat status information from the detail header. ## What is the new behavior? Rebuilds the Overview around stable **Pipeline health** and **Replicated tables** sections. It adds layout-matched loading geometry, prioritised pipeline notices, initial-sync progress, clearer empty states, and accessible loading announcements. Complete pipeline configuration remains deferred to #49631. | Before | After | | --- | --- | | <img width="1024" height="759" alt="54861" src="https://github.com/user-attachments/assets/56e5cc5a-5d49-44c8-94d7-e1f1e0c827d5" /> | <img width="1024" height="759" alt="Replication Database Agua Basket Supabase" src="https://github.com/user-attachments/assets/43b6d0f6-6fd5-47f9-b3e5-788a33511304" /> | This is the final independent slice in the review series: #50443, #50444, #50445, #50446, then this PR. Each PR targets `master` and can merge on its own. Rebase this PR as earlier slices merge. ## To test 1. Open `/project/<ref>/database/replication` and select a pipeline. 2. Throttle the initial requests and confirm **Pipeline health** and **Replicated tables** keep their final geometry while loading. 3. Check running, initial-sync, stopped, failed, disconnected and unavailable states. 4. Confirm the Overview contains Pipeline health and Replicated tables only, without a Configuration section. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Reorganized replication pipeline status into Pipeline health and Replicated tables sections. - Added loading skeletons with accessible status announcements. - Added clearer notices for pipeline health, failed or disconnected pipelines, paused updates, lag, and synchronization progress. - Improved empty states when table data is unavailable or the pipeline is inactive. - Added options to view logs and reset failed tables. - **Tests** - Added coverage for loading behavior, pipeline notices, table counts, synchronization progress, and empty states. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
127 lines
3.9 KiB
TypeScript
127 lines
3.9 KiB
TypeScript
import { getPipelineDisplayState, normalizePipelineStatusName } from '../Pipeline.utils'
|
||
import { PipelineStatusName } from '../Replication.constants'
|
||
import { TableState } from './ReplicationPipelineStatus.types'
|
||
import { getInitialSyncProgress } from './ReplicationPipelineStatus.utils'
|
||
import { ReplicationPipelineStatusData } from '@/data/replication/pipeline-status-query'
|
||
import { PipelineStatusRequestStatus } from '@/state/replication-pipeline-request-status'
|
||
|
||
export const getTableStatusEmptyState = ({
|
||
isDisabled,
|
||
disabledStateConfig,
|
||
statusName,
|
||
}: {
|
||
isDisabled: boolean
|
||
disabledStateConfig: { title: string; message: string }
|
||
statusName?: ReplicationPipelineStatusData['status']['name']
|
||
}) => {
|
||
if (isDisabled) {
|
||
return { title: disabledStateConfig.title, description: disabledStateConfig.message }
|
||
}
|
||
|
||
if (statusName === PipelineStatusName.STOPPED) {
|
||
return { title: 'Pipeline stopped', description: 'Start the pipeline to begin replication' }
|
||
}
|
||
|
||
if (statusName === PipelineStatusName.FAILED) {
|
||
return {
|
||
title: 'Pipeline failed',
|
||
description: 'Restart the pipeline or reset your tables to recover',
|
||
}
|
||
}
|
||
|
||
return {
|
||
title: 'No table data yet',
|
||
description: 'Table status appears here once replication begins',
|
||
}
|
||
}
|
||
|
||
export interface PipelineStateNotice {
|
||
type: 'note' | 'warning' | 'destructive'
|
||
title: string
|
||
description: string
|
||
showLogsLink: boolean
|
||
}
|
||
|
||
const plural = (count: number, singular: string, pluralForm = `${singular}s`) =>
|
||
`${count} ${count === 1 ? singular : pluralForm}`
|
||
|
||
export const getInitialSyncSummary = ({
|
||
copyingCount,
|
||
queuedCount,
|
||
totalCount,
|
||
}: ReturnType<typeof getInitialSyncProgress>) => {
|
||
if (copyingCount > 0 && queuedCount > 0) {
|
||
return `${copyingCount} of ${plural(totalCount, 'table')} ${copyingCount === 1 ? 'is' : 'are'} copying and ${queuedCount} ${queuedCount === 1 ? 'is' : 'are'} waiting.`
|
||
}
|
||
if (copyingCount > 0) {
|
||
return `${copyingCount} of ${plural(totalCount, 'table')} ${copyingCount === 1 ? 'is' : 'are'} copying.`
|
||
}
|
||
if (queuedCount > 0) {
|
||
return `${plural(queuedCount, 'table')} ${queuedCount === 1 ? 'is' : 'are'} waiting to copy.`
|
||
}
|
||
return 'The last tables are finishing their copy.'
|
||
}
|
||
|
||
export const getPipelineStateNotice = ({
|
||
requestStatus,
|
||
statusName,
|
||
tableStatuses,
|
||
}: {
|
||
requestStatus: PipelineStatusRequestStatus
|
||
statusName?: ReplicationPipelineStatusData['status']['name']
|
||
tableStatuses: { state: { name: TableState['state']['name'] } }[]
|
||
}): PipelineStateNotice | undefined => {
|
||
const displayState = getPipelineDisplayState(
|
||
requestStatus,
|
||
normalizePipelineStatusName(statusName)
|
||
)
|
||
|
||
if (displayState.type === 'loading') {
|
||
return {
|
||
type: 'note',
|
||
title: displayState.title,
|
||
description: displayState.message,
|
||
showLogsLink: false,
|
||
}
|
||
}
|
||
|
||
if (displayState.key === 'failed') {
|
||
return {
|
||
type: 'destructive',
|
||
title: displayState.title,
|
||
description:
|
||
'Replication has stopped. Restart the pipeline to resume from its last checkpoint. Table states below are from before it failed.',
|
||
showLogsLink: true,
|
||
}
|
||
}
|
||
|
||
if (displayState.key === 'stopped') {
|
||
return {
|
||
type: 'note',
|
||
title: displayState.title,
|
||
description:
|
||
'Changes to your source tables wait in Postgres until you start the pipeline again. Table states below are from before it stopped.',
|
||
showLogsLink: false,
|
||
}
|
||
}
|
||
|
||
if (displayState.key === 'unknown') {
|
||
return {
|
||
type: 'warning',
|
||
title: displayState.title,
|
||
description: 'We can’t tell whether replication is running',
|
||
showLogsLink: true,
|
||
}
|
||
}
|
||
|
||
const progress = getInitialSyncProgress(tableStatuses)
|
||
if (progress.syncingCount === 0) return undefined
|
||
|
||
return {
|
||
type: 'note',
|
||
title: 'Initial sync is running',
|
||
description: `${getInitialSyncSummary(progress)} Each table starts streaming as its copy finishes.`,
|
||
showLogsLink: false,
|
||
}
|
||
}
|