From 0c1da8fd098932af45146f5f1cc63a668d838985 Mon Sep 17 00:00:00 2001
From: Danny White <3104761+dnywh@users.noreply.github.com>
Date: Mon, 17 Aug 2026 11:46:41 +1000
Subject: [PATCH] feat(studio): tighten PrivateLink sheet fields (#49085)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## What kind of change does this PR introduce?
Feature
## What is the current behavior?
Add connection field order and nickname handling are harder to scan.
Empty description can still show up as a blank name.
## What is the new behavior?
Add connection is AWS account ID, then database, then optional
description. An empty description is omitted from the list title.
| Before | After |
| --- | --- |
|
|
|
## Additional context
Stacked on #49084. See #49030 for the end state, as it may already
include fixes you might propose.
## To test
- **Project Settings → Integrations → AWS PrivateLink → Add
connection.** Confirm field order: account ID, database, description.
- Save once with a description and once without. Without one, the row
title should fall back to the account ID.
## Summary by CodeRabbit
- **New Features**
- Added AWS account ID and database target fields to the PrivateLink
setup form.
- Added validation and improved preservation of entered values while
editing.
- Made the connection description optional.
- Updated connection status labels and badges for clearer status
visibility.
- **Documentation**
- Updated PrivateLink setup instructions to reflect the revised field
order and optional description.
---
.../content/guides/platform/privatelink.mdx | 6 +-
.../AWSPrivateLink/AWSPrivateLinkForm.tsx | 227 +++++++-----------
2 files changed, 94 insertions(+), 139 deletions(-)
diff --git a/apps/docs/content/guides/platform/privatelink.mdx b/apps/docs/content/guides/platform/privatelink.mdx
index 8d079f5d3bc..cdaa3aaeb14 100644
--- a/apps/docs/content/guides/platform/privatelink.mdx
+++ b/apps/docs/content/guides/platform/privatelink.mdx
@@ -44,9 +44,9 @@ Navigate to your project's Integrations section to set up PrivateLink:
2. Navigate to [**Settings** > **Integrations**](/dashboard/project/_/settings/integrations)
3. Find the **AWS PrivateLink** section
4. Click **Add connection**
-5. Provide a description (recommended)
-6. Select the database target: the primary database or a specific read replica
-7. Enter your AWS account ID
+5. Enter the destination AWS account ID
+6. Select the database: the primary database or a specific read replica
+7. Optionally add a description
8. Click **Add connection** to submit
Each database, whether the primary or a read replica, needs its own connection. Create a separate connection for every database you want to reach over PrivateLink.
diff --git a/apps/studio/components/interfaces/Settings/Integrations/AWSPrivateLink/AWSPrivateLinkForm.tsx b/apps/studio/components/interfaces/Settings/Integrations/AWSPrivateLink/AWSPrivateLinkForm.tsx
index 52b0ba6ac35..53c293ecddc 100644
--- a/apps/studio/components/interfaces/Settings/Integrations/AWSPrivateLink/AWSPrivateLinkForm.tsx
+++ b/apps/studio/components/interfaces/Settings/Integrations/AWSPrivateLink/AWSPrivateLinkForm.tsx
@@ -1,7 +1,7 @@
+import { zodResolver } from '@hookform/resolvers/zod'
import { useFlag } from 'common'
import { ExternalLink } from 'lucide-react'
import Link from 'next/link'
-import { useEffect } from 'react'
import { useForm } from 'react-hook-form'
import { toast } from 'sonner'
import {
@@ -27,7 +27,9 @@ import {
import { Admonition } from 'ui-patterns/Admonition'
import { Input as CopyableInput } from 'ui-patterns/DataInputs/Input'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
+import { z } from 'zod'
+import { getConnectionStatusUi } from './AWSPrivateLink.utils'
import { InlineLink } from '@/components/ui/InlineLink'
import { useAWSAccountCreateMutation } from '@/data/aws-accounts/aws-account-create-mutation'
import type { AWSAccount } from '@/data/aws-accounts/aws-accounts-query'
@@ -36,18 +38,25 @@ import { formatDatabaseID, formatDatabaseRegion } from '@/data/read-replicas/rep
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { DOCS_URL } from '@/lib/constants'
+const FORM_ID = 'privatelink-connection-form'
+
+const FormSchema = z.object({
+ awsAccountId: z
+ .string()
+ .trim()
+ .regex(/^\d{12}$/, 'Enter a 12-digit AWS account ID'),
+ databaseIdentifier: z.string().min(1, 'Select a database'),
+ accountName: z.string(),
+})
+
+type FormValues = z.infer
+
interface AWSPrivateLinkFormProps {
account?: AWSAccount
open: boolean
onOpenChange: (open: boolean) => void
}
-interface FormValues {
- awsAccountId: string
- accountName: string
- databaseIdentifier: string
-}
-
export const AWSPrivateLinkForm = ({ account, open, onOpenChange }: AWSPrivateLinkFormProps) => {
const isNew = !account
const { data: project } = useSelectedProjectQuery()
@@ -62,40 +71,24 @@ export const AWSPrivateLinkForm = ({ account, open, onOpenChange }: AWSPrivateLi
const { mutate: createAccount, isPending } = useAWSAccountCreateMutation()
const readReplicas = databases.filter((database) => database.identifier !== project?.ref)
-
- const defaultValues = {
+ const showDatabaseTarget = showPrivateLinkReadReplica || !isNew
+ const statusUi = getConnectionStatusUi(account?.status)
+ const formValues: FormValues = {
awsAccountId: account?.aws_account_id ?? '',
- accountName: account?.account_name ?? '',
databaseIdentifier: account?.database_identifier ?? project?.ref ?? '',
+ accountName: account?.account_name ?? '',
}
- const form = useForm({ defaultValues })
-
- const title =
- account?.status === 'ASSOCIATION_ACCEPTED'
- ? 'This connection is active'
- : account?.status === 'READY'
- ? 'Connection is ready to accept'
- : account?.status === 'CREATING'
- ? 'This connection is being created'
- : account?.status === 'DELETING'
- ? 'This connection is being deleted'
- : account?.status === 'ASSOCIATION_REQUEST_EXPIRED'
- ? 'This request has expired'
- : account?.status === 'CREATION_FAILED'
- ? "Couldn't create this connection"
- : 'This connection needs to be accepted by the AWS account owner.'
-
- const description =
- account?.status === 'ASSOCIATION_ACCEPTED'
- ? 'The AWS account owner has accepted the resource share.'
- : account?.status === 'READY'
- ? 'It may be waiting acceptance from the AWS account owner. Requests expire after 12 hours.'
- : account?.status === 'ASSOCIATION_REQUEST_EXPIRED'
- ? 'Add a new connection to try again.'
- : account?.status === 'CREATION_FAILED'
- ? 'Add a new connection to try again.'
- : ''
+ const form = useForm({
+ resolver: zodResolver(FormSchema),
+ defaultValues: {
+ awsAccountId: '',
+ databaseIdentifier: '',
+ accountName: '',
+ },
+ values: formValues,
+ resetOptions: { keepDirtyValues: true },
+ })
const onSubmit = (values: FormValues) => {
if (!project) return
@@ -104,7 +97,7 @@ export const AWSPrivateLinkForm = ({ account, open, onOpenChange }: AWSPrivateLi
{
projectRef: project.ref,
awsAccountId: values.awsAccountId,
- accountName: values.accountName,
+ accountName: values.accountName.trim() || undefined,
databaseIdentifier:
values.databaseIdentifier && values.databaseIdentifier !== project.ref
? values.databaseIdentifier
@@ -112,7 +105,7 @@ export const AWSPrivateLinkForm = ({ account, open, onOpenChange }: AWSPrivateLi
},
{
onSuccess: () => {
- form.reset(defaultValues)
+ form.reset(formValues)
toast.success('Connection added')
onOpenChange(false)
},
@@ -121,17 +114,13 @@ export const AWSPrivateLinkForm = ({ account, open, onOpenChange }: AWSPrivateLi
}
}
- // Reset form when account changes
- useEffect(() => {
- form.reset({
- awsAccountId: account?.aws_account_id ?? '',
- accountName: account?.account_name ?? '',
- databaseIdentifier: account?.database_identifier ?? project?.ref ?? '',
- })
- }, [account, form, project?.ref])
+ const handleOpenChange = (nextOpen: boolean) => {
+ if (!nextOpen) form.reset(formValues)
+ onOpenChange(nextOpen)
+ }
return (
-
+
{isNew ? 'Add connection' : 'Connection details'}
@@ -143,96 +132,72 @@ export const AWSPrivateLinkForm = ({ account, open, onOpenChange }: AWSPrivateLi