diff --git a/.github/workflows/docs-e2e.yml b/.github/workflows/docs-e2e.yml index 7a6b299186afe..06c68d4c2f127 100644 --- a/.github/workflows/docs-e2e.yml +++ b/.github/workflows/docs-e2e.yml @@ -1,21 +1,14 @@ name: Docs E2E Tests +# "Docs E2E" is a required status check on master, so this workflow must +# produce a check run on every PR — a `paths` trigger filter would leave +# non-docs PRs waiting on a check that never reports. Path scoping happens +# in the "Detect changed paths" step instead; when nothing docs-related +# changed, the remaining steps are skipped and the check reports green. on: pull_request: types: [opened, synchronize, reopened, ready_for_review, converted_to_draft] branches: ['master'] - paths: - - 'apps/docs/content/guides/**/*.mdx' - - 'apps/docs/content/troubleshooting/**/*.mdx' - - 'apps/docs/content/_partials/**' - - 'e2e/docs/features/**' - - 'e2e/docs/utils/**' - - 'e2e/docs/scripts/**' - - 'e2e/docs/playwright.config.ts' - - 'e2e/docs/package.json' - - 'e2e/docs/tsconfig.json' - - 'pnpm-lock.yaml' - - '.github/workflows/docs-e2e.yml' workflow_dispatch: inputs: base_url: @@ -49,7 +42,32 @@ jobs: runs-on: blacksmith-4vcpu-ubuntu-2404 steps: + # Runs before checkout — reads the PR file list from the API. `docs` + # mirrors the path scope this workflow used to have as a trigger filter; + # `docs_app` decides whether a Vercel docs preview exists to test against. + - name: Detect changed paths + id: changes + if: github.event_name == 'pull_request' + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + with: + filters: | + docs: + - 'apps/docs/content/guides/**/*.mdx' + - 'apps/docs/content/troubleshooting/**/*.mdx' + - 'apps/docs/content/_partials/**' + - 'e2e/docs/features/**' + - 'e2e/docs/utils/**' + - 'e2e/docs/scripts/**' + - 'e2e/docs/playwright.config.ts' + - 'e2e/docs/package.json' + - 'e2e/docs/tsconfig.json' + - 'pnpm-lock.yaml' + - '.github/workflows/docs-e2e.yml' + docs_app: + - 'apps/docs/**' + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + if: github.event_name == 'workflow_dispatch' || steps.changes.outputs.docs == 'true' with: persist-credentials: false # Need full history on PRs so we can diff against the base branch. @@ -65,6 +83,7 @@ jobs: apps/docs/scripts/federated-content/sources - name: Use Node.js + if: github.event_name == 'workflow_dispatch' || steps.changes.outputs.docs == 'true' uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version-file: '.nvmrc' @@ -73,6 +92,7 @@ jobs: # URLs. Harness-only PRs resolve to skip=true and exit before Playwright. - name: Resolve docs E2E scope id: scope + if: github.event_name == 'workflow_dispatch' || steps.changes.outputs.docs == 'true' env: EVENT_NAME: ${{ github.event_name }} BASE_REF: ${{ github.base_ref }} @@ -98,29 +118,21 @@ jobs: run: echo "No in-scope docs pages changed; skipping Playwright suite." - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 - if: steps.scope.outputs.skip != 'true' + if: steps.scope.outputs.skip == 'false' name: Install pnpm with: run_install: false - name: Enable pnpm store cache - if: steps.scope.outputs.skip != 'true' + if: steps.scope.outputs.skip == 'false' uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version-file: '.nvmrc' cache: 'pnpm' # Vercel skips the docs preview when a PR only changes the harness - # (e2e/docs, workflow). Wait for a preview only when apps/docs changed. - - name: Detect docs app changes - if: steps.scope.outputs.skip != 'true' && github.event_name == 'pull_request' - id: filter - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 - with: - filters: | - docs_app: - - 'apps/docs/**' - + # (e2e/docs, workflow), so wait for a preview only when apps/docs changed. + # # Vercel's GitHub App stopped writing GitHub Deployment objects on # 2026-02-17 (broken app auth), so vercel/wait-for-deployment-action # times out polling that API even though the preview builds fine. @@ -128,7 +140,7 @@ jobs: # those — then resolve the deployment it points to via Vercel's own API # to get the actual preview URL. See scripts/waitForVercelDocsPreview.js. - name: Wait for Vercel docs preview - if: steps.scope.outputs.skip != 'true' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && steps.filter.outputs.docs_app == 'true' + if: steps.scope.outputs.skip == 'false' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && steps.changes.outputs.docs_app == 'true' id: deployment run: node scripts/waitForVercelDocsPreview.js env: @@ -138,13 +150,13 @@ jobs: VERCEL_TEAM_ID: ${{ secrets.VERCEL_TEAM_ID }} - name: Resolve base URL - if: steps.scope.outputs.skip != 'true' + if: steps.scope.outputs.skip == 'false' id: base-url env: EVENT_NAME: ${{ github.event_name }} BASE_URL_INPUT: ${{ inputs.base_url }} DEPLOYMENT_URL: ${{ steps.deployment.outputs.deployment-url }} - DOCS_APP_CHANGED: ${{ steps.filter.outputs.docs_app }} + DOCS_APP_CHANGED: ${{ steps.changes.outputs.docs_app }} run: | if [ "$EVENT_NAME" = "workflow_dispatch" ]; then printf 'url=%s\n' "$BASE_URL_INPUT" >> "$GITHUB_OUTPUT" @@ -159,15 +171,15 @@ jobs: fi - name: Install dependencies - if: steps.scope.outputs.skip != 'true' + if: steps.scope.outputs.skip == 'false' run: pnpm install --frozen-lockfile --filter=e2e-docs... - name: Install Playwright Chromium - if: steps.scope.outputs.skip != 'true' + if: steps.scope.outputs.skip == 'false' run: pnpm -C e2e/docs exec playwright install chromium --with-deps --only-shell - name: Run docs E2E - if: steps.scope.outputs.skip != 'true' + if: steps.scope.outputs.skip == 'false' working-directory: e2e/docs run: pnpm run e2e:docs env: @@ -176,7 +188,7 @@ jobs: VERCEL_AUTOMATION_BYPASS_SECRET: ${{ steps.base-url.outputs.use_bypass == 'true' && secrets.VERCEL_AUTOMATION_BYPASS_DOCS || '' }} - name: Upload Playwright report - if: failure() && steps.scope.outputs.skip != 'true' + if: failure() && steps.scope.outputs.skip == 'false' uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: docs-playwright-report diff --git a/apps/studio/components/interfaces/Database/Replication/Destinations.tsx b/apps/studio/components/interfaces/Database/Replication/Destinations.tsx index f316069eb7769..ea4482fff97fc 100644 --- a/apps/studio/components/interfaces/Database/Replication/Destinations.tsx +++ b/apps/studio/components/interfaces/Database/Replication/Destinations.tsx @@ -25,13 +25,13 @@ import { Input } from 'ui-patterns/DataInputs/Input' import { EmptyStatePresentational } from 'ui-patterns/EmptyStatePresentational' import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' -import { REPLICA_STATUS } from '../../Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.constants' import { DestinationPanel } from './DestinationPanel/DestinationPanel' import { DestinationType } from './DestinationPanel/DestinationPanel.types' import { DestinationRow } from './DestinationRow' import { DisablePipelinesDialog } from './DisablePipelinesDialog' import { EnablePipelinesModal } from './EnablePipelinesCallout' import { ReadReplicaRow } from './ReadReplicas/ReadReplicaRow' +import { REPLICA_STATUS } from './Replication.constants' import { useIsETLBigQueryPrivateAlpha, useIsETLClickHousePrivateAlpha, diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DropReplicaConfirmationModal.tsx b/apps/studio/components/interfaces/Database/Replication/ReadReplicas/DropReplicaConfirmationModal.tsx similarity index 76% rename from apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DropReplicaConfirmationModal.tsx rename to apps/studio/components/interfaces/Database/Replication/ReadReplicas/DropReplicaConfirmationModal.tsx index b66f78eebb1cc..40be7d2e1f60b 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DropReplicaConfirmationModal.tsx +++ b/apps/studio/components/interfaces/Database/Replication/ReadReplicas/DropReplicaConfirmationModal.tsx @@ -1,9 +1,10 @@ import { useQueryClient } from '@tanstack/react-query' import { useParams } from 'common' import { toast } from 'sonner' -import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' +import { ConfirmationModal } from 'ui-patterns/Dialogs/ConfirmationModal' -import { REPLICA_STATUS } from './InstanceConfiguration.constants' +import { REPLICA_STATUS } from '../Replication.constants' +import { InlineLink } from '@/components/ui/InlineLink' import { replicaKeys } from '@/data/read-replicas/keys' import { useReadReplicaRemoveMutation } from '@/data/read-replicas/replica-remove-mutation' import type { Database } from '@/data/read-replicas/replicas-query' @@ -23,6 +24,26 @@ export const DropReplicaConfirmationModal = ({ const { ref: projectRef } = useParams() const queryClient = useQueryClient() const formattedId = formatDatabaseID(selectedReplica?.identifier ?? '') + + const getRemoveReplicaErrorMessage = (message: string) => { + const isPrivateLinkAssociationError = + /private\s*link/i.test(message) && /association/i.test(message) + + if (isPrivateLinkAssociationError) { + return ( + + Remove the replica{' '} + + PrivateLink association + {' '} + before dropping this read replica + + ) + } + + return `Failed to remove read replica: ${message}` + } + const { mutate: removeReadReplica, isPending: isRemoving } = useReadReplicaRemoveMutation({ onSuccess: () => { toast.success(`Tearing down read replica (ID: ${formattedId})`) @@ -44,6 +65,9 @@ export const DropReplicaConfirmationModal = ({ onSuccess() onCancel() }, + onError: (error) => { + toast.error(getRemoveReplicaErrorMessage(error.message)) + }, }) const onConfirmRemove = async () => { diff --git a/apps/studio/components/interfaces/Database/Replication/ReadReplicas/ReadReplicaDetails.tsx b/apps/studio/components/interfaces/Database/Replication/ReadReplicas/ReadReplicaDetails.tsx index 75f200a5a2822..15d9a3916ae2a 100644 --- a/apps/studio/components/interfaces/Database/Replication/ReadReplicas/ReadReplicaDetails.tsx +++ b/apps/studio/components/interfaces/Database/Replication/ReadReplicas/ReadReplicaDetails.tsx @@ -17,8 +17,8 @@ import { Input } from 'ui-patterns/DataInputs/Input' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' +import { REPLICA_STATUS } from '../Replication.constants' import { REPORT_DATERANGE_HELPER_LABELS } from '@/components/interfaces/Reports/Reports.constants' -import { REPLICA_STATUS } from '@/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.constants' import { ScaffoldContainer, ScaffoldSection } from '@/components/layouts/Scaffold' import { useInfraMonitoringAttributesQuery } from '@/data/analytics/infra-monitoring-query' import { useLoadBalancersQuery } from '@/data/read-replicas/load-balancers-query' diff --git a/apps/studio/components/interfaces/Database/Replication/ReadReplicas/ReadReplicaRow.tsx b/apps/studio/components/interfaces/Database/Replication/ReadReplicas/ReadReplicaRow.tsx index b278717dda84c..51ee5787cdb61 100644 --- a/apps/studio/components/interfaces/Database/Replication/ReadReplicas/ReadReplicaRow.tsx +++ b/apps/studio/components/interfaces/Database/Replication/ReadReplicas/ReadReplicaRow.tsx @@ -20,10 +20,10 @@ import { } from 'ui' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' +import { REPLICA_STATUS } from '../Replication.constants' +import { DropReplicaConfirmationModal } from './DropReplicaConfirmationModal' import { getIsInTransition, getStatusLabel } from './ReadReplicas.utils' -import { DropReplicaConfirmationModal } from '@/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DropReplicaConfirmationModal' -import { REPLICA_STATUS } from '@/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.constants' -import { RestartReplicaConfirmationModal } from '@/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/RestartReplicaConfirmationModal' +import { RestartReplicaConfirmationModal } from './RestartReplicaConfirmationModal' import { useReplicationLagQuery } from '@/data/read-replicas/replica-lag-query' import { type Database } from '@/data/read-replicas/replicas-query' import { formatDatabaseID } from '@/data/read-replicas/replicas.utils' diff --git a/apps/studio/components/interfaces/Database/Replication/ReadReplicas/ReadReplicas.utils.ts b/apps/studio/components/interfaces/Database/Replication/ReadReplicas/ReadReplicas.utils.ts index 272f3d6edfe79..1ecd153801673 100644 --- a/apps/studio/components/interfaces/Database/Replication/ReadReplicas/ReadReplicas.utils.ts +++ b/apps/studio/components/interfaces/Database/Replication/ReadReplicas/ReadReplicas.utils.ts @@ -1,4 +1,4 @@ -import { REPLICA_STATUS } from '@/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.constants' +import { REPLICA_STATUS } from '../Replication.constants' import { ReplicaInitializationStatus } from '@/data/read-replicas/replicas-status-query' export const getIsInTransition = ({ diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/RestartReplicaConfirmationModal.tsx b/apps/studio/components/interfaces/Database/Replication/ReadReplicas/RestartReplicaConfirmationModal.tsx similarity index 98% rename from apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/RestartReplicaConfirmationModal.tsx rename to apps/studio/components/interfaces/Database/Replication/ReadReplicas/RestartReplicaConfirmationModal.tsx index c6f99f98dcabb..c8a6c2c57863f 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/RestartReplicaConfirmationModal.tsx +++ b/apps/studio/components/interfaces/Database/Replication/ReadReplicas/RestartReplicaConfirmationModal.tsx @@ -3,7 +3,7 @@ import { useParams } from 'common' import { toast } from 'sonner' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' -import { REPLICA_STATUS } from './InstanceConfiguration.constants' +import { REPLICA_STATUS } from '../Replication.constants' import { useProjectRestartMutation } from '@/data/projects/project-restart-mutation' import { replicaKeys } from '@/data/read-replicas/keys' import { Database } from '@/data/read-replicas/replicas-query' diff --git a/apps/studio/components/interfaces/Database/Replication/Replication.constants.ts b/apps/studio/components/interfaces/Database/Replication/Replication.constants.ts index 3bcb22a7eca32..ddefacbe243d3 100644 --- a/apps/studio/components/interfaces/Database/Replication/Replication.constants.ts +++ b/apps/studio/components/interfaces/Database/Replication/Replication.constants.ts @@ -1,3 +1,7 @@ +import { components } from 'api-types' + +import { PROJECT_STATUS } from '@/lib/constants' + export const STATUS_REFRESH_FREQUENCY_MS: number = 10000 // 10 seconds export enum PipelineStatusName { @@ -8,3 +12,11 @@ export enum PipelineStatusName { STOPPING = 'stopping', UNKNOWN = 'unknown', } + +export const REPLICA_STATUS: { + [key: string]: components['schemas']['DatabaseStatusResponse']['status'] +} = { + ...PROJECT_STATUS, + INIT_READ_REPLICA: 'INIT_READ_REPLICA', + INIT_READ_REPLICA_FAILED: 'INIT_READ_REPLICA_FAILED', +} diff --git a/apps/studio/components/interfaces/Database/Replication/ReplicationDiagram/Edges.tsx b/apps/studio/components/interfaces/Database/Replication/ReplicationDiagram/Edges.tsx index 4f35e64e14e4f..757735cd0f915 100644 --- a/apps/studio/components/interfaces/Database/Replication/ReplicationDiagram/Edges.tsx +++ b/apps/studio/components/interfaces/Database/Replication/ReplicationDiagram/Edges.tsx @@ -5,8 +5,7 @@ import { useMemo } from 'react' import { cn } from 'ui' import { getStatusName } from '../Pipeline.utils' -import { STATUS_REFRESH_FREQUENCY_MS } from '../Replication.constants' -import { REPLICA_STATUS } from '@/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.constants' +import { REPLICA_STATUS, STATUS_REFRESH_FREQUENCY_MS } from '../Replication.constants' import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query' import { useReplicationPipelineStatusQuery } from '@/data/replication/pipeline-status-query' import { useReplicationPipelinesQuery } from '@/data/replication/pipelines-query' diff --git a/apps/studio/components/interfaces/ProjectHome/TopSection.tsx b/apps/studio/components/interfaces/ProjectHome/TopSection.tsx index 4002e029d2f6d..11247caecdad6 100644 --- a/apps/studio/components/interfaces/ProjectHome/TopSection.tsx +++ b/apps/studio/components/interfaces/ProjectHome/TopSection.tsx @@ -97,7 +97,7 @@ export const TopSection = () => { )} > - + diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DropAllReplicasConfirmationModal.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DropAllReplicasConfirmationModal.tsx deleted file mode 100644 index b7e8e6d20087d..0000000000000 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DropAllReplicasConfirmationModal.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { useQueryClient } from '@tanstack/react-query' -import { useParams } from 'common' -import { toast } from 'sonner' -import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' - -import { replicaKeys } from '@/data/read-replicas/keys' -import { useReadReplicaRemoveMutation } from '@/data/read-replicas/replica-remove-mutation' -import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query' - -interface DropAllReplicasConfirmationModalProps { - visible: boolean - onSuccess: () => void - onCancel: () => void -} - -const DropAllReplicasConfirmationModal = ({ - visible, - onSuccess, - onCancel, -}: DropAllReplicasConfirmationModalProps) => { - const { ref: projectRef } = useParams() - const queryClient = useQueryClient() - const { data: databases } = useReadReplicasQuery({ projectRef }) - const { mutateAsync: removeReadReplica, isPending: isRemoving } = useReadReplicaRemoveMutation() - - const onConfirmRemove = async () => { - if (!projectRef) return console.error('Project is required') - if (databases === undefined) return console.error('Unable to retrieve replicas') - if (databases.length === 1) toast('Your project has no read replicas') - - const replicas = databases.filter((db) => db.identifier !== projectRef) - try { - await Promise.all( - replicas.map((db) => - removeReadReplica({ - projectRef, - identifier: db.identifier, - invalidateReplicaQueries: false, - }) - ) - ) - toast.success(`Tearing down all read replicas`) - - await Promise.all([ - queryClient.invalidateQueries({ queryKey: replicaKeys.list(projectRef) }), - queryClient.invalidateQueries({ queryKey: replicaKeys.loadBalancers(projectRef) }), - ]) - - onSuccess() - onCancel() - } catch (error) { - toast.error('Failed to drop all replicas') - } - } - - return ( - onCancel()} - onConfirm={() => onConfirmRemove()} - alert={{ - title: 'This action cannot be undone', - description: 'You may still deploy new replicas in this region thereafter', - }} - > -

