Files
Danny White 7ab3f32625 feat(studio): rebuild the pipeline overview (#49630)
## 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 -->
2026-09-17 16:31:04 +10:00

127 lines
3.9 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
}
}