diff --git a/mathesar_ui/src/stores/databases.ts b/mathesar_ui/src/stores/databases.ts index fdd2b2666a..8bbb018f14 100644 --- a/mathesar_ui/src/stores/databases.ts +++ b/mathesar_ui/src/stores/databases.ts @@ -66,7 +66,7 @@ class ConnectionsStore { /** TODO_3311: remove this or utilize it */ readonly requestStatus: Writable = writable(); - /** TODO_3311: make this a Map */ + /** TODO_3311: make this a Map and sort by nickname */ readonly connections = writable([]); /** TODO_3311: make this derived */ diff --git a/mathesar_ui/src/systems/connections/AddConnection.svelte b/mathesar_ui/src/systems/connections/AddConnection.svelte index ada98e6930..710a700494 100644 --- a/mathesar_ui/src/systems/connections/AddConnection.svelte +++ b/mathesar_ui/src/systems/connections/AddConnection.svelte @@ -37,26 +37,33 @@ import { assertExhaustive } from '@mathesar/utils/typeUtils'; import GeneralConnection from './GeneralConnection.svelte'; import { - defaultGeneralConnection, generalConnections, getConnectionReference, getUsername, + pickDefaultGeneralConnection, } from './generalConnections'; - const credentialsStrategyOptions = ['reuse', 'new'] as const; - type CredentialsStrategy = (typeof credentialsStrategyOptions)[number]; - const credentialsStrategyLabels: Record = { - reuse: $_('reuse_credentials_from_known_connection'), - new: $_('enter_new_credentials'), - }; + interface CredentialsStrategy { + /** We want to reuse the credentials from a known connection */ + reuse: boolean; + /** The user can modify the strategy */ + modifiable: boolean; + } + function getCredentialsStrategyLabel(s: CredentialsStrategy) { + return s.reuse + ? $_('reuse_credentials_from_known_connection') + : $_('enter_new_credentials'); + } - const userTypeOptions = ['existing', 'create'] as const; - type UserTypeChoice = (typeof userTypeOptions)[number]; - const userTypeLabels: Record = { - existing: $_('use_existing_pg_user'), - create: $_('create_new_pg_user'), - }; - type UserType = UserTypeChoice | 'forceExisting'; + interface UserStrategy { + /** We want to create a new PostgreSQL user */ + create: boolean; + /** The UI user can modify the strategy */ + modifiable: boolean; + } + function getUserStrategyLabel(s: UserStrategy) { + return s.create ? $_('create_new_pg_user') : $_('use_existing_pg_user'); + } type InstallationSchema = SampleDataSchemaIdentifier | 'internal'; const installationSchemaOptions: InstallationSchema[] = [ @@ -78,51 +85,53 @@ export let onExit: () => void; - $: availableConnections = $generalConnections; + $: availableConnectionsToReuse = $generalConnections; + $: defaultConnectionToReuse = + pickDefaultGeneralConnection($generalConnections); $: namedConnections = connectionsStore.connections; $: connectionNames = new Set($namedConnections.map((c) => c.nickname)); - - // Common fields - $: credentialsStrategy = requiredField( - !!$defaultGeneralConnection ? 'reuse' : 'new', + $: someUserDbConnectionsExist = $generalConnections.some( + (c) => c.type === 'user_database', ); - $: userType = requiredField( - $defaultGeneralConnection ? 'existing' : 'forceExisting', + $: defaultDatabaseName = someUserDbConnectionsExist ? '' : 'mathesar'; + $: credentialsStrategy = requiredField( + defaultConnectionToReuse + ? { reuse: true, modifiable: true } + : { reuse: false, modifiable: false }, ); - $: databaseName = requiredField(''); + $: databaseName = requiredField(defaultDatabaseName); $: createDatabase = requiredField(true); $: installationSchemas = requiredField(['internal']); $: connectionNickname = requiredField('', [uniqueWith(connectionNames)]); - - // Fields for the 'fromKnownConnection' strategy - $: connectionToReuse = requiredField($defaultGeneralConnection); - - // Fields for the 'fromScratch' strategy - $: host = requiredField(''); + $: connectionToReuse = requiredField(defaultConnectionToReuse); + $: host = requiredField('localhost'); $: port = requiredField(5432); $: existingUserName = requiredField(''); $: existingPassword = requiredField(''); - - // Fields for the 'withNewUser' strategy - $: bootstrapConnection = requiredField($defaultGeneralConnection); + $: availableBootstrapConnections = $generalConnections.filter( + (c) => c.connection.host === $host && c.connection.port === $port, + ); + $: defaultBootstrapConnection = pickDefaultGeneralConnection( + availableBootstrapConnections, + ); + $: userStrategy = requiredField( + defaultBootstrapConnection + ? { create: false, modifiable: true } + : { create: false, modifiable: false }, + ); + $: bootstrapConnection = requiredField(defaultBootstrapConnection); $: newUserName = requiredField(''); $: newPassword = requiredField(''); $: confirmPassword = requiredField(''); $: overallStrategy = (() => { - if ($credentialsStrategy === 'reuse') { + if ($credentialsStrategy.reuse) { return 'fromKnownConnection' as const; } - if ($credentialsStrategy === 'new' || $credentialsStrategy === 'forceNew') { - if ($userType === 'existing' || $userType === 'forceExisting') { - return 'fromScratch' as const; - } - if ($userType === 'create') { - return 'withNewUser' as const; - } - return assertExhaustive($userType); + if ($userStrategy.create) { + return 'withNewUser' as const; } - return assertExhaustive($credentialsStrategy); + return 'fromScratch' as const; })(); $: form = (() => { const commonFields = { @@ -167,18 +176,18 @@ } })(); - $: canCreateDb = $credentialsStrategy === 'reuse' || $userType === 'create'; + $: canCreateDb = $credentialsStrategy.reuse || $userStrategy.create; $: databaseNameHelp = canCreateDb ? undefined : $_('this_database_must_exist_already'); $: databaseCreationUser = (() => { switch (overallStrategy) { case 'fromKnownConnection': - return getUsername($connectionToReuse); + return $connectionToReuse && getUsername($connectionToReuse); case 'fromScratch': return undefined; case 'withNewUser': - return getUsername($bootstrapConnection); + return $bootstrapConnection && getUsername($bootstrapConnection); default: return assertExhaustive(overallStrategy); } @@ -198,15 +207,20 @@ nickname: $connectionNickname, }; switch (overallStrategy) { - case 'fromKnownConnection': + case 'fromKnownConnection': { + const connection = $connectionToReuse; + if (!connection) { + throw new Error('Bug: $connectionToReuse is undefined'); + } return connectionsStore.createFromKnownConnection({ ...commonProps, credentials: { - connection: getConnectionReference($connectionToReuse), + connection: getConnectionReference(connection), }, create_database: $createDatabase, }); - case 'fromScratch': + } + case 'fromScratch': { return connectionsStore.createFromScratch({ ...commonProps, credentials: { @@ -216,16 +230,22 @@ password: $existingPassword, }, }); - case 'withNewUser': + } + case 'withNewUser': { + const connection = $bootstrapConnection; + if (!connection) { + throw new Error('Bug: $bootstrapConnection is undefined'); + } return connectionsStore.createWithNewUser({ ...commonProps, credentials: { user: $newUserName, password: $newPassword, - create_user_via: getConnectionReference($bootstrapConnection), + create_user_via: getConnectionReference(connection), }, create_database: $createDatabase, }); + } default: return assertExhaustive(overallStrategy); } @@ -244,23 +264,27 @@
- {#if $credentialsStrategy !== 'forceNew'} + {#if $credentialsStrategy.modifiable}
{$_('database_server_credentials')}
credentialsStrategyLabels[o]} + options={[ + { reuse: true, modifiable: true }, + { reuse: false, modifiable: true }, + ]} + valuesAreEqual={(a, b) => a?.reuse === b?.reuse} + getRadioLabel={getCredentialsStrategyLabel} />
{/if} - {#if $credentialsStrategy === 'reuse'} + {#if $credentialsStrategy.reuse} ({ component: GeneralConnection, props: { generalConnection }, @@ -338,10 +351,21 @@ /> {:else} - {assertExhaustive($userType)} + + + + + + + {/if} - {:else} - {assertExhaustive($credentialsStrategy)} {/if} diff --git a/mathesar_ui/src/systems/connections/generalConnections.ts b/mathesar_ui/src/systems/connections/generalConnections.ts index 9bcaaa4cf3..1fc238672e 100644 --- a/mathesar_ui/src/systems/connections/generalConnections.ts +++ b/mathesar_ui/src/systems/connections/generalConnections.ts @@ -51,28 +51,42 @@ export const generalConnections: Readable = derived( ], ); -/** - * The connection to pre-select when asking the user what connection they want - * to utilize for managing other connections (e.g. creating a new connection). - */ -export const defaultGeneralConnection = derived( - generalConnections, - (connections) => { - if (connections.length === 0) return undefined; - const internalConnection = connections.find( - (connection) => connection.type === 'internal_database', - ); - if (internalConnection) return internalConnection; - const userDatabaseConnections = connections.filter( - isUserDatabaseConnection, - ); +// /** +// * The connection to pre-select when asking the user what connection they want +// * to utilize for managing other connections (e.g. creating a new connection). +// */ +// export const defaultGeneralConnection = derived( +// generalConnections, +// (connections) => { +// if (connections.length === 0) return undefined; +// const internalConnection = connections.find( +// (connection) => connection.type === 'internal_database', +// ); +// if (internalConnection) return internalConnection; +// const userDatabaseConnections = connections.filter( +// isUserDatabaseConnection, +// ); - // Return the connection with the highest ID - return userDatabaseConnections.reduce((a, b) => - a.connection.id > b.connection.id ? a : b, - ); - }, -); +// // Return the connection with the highest ID +// return userDatabaseConnections.reduce((a, b) => +// a.connection.id > b.connection.id ? a : b, +// ); +// }, +// ); + +export function pickDefaultGeneralConnection(connections: GeneralConnection[]) { + if (connections.length === 0) return undefined; + const internalConnection = connections.find( + (connection) => connection.type === 'internal_database', + ); + if (internalConnection) return internalConnection; + const userDatabaseConnections = connections.filter(isUserDatabaseConnection); + + // Return the connection with the highest ID + return userDatabaseConnections.reduce((a, b) => + a.connection.id > b.connection.id ? a : b, + ); +} export function getUsername({ connection }: GeneralConnection): string { return 'user' in connection ? connection.user : connection.username;