From f4450bfcea2555751e68bfaafad61024ea20a8a7 Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Fri, 17 Oct 2025 12:38:16 +0200 Subject: [PATCH] connection string full layout --- .../interfaces/Connect/Connect.constants.ts | 29 -- .../components/interfaces/Connect/Connect.tsx | 28 +- .../Connect/DatabaseConnectionString.tsx | 393 +++++++----------- 3 files changed, 160 insertions(+), 290 deletions(-) diff --git a/apps/studio/components/interfaces/Connect/Connect.constants.ts b/apps/studio/components/interfaces/Connect/Connect.constants.ts index 98466ea6fdc..6636fdc76ca 100644 --- a/apps/studio/components/interfaces/Connect/Connect.constants.ts +++ b/apps/studio/components/interfaces/Connect/Connect.constants.ts @@ -356,32 +356,3 @@ export const CONNECTION_TYPES = [ export const PGBOUNCER_ENABLED_BUT_NO_IPV4_ADDON_TEXT = 'Purchase IPv4 add-on or use Shared Pooler if on a IPv4 network' export const IPV4_ADDON_TEXT = 'Connections are IPv4 proxied with IPv4 add-on' - -export type ConnectionStringMethod = 'direct' | 'transaction' | 'session' - -export const connectionStringMethodOptions: Record< - ConnectionStringMethod, - { value: string; label: string; description: string; badge: string } -> = { - direct: { - value: 'direct', - label: 'Direct connection', - description: - 'Ideal for applications with persistent and long-lived connections, such as those running on virtual machines or long-standing containers.', - badge: 'IPv4 Compatible', - }, - transaction: { - value: 'transaction', - label: 'Transaction pooler', - description: - 'Ideal for stateless applications like serverless functions where each interaction with Postgres is brief and isolated.', - badge: 'IPv4 Compatible', - }, - session: { - value: 'session', - label: 'Session pooler', - description: - 'Only recommended as an alternative to Direct Connection, when connecting via an IPv4 network.', - badge: 'IPv4 Only', - }, -} diff --git a/apps/studio/components/interfaces/Connect/Connect.tsx b/apps/studio/components/interfaces/Connect/Connect.tsx index 3a8cb3a7c52..5f207ea614c 100644 --- a/apps/studio/components/interfaces/Connect/Connect.tsx +++ b/apps/studio/components/interfaces/Connect/Connect.tsx @@ -87,9 +87,6 @@ export const Connect = () => { const [queryFramework, setQueryFramework] = useQueryState('framework', parseAsString) const [queryUsing, setQueryUsing] = useQueryState('using', parseAsString) const [queryWith, setQueryWith] = useQueryState('with', parseAsString) - const [_, setQueryType] = useQueryState('type', parseAsString) - const [__, setQuerySource] = useQueryState('source', parseAsString) - const [___, setQueryMethod] = useQueryState('method', parseAsString) const [connectionObject, setConnectionObject] = useState(FRAMEWORKS) const [selectedParent, setSelectedParent] = useState(connectionObject[0].key) // aka nextjs @@ -245,20 +242,13 @@ export const Connect = () => { selectedGrandchild, }) - const resetQueryStates = () => { - setQueryFramework(null) - setQueryUsing(null) - setQueryWith(null) - setQueryType(null) - setQuerySource(null) - setQueryMethod(null) - } - const handleDialogChange = (open: boolean) => { if (!open) { setShowConnect(null) setTab(null) - resetQueryStates() + setQueryFramework(null) + setQueryUsing(null) + setQueryWith(null) } else { setShowConnect(open) } @@ -321,8 +311,8 @@ export const Connect = () => { Connect - - + + Connect to your project {connectionTypes.length === 1 ? ` via ${connectionTypes[0].label.toLowerCase()}` : null} @@ -332,13 +322,7 @@ export const Connect = () => { - { - resetQueryStates() - handleConnectionType(value) - }} - > + handleConnectionType(value)}> {connectionTypes.length > 1 ? ( {connectionTypes.map((type) => ( diff --git a/apps/studio/components/interfaces/Connect/DatabaseConnectionString.tsx b/apps/studio/components/interfaces/Connect/DatabaseConnectionString.tsx index feb575411e4..e7d36449fdc 100644 --- a/apps/studio/components/interfaces/Connect/DatabaseConnectionString.tsx +++ b/apps/studio/components/interfaces/Connect/DatabaseConnectionString.tsx @@ -1,6 +1,5 @@ -import { ChevronDown, GlobeIcon, InfoIcon } from 'lucide-react' -import { parseAsString, useQueryState } from 'nuqs' -import { HTMLAttributes, ReactNode, useEffect, useMemo, useState } from 'react' +import { ChevronDown } from 'lucide-react' +import { HTMLAttributes, ReactNode, useMemo, useState } from 'react' import { useParams } from 'common' import { getAddons } from 'components/interfaces/Billing/Subscription/Subscription.utils' @@ -19,6 +18,7 @@ import { pluckObjectFields } from 'lib/helpers' import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' import { Badge, + Button, CodeBlock, CollapsibleContent_Shadcn_, CollapsibleTrigger_Shadcn_, @@ -35,12 +35,10 @@ import { import { Admonition } from 'ui-patterns' import { CONNECTION_PARAMETERS, - type ConnectionStringMethod, DATABASE_CONNECTION_TYPES, DatabaseConnectionType, IPV4_ADDON_TEXT, PGBOUNCER_ENABLED_BUT_NO_IPV4_ADDON_TEXT, - connectionStringMethodOptions, } from './Connect.constants' import { CodeBlockFileHeader, ConnectionPanel } from './ConnectionPanel' import { getConnectionStrings } from './DatabaseSettings.utils' @@ -52,7 +50,7 @@ const StepLabel = ({ ...props }: { number: number; children: ReactNode } & HTMLAttributes) => (
-
+
{number}
{children} @@ -68,74 +66,12 @@ export const DatabaseConnectionString = () => { const { data: org } = useSelectedOrganizationQuery() const state = useDatabaseSelectorStateSnapshot() - // URL state management - const [queryType, setQueryType] = useQueryState('type', parseAsString.withDefault('uri')) - const [querySource, setQuerySource] = useQueryState('source', parseAsString) - const [queryMethod, setQueryMethod] = useQueryState('method', parseAsString.withDefault('direct')) - const [selectedTab, setSelectedTab] = useState('uri') - const [selectedMethod, setSelectedMethod] = useState('direct') const sharedPoolerPreferred = useMemo(() => { return org?.plan?.id === 'free' }, [org]) - // Sync URL state with component state on mount and when URL changes - useEffect(() => { - const validTypes = DATABASE_CONNECTION_TYPES.map((t) => t.id) - if (queryType && validTypes.includes(queryType as DatabaseConnectionType)) { - setSelectedTab(queryType as DatabaseConnectionType) - } else if (queryType && !validTypes.includes(queryType as DatabaseConnectionType)) { - setQueryType('uri') - setSelectedTab('uri') - } - - const validMethods: ConnectionStringMethod[] = ['direct', 'transaction', 'session'] - if (queryMethod && validMethods.includes(queryMethod as ConnectionStringMethod)) { - setSelectedMethod(queryMethod as ConnectionStringMethod) - } else if (queryMethod && !validMethods.includes(queryMethod as ConnectionStringMethod)) { - setQueryMethod('direct') - setSelectedMethod('direct') - } - - if (querySource && querySource !== state.selectedDatabaseId) { - state.setSelectedDatabaseId(querySource) - } else if (!querySource && state.selectedDatabaseId !== projectRef) { - state.setSelectedDatabaseId(projectRef) - } - }, [queryType, queryMethod, querySource, state]) - - // Sync component state changes back to URL - const handleTabChange = (connectionType: DatabaseConnectionType) => { - setSelectedTab(connectionType) - setQueryType(connectionType) - } - - const handleMethodChange = (method: ConnectionStringMethod) => { - setSelectedMethod(method) - setQueryMethod(method) - } - - const handleDatabaseChange = (databaseId: string) => { - if (databaseId === projectRef) { - setQuerySource(null) - } else { - setQuerySource(databaseId) - } - } - - // Sync database selector state changes back to URL - useEffect(() => { - if (state.selectedDatabaseId && state.selectedDatabaseId !== querySource) { - // Only set source in URL if it's not the primary database - if (state.selectedDatabaseId === projectRef) { - setQuerySource(null) - } else { - setQuerySource(state.selectedDatabaseId) - } - } - }, [state.selectedDatabaseId, querySource, projectRef]) - const { data: pgbouncerConfig, error: pgbouncerError, @@ -200,14 +136,14 @@ export const DatabaseConnectionString = () => { const handleCopy = ( connectionTypeId: string, - connectionStringMethod: 'direct' | 'transaction_pooler' | 'session_pooler' + connectionMethod: 'direct' | 'transaction_pooler' | 'session_pooler' ) => { const connectionInfo = DATABASE_CONNECTION_TYPES.find((type) => type.id === connectionTypeId) const connectionType = connectionInfo?.label ?? 'Unknown' const lang = connectionInfo?.lang ?? 'Unknown' sendEvent({ action: 'connection_string_copied', - properties: { connectionType, lang, connectionMethod: connectionStringMethod }, + properties: { connectionType, lang, connectionMethod }, groups: { project: projectRef ?? 'Unknown', organization: org?.slug ?? 'Unknown' }, }) } @@ -285,7 +221,12 @@ export const DatabaseConnectionString = () => { Type - + + setSelectedTab(connectionType) + } + > @@ -298,32 +239,7 @@ export const DatabaseConnectionString = () => {
- -
- - Method - - - - - {connectionStringMethodOptions[selectedMethod].label} - - - - {Object.keys(connectionStringMethodOptions).map((method) => ( - - ))} - - -
+
{isLoading && ( @@ -342,16 +258,15 @@ export const DatabaseConnectionString = () => {
{/* // handle non terminal examples */} {hasCodeExamples && ( -
-
- +
+
+ Install the following - {exampleInstallCommands?.map((cmd) => ( + {exampleInstallCommands?.map((cmd, i) => ( { ))}
{exampleFiles && exampleFiles?.length > 0 && ( -
- +
+ Add file to project - {exampleFiles?.map((file) => ( -
+ {exampleFiles?.map((file, i) => ( +
{
{hasCodeExamples && (
- Connect to your database + Choose type of connection
)} -
- {selectedMethod === 'direct' && ( - handleCopy(selectedTab, 'direct')} - /> - )} +
+ handleCopy(selectedTab, 'direct')} + /> - {selectedMethod === 'transaction' && IS_PLATFORM && ( - handleCopy(selectedTab, 'transaction_pooler')} - /> - )} - - {selectedMethod === 'session' && IS_PLATFORM && ( + {IS_PLATFORM && ( <> + handleCopy(selectedTab, 'transaction_pooler')} + > + {!sharedPoolerPreferred && !ipv4Addon && ( + + + + + +

+ Only recommended when your network does not support IPv6. Added latency + compared to dedicated pooler. +

+ handleCopy(selectedTab, 'transaction_pooler')} + /> +
+
+ )} +
+ {sharedPoolerPreferred && ipv4Addon && ( {

)} + {
{examplePostInstallCommands && ( -
- - Add the configuration package to read the settings - - {examplePostInstallCommands?.map((cmd) => ( - - {cmd} - - ))} +
+
+ + Add the configuration package to read the settings + + {examplePostInstallCommands?.map((cmd, i) => ( + + {cmd} + + ))} +
)}
@@ -581,43 +536,3 @@ export const DatabaseConnectionString = () => {
) } - -const ConnectionStringMethodSelectItem = ({ - method, - poolerBadge, -}: { - method: ConnectionStringMethod - poolerBadge: string -}) => ( - -
-
{connectionStringMethodOptions[method].label}
-
- {connectionStringMethodOptions[method].description} -
-
- {method === 'session' ? ( - - - {connectionStringMethodOptions[method].badge} - - ) : ( - - - {connectionStringMethodOptions[method].badge} - - )} - {method === 'transaction' && ( - - {poolerBadge} - - )} - {method === 'session' && ( - - Shared Pooler - - )} -
-
-
-)