diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/ReadReplicas/ReadReplicasSection.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/ReadReplicas/ReadReplicasSection.tsx index 33cadc637a8..3b8d1121d7e 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/ReadReplicas/ReadReplicasSection.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/ReadReplicas/ReadReplicasSection.tsx @@ -2,7 +2,7 @@ import { useParams } from 'common' import { Database } from 'icons' import { Plus } from 'lucide-react' import { parseAsBoolean, useQueryState } from 'nuqs' -import { useEffect, useMemo, useState } from 'react' +import { useEffect, useState } from 'react' import { Button, Card, CardContent, Table, TableBody, TableHead, TableHeader, TableRow } from 'ui' import { EmptyStatePresentational } from 'ui-patterns/EmptyStatePresentational' import { @@ -44,12 +44,15 @@ export const ReadReplicasSection = () => { isPending: isDatabasesLoading, isError: isDatabasesError, isSuccess: isDatabasesSuccess, - } = useReadReplicasQuery({ projectRef }, { refetchInterval: statusRefetchInterval }) - - const readReplicas = useMemo( - () => databases.filter((x) => x.identifier !== projectRef), - [databases, projectRef] + } = useReadReplicasQuery( + { projectRef }, + { + enabled: infrastructureReadReplicas, + refetchInterval: infrastructureReadReplicas ? statusRefetchInterval : false, + } ) + + const readReplicas = databases.filter((database) => database.identifier !== projectRef) const hasReplicas = isDatabasesSuccess && readReplicas.length > 0 useEffect(() => { @@ -60,9 +63,11 @@ export const ReadReplicasSection = () => { REPLICA_STATUS.ACTIVE_UNHEALTHY, REPLICA_STATUS.INIT_READ_REPLICA_FAILED, ] - const replicasInTransition = readReplicas.filter((db) => !fixedStatuses.includes(db.status)) + const replicasInTransition = databases.filter( + (database) => database.identifier !== projectRef && !fixedStatuses.includes(database.status) + ) if (replicasInTransition.length === 0) setStatusRefetchInterval(false) - }, [isDatabasesSuccess, readReplicas]) + }, [isDatabasesSuccess, databases, projectRef]) if (!infrastructureReadReplicas) return null diff --git a/apps/studio/tests/components/Settings/Infrastructure/ReadReplicasSection.test.tsx b/apps/studio/tests/components/Settings/Infrastructure/ReadReplicasSection.test.tsx index 665ff7e5280..0850bc2b732 100644 --- a/apps/studio/tests/components/Settings/Infrastructure/ReadReplicasSection.test.tsx +++ b/apps/studio/tests/components/Settings/Infrastructure/ReadReplicasSection.test.tsx @@ -1,6 +1,6 @@ import { screen } from '@testing-library/react' import { HttpResponse } from 'msw' -import { describe, expect, test, vi } from 'vitest' +import { beforeEach, describe, expect, test, vi } from 'vitest' import { ReadReplicasSection } from '@/components/interfaces/Settings/Infrastructure/ReadReplicas/ReadReplicasSection' import type { components } from '@/data/api' @@ -11,43 +11,56 @@ type DatabaseDetailResponse = components['schemas']['DatabaseDetailResponse'] type DatabaseStatusResponse = components['schemas']['DatabaseStatusResponse'] type LoadBalancerDetailResponse = components['schemas']['LoadBalancerDetailResponse'] -vi.mock('@/hooks/misc/useIsFeatureEnabled', () => ({ - useIsFeatureEnabled: () => ({ infrastructureReadReplicas: true }), +const { mockUseIsFeatureEnabled } = vi.hoisted(() => ({ + mockUseIsFeatureEnabled: vi.fn(() => ({ infrastructureReadReplicas: true })), })) +vi.mock('@/hooks/misc/useIsFeatureEnabled', () => ({ + useIsFeatureEnabled: mockUseIsFeatureEnabled, +})) + +const addReplicaListMocks = () => { + addAPIMock({ + method: 'get', + path: '/platform/projects/:ref/databases', + response: () => + HttpResponse.json([ + { + cloud_provider: 'AWS', + connectionString: 'postgresql://postgres:password@db.default.supabase.co:5432/postgres', + db_host: 'db.default.supabase.co', + db_name: 'postgres', + db_port: 5432, + db_user: 'postgres', + identifier: 'default', + inserted_at: '2026-01-01T00:00:00.000Z', + region: 'us-east-1', + restUrl: 'https://default.supabase.co', + size: 't4g.small', + status: 'ACTIVE_HEALTHY', + }, + ]), + }) + addAPIMock({ + method: 'get', + path: '/platform/projects/:ref/databases-statuses', + response: () => HttpResponse.json([]), + }) + addAPIMock({ + method: 'get', + path: '/platform/projects/:ref/load-balancers', + response: () => HttpResponse.json([]), + }) +} + describe('ReadReplicasSection', () => { + beforeEach(() => { + mockUseIsFeatureEnabled.mockReturnValue({ infrastructureReadReplicas: true }) + }) + test('renders the read replicas section with add CTA and empty state', async () => { - addAPIMock({ - method: 'get', - path: '/platform/projects/:ref/databases', - response: () => - HttpResponse.json([ - { - cloud_provider: 'AWS', - connectionString: 'postgresql://postgres:password@db.default.supabase.co:5432/postgres', - db_host: 'db.default.supabase.co', - db_name: 'postgres', - db_port: 5432, - db_user: 'postgres', - identifier: 'default', - inserted_at: '2026-01-01T00:00:00.000Z', - region: 'us-east-1', - restUrl: 'https://default.supabase.co', - size: 't4g.small', - status: 'ACTIVE_HEALTHY', - }, - ]), - }) - addAPIMock({ - method: 'get', - path: '/platform/projects/:ref/databases-statuses', - response: () => HttpResponse.json([]), - }) - addAPIMock({ - method: 'get', - path: '/platform/projects/:ref/load-balancers', - response: () => HttpResponse.json([]), - }) + mockUseIsFeatureEnabled.mockReturnValue({ infrastructureReadReplicas: true }) + addReplicaListMocks() customRender() @@ -55,4 +68,24 @@ describe('ReadReplicasSection', () => { expect(await screen.findByText('No read replicas')).toBeInTheDocument() expect(screen.getAllByRole('button', { name: /Add read replica/i }).length).toBeGreaterThan(0) }) + + test('does not fetch replicas when the feature is disabled', async () => { + mockUseIsFeatureEnabled.mockReturnValue({ infrastructureReadReplicas: false }) + + let fetchedReplicas = false + addAPIMock({ + method: 'get', + path: '/platform/projects/:ref/databases', + response: () => { + fetchedReplicas = true + return HttpResponse.json([]) + }, + }) + + customRender() + + expect(screen.queryByText('Read replicas')).not.toBeInTheDocument() + await new Promise((resolve) => setTimeout(resolve, 50)) + expect(fetchedReplicas).toBe(false) + }) })