diff --git a/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus/ReplicationPipelineStatus.test.tsx b/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus/ReplicationPipelineStatus.test.tsx index 3006b3ed5fa..91f107ef1d1 100644 --- a/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus/ReplicationPipelineStatus.test.tsx +++ b/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus/ReplicationPipelineStatus.test.tsx @@ -1,4 +1,4 @@ -import { screen } from '@testing-library/react' +import { screen, within } from '@testing-library/react' import type { components } from 'api-types' import { HttpResponse } from 'msw' import { describe, expect, test, vi } from 'vitest' @@ -29,6 +29,43 @@ const pipeline: PipelineResponse = { tenant_id: 'default', } +const mockPipelineQueries = ({ + tableStatuses = [], +}: { + tableStatuses?: PipelineReplicationStatusResponse['table_statuses'] +} = {}) => { + addAPIMock({ + method: 'get', + path: '/platform/replication/:ref/pipelines/:pipeline_id', + response: () => HttpResponse.json(pipeline), + }) + addAPIMock({ + method: 'get', + path: '/platform/replication/:ref/pipelines/:pipeline_id/status', + response: () => + HttpResponse.json({ + pipeline_id: 42, + status: { name: 'started' }, + }), + }) + addAPIMock({ + method: 'get', + path: '/platform/replication/:ref/pipelines/:pipeline_id/replication-status', + response: () => + HttpResponse.json({ + pipeline_id: 42, + apply_lag: { + active: true, + wal_status: 'reserved', + restart_lsn_bytes: 0, + confirmed_flush_lsn_bytes: 0, + safe_wal_size_bytes: null, + }, + table_statuses: tableStatuses, + }), + }) +} + describe('ReplicationPipelineStatus', () => { test('preserves the overview structure while pipeline details load', async () => { let resolvePipeline: (value: PipelineResponse) => void = () => {} @@ -92,4 +129,61 @@ describe('ReplicationPipelineStatus', () => { expect(await screen.findByText('No table data yet')).toBeVisible() expect(screen.getByRole('status')).toHaveTextContent('') }) + + test('defaults to sorting replicated tables by schema and name', async () => { + // API order is reverse of schema.name, and statuses would reorder differently if status + // were the default (error before copying before live). + mockPipelineQueries({ + tableStatuses: [ + { + id: 3, + schema: 'public', + name: 'zebra', + table_id: 3, + table_name: 'public.zebra', + state: { + name: 'error', + reason: 'sync failed', + retry_policy: { policy: 'manual_retry' }, + }, + }, + { + id: 2, + schema: 'public', + name: 'alpha', + table_id: 2, + table_name: 'public.alpha', + state: { name: 'following_wal' }, + }, + { + id: 1, + schema: 'auth', + name: 'users', + table_id: 1, + table_name: 'auth.users', + state: { name: 'copying_table' }, + }, + ], + }) + + customRender( + + + + ) + + expect(await screen.findByText('zebra')).toBeVisible() + expect(screen.getByRole('columnheader', { name: 'Table' })).toHaveAttribute( + 'aria-sort', + 'ascending' + ) + + const table = screen.getByRole('table') + const tableNames = within(table) + .getAllByRole('row') + .map((row) => within(row).queryAllByRole('cell')[0]?.textContent) + .filter((name): name is string => Boolean(name)) + + expect(tableNames).toEqual(['auth.users', 'public.alpha', 'public.zebra']) + }) }) diff --git a/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus/ReplicationPipelineStatus.tsx b/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus/ReplicationPipelineStatus.tsx index ef91a267b74..a845ea53c59 100644 --- a/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus/ReplicationPipelineStatus.tsx +++ b/apps/studio/components/interfaces/Database/Replication/ReplicationPipelineStatus/ReplicationPipelineStatus.tsx @@ -166,7 +166,7 @@ export const ReplicationPipelineStatus = () => { const applyLagMetrics = replicationStatusData?.apply_lag - const [sort, setSort] = useState('status:asc') + const [sort, setSort] = useState('table:asc') const [sortColumn, sortDirection] = sort.split(':') as [TableSortColumn, 'asc' | 'desc'] const getAriaSort = (column: TableSortColumn) => {