From 270925b6809e1164e8845c72ce475243f4e8edc7 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Tue, 4 Aug 2026 14:58:43 +0800 Subject: [PATCH 1/4] feat(studio): add dashboard_auth:sign_in_with_chatgpt enabled feature (#48677) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a `dashboard_auth:sign_in_with_chatgpt` enabled-features flag so deployments can disable the sign in with ChatGPT button via `disabled_features`, the same way `dashboard_auth:sign_in_with_github` works. Previously the button was only gated by the ConfigCat rollout flag / localStorage opt-in, so white-labeled deployments with custom auth providers had no way to turn it off. **Added:** - `dashboard_auth:sign_in_with_chatgpt` (default `true`) in `enabled-features.json` + schema - Tests covering the feature-disabled state **Changed:** - `useEnabledIdentityProviders` now gates ChatGPT as `featureEnabled && (localStorageOptIn || configCatFlag)` — the feature flag is the static kill switch, the existing OR'd pair remains the rollout mechanism ## To test - Sign-in and sign-up pages behave exactly as before by default (flag defaults to `true`, ConfigCat/localStorage rollout gate unchanged) - With `dashboard_auth:sign_in_with_chatgpt` in a profile's `disabled_features`, the ChatGPT button no longer renders even with `?siwc-enabled=1` or the ConfigCat flag on - GitHub button gating unaffected ## Summary by CodeRabbit - **New Features** - Added a feature flag to control ChatGPT sign-in availability. - ChatGPT sign-in is now available only when the feature is enabled and an applicable rollout or opt-in condition is met. - **Tests** - Expanded coverage for ChatGPT and GitHub sign-in provider availability under different feature-flag and rollout conditions. Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com> --- .../useEnabledIdentityProviders.test.ts | 57 +++++++++++++++---- .../hooks/misc/useEnabledIdentityProviders.ts | 22 ++++--- .../enabled-features/enabled-features.json | 1 + .../enabled-features.schema.json | 5 ++ 4 files changed, 66 insertions(+), 19 deletions(-) diff --git a/apps/studio/hooks/misc/__tests__/useEnabledIdentityProviders.test.ts b/apps/studio/hooks/misc/__tests__/useEnabledIdentityProviders.test.ts index 38ef489246726..8c42bd61283cb 100644 --- a/apps/studio/hooks/misc/__tests__/useEnabledIdentityProviders.test.ts +++ b/apps/studio/hooks/misc/__tests__/useEnabledIdentityProviders.test.ts @@ -24,15 +24,20 @@ vi.mock('common', async (importOriginal) => ({ useFlag: mockUseFlag, })) +function mockFeatures({ github = false, chatgpt = true }: { github?: boolean; chatgpt?: boolean }) { + mockIsFeatureEnabled.mockReturnValue({ + dashboardAuthSignInWithGithub: github, + dashboardAuthSignInWithChatgpt: chatgpt, + }) +} + describe('useEnabledIdentityProviders', () => { beforeEach(() => { mockUseFlag.mockReset() }) it('returns every provider when all flags are enabled', () => { - mockIsFeatureEnabled.mockReturnValue({ - dashboardAuthSignInWithGithub: true, - }) + mockFeatures({ github: true, chatgpt: true }) mockUseLocalStorageQuery.mockReturnValue([true]) mockUseFlag.mockReturnValue(true) @@ -42,9 +47,7 @@ describe('useEnabledIdentityProviders', () => { }) it('returns no providers when all flags are disabled', () => { - mockIsFeatureEnabled.mockReturnValue({ - dashboardAuthSignInWithGithub: false, - }) + mockFeatures({ github: false, chatgpt: false }) mockUseLocalStorageQuery.mockReturnValue([false]) mockUseFlag.mockReturnValue(false) @@ -54,7 +57,7 @@ describe('useEnabledIdentityProviders', () => { }) it('includes ChatGPT when localStorage is true and configcat is true', () => { - mockIsFeatureEnabled.mockReturnValue({ dashboardAuthSignInWithGithub: false }) + mockFeatures({ chatgpt: true }) mockUseLocalStorageQuery.mockReturnValue([true]) mockUseFlag.mockReturnValue(true) @@ -64,7 +67,7 @@ describe('useEnabledIdentityProviders', () => { }) it('includes ChatGPT when localStorage is true and configcat is false', () => { - mockIsFeatureEnabled.mockReturnValue({ dashboardAuthSignInWithGithub: false }) + mockFeatures({ chatgpt: true }) mockUseLocalStorageQuery.mockReturnValue([true]) mockUseFlag.mockReturnValue(false) @@ -74,7 +77,7 @@ describe('useEnabledIdentityProviders', () => { }) it('includes ChatGPT when localStorage is false and configcat is true', () => { - mockIsFeatureEnabled.mockReturnValue({ dashboardAuthSignInWithGithub: false }) + mockFeatures({ chatgpt: true }) mockUseLocalStorageQuery.mockReturnValue([false]) mockUseFlag.mockReturnValue(true) @@ -84,7 +87,7 @@ describe('useEnabledIdentityProviders', () => { }) it('excludes ChatGPT when localStorage is false and configcat is false', () => { - mockIsFeatureEnabled.mockReturnValue({ dashboardAuthSignInWithGithub: false }) + mockFeatures({ chatgpt: true }) mockUseLocalStorageQuery.mockReturnValue([false]) mockUseFlag.mockReturnValue(false) @@ -93,8 +96,38 @@ describe('useEnabledIdentityProviders', () => { expect(result.current).toEqual([]) }) + it('excludes ChatGPT when its feature flag is disabled but configcat is true', () => { + mockFeatures({ chatgpt: false }) + mockUseLocalStorageQuery.mockReturnValue([false]) + mockUseFlag.mockReturnValue(true) + + const { result } = renderHook(() => useEnabledIdentityProviders()) + + expect(result.current).toEqual([]) + }) + + it('excludes ChatGPT when its feature flag is disabled but localStorage is opted in', () => { + mockFeatures({ chatgpt: false }) + mockUseLocalStorageQuery.mockReturnValue([true]) + mockUseFlag.mockReturnValue(false) + + const { result } = renderHook(() => useEnabledIdentityProviders()) + + expect(result.current).toEqual([]) + }) + + it('excludes ChatGPT when its feature flag is disabled and both rollout gates are on', () => { + mockFeatures({ github: true, chatgpt: false }) + mockUseLocalStorageQuery.mockReturnValue([true]) + mockUseFlag.mockReturnValue(true) + + const { result } = renderHook(() => useEnabledIdentityProviders()) + + expect(result.current).toEqual([GITHUB_IDENTITY_PROVIDER]) + }) + it('includes GitHub when its feature flag is enabled', () => { - mockIsFeatureEnabled.mockReturnValue({ dashboardAuthSignInWithGithub: true }) + mockFeatures({ github: true, chatgpt: true }) mockUseLocalStorageQuery.mockReturnValue([false]) mockUseFlag.mockReturnValue(false) @@ -104,7 +137,7 @@ describe('useEnabledIdentityProviders', () => { }) it('excludes GitHub when its feature flag is disabled', () => { - mockIsFeatureEnabled.mockReturnValue({ dashboardAuthSignInWithGithub: false }) + mockFeatures({ github: false, chatgpt: true }) mockUseLocalStorageQuery.mockReturnValue([false]) mockUseFlag.mockReturnValue(false) diff --git a/apps/studio/hooks/misc/useEnabledIdentityProviders.ts b/apps/studio/hooks/misc/useEnabledIdentityProviders.ts index 939a7e0e9ff29..681bedce42bb9 100644 --- a/apps/studio/hooks/misc/useEnabledIdentityProviders.ts +++ b/apps/studio/hooks/misc/useEnabledIdentityProviders.ts @@ -14,14 +14,19 @@ import { * To add a provider: declare its config in `lib/external-identity-providers.ts`, add a * `dashboard_auth:sign_in_with_*` flag, and gate it here. * - * ChatGPT is a deliberate exception: it's rolled out via the `ShowSignInWithChatGptButton` - * ConfigCat flag OR'd with a manual, localStorage-only opt-in switch - * (`LOCAL_STORAGE_KEYS.SIGN_IN_CHATGPT_ENABLED`, flippable via the `?siwc-enabled=1` query param — - * see `useSiwcQueryParamOptIn`), instead of the static `dashboard_auth:sign_in_with_*` pattern. + * ChatGPT carries an extra rollout gate on top of its `dashboard_auth:sign_in_with_chatgpt` flag: + * the feature flag must be enabled AND either the `ShowSignInWithChatGptButton` ConfigCat flag or a + * manual, localStorage-only opt-in switch (`LOCAL_STORAGE_KEYS.SIGN_IN_CHATGPT_ENABLED`, flippable + * via the `?siwc-enabled=1` query param — see `useSiwcQueryParamOptIn`) must be on. The feature flag + * is the static kill switch; the OR'd pair is the progressive rollout mechanism. */ export function useEnabledIdentityProviders(): ExternalIdentityProviderConfig[] { - const { dashboardAuthSignInWithGithub: githubEnabled } = useIsFeatureEnabled([ + const { + dashboardAuthSignInWithGithub: githubEnabled, + dashboardAuthSignInWithChatgpt: chatgptFeatureEnabled, + } = useIsFeatureEnabled([ 'dashboard_auth:sign_in_with_github', + 'dashboard_auth:sign_in_with_chatgpt', ]) const [chatgptLocalStorageEnabled] = useLocalStorageQuery( @@ -30,12 +35,15 @@ export function useEnabledIdentityProviders(): ExternalIdentityProviderConfig[] ) const chatGptConfigCatFlagEnabled = useFlag('ShowSignInWithChatGptButton') + const isChatGptEnabled = + chatgptFeatureEnabled && (chatgptLocalStorageEnabled || chatGptConfigCatFlagEnabled) + return useMemo( () => [ githubEnabled && GITHUB_IDENTITY_PROVIDER, - (chatgptLocalStorageEnabled || chatGptConfigCatFlagEnabled) && CHATGPT_IDENTITY_PROVIDER, + isChatGptEnabled && CHATGPT_IDENTITY_PROVIDER, ].filter((p): p is ExternalIdentityProviderConfig => Boolean(p)), - [githubEnabled, chatgptLocalStorageEnabled, chatGptConfigCatFlagEnabled] + [githubEnabled, isChatGptEnabled] ) } diff --git a/packages/common/enabled-features/enabled-features.json b/packages/common/enabled-features/enabled-features.json index 7e4fcb40075d5..bdf512b1e13bc 100644 --- a/packages/common/enabled-features/enabled-features.json +++ b/packages/common/enabled-features/enabled-features.json @@ -34,6 +34,7 @@ "dashboard_auth:sign_up": true, "dashboard_auth:sign_in_with_github": true, + "dashboard_auth:sign_in_with_chatgpt": true, "dashboard_auth:sign_in_with_sso": true, "dashboard_auth:sign_in_with_email": true, "dashboard_auth:show_testimonial": true, diff --git a/packages/common/enabled-features/enabled-features.schema.json b/packages/common/enabled-features/enabled-features.schema.json index 0a63c3e2b63d9..4ca4fda54b807 100644 --- a/packages/common/enabled-features/enabled-features.schema.json +++ b/packages/common/enabled-features/enabled-features.schema.json @@ -115,6 +115,10 @@ "type": "boolean", "description": "Enable the sign in with github provider" }, + "dashboard_auth:sign_in_with_chatgpt": { + "type": "boolean", + "description": "Enable the sign in with ChatGPT provider" + }, "dashboard_auth:sign_in_with_sso": { "type": "boolean", "description": "Enable the sign in with sso provider" @@ -468,6 +472,7 @@ "billing:all", "dashboard_auth:sign_up", "dashboard_auth:sign_in_with_github", + "dashboard_auth:sign_in_with_chatgpt", "dashboard_auth:sign_in_with_sso", "dashboard_auth:sign_in_with_email", "dashboard_auth:show_tos", From 3fdaf14b4e8ca1b86913b026047c6b72aa34a45e Mon Sep 17 00:00:00 2001 From: Ana <30495040+ana1337x@users.noreply.github.com> Date: Tue, 4 Aug 2026 02:58:46 -0400 Subject: [PATCH 2/4] chore(www): update Edge Functions customer quote to eXp Realty (#48667) ## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? - Updates the customer testimonial on the Edge Functions product page ## What is the current behavior? The Edge Functions page shows an older customer quote. ## What is the new behavior? - The quote is from Seth Siegler, Chief Innovation Officer at eXp Realty - The quote is taken verbatim from the published eXp Realty customer story - The attribution links to /customers/exprealty - Uses the existing seth-siegler.jpg avatar already in the repo; no new assets ## Additional context N/A ## Summary by CodeRabbit * **Content Updates** * Updated the Edge Functions customer testimonial with a new quote, customer attribution, profile image, role, and link. Co-authored-by: Ana --- .../_components/EdgeFunctionsContent.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/www/app/(products)/edge-functions/_components/EdgeFunctionsContent.tsx b/apps/www/app/(products)/edge-functions/_components/EdgeFunctionsContent.tsx index ed9e1b7a0ca11..a0cc012abfa76 100644 --- a/apps/www/app/(products)/edge-functions/_components/EdgeFunctionsContent.tsx +++ b/apps/www/app/(products)/edge-functions/_components/EdgeFunctionsContent.tsx @@ -17,13 +17,13 @@ export function EdgeFunctionsContent() {
From f877413e051ca3cfc9db2fa228829604aff5a851 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Tue, 4 Aug 2026 15:37:12 +0800 Subject: [PATCH 3/4] fix(docs ci): report Docs E2E check on every PR (#48681) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit \"Docs E2E\" is a required status check on master, but the workflow only triggered on docs-related paths. A required check whose workflow never starts creates no check run at all, so every non-docs PR sat blocked on \"Expected — waiting for status to be reported\" (e.g. #48677). The fix relies on the asymmetry in how branch protection treats the two kinds of skipping: a job skipped via an `if:` condition still reports a check run (counted as passing), while a workflow filtered out by `paths:` reports nothing. **Changed:** - Dropped the `paths:` filter from the `pull_request` trigger — the workflow now runs on every PR to master - Added a `dorny/paths-filter` step (same pattern as `studio-e2e-test.yml`) carrying the exact path list the trigger used to have; it runs before checkout using the API, so non-docs PRs skip the expensive full-history checkout entirely and report green in seconds - Folded the later \"Detect docs app changes\" step into the same filter (`docs_app` output) - Flipped downstream step guards from `skip != 'true'` to `skip == 'false'` so they stay off when the scope step itself was skipped (its output is empty then, and empty `!= 'true'` would have run them) This also fixes draft PRs: the job-level draft condition now produces a skipped-but-reported check instead of nothing, and `ready_for_review` triggers a real run. No behavior change for docs PRs or `workflow_dispatch` runs. The required-check context (`Docs E2E`) keeps its name. ## To test - On this PR (docs-related since it edits the workflow): the full suite should run as before - On a non-docs PR after merge: \"Docs E2E\" reports green in seconds instead of hanging as \"Expected\" - On a draft PR: check reports as skipped, run happens on ready-for-review ## Summary by CodeRabbit * **Tests** * Documentation end-to-end checks now report a status for every qualifying pull request. * Documentation changes automatically run the relevant browser tests and upload test reports. * Pull requests without documentation changes receive a successful skipped check. * Preview environment validation now runs only when documentation changes are detected. Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com> --- .github/workflows/docs-e2e.yml | 74 ++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 31 deletions(-) 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 From 9dc5c0faf320e6b9dbb617861675a3054f576e47 Mon Sep 17 00:00:00 2001 From: Stephen Morgan Date: Tue, 4 Aug 2026 20:47:48 +1200 Subject: [PATCH 4/4] feat: privatelink read replica (#48642) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Frontend changes for new privatelink functionality. The updates to the API are already live, but will be putting the UI changes behind a feature flag while we do some full end to end testing. Changes to Integration page: image Changes to create associations page: image Fixes SEC-919 ## Summary by CodeRabbit - **New Features** - AWS PrivateLink connections can target the primary database or a read replica. - Connection lists and confirmation dialogs now identify the selected database clearly. - Replica details include improved status information and a “Manage replica” link. - **Bug Fixes** - Replica removal errors now provide clearer guidance and link to integration settings when PrivateLink issues occur. - **Updates** - Infrastructure diagrams focus on visualization, with replica management actions moved to dedicated management views. --------- Co-authored-by: Joshen Lim --- .../Database/Replication/Destinations.tsx | 2 +- .../DropReplicaConfirmationModal.tsx | 28 +++- .../ReadReplicas/ReadReplicaDetails.tsx | 2 +- .../ReadReplicas/ReadReplicaRow.tsx | 6 +- .../ReadReplicas/ReadReplicas.utils.ts | 2 +- .../RestartReplicaConfirmationModal.tsx | 2 +- .../Replication/Replication.constants.ts | 12 ++ .../Replication/ReplicationDiagram/Edges.tsx | 3 +- .../interfaces/ProjectHome/TopSection.tsx | 2 +- .../DropAllReplicasConfirmationModal.tsx | 80 ----------- .../InfrastructureConfiguration/Edge.tsx | 3 +- .../InstanceConfiguration.constants.ts | 11 -- .../InstanceConfiguration.tsx | 129 ++---------------- .../InstanceConfiguration.utils.ts | 6 - .../InstanceNode.tsx | 2 +- .../InfrastructureConfiguration/MapView.tsx | 98 ++++--------- .../AWSPrivateLinkAccountItem.tsx | 12 ++ .../AWSPrivateLink/AWSPrivateLinkForm.tsx | 86 ++++++++++-- .../AWSPrivateLink/AWSPrivateLinkSection.tsx | 21 ++- .../studio/components/ui/DatabaseSelector.tsx | 2 +- .../aws-account-create-mutation.ts | 3 + .../aws-account-delete-mutation.ts | 36 +++-- .../data/aws-accounts/aws-accounts-query.ts | 19 +-- .../replication/replica/[replicaId].tsx | 6 +- 24 files changed, 234 insertions(+), 339 deletions(-) rename apps/studio/components/interfaces/{Settings/Infrastructure/InfrastructureConfiguration => Database/Replication/ReadReplicas}/DropReplicaConfirmationModal.tsx (76%) rename apps/studio/components/interfaces/{Settings/Infrastructure/InfrastructureConfiguration => Database/Replication/ReadReplicas}/RestartReplicaConfirmationModal.tsx (98%) delete mode 100644 apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DropAllReplicasConfirmationModal.tsx 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:

-
    -
  • Network traffic from this region may slow down
  • -
-
- ) -} - -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 -