diff --git a/lib/features/connections/connection_default_name.dart b/lib/features/connections/connection_default_name.dart new file mode 100644 index 00000000..ab6274ee --- /dev/null +++ b/lib/features/connections/connection_default_name.dart @@ -0,0 +1,23 @@ +/// The name a connection gets when the user leaves the name field empty. +/// +/// With a URI the name comes from the URI's host and port, not from the +/// form's host field (which a URI overrides): `Redis: cache.example.com:6380`. +/// The port is the URI's, else [defaultPort]; a `+srv` URI has none. A URI +/// without a host (a multi-host replica set list, a malformed one) gives +/// `Redis (URI)`. Without a URI the name is [fields]: +/// `Redis localhost:6379`. +String defaultConnectionName({ + required String label, + required int defaultPort, + required String uri, + required String fields, +}) { + final text = uri.trim(); + if (text.isEmpty) return fields; + final parsed = Uri.tryParse(text); + if (parsed == null || parsed.host.isEmpty) return '$label (URI)'; + final srv = parsed.scheme.endsWith('+srv'); + if (srv) return '$label: ${parsed.host}'; + final port = parsed.hasPort ? parsed.port : defaultPort; + return '$label: ${parsed.host}:$port'; +} diff --git a/lib/features/mongodb/mongodb_connection_form.dart b/lib/features/mongodb/mongodb_connection_form.dart index e519f1bc..c9303d4d 100644 --- a/lib/features/mongodb/mongodb_connection_form.dart +++ b/lib/features/mongodb/mongodb_connection_form.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart' as material; +import 'package:querya_desktop/features/connections/connection_default_name.dart'; import 'package:querya_desktop/core/security/connection_environment.dart'; import 'package:querya_desktop/features/connections/environment_section.dart'; import 'package:querya_desktop/core/database/mongodb_connection.dart'; @@ -315,8 +316,14 @@ class _MongoConnectionFormContentState final data = _formData; if (!data.isValid) return; - final displayName = - data.name.isNotEmpty ? data.name : 'MongoDB ${data.host}:${data.port}'; + final displayName = data.name.isNotEmpty + ? data.name + : defaultConnectionName( + label: 'MongoDB', + defaultPort: 27017, + uri: data.connectionString ?? '', + fields: 'MongoDB ${data.host}:${data.port}', + ); final initial = widget.initial; var row = ConnectionRow( diff --git a/lib/features/mysql/mysql_connection_form.dart b/lib/features/mysql/mysql_connection_form.dart index 60257bb4..5a54e596 100644 --- a/lib/features/mysql/mysql_connection_form.dart +++ b/lib/features/mysql/mysql_connection_form.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart' as material; +import 'package:querya_desktop/features/connections/connection_default_name.dart'; import 'package:querya_desktop/core/security/connection_environment.dart'; import 'package:querya_desktop/features/connections/environment_section.dart'; import 'package:querya_desktop/core/database/mysql_connection.dart'; @@ -247,9 +248,13 @@ class _MysqlConnectionFormContentState final uri = _effectiveConnectionUri(); final displayName = name.isNotEmpty ? name - : (uri.isNotEmpty - ? 'MySQL (URI)' - : 'MySQL $host:$port${database.isNotEmpty ? '/$database' : ''}'); + : defaultConnectionName( + label: 'MySQL', + defaultPort: 3306, + uri: uri, + fields: + 'MySQL $host:$port${database.isNotEmpty ? '/$database' : ''}', + ); final initial = widget.initial; var row = ConnectionRow( id: initial?.id, diff --git a/lib/features/postgresql/postgresql_connection_form.dart b/lib/features/postgresql/postgresql_connection_form.dart index e113c75d..9f4bef21 100644 --- a/lib/features/postgresql/postgresql_connection_form.dart +++ b/lib/features/postgresql/postgresql_connection_form.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart' as material; +import 'package:querya_desktop/features/connections/connection_default_name.dart'; import 'package:querya_desktop/core/security/connection_environment.dart'; import 'package:querya_desktop/features/connections/environment_section.dart'; import 'package:querya_desktop/core/database/postgres_connection.dart'; @@ -335,13 +336,14 @@ class _PostgresConnectionFormContentState } } - final effectiveHost = uriHost ?? host; - final effectivePort = uriPort ?? port; final displayName = name.isNotEmpty ? name - : (effectiveUri.isNotEmpty - ? 'PostgreSQL: $effectiveHost:$effectivePort' - : 'PostgreSQL $host:$port/$database'); + : defaultConnectionName( + label: 'PostgreSQL', + defaultPort: 5432, + uri: effectiveUri, + fields: 'PostgreSQL $host:$port/$database', + ); final initial = widget.initial; var row = ConnectionRow( id: initial?.id, diff --git a/lib/features/redis/redis_connection_form.dart b/lib/features/redis/redis_connection_form.dart index 1baeae94..d1461ec0 100644 --- a/lib/features/redis/redis_connection_form.dart +++ b/lib/features/redis/redis_connection_form.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart' as material; +import 'package:querya_desktop/features/connections/connection_default_name.dart'; import 'package:querya_desktop/core/security/connection_environment.dart'; import 'package:querya_desktop/features/connections/environment_section.dart'; import 'package:querya_desktop/core/database/redis_connection.dart'; @@ -210,7 +211,14 @@ class _RedisConnectionFormContentState final host = _hostController.text.trim(); final port = int.tryParse(_portController.text.trim()) ?? 6379; final uri = _effectiveConnectionUri(); - final displayName = name.isNotEmpty ? name : 'Redis $host:$port'; + final displayName = name.isNotEmpty + ? name + : defaultConnectionName( + label: 'Redis', + defaultPort: 6379, + uri: uri, + fields: 'Redis $host:$port', + ); final initial = widget.initial; var row = ConnectionRow( id: id, diff --git a/test/e2e/01_connection_lifecycle/e2e_connection_form_test.dart b/test/e2e/01_connection_lifecycle/e2e_connection_form_test.dart index 0ef582fc..42783ea5 100644 --- a/test/e2e/01_connection_lifecycle/e2e_connection_form_test.dart +++ b/test/e2e/01_connection_lifecycle/e2e_connection_form_test.dart @@ -162,6 +162,8 @@ void main() { final secrets = await tester .runAsync(() => ConnectionSecretsStore.readForConnection(row.id!)); expect(secrets!.connectionString, contains(host)); + // The name comes from the URI's host, not the form's default one (#1257). + expect(row.name, contains(host)); // The sidebar reloads from the database after the save. await waitFor(tester, inSidebar(row.name), 'the new connection'); await app.close(tester); diff --git a/test/features/connections/connection_default_name_test.dart b/test/features/connections/connection_default_name_test.dart new file mode 100644 index 00000000..ae776b2d --- /dev/null +++ b/test/features/connections/connection_default_name_test.dart @@ -0,0 +1,38 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:querya_desktop/features/connections/connection_default_name.dart'; + +String _name(String label, int port, String uri) => defaultConnectionName( + label: label, + defaultPort: port, + uri: uri, + fields: '$label localhost:$port', + ); + +void main() { + test('a URI names the connection after its host and port', () { + expect(_name('Redis', 6379, 'redis://:p@cache.example.com:6380'), + 'Redis: cache.example.com:6380'); + expect(_name('MySQL', 3306, 'mysql://u:p@my.example.com:3307/app'), + 'MySQL: my.example.com:3307'); + expect(_name('MongoDB', 27017, 'mongodb://u:p@mongo.example.com:27018/app'), + 'MongoDB: mongo.example.com:27018'); + }); + + test('a URI without a port uses the type default', () { + expect(_name('Redis', 6379, 'redis://cache.example.com'), + 'Redis: cache.example.com:6379'); + }); + + test('a +srv URI has no port', () { + expect(_name('MongoDB', 27017, 'mongodb+srv://u:p@c0.example.net/db'), + 'MongoDB: c0.example.net'); + }); + + test('a URI without a host falls back to (URI)', () { + expect(_name('Redis', 6379, 'not a uri'), 'Redis (URI)'); + }); + + test('without a URI the name comes from the fields', () { + expect(_name('Redis', 6379, ' '), 'Redis localhost:6379'); + }); +}