Before deleting all replicas, consider:

- -
- ) -} - -export default DropAllReplicasConfirmationModal diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/Edge.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/Edge.tsx index 2e078341cd741..e260871c7638a 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/Edge.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/Edge.tsx @@ -3,7 +3,8 @@ import { useParams } from 'common' import { Loader2 } from 'lucide-react' import { Tooltip, TooltipContent, TooltipTrigger } from 'ui' -import { EdgeData, REPLICA_STATUS } from './InstanceConfiguration.constants' +import { EdgeData } from './InstanceConfiguration.constants' +import { REPLICA_STATUS } from '@/components/interfaces/Database/Replication/Replication.constants' import { useReplicationLagQuery } from '@/data/read-replicas/replica-lag-query' import { formatDatabaseID } from '@/data/read-replicas/replicas.utils' diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.constants.ts b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.constants.ts index 25736d4eb576b..2d49e4739ccad 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.constants.ts +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.constants.ts @@ -2,9 +2,6 @@ import { ReadReplicaSetupError, ReadReplicaSetupProgress } from '@supabase/share import type { AWS_REGIONS_KEYS } from 'shared-data' import { AWS_REGIONS } from 'shared-data' -import { components } from '@/data/api' -import { PROJECT_STATUS } from '@/lib/constants' - export interface Region { key: AWS_REGIONS_KEYS name: string @@ -59,14 +56,6 @@ export const NODE_HEIGHT_FALLBACKS: Record = { REGION: REGION_NODE_HEIGHT, } -export const REPLICA_STATUS: { - [key: string]: components['schemas']['DatabaseStatusResponse']['status'] -} = { - ...PROJECT_STATUS, - INIT_READ_REPLICA: 'INIT_READ_REPLICA', - INIT_READ_REPLICA_FAILED: 'INIT_READ_REPLICA_FAILED', -} - // [Joshen] Coordinates from https://github.com/tobilg/aws-edge-locations/blob/main/data/aws-edge-locations.json // In the format of [lon, lat] export const AWS_REGIONS_COORDINATES: { [key: string]: [number, number] } = { diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.tsx index 4534fba7ceac5..5123d172de06e 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { Background, ColorMode, @@ -9,74 +8,42 @@ import { useReactFlow, } from '@xyflow/react' import { partition } from 'lodash' -import { ChevronDown, Globe2, Loader2, Network } from 'lucide-react' +import { Globe2, Loader2, Network } from 'lucide-react' import { useTheme } from 'next-themes' -import Link from 'next/link' import { useEffect, useEffectEvent, useMemo, useState } from 'react' import '@xyflow/react/dist/style.css' import { useParams } from 'common' -import { useRouter } from 'next/router' -import { - Button, - cn, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from 'ui' +import { Button, cn } from 'ui' -import DropAllReplicasConfirmationModal from './DropAllReplicasConfirmationModal' -import { DropReplicaConfirmationModal } from './DropReplicaConfirmationModal' import { SmoothstepEdge } from './Edge' -import { REPLICA_STATUS } from './InstanceConfiguration.constants' import { addRegionNodes, generateNodes, getDagreGraphLayout } from './InstanceConfiguration.utils' import { LoadBalancerNode, PrimaryNode, RegionNode, ReplicaNode } from './InstanceNode' import MapView from './MapView' -import { RestartReplicaConfirmationModal } from './RestartReplicaConfirmationModal' -import { getInfrastructurePath } from '@/components/interfaces/Settings/Infrastructure/Infrastructure.utils' +import { REPLICA_STATUS } from '@/components/interfaces/Database/Replication/Replication.constants' import { AlertError } from '@/components/ui/AlertError' -import { ButtonTooltip } from '@/components/ui/ButtonTooltip' import { useLoadBalancersQuery } from '@/data/read-replicas/load-balancers-query' -import { Database, useReadReplicasQuery } from '@/data/read-replicas/replicas-query' +import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query' import { ReplicaInitializationStatus, useReadReplicasStatusesQuery, } from '@/data/read-replicas/replicas-status-query' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' -import { - useIsAwsCloudProvider, - useIsOrioleDb, - useSelectedProjectQuery, -} from '@/hooks/misc/useSelectedProject' +import { useIsAwsCloudProvider, useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { timeout } from '@/lib/helpers' -interface InstanceConfigurationUIProps { - diagramOnly?: boolean -} - -const InstanceConfigurationUI = ({ diagramOnly = false }: InstanceConfigurationUIProps) => { - const router = useRouter() +const InstanceConfigurationUI = () => { const reactFlow = useReactFlow() - const isOrioleDb = useIsOrioleDb() const { resolvedTheme } = useTheme() const { ref: projectRef } = useParams() const { isPending: isLoadingProject } = useSelectedProjectQuery() const isAws = useIsAwsCloudProvider() const { infrastructureReadReplicas } = useIsFeatureEnabled(['infrastructure:read_replicas']) - const newReplicaURL = `/project/${projectRef}/database/replication?destinationType=Read+Replica` const [view, setView] = useState<'flow' | 'map'>('flow') - const [showDeleteAllModal, setShowDeleteAllModal] = useState(false) const [refetchInterval, setRefetchInterval] = useState(10000) - const [selectedReplicaToDrop, setSelectedReplicaToDrop] = useState() - const [selectedReplicaToRestart, setSelectedReplicaToRestart] = useState() - - const { can: canManageReplicas } = useAsyncCheckPermissions(PermissionAction.CREATE, 'projects') const { data: loadBalancers, @@ -152,8 +119,6 @@ const InstanceConfigurationUI = ({ diagramOnly = false }: InstanceConfigurationU primary, replicas, loadBalancers: loadBalancers ?? [], - onSelectRestartReplica: setSelectedReplicaToRestart, - onSelectDropReplica: setSelectedReplicaToDrop, }) : [], [isSuccessReplicas, isSuccessLoadBalancers, primary, replicas, loadBalancers] @@ -257,9 +222,9 @@ const InstanceConfigurationUI = ({ diagramOnly = false }: InstanceConfigurationU }, [nodesInitialized]) return ( -
+
@@ -269,48 +234,8 @@ const InstanceConfigurationUI = ({ diagramOnly = false }: InstanceConfigurationU {isError && } {isSuccessReplicas && !isLoadingProject && ( <> - {!diagramOnly && infrastructureReadReplicas && ( + {infrastructureReadReplicas && (
-
- 0 ? 'rounded-r-none' : '')} - tooltip={{ - content: { - side: 'bottom', - text: !canManageReplicas - ? 'You need additional permissions to deploy replicas' - : isOrioleDb - ? 'Read replicas are not supported with OrioleDB' - : undefined, - }, - }} - > - Deploy a new replica - - {replicas.length > 0 && ( - - -
{isAws && (
- - {!diagramOnly && ( - <> - setRefetchInterval(5000)} - onCancel={() => setSelectedReplicaToDrop(undefined)} - /> - - setRefetchInterval(5000)} - onCancel={() => setShowDeleteAllModal(false)} - /> - - setRefetchInterval(5000)} - onCancel={() => setSelectedReplicaToRestart(undefined)} - /> - - )}
) } -interface InstanceConfigurationProps { - diagramOnly?: boolean -} - -export const InstanceConfiguration = ({ diagramOnly = false }: InstanceConfigurationProps) => { +export const InstanceConfiguration = () => { return ( - + ) } diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.utils.ts b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.utils.ts index 87f9d42472276..81e89e5ae2b2d 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.utils.ts +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.utils.ts @@ -21,14 +21,10 @@ export const generateNodes = ({ primary, replicas, loadBalancers, - onSelectRestartReplica, - onSelectDropReplica, }: { primary: Database replicas: Database[] loadBalancers: LoadBalancer[] - onSelectRestartReplica: (database: Database) => void - onSelectDropReplica: (database: Database) => void }): Node[] => { const position = { x: 0, y: 0 } const regions = groupBy(replicas, (d) => { @@ -107,8 +103,6 @@ export const generateNodes = ({ inserted_at: database.inserted_at, computeSize: database.size, status: database.status, - onSelectRestartReplica: () => onSelectRestartReplica(database), - onSelectDropReplica: () => onSelectDropReplica(database), }, } }) diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx index a36da60c59e37..725260ff620b7 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx @@ -29,11 +29,11 @@ import { NODE_WIDTH, PrimaryNodeData, REGION_NODE_HEIGHT, - REPLICA_STATUS, ReplicaNodeData, } from './InstanceConfiguration.constants' import { formatSeconds } from './InstanceConfiguration.utils' import { metricColor } from './InstanceNode.utils' +import { REPLICA_STATUS } from '@/components/interfaces/Database/Replication/Replication.constants' import { SparkBar } from '@/components/ui/SparkBar' import { DatabaseInitEstimations, diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/MapView.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/MapView.tsx index c8bc70c01fb2f..b551b4a707da5 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/MapView.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/MapView.tsx @@ -1,4 +1,3 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import dayjs from 'dayjs' import { partition, uniqBy } from 'lodash' @@ -14,7 +13,6 @@ import { Marker, ZoomableGroup, } from 'react-simple-maps' -import type { AWS_REGIONS_KEYS } from 'shared-data' import { Badge, Button, @@ -25,31 +23,18 @@ import { DropdownMenuTrigger, ScrollArea, } from 'ui' +import { TimestampInfo } from 'ui-patterns/TimestampInfo' -import { AVAILABLE_REPLICA_REGIONS, REPLICA_STATUS } from './InstanceConfiguration.constants' +import { AVAILABLE_REPLICA_REGIONS } from './InstanceConfiguration.constants' import GeographyData from './MapData.json' -import { ButtonTooltip } from '@/components/ui/ButtonTooltip' -import { DropdownMenuItemTooltip } from '@/components/ui/DropdownMenuItemTooltip' -import { Database, useReadReplicasQuery } from '@/data/read-replicas/replicas-query' +import { REPLICA_STATUS } from '@/components/interfaces/Database/Replication/Replication.constants' +import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query' import { formatDatabaseID } from '@/data/read-replicas/replicas.utils' -import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' import { BASE_PATH } from '@/lib/constants' import { useDatabaseSelectorStateSnapshot } from '@/state/database-selector' -// [Joshen] Foresee that we'll skip this view for initial launch - -interface MapViewProps { - onSelectDeployNewReplica: (region: AWS_REGIONS_KEYS) => void - onSelectRestartReplica: (database: Database) => void - onSelectDropReplica: (database: Database) => void -} - -const MapView = ({ - onSelectDeployNewReplica, - onSelectRestartReplica, - onSelectDropReplica, -}: MapViewProps) => { +const MapView = () => { const { ref } = useParams() const dbSelectorState = useDatabaseSelectorStateSnapshot() const { projectHomepageShowInstanceSize } = useIsFeatureEnabled([ @@ -64,7 +49,6 @@ const MapView = ({ y: number region: { key: string; country?: string; name?: string; region?: string } }>() - const { can: canManageReplicas } = useAsyncCheckPermissions(PermissionAction.CREATE, 'projects') const [, setShowConnect] = useQueryState('showConnect', parseAsBoolean.withDefault(false)) const { data } = useReadReplicasQuery({ projectRef: ref }) @@ -260,7 +244,7 @@ const MapView = ({ 2 ? '180px' : 'auto' }}>
    {databasesInSelectedRegion.map((database) => { - const created = dayjs(database.inserted_at).format('DD MMM YYYY, HH:mm:ss (ZZ)') + const created = dayjs(database.inserted_at).format('DD MMM YYYY') return (
  • Unhealthy )}

    -

    - AWS{projectHomepageShowInstanceSize ? ` • ${database.size}` : ''} -

    - {database.identifier !== ref && ( -

    Created on: {created}

    - )} +
    +

    + AWS{projectHomepageShowInstanceSize ? ` • ${database.size}` : ''} +

    + {database.identifier !== ref && ( +

    + Created on:{' '} + +

    + )} +
{database.identifier !== ref && ( @@ -310,40 +299,16 @@ const MapView = ({ > View connection string - - - View replication lag - - - onSelectRestartReplica(database)} - disabled={database.status !== REPLICA_STATUS.ACTIVE_HEALTHY} - > - Restart replica + + + Manage replica + - - onSelectDropReplica(database)} - tooltip={{ - content: { - side: 'left', - text: 'You need additional permissions to drop replicas', - }, - }} - > - Drop replica - )} @@ -355,25 +320,10 @@ const MapView = ({ )}
0 ? 'border-t' : '' }`} > - onSelectDeployNewReplica(selectedRegion.key)} - tooltip={{ - content: { - side: 'bottom', - text: !canManageReplicas - ? 'You need additional permissions to deploy replicas' - : undefined, - }, - }} - > - Deploy new replica here -