diff --git a/src/Cli.Tests/ConfigureOptionsTests.cs b/src/Cli.Tests/ConfigureOptionsTests.cs index 75b7292a6b..450414c12c 100644 --- a/src/Cli.Tests/ConfigureOptionsTests.cs +++ b/src/Cli.Tests/ConfigureOptionsTests.cs @@ -955,6 +955,102 @@ public void TestFailureWhenAddingSetSessionContextToMySQLDatabase() } /// + /// Tests adding data-source.health.name to a config that doesn't have a health section. + /// This method verifies that the health.name can be added to a data source configuration + /// that doesn't previously have a health section. + /// Command: dab configure --data-source.health.name "My Data Source" + /// + [TestMethod] + public void TestAddDataSourceHealthName() + { + // Arrange + SetupFileSystemWithInitialConfig(INITIAL_CONFIG); + + ConfigureOptions options = new( + dataSourceHealthName: "My Data Source", + config: TEST_RUNTIME_CONFIG_FILE + ); + + // Act + bool isSuccess = TryConfigureSettings(options, _runtimeConfigLoader!, _fileSystem!); + + // Assert + Assert.IsTrue(isSuccess); + string updatedConfig = _fileSystem!.File.ReadAllText(TEST_RUNTIME_CONFIG_FILE); + Assert.IsTrue(RuntimeConfigLoader.TryParseConfig(updatedConfig, out RuntimeConfig? config)); + Assert.IsNotNull(config.DataSource); + Assert.IsNotNull(config.DataSource.Health); + Assert.AreEqual("My Data Source", config.DataSource.Health.Name); + Assert.IsTrue(config.DataSource.Health.Enabled); // Default value + } + + /// + /// Tests updating data-source.health.name on a config that already has a health section. + /// This method verifies that the health.name can be updated while preserving other health settings. + /// Command: dab configure --data-source.health.name "Updated Name" + /// + [DataTestMethod] + [DataRow("New Name", DisplayName = "Update health name with a simple string")] + [DataRow("This is the value", DisplayName = "Update health name with the example from the issue")] + public void TestUpdateDataSourceHealthName(string healthName) + { + // Arrange - Config with existing health section + string configWithHealth = @" + { + ""$schema"": ""test"", + ""data-source"": { + ""database-type"": ""mssql"", + ""connection-string"": ""testconnectionstring"", + ""health"": { + ""enabled"": false, + ""threshold-ms"": 2000 + } + }, + ""runtime"": { + ""rest"": { + ""enabled"": true, + ""path"": ""/api"" + }, + ""graphql"": { + ""enabled"": true, + ""path"": ""/graphql"", + ""allow-introspection"": true + }, + ""host"": { + ""mode"": ""development"", + ""cors"": { + ""origins"": [], + ""allow-credentials"": false + }, + ""authentication"": { + ""provider"": ""StaticWebApps"" + } + } + }, + ""entities"": {} + }"; + SetupFileSystemWithInitialConfig(configWithHealth); + + ConfigureOptions options = new( + dataSourceHealthName: healthName, + config: TEST_RUNTIME_CONFIG_FILE + ); + + // Act + bool isSuccess = TryConfigureSettings(options, _runtimeConfigLoader!, _fileSystem!); + + // Assert + Assert.IsTrue(isSuccess); + string updatedConfig = _fileSystem!.File.ReadAllText(TEST_RUNTIME_CONFIG_FILE); + Assert.IsTrue(RuntimeConfigLoader.TryParseConfig(updatedConfig, out RuntimeConfig? config)); + Assert.IsNotNull(config.DataSource); + Assert.IsNotNull(config.DataSource.Health); + Assert.AreEqual(healthName, config.DataSource.Health.Name); + // Verify existing health settings are preserved + Assert.IsFalse(config.DataSource.Health.Enabled); + Assert.AreEqual(2000, config.DataSource.Health.ThresholdMs); + } + /// Tests that running "dab configure --runtime.mcp.description {value}" on a config with various values results /// in runtime config update. Takes in updated value for mcp.description and /// validates whether the runtime config reflects those updated values diff --git a/src/Cli/Commands/ConfigureOptions.cs b/src/Cli/Commands/ConfigureOptions.cs index 2d15645413..14234d24d7 100644 --- a/src/Cli/Commands/ConfigureOptions.cs +++ b/src/Cli/Commands/ConfigureOptions.cs @@ -28,6 +28,7 @@ public ConfigureOptions( string? dataSourceOptionsContainer = null, string? dataSourceOptionsSchema = null, bool? dataSourceOptionsSetSessionContext = null, + string? dataSourceHealthName = null, int? depthLimit = null, bool? runtimeGraphQLEnabled = null, string? runtimeGraphQLPath = null, @@ -82,6 +83,7 @@ public ConfigureOptions( DataSourceOptionsContainer = dataSourceOptionsContainer; DataSourceOptionsSchema = dataSourceOptionsSchema; DataSourceOptionsSetSessionContext = dataSourceOptionsSetSessionContext; + DataSourceHealthName = dataSourceHealthName; // GraphQL DepthLimit = depthLimit; RuntimeGraphQLEnabled = runtimeGraphQLEnabled; @@ -155,6 +157,9 @@ public ConfigureOptions( [Option("data-source.options.set-session-context", Required = false, HelpText = "Enable session context. Allowed values: true (default), false.")] public bool? DataSourceOptionsSetSessionContext { get; } + [Option("data-source.health.name", Required = false, HelpText = "Identifier for data source in health check report.")] + public string? DataSourceHealthName { get; } + [Option("runtime.graphql.depth-limit", Required = false, HelpText = "Max allowed depth of the nested query. Allowed values: (0,2147483647] inclusive. Default is infinity. Use -1 to remove limit.")] public int? DepthLimit { get; } diff --git a/src/Cli/ConfigGenerator.cs b/src/Cli/ConfigGenerator.cs index 7d81c46658..05b229c83e 100644 --- a/src/Cli/ConfigGenerator.cs +++ b/src/Cli/ConfigGenerator.cs @@ -684,6 +684,27 @@ private static bool TryUpdateConfiguredDataSourceOptions( dbOptions.Add(namingPolicy.ConvertName(nameof(MsSqlOptions.SetSessionContext)), options.DataSourceOptionsSetSessionContext.Value); } + // Handle health.name option + if (options.DataSourceHealthName is not null) + { + // If there's no existing health config, create one with the name + // Note: passing enabled: null triggers the base constructor logic that sets Enabled = true + if (datasourceHealthCheckConfig is null) + { + datasourceHealthCheckConfig = new DatasourceHealthCheckConfig(enabled: null, name: options.DataSourceHealthName); + } + else + { + // Update the existing health config with the new name while preserving other settings + // Preserve threshold only if it was explicitly set by the user + int? thresholdToPreserve = datasourceHealthCheckConfig.UserProvidedThresholdMs ? datasourceHealthCheckConfig.ThresholdMs : null; + datasourceHealthCheckConfig = new DatasourceHealthCheckConfig( + enabled: datasourceHealthCheckConfig.UserProvidedEnabled ? datasourceHealthCheckConfig.Enabled : null, + name: options.DataSourceHealthName, + thresholdMs: thresholdToPreserve); + } + } + dbOptions = EnumerableUtilities.IsNullOrEmpty(dbOptions) ? null : dbOptions; DataSource dataSource = new(dbType, dataSourceConnectionString, dbOptions, datasourceHealthCheckConfig); runtimeConfig = runtimeConfig with { DataSource = dataSource }; diff --git a/src/Config/Converters/DatasourceHealthOptionsConvertorFactory.cs b/src/Config/Converters/DatasourceHealthOptionsConvertorFactory.cs index d8286ff7a0..e2df4e8a00 100644 --- a/src/Config/Converters/DatasourceHealthOptionsConvertorFactory.cs +++ b/src/Config/Converters/DatasourceHealthOptionsConvertorFactory.cs @@ -114,11 +114,21 @@ public HealthCheckOptionsConverter(DeserializationVariableReplacementSettings? r public override void Write(Utf8JsonWriter writer, DatasourceHealthCheckConfig value, JsonSerializerOptions options) { - if (value?.UserProvidedEnabled is true) + // Write the health object if any of these conditions are met: + // - enabled was explicitly provided by the user + // - name property has a value + // - threshold was explicitly provided by the user + if (value?.UserProvidedEnabled is true || value?.Name is not null || value?.UserProvidedThresholdMs is true) { writer.WriteStartObject(); - writer.WritePropertyName("enabled"); - JsonSerializer.Serialize(writer, value.Enabled, options); + + // Only write enabled if it was explicitly provided by the user + if (value?.UserProvidedEnabled is true) + { + writer.WritePropertyName("enabled"); + JsonSerializer.Serialize(writer, value.Enabled, options); + } + if (value?.Name is not null) { writer.WritePropertyName("name");