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

95 lines
3.0 KiB
TypeScript

import { describe, expect, test } from 'vitest'
import {
getInitialSyncSummary,
getPipelineStateNotice,
getTableStatusEmptyState,
} from './PipelineOverview.utils'
import { PipelineStatusRequestStatus } from '@/state/replication-pipeline-request-status'
const disabledStateConfig = { title: 'Starting pipeline', message: 'This can take a moment.' }
const liveTables = (count: number) =>
Array.from({ length: count }, () => ({ state: { name: 'following_wal' as const } }))
describe('getTableStatusEmptyState', () => {
test.each([
[true, 'stopped' as const, 'Starting pipeline'],
[false, 'stopped' as const, 'Pipeline stopped'],
[false, 'failed' as const, 'Pipeline failed'],
[false, 'started' as const, 'No table data yet'],
])('returns the appropriate empty state', (isDisabled, statusName, title) => {
expect(getTableStatusEmptyState({ isDisabled, disabledStateConfig, statusName }).title).toBe(
title
)
})
})
describe('getPipelineStateNotice', () => {
test('omits a notice for a healthy running pipeline', () => {
expect(
getPipelineStateNotice({
requestStatus: PipelineStatusRequestStatus.None,
statusName: 'started',
tableStatuses: liveTables(3),
})
).toBeUndefined()
})
test.each([
['failed' as const, 'destructive', true],
['stopped' as const, 'note', false],
])('explains a %s pipeline', (statusName, type, showLogsLink) => {
expect(
getPipelineStateNotice({
requestStatus: PipelineStatusRequestStatus.None,
statusName,
tableStatuses: liveTables(3),
})
).toMatchObject({ type, showLogsLink })
})
test('reports a requested transition ahead of the API status', () => {
expect(
getPipelineStateNotice({
requestStatus: PipelineStatusRequestStatus.StartRequested,
statusName: 'stopped',
tableStatuses: liveTables(3),
})?.title
).toBe('Starting pipeline')
})
test('distinguishes copying tables from queued tables', () => {
expect(
getPipelineStateNotice({
requestStatus: PipelineStatusRequestStatus.None,
statusName: 'started',
tableStatuses: [
...liveTables(2),
{ state: { name: 'copying_table' as const } },
{ state: { name: 'queued' as const } },
],
})?.description
).toContain('1 of 4 tables is copying and 1 is waiting.')
})
})
describe('getInitialSyncSummary', () => {
test.each([
[4, 3, 8, '4 of 8 tables are copying and 3 are waiting.'],
[1, 1, 4, '1 of 4 tables is copying and 1 is waiting.'],
[2, 0, 8, '2 of 8 tables are copying.'],
[0, 3, 3, '3 tables are waiting to copy.'],
[1, 0, 1, '1 of 1 table is copying.'],
[0, 0, 2, 'The last tables are finishing their copy.'],
])('summarises initial sync progress', (copyingCount, queuedCount, totalCount, expected) => {
expect(
getInitialSyncSummary({
syncingCount: copyingCount + queuedCount,
copyingCount,
queuedCount,
totalCount,
})
).toBe(expected)
})
})