mirror of
https://github.com/supabase/supabase.git
synced 2026-09-22 13:37:53 +08:00
0ddf2006d3
Studio-side guard for Multigres (`high_availability`) projects, mirroring the platform API guard from supabase/platform#37527. Pause, restore/PITR, and add-on affordances now show a clear "unavailable on High Availability projects" state instead of failing with a 400 after the click. <img width="1195" height="632" alt="Screenshot 2026-09-04 at 2 03 11 PM" src="https://github.com/user-attachments/assets/718c09f2-d92b-49dc-90ed-5d9ff810b03d" /> **Added:** - Pause project button is disabled on HA projects with a tooltip - Scheduled backups tab short-circuits to an HA empty state (matches the existing PITR tab). Per-row Restore buttons are also disabled with a tooltip as defense in depth, since BackupItem is reusable - Restore to new project shows an HA admonition ahead of the permission / PG15 / physical-backup checks - Add-ons page shows a page-level HA notice, all three rows are locked with a tooltip, and the side panels are not mounted on HA so `?panel=pitr|ipv4|customDomain` deep links are inert - Component tests for `PauseProjectButton` and `BackupItem`, plus unit tests for the new `isHighAvailability` branch in `Addons.utils.ts` **Changed:** - Add-ons rows are now consistent: the IPv4 row uses the same padlock tooltip as PITR and custom domain instead of a tooltip on the badge. Same disabled-reason strings as before, just surfaced via the padlock on non-HA projects too - `BackupItem` tooltip text extracted into a `getTooltipText()` function (mirrors `PauseProjectButton`) - `HighAvailabilityDisabledSectionNotice` accepts a `className` Detection reuses the existing `useIsHighAvailability()` hook, which the rest of Studio already treats as the Multigres signal. ## To test Use an HA project (`project.high_availability === true`) and a normal project. HA project: - Settings > General: "Pause project" is disabled, tooltip reads "Pausing is unavailable on High Availability projects" - Database > Backups > Scheduled backups: HA empty state, no "No backups yet" / daily backup copy - Database > Backups > Restore to new project: HA admonition, no restore controls - Settings > Add-ons: notice at the top, padlock on all three rows with per-row tooltip, clicking rows does nothing, and `?panel=pitr` / `?panel=ipv4` / `?panel=customDomain` open nothing Normal project (regression): - No "High Availability" strings on any of the above pages - Pause button enabled (or disabled only for its usual reasons, e.g. paid plan) - Add-on rows open their side panels on click and via `?panel=pitr` - Scheduled backups tab shows its normal list / empty state <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Features** - High Availability projects now clearly indicate when scheduled backups, backup restoration, project pausing, IPv4, PITR, and custom domains are unavailable. - Added explanatory notices, disabled controls, and tooltips throughout affected settings and backup screens. - Restore-to-new-project workflows now provide guidance to contact support when unavailable. - **Bug Fixes** - Improved consistency of availability messaging across High Availability project settings and database backup actions. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
266 lines
7.3 KiB
TypeScript
266 lines
7.3 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import {
|
|
getCustomDomainDisabledReason,
|
|
getIPv4DisabledReason,
|
|
getPitrAlertState,
|
|
getPitrDisabledReason,
|
|
} from './Addons.utils'
|
|
|
|
describe('getIPv4DisabledReason', () => {
|
|
it('returns the High Availability message before other checks', () => {
|
|
expect(
|
|
getIPv4DisabledReason({
|
|
isAws: false,
|
|
isProjectActive: false,
|
|
projectUpdateDisabled: true,
|
|
canUpdateIPv4: false,
|
|
ipv4Enabled: false,
|
|
isHighAvailability: true,
|
|
})
|
|
).toBe('Dedicated IPv4 address is unavailable on High Availability projects')
|
|
})
|
|
|
|
it('returns the AWS-only message for non-AWS projects', () => {
|
|
expect(
|
|
getIPv4DisabledReason({
|
|
isAws: false,
|
|
isProjectActive: true,
|
|
projectUpdateDisabled: false,
|
|
canUpdateIPv4: true,
|
|
ipv4Enabled: false,
|
|
isHighAvailability: false,
|
|
})
|
|
).toBe('Dedicated IPv4 address is only available for AWS projects')
|
|
})
|
|
|
|
it('returns the inactive-project message before other checks', () => {
|
|
expect(
|
|
getIPv4DisabledReason({
|
|
isAws: true,
|
|
isProjectActive: false,
|
|
projectUpdateDisabled: true,
|
|
canUpdateIPv4: false,
|
|
ipv4Enabled: false,
|
|
isHighAvailability: false,
|
|
})
|
|
).toBe('Project must be active to update IPv4')
|
|
})
|
|
|
|
it('returns the updates-disabled message when project updates are blocked', () => {
|
|
expect(
|
|
getIPv4DisabledReason({
|
|
isAws: true,
|
|
isProjectActive: true,
|
|
projectUpdateDisabled: true,
|
|
canUpdateIPv4: true,
|
|
ipv4Enabled: false,
|
|
isHighAvailability: false,
|
|
})
|
|
).toBe('Project updates are currently disabled')
|
|
})
|
|
|
|
it('returns the IPv6 requirement when IPv4 cannot be added yet', () => {
|
|
expect(
|
|
getIPv4DisabledReason({
|
|
isAws: true,
|
|
isProjectActive: true,
|
|
projectUpdateDisabled: false,
|
|
canUpdateIPv4: false,
|
|
ipv4Enabled: false,
|
|
isHighAvailability: false,
|
|
})
|
|
).toBe('You can only add IPv4 when your project network configuration is set to IPv6')
|
|
})
|
|
|
|
it('returns undefined when IPv4 is already enabled', () => {
|
|
expect(
|
|
getIPv4DisabledReason({
|
|
isAws: true,
|
|
isProjectActive: true,
|
|
projectUpdateDisabled: false,
|
|
canUpdateIPv4: false,
|
|
ipv4Enabled: true,
|
|
isHighAvailability: false,
|
|
})
|
|
).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('getPitrDisabledReason', () => {
|
|
it('returns the High Availability message before other checks', () => {
|
|
expect(
|
|
getPitrDisabledReason({
|
|
isProjectActive: false,
|
|
projectUpdateDisabled: true,
|
|
hasHipaaAddon: true,
|
|
sufficientPgVersion: false,
|
|
isOrioleDbInAws: true,
|
|
isHighAvailability: true,
|
|
})
|
|
).toBe('Point in time recovery is unavailable on High Availability projects')
|
|
})
|
|
|
|
it('returns the inactive-project message first', () => {
|
|
expect(
|
|
getPitrDisabledReason({
|
|
isProjectActive: false,
|
|
projectUpdateDisabled: true,
|
|
hasHipaaAddon: true,
|
|
sufficientPgVersion: false,
|
|
isOrioleDbInAws: true,
|
|
isHighAvailability: false,
|
|
})
|
|
).toBe('Project must be active to update PITR')
|
|
})
|
|
|
|
it('returns the updates-disabled message before feature-specific checks', () => {
|
|
expect(
|
|
getPitrDisabledReason({
|
|
isProjectActive: true,
|
|
projectUpdateDisabled: true,
|
|
hasHipaaAddon: true,
|
|
sufficientPgVersion: false,
|
|
isOrioleDbInAws: true,
|
|
isHighAvailability: false,
|
|
})
|
|
).toBe('Project updates are currently disabled')
|
|
})
|
|
|
|
it('returns the HIPAA message when HIPAA is enabled', () => {
|
|
expect(
|
|
getPitrDisabledReason({
|
|
isProjectActive: true,
|
|
projectUpdateDisabled: false,
|
|
hasHipaaAddon: true,
|
|
sufficientPgVersion: true,
|
|
isOrioleDbInAws: false,
|
|
isHighAvailability: false,
|
|
})
|
|
).toBe('PITR cannot be changed with HIPAA enabled')
|
|
})
|
|
|
|
it('returns the legacy-project message when the database version is too old', () => {
|
|
expect(
|
|
getPitrDisabledReason({
|
|
isProjectActive: true,
|
|
projectUpdateDisabled: false,
|
|
hasHipaaAddon: false,
|
|
sufficientPgVersion: false,
|
|
isOrioleDbInAws: false,
|
|
isHighAvailability: false,
|
|
})
|
|
).toBe('Your project is too old to enable PITR')
|
|
})
|
|
|
|
it('returns the OrioleDB message when PITR is unsupported', () => {
|
|
expect(
|
|
getPitrDisabledReason({
|
|
isProjectActive: true,
|
|
projectUpdateDisabled: false,
|
|
hasHipaaAddon: false,
|
|
sufficientPgVersion: true,
|
|
isOrioleDbInAws: true,
|
|
isHighAvailability: false,
|
|
})
|
|
).toBe('Point in time recovery is not supported with OrioleDB')
|
|
})
|
|
|
|
it('returns undefined when PITR can be updated', () => {
|
|
expect(
|
|
getPitrDisabledReason({
|
|
isProjectActive: true,
|
|
projectUpdateDisabled: false,
|
|
hasHipaaAddon: false,
|
|
sufficientPgVersion: true,
|
|
isOrioleDbInAws: false,
|
|
isHighAvailability: false,
|
|
})
|
|
).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('getCustomDomainDisabledReason', () => {
|
|
it('returns the High Availability message before other checks', () => {
|
|
expect(
|
|
getCustomDomainDisabledReason({
|
|
isProjectActive: false,
|
|
projectUpdateDisabled: true,
|
|
isHighAvailability: true,
|
|
})
|
|
).toBe('Custom domain is unavailable on High Availability projects')
|
|
})
|
|
|
|
it('returns the inactive-project message', () => {
|
|
expect(
|
|
getCustomDomainDisabledReason({
|
|
isProjectActive: false,
|
|
projectUpdateDisabled: true,
|
|
isHighAvailability: false,
|
|
})
|
|
).toBe('Project must be active to update custom domain')
|
|
})
|
|
|
|
it('returns the updates-disabled message when applicable', () => {
|
|
expect(
|
|
getCustomDomainDisabledReason({
|
|
isProjectActive: true,
|
|
projectUpdateDisabled: true,
|
|
isHighAvailability: false,
|
|
})
|
|
).toBe('Project updates are currently disabled')
|
|
})
|
|
|
|
it('returns undefined when the custom domain panel can be opened', () => {
|
|
expect(
|
|
getCustomDomainDisabledReason({
|
|
isProjectActive: true,
|
|
projectUpdateDisabled: false,
|
|
isHighAvailability: false,
|
|
})
|
|
).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('getPitrAlertState', () => {
|
|
it('prefers the HIPAA alert over other blocking reasons', () => {
|
|
expect(
|
|
getPitrAlertState({
|
|
hasHipaaAddon: true,
|
|
sufficientPgVersion: false,
|
|
isOrioleDbInAws: true,
|
|
})
|
|
).toBe('hipaa')
|
|
})
|
|
|
|
it('returns the legacy-project alert when HIPAA is not enabled', () => {
|
|
expect(
|
|
getPitrAlertState({
|
|
hasHipaaAddon: false,
|
|
sufficientPgVersion: false,
|
|
isOrioleDbInAws: true,
|
|
})
|
|
).toBe('legacy-project')
|
|
})
|
|
|
|
it('returns the OrioleDB alert when it is the remaining blocker', () => {
|
|
expect(
|
|
getPitrAlertState({
|
|
hasHipaaAddon: false,
|
|
sufficientPgVersion: true,
|
|
isOrioleDbInAws: true,
|
|
})
|
|
).toBe('orioledb')
|
|
})
|
|
|
|
it('returns undefined when no PITR alert is needed', () => {
|
|
expect(
|
|
getPitrAlertState({
|
|
hasHipaaAddon: false,
|
|
sufficientPgVersion: true,
|
|
isOrioleDbInAws: false,
|
|
})
|
|
).toBeUndefined()
|
|
})
|
|
})
|