Files
supabase/apps/studio/components/interfaces/Settings/Infrastructure/ReadReplicas/ReadReplicasSection.tsx
Joshen Lim 0b5be1fada Disable read replica creation CTAs for HA projects (#49629)
## Context

As per PR title - disables creation of read replicas for HA projects.
This involves:
- Hiding new replica CTA in `DatabaseSelector`
  - Used in pages like reports
- Hiding new replica CTA in `DatabaseParametersSubMenu`
  - Used in SQL Editor + Explorer
- Disabling new replica CTA in settings/infrastructure under Read
Replicas
<img width="1065" height="401" alt="image"
src="https://github.com/user-attachments/assets/18c59cee-2f47-4874-9861-8211684282ab"
/>
<img width="418" height="366" alt="image"
src="https://github.com/user-attachments/assets/553cf75b-ff95-41c0-beca-357430a7e888"
/>



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Bug Fixes

- Updated read replica controls to accurately reflect project
availability.
- Hid read replica creation options for high-availability projects.
- Prevented read replica deployment for high-availability projects.
- Added an explanatory notice and guidance when read replicas are
unavailable due to high-availability settings.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-27 22:13:29 +08:00

176 lines
6.1 KiB
TypeScript

import { useParams } from 'common'
import { Database } from 'icons'
import { Plus } from 'lucide-react'
import { parseAsBoolean, useQueryState } from 'nuqs'
import { useEffect, useState } from 'react'
import { Button, Card, CardContent, Table, TableBody, TableHead, TableHeader, TableRow } from 'ui'
import { EmptyStatePresentational } from 'ui-patterns/EmptyStatePresentational'
import {
PageSection,
PageSectionAside,
PageSectionContent,
PageSectionDescription,
PageSectionMeta,
PageSectionSummary,
PageSectionTitle,
} from 'ui-patterns/PageSection'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import { AddReadReplicaDialog } from './AddReadReplicaDialog'
import { ReadReplicaRow } from './ReadReplicaRow'
import { REPLICA_STATUS } from './ReadReplicas.constants'
import type { RecommendedComputeForReadReplicas } from './recommendCompute'
import { AlertError } from '@/components/ui/AlertError'
import { DocsButton } from '@/components/ui/DocsButton'
import { HighAvailabilityDisabledSectionNotice } from '@/components/ui/HighAvailability/HighAvailabilityDisabledSectionNotice'
import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query'
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
import { useIsHighAvailability } from '@/hooks/misc/useSelectedProject'
import { DOCS_URL } from '@/lib/constants'
interface ReadReplicasSectionProps {
onRecommendCompute: (size: RecommendedComputeForReadReplicas) => void
}
export const ReadReplicasSection = ({ onRecommendCompute }: ReadReplicasSectionProps) => {
const { ref: projectRef } = useParams()
const isHighAvailability = useIsHighAvailability()
const { infrastructureReadReplicas } = useIsFeatureEnabled(['infrastructure:read_replicas'])
const [isAddReplicaOpen, setAddReplica] = useQueryState(
'addReplica',
parseAsBoolean.withDefault(false).withOptions({
history: 'push',
clearOnDefault: true,
scroll: false,
})
)
const [statusRefetchInterval, setStatusRefetchInterval] = useState<number | false>(5000)
const {
data: databases = [],
error: databasesError,
isPending: isDatabasesLoading,
isError: isDatabasesError,
isSuccess: isDatabasesSuccess,
} = useReadReplicasQuery(
{ projectRef },
{
enabled: infrastructureReadReplicas,
refetchInterval: infrastructureReadReplicas ? statusRefetchInterval : false,
}
)
const readReplicas = databases.filter((database) => database.identifier !== projectRef)
const hasReplicas = isDatabasesSuccess && readReplicas.length > 0
useEffect(() => {
if (!isDatabasesSuccess) return
const fixedStatuses = [
REPLICA_STATUS.ACTIVE_HEALTHY,
REPLICA_STATUS.ACTIVE_UNHEALTHY,
REPLICA_STATUS.INIT_READ_REPLICA_FAILED,
]
const replicasInTransition = databases.filter(
(database) => database.identifier !== projectRef && !fixedStatuses.includes(database.status)
)
if (replicasInTransition.length === 0) setStatusRefetchInterval(false)
}, [isDatabasesSuccess, databases, projectRef])
if (!infrastructureReadReplicas) return null
return (
<>
<PageSection>
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle>Read replicas</PageSectionTitle>
<PageSectionDescription>
Scale reads or serve queries closer to users.
</PageSectionDescription>
</PageSectionSummary>
<PageSectionAside>
<DocsButton href={`${DOCS_URL}/guides/platform/read-replicas`} />
{!isHighAvailability && (
<Button
type="button"
variant="primary"
icon={<Plus />}
onClick={() => setAddReplica(true)}
>
Add read replica
</Button>
)}
</PageSectionAside>
</PageSectionMeta>
<HighAvailabilityDisabledSectionNotice title="Read replicas are unavailable on High Availability projects" />
<PageSectionContent className="flex flex-col gap-y-4">
{isDatabasesError && (
<AlertError error={databasesError} subject="Failed to retrieve read replicas" />
)}
{isDatabasesLoading && <GenericSkeletonLoader />}
{isDatabasesSuccess && hasReplicas && (
<Card>
<CardContent className="p-0">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[20px]" />
<TableHead className="w-[250px]">Name</TableHead>
<TableHead className="w-[150px]">Status</TableHead>
<TableHead className="w-[150px]">Lag</TableHead>
<TableHead />
</TableRow>
</TableHeader>
<TableBody>
{readReplicas.map((replica) => (
<ReadReplicaRow
key={replica.identifier}
replica={replica}
onUpdateReplica={() => setStatusRefetchInterval(5000)}
/>
))}
</TableBody>
</Table>
</CardContent>
</Card>
)}
{isDatabasesSuccess && !hasReplicas && (
<EmptyStatePresentational
icon={Database}
title="No read replicas"
description="All reads and writes currently go to the primary."
>
{!isHighAvailability && (
<Button
type="button"
variant="default"
icon={<Plus />}
onClick={() => setAddReplica(true)}
>
Add read replica
</Button>
)}
</EmptyStatePresentational>
)}
</PageSectionContent>
</PageSection>
<AddReadReplicaDialog
open={isAddReplicaOpen}
onOpenChange={setAddReplica}
onSuccess={() => setStatusRefetchInterval(5000)}
onRecommendCompute={onRecommendCompute}
/>
</>
)
}