feat(configmanager): add ConnectionStrings editor with test connection support
Adds a new ConnectionStrings section to ConfigManager allowing users to manage database connection strings with provider selection, connection testing, and visual feedback for connection state.
This commit is contained in:
@@ -0,0 +1,421 @@
|
||||
# ConnectionStrings Editor - Implementation Plan
|
||||
|
||||
## Overview
|
||||
|
||||
Step-by-step implementation guide for adding ConnectionStrings editor to ConfigManager.
|
||||
|
||||
**Estimated tasks:** 12 tasks in 4 batches
|
||||
|
||||
---
|
||||
|
||||
## Batch 1: Data Models
|
||||
|
||||
### Task 1: Create ConnectionProvider enum
|
||||
|
||||
**File:** `NEW/src/Utils/JdeScoping.ConfigManager/Models/ConnectionProvider.cs`
|
||||
|
||||
```csharp
|
||||
namespace JdeScoping.ConfigManager.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Database provider types supported by the ConnectionStrings editor.
|
||||
/// </summary>
|
||||
public enum ConnectionProvider
|
||||
{
|
||||
Generic,
|
||||
SqlServer,
|
||||
Oracle
|
||||
}
|
||||
```
|
||||
|
||||
**Verification:** Build succeeds.
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Create ConnectionStringEntry model
|
||||
|
||||
**File:** `NEW/src/Utils/JdeScoping.ConfigManager/Models/ConnectionStringEntry.cs`
|
||||
|
||||
```csharp
|
||||
namespace JdeScoping.ConfigManager.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single connection string entry with provider-specific fields.
|
||||
/// </summary>
|
||||
public class ConnectionStringEntry
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public ConnectionProvider Provider { get; set; } = ConnectionProvider.Generic;
|
||||
|
||||
// SqlServer fields
|
||||
public string? Server { get; set; }
|
||||
public string? Database { get; set; }
|
||||
public string? UserId { get; set; }
|
||||
public string? Password { get; set; }
|
||||
public string Encrypt { get; set; } = "True";
|
||||
public bool TrustServerCertificate { get; set; }
|
||||
public int ConnectionTimeout { get; set; } = 30;
|
||||
public string? ApplicationName { get; set; }
|
||||
|
||||
// Oracle fields
|
||||
public string? Host { get; set; }
|
||||
public int Port { get; set; } = 1521;
|
||||
public string? ServiceName { get; set; }
|
||||
|
||||
// Generic fields
|
||||
public string? RawConnectionString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Generates the connection string based on the Provider type.
|
||||
/// </summary>
|
||||
public string GenerateConnectionString()
|
||||
{
|
||||
return Provider switch
|
||||
{
|
||||
ConnectionProvider.SqlServer => GenerateSqlServerConnectionString(),
|
||||
ConnectionProvider.Oracle => GenerateOracleConnectionString(),
|
||||
ConnectionProvider.Generic => RawConnectionString ?? string.Empty,
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
private string GenerateSqlServerConnectionString()
|
||||
{
|
||||
var parts = new List<string>();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Server))
|
||||
parts.Add($"Server={Server}");
|
||||
if (!string.IsNullOrWhiteSpace(Database))
|
||||
parts.Add($"Database={Database}");
|
||||
if (!string.IsNullOrWhiteSpace(UserId))
|
||||
parts.Add($"User Id={UserId}");
|
||||
if (!string.IsNullOrWhiteSpace(Password))
|
||||
parts.Add($"Password={Password}");
|
||||
if (!string.IsNullOrWhiteSpace(Encrypt))
|
||||
parts.Add($"Encrypt={Encrypt}");
|
||||
if (TrustServerCertificate)
|
||||
parts.Add("TrustServerCertificate=True");
|
||||
if (ConnectionTimeout != 30)
|
||||
parts.Add($"Connection Timeout={ConnectionTimeout}");
|
||||
if (!string.IsNullOrWhiteSpace(ApplicationName))
|
||||
parts.Add($"Application Name={ApplicationName}");
|
||||
|
||||
return string.Join(";", parts);
|
||||
}
|
||||
|
||||
private string GenerateOracleConnectionString()
|
||||
{
|
||||
var parts = new List<string>();
|
||||
|
||||
var host = Host ?? "localhost";
|
||||
var port = Port > 0 ? Port : 1521;
|
||||
var service = ServiceName ?? "";
|
||||
|
||||
parts.Add($"Data Source=//{host}:{port}/{service}");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(UserId))
|
||||
parts.Add($"User Id={UserId}");
|
||||
if (!string.IsNullOrWhiteSpace(Password))
|
||||
parts.Add($"Password={Password}");
|
||||
if (ConnectionTimeout > 0 && ConnectionTimeout != 30)
|
||||
parts.Add($"Connection Timeout={ConnectionTimeout}");
|
||||
|
||||
return string.Join(";", parts);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Verification:** Build succeeds.
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Create ConnectionStringsSection model
|
||||
|
||||
**File:** `NEW/src/Utils/JdeScoping.ConfigManager/Models/ConnectionStringsSection.cs`
|
||||
|
||||
```csharp
|
||||
namespace JdeScoping.ConfigManager.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration section for connection strings.
|
||||
/// </summary>
|
||||
public class ConnectionStringsSection
|
||||
{
|
||||
public List<ConnectionStringEntry> Entries { get; set; } = new();
|
||||
}
|
||||
```
|
||||
|
||||
**Verification:** Build succeeds.
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Add ConnectionStrings to ConfigModel
|
||||
|
||||
**File:** `NEW/src/Utils/JdeScoping.ConfigManager/Models/ConfigModel.cs`
|
||||
|
||||
Add property to existing ConfigModel class:
|
||||
|
||||
```csharp
|
||||
public ConnectionStringsSection ConnectionStrings { get; set; } = new();
|
||||
```
|
||||
|
||||
**Verification:** Build succeeds.
|
||||
|
||||
---
|
||||
|
||||
## Batch 2: ViewModels
|
||||
|
||||
### Task 5: Create ConnectionStringEntryViewModel
|
||||
|
||||
**File:** `NEW/src/Utils/JdeScoping.ConfigManager/ViewModels/Forms/ConnectionStringEntryViewModel.cs`
|
||||
|
||||
Create ViewModel that wraps ConnectionStringEntry with:
|
||||
- All properties exposed with change notification
|
||||
- `GeneratedConnectionString` computed property
|
||||
- `IsPasswordVisible` toggle property
|
||||
- `TogglePasswordVisibilityCommand`
|
||||
- `ProviderDisplay` and `ServerDisplay` for table columns
|
||||
|
||||
Key implementation details:
|
||||
- Constructor takes `ConnectionStringEntry model` and `Action onChanged`
|
||||
- All setters call `OnPropertyChanged()` and `_onChanged()`
|
||||
- When Provider changes, also notify `GeneratedConnectionString`
|
||||
- ServerDisplay returns Server (SqlServer), Host (Oracle), or "-" (Generic)
|
||||
|
||||
**Verification:** Build succeeds.
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Create ConnectionStringsFormViewModel
|
||||
|
||||
**File:** `NEW/src/Utils/JdeScoping.ConfigManager/ViewModels/Forms/ConnectionStringsFormViewModel.cs`
|
||||
|
||||
Create main ViewModel with:
|
||||
- `Connections : ObservableCollection<ConnectionStringEntryViewModel>`
|
||||
- `SelectedConnection : ConnectionStringEntryViewModel?`
|
||||
- `HasSelection : bool` (computed from SelectedConnection != null)
|
||||
- `ConnectionCount : int` (computed from Connections.Count)
|
||||
- `AvailableProviders : IReadOnlyList<ConnectionProvider>`
|
||||
- `EncryptOptions : IReadOnlyList<string>` = ["True", "False", "Strict"]
|
||||
|
||||
Commands:
|
||||
- `AddConnectionCommand` - creates new entry with default name "NewConnection", selects it
|
||||
- `DeleteConnectionCommand` - requires confirmation via IDialogService, removes selected
|
||||
- `ValidateConnectionCommand` - validates syntax, shows result via IDialogService
|
||||
- `TestConnectionCommand` - tests actual connection, shows modal result
|
||||
|
||||
Constructor takes:
|
||||
- `ConnectionStringsSection model`
|
||||
- `Action onChanged`
|
||||
- `IDialogService dialogService`
|
||||
|
||||
**Verification:** Build succeeds.
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Create IConnectionTestService interface and implementation
|
||||
|
||||
**File:** `NEW/src/Utils/JdeScoping.ConfigManager/Services/IConnectionTestService.cs`
|
||||
|
||||
```csharp
|
||||
namespace JdeScoping.ConfigManager.Services;
|
||||
|
||||
public interface IConnectionTestService
|
||||
{
|
||||
Task<ConnectionTestResult> TestConnectionAsync(string connectionString, ConnectionProvider provider);
|
||||
}
|
||||
|
||||
public class ConnectionTestResult
|
||||
{
|
||||
public bool Success { get; init; }
|
||||
public string Message { get; init; } = string.Empty;
|
||||
public TimeSpan? Duration { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
**File:** `NEW/src/Utils/JdeScoping.ConfigManager/Services/ConnectionTestService.cs`
|
||||
|
||||
Implementation that:
|
||||
- Uses `Microsoft.Data.SqlClient` for SqlServer
|
||||
- Uses `Oracle.ManagedDataAccess.Client` for Oracle (or stub if not available)
|
||||
- Returns success/failure with timing and error message
|
||||
|
||||
**Verification:** Build succeeds.
|
||||
|
||||
---
|
||||
|
||||
## Batch 3: Views (AXAML)
|
||||
|
||||
### Task 8: Create ConnectionStringsFormView.axaml
|
||||
|
||||
**File:** `NEW/src/Utils/JdeScoping.ConfigManager/Views/Forms/ConnectionStringsFormView.axaml`
|
||||
|
||||
Create view with:
|
||||
1. Header section ("Connection Strings" with separator)
|
||||
2. Connections list section:
|
||||
- DataGrid with Name, Provider, Server columns
|
||||
- Height="200", selection mode single
|
||||
- Toolbar with Add, Delete buttons
|
||||
3. Placeholder section (when no selection):
|
||||
- "Select a connection string to edit" text
|
||||
4. Edit form section (when selection exists):
|
||||
- Name + Provider fields (always visible)
|
||||
- ContentControl with DataTemplates for provider-specific fields
|
||||
5. Action buttons section:
|
||||
- Validate, Test Connection buttons
|
||||
|
||||
Use existing form styling:
|
||||
- Background="#0D0F12", BorderBrush="#2D3540"
|
||||
- Input Background="#232A35"
|
||||
- FontFamily="JetBrains Mono" for values
|
||||
- MaxWidth="800" (wider than other forms for table)
|
||||
|
||||
**Verification:** Build succeeds and view renders.
|
||||
|
||||
---
|
||||
|
||||
### Task 9: Create provider-specific DataTemplates
|
||||
|
||||
Within ConnectionStringsFormView.axaml, create DataTemplates in UserControl.Resources:
|
||||
|
||||
**SqlServerTemplate:**
|
||||
- Server, Database fields (row)
|
||||
- UserId, Password fields (row, password with reveal button)
|
||||
- Encrypt dropdown, TrustServerCertificate checkbox (row)
|
||||
- ConnectionTimeout, ApplicationName fields (row)
|
||||
- Connection string preview box
|
||||
|
||||
**OracleTemplate:**
|
||||
- Host, Port fields (row)
|
||||
- ServiceName field
|
||||
- UserId, Password fields (row, password with reveal button)
|
||||
- ConnectionTimeout field
|
||||
- Connection string preview box
|
||||
|
||||
**GenericTemplate:**
|
||||
- Info banner explaining generic usage
|
||||
- Connection String multiline TextBox
|
||||
|
||||
Use ContentControl with binding to switch templates based on Provider.
|
||||
|
||||
**Verification:** Build succeeds, templates render correctly.
|
||||
|
||||
---
|
||||
|
||||
### Task 10: Create ConnectionStringsFormView.axaml.cs code-behind
|
||||
|
||||
**File:** `NEW/src/Utils/JdeScoping.ConfigManager/Views/Forms/ConnectionStringsFormView.axaml.cs`
|
||||
|
||||
Standard Avalonia code-behind:
|
||||
|
||||
```csharp
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Views.Forms;
|
||||
|
||||
public partial class ConnectionStringsFormView : UserControl
|
||||
{
|
||||
public ConnectionStringsFormView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Verification:** Build succeeds.
|
||||
|
||||
---
|
||||
|
||||
## Batch 4: Integration & Tests
|
||||
|
||||
### Task 11: Integrate into MainWindowViewModel
|
||||
|
||||
**File:** `NEW/src/Utils/JdeScoping.ConfigManager/ViewModels/MainWindowViewModel.cs`
|
||||
|
||||
Updates needed:
|
||||
|
||||
1. In `BuildSettingsNodes()` method, add ConnectionStrings node:
|
||||
```csharp
|
||||
new TreeNodeViewModel("ConnectionStrings", "🔗", TreeNodeType.SettingsSection)
|
||||
{
|
||||
SectionKey = "ConnectionStrings"
|
||||
}
|
||||
```
|
||||
|
||||
2. In `LoadFormViewModelForNode()` method, add case for ConnectionStrings:
|
||||
```csharp
|
||||
"ConnectionStrings" => new ConnectionStringsFormViewModel(
|
||||
_appSettingsConfig!.ConnectionStrings,
|
||||
MarkCurrentNodeModified,
|
||||
_dialogService,
|
||||
_connectionTestService),
|
||||
```
|
||||
|
||||
3. Register `IConnectionTestService` in DI (App.axaml.cs or Program.cs)
|
||||
|
||||
**Verification:**
|
||||
- Build succeeds
|
||||
- ConnectionStrings appears in Settings tree
|
||||
- Clicking node shows form
|
||||
|
||||
---
|
||||
|
||||
### Task 12: Add unit tests
|
||||
|
||||
**File:** `NEW/tests/JdeScoping.ConfigManager.Tests/Models/ConnectionStringEntryTests.cs`
|
||||
|
||||
Test cases:
|
||||
- `GenerateConnectionString_SqlServer_ProducesCorrectFormat`
|
||||
- `GenerateConnectionString_SqlServer_OmitsDefaultTimeout`
|
||||
- `GenerateConnectionString_Oracle_ProducesEZConnectFormat`
|
||||
- `GenerateConnectionString_Generic_ReturnsRawString`
|
||||
- `DefaultValues_AreCorrect`
|
||||
|
||||
**File:** `NEW/tests/JdeScoping.ConfigManager.Tests/ViewModels/Forms/ConnectionStringEntryViewModelTests.cs`
|
||||
|
||||
Test cases:
|
||||
- `Constructor_InitializesFromModel`
|
||||
- `PropertyChange_UpdatesModel`
|
||||
- `PropertyChange_InvokesOnChanged`
|
||||
- `TogglePasswordVisibility_TogglesIsPasswordVisible`
|
||||
- `ProviderDisplay_ReturnsCorrectString`
|
||||
- `ServerDisplay_ReturnsServerForSqlServer`
|
||||
- `ServerDisplay_ReturnsHostForOracle`
|
||||
- `ServerDisplay_ReturnsDashForGeneric`
|
||||
|
||||
**File:** `NEW/tests/JdeScoping.ConfigManager.Tests/ViewModels/Forms/ConnectionStringsFormViewModelTests.cs`
|
||||
|
||||
Test cases:
|
||||
- `Constructor_InitializesFromModel`
|
||||
- `AddConnection_CreatesNewEntryAndSelectsIt`
|
||||
- `DeleteConnection_RemovesSelectedEntry`
|
||||
- `SelectedConnection_UpdatesHasSelection`
|
||||
- `HasSelection_IsFalseWhenNothingSelected`
|
||||
- `ConnectionCount_ReflectsCollectionSize`
|
||||
|
||||
**Verification:** All tests pass.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Batch | Tasks | Description |
|
||||
|-------|-------|-------------|
|
||||
| 1 | 1-4 | Data models (ConnectionProvider, ConnectionStringEntry, ConnectionStringsSection, ConfigModel update) |
|
||||
| 2 | 5-7 | ViewModels (ConnectionStringEntryViewModel, ConnectionStringsFormViewModel, IConnectionTestService) |
|
||||
| 3 | 8-10 | Views (ConnectionStringsFormView, DataTemplates, code-behind) |
|
||||
| 4 | 11-12 | Integration (MainWindowViewModel, DI registration) and unit tests |
|
||||
|
||||
## Post-Implementation Verification
|
||||
|
||||
After all tasks complete:
|
||||
|
||||
1. `dotnet build NEW/JdeScoping.slnx` - should succeed
|
||||
2. `dotnet test NEW/JdeScoping.slnx` - all tests should pass
|
||||
3. Run ConfigManager app:
|
||||
- Open a config folder
|
||||
- Navigate to Settings → ConnectionStrings
|
||||
- Add a new SqlServer connection
|
||||
- Fill in fields, verify preview updates
|
||||
- Test connection works
|
||||
- Save config, verify appsettings.json updated
|
||||
Reference in New Issue
Block a user