mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
## What kind of change does this PR introduce? Studio interface improvement. ## What is the current behavior? Read replica creation uses an oversized sheet, with region selection, eligibility guidance, and pricing all awkwardly competing for space. The cost estimate can briefly show a compute-only subtotal while disk pricing is still loading. ## What is the new behavior? Read replica creation uses a focused, centred dialog with a vertical region field, contextual eligibility guidance, and a separate cost breakdown. Disabled forms omit redundant deployment-location text. The additional monthly cost appears only after both compute and disk pricing inputs are available. | Before | After | | --- | --- | | <img width="1024" height="759" alt="Infrastructure Settings Chives Pantry Supabase" src="https://github.com/user-attachments/assets/afb0a9a6-3575-4d65-98a3-f21b23a032ac" /> | <img width="1024" height="759" alt="Infrastructure Settings Chives Pantry Supabase" src="https://github.com/user-attachments/assets/89fd96f8-8f95-4b88-b1fd-505a261ff9a0" /> | | <img width="1024" height="759" alt="Infrastructure Settings Chisel Toolshed Supabase" src="https://github.com/user-attachments/assets/dd110b3a-6aca-4074-9a69-2dc711f4d1c7" /> | <img width="1024" height="759" alt="Infrastructure Settings Chisel Toolshed Supabase" src="https://github.com/user-attachments/assets/894bbd81-01d5-411e-8279-1bd2e5839cff" /> | | <img width="1024" height="759" alt="Infrastructure Settings Chisel Toolshed Supabase" src="https://github.com/user-attachments/assets/09cbd315-c13d-4987-b274-daf4f91ea10d" /> | <img width="1024" height="759" alt="Infrastructure Settings Chisel Toolshed Supabase" src="https://github.com/user-attachments/assets/a1effb33-1bf2-450a-8cd8-0de8675231c9" /> | ## To test - Open `/project/<ref>/settings/infrastructure` and select **Add read replica** from the section header or empty state. Confirm the dialog opens and closes using Close, Escape, backdrop, and Cancel. - On an eligible project, confirm the pricing note initially reads **Estimated additional cost**, then adds **of $X/month** once pricing loads. **View breakdown** should remain disabled until then. - Change the region and open **View breakdown**. Confirm the monthly cost table has standard row borders and an estimated total. - On a project below Small compute, confirm the region field is disabled, its deployment-location description is hidden, and **Change compute** returns to the compute controls. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Replaced the add read replica sheet with a dialog-based setup experience. * Added region details, eligibility guidance, compute recommendations, and estimated pricing. * Added retry options when pricing information fails to load. * **UI Improvements** * Updated warning messages, documentation links, and action labels. * Improved dialog behavior and deferred data loading until opened. * **Tests** * Expanded coverage for dialog behavior, eligibility warnings, pricing errors, retries, and recommendations. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
66 lines
2.7 KiB
TypeScript
66 lines
2.7 KiB
TypeScript
import { screen } from '@testing-library/react'
|
||
import userEvent from '@testing-library/user-event'
|
||
import { describe, expect, test, vi } from 'vitest'
|
||
|
||
import { ReadReplicaPricingDialog } from '@/components/interfaces/Settings/Infrastructure/ReadReplicas/ReadReplicaForm/ReadReplicaPricingDialog'
|
||
import { customRender } from '@/tests/lib/custom-render'
|
||
|
||
vi.mock('@/hooks/misc/useSelectedProject', () => ({
|
||
useSelectedProjectQuery: () => ({ data: { cloud_provider: 'AWS' } }),
|
||
}))
|
||
|
||
const replicaCost = {
|
||
isLoading: false,
|
||
isError: false,
|
||
retry: vi.fn(),
|
||
totalCost: '$85.94',
|
||
compute: { label: 'Small', cost: '$15.00', priceDescription: '$15/month' },
|
||
disk: { type: 'gp3', label: '125 GB (gp3)', cost: '$15.63' },
|
||
iops: { label: '3,000 IOPS', cost: '$30.00' },
|
||
throughput: { label: '125 MB/s', cost: '$25.31' },
|
||
} as const
|
||
|
||
describe('ReadReplicaPricingDialog', () => {
|
||
test('does not show a partial estimate while pricing data loads', () => {
|
||
customRender(<ReadReplicaPricingDialog replicaCost={{ ...replicaCost, isLoading: true }} />)
|
||
|
||
expect(screen.getByText('Estimated additional cost')).toBeInTheDocument()
|
||
expect(screen.queryByText(/Estimated additional cost of/)).not.toBeInTheDocument()
|
||
expect(screen.getByRole('button', { name: 'View breakdown' })).toBeDisabled()
|
||
})
|
||
|
||
test('shows the estimate once all pricing data is available', () => {
|
||
customRender(<ReadReplicaPricingDialog replicaCost={replicaCost} />)
|
||
|
||
expect(screen.getByText('Estimated additional cost of $85.94/month')).toBeInTheDocument()
|
||
expect(screen.getByRole('button', { name: 'View breakdown' })).toBeEnabled()
|
||
})
|
||
|
||
test('offers to retry when pricing data cannot be loaded', async () => {
|
||
const user = userEvent.setup()
|
||
const retry = vi.fn()
|
||
|
||
customRender(
|
||
<ReadReplicaPricingDialog replicaCost={{ ...replicaCost, isError: true, retry }} />
|
||
)
|
||
|
||
expect(screen.getByText('Unable to estimate additional cost')).toBeInTheDocument()
|
||
expect(screen.getByText('We couldn’t load the required pricing data.')).toBeInTheDocument()
|
||
|
||
await user.click(screen.getByRole('button', { name: 'Retry' }))
|
||
|
||
expect(retry).toHaveBeenCalledOnce()
|
||
})
|
||
|
||
test('keeps the error state stable while retrying', () => {
|
||
customRender(
|
||
<ReadReplicaPricingDialog replicaCost={{ ...replicaCost, isError: true, isLoading: true }} />
|
||
)
|
||
|
||
expect(screen.getByText('Unable to estimate additional cost')).toBeInTheDocument()
|
||
expect(screen.queryByText('Estimated additional cost')).not.toBeInTheDocument()
|
||
expect(screen.getByRole('button', { name: 'Retry' })).toBeDisabled()
|
||
expect(screen.queryByRole('button', { name: 'View breakdown' })).not.toBeInTheDocument()
|
||
})
|
||
})
|