From 1f0644da0d71d68cde606543a59bd98a3c67a54d Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Mon, 11 Sep 2023 15:54:09 -0700 Subject: [PATCH 01/40] Adds telemetry pipeline. Adds evaluationevent, publisher interface, and a publisher instance for app insights. --- Microsoft.FeatureManagement.sln | 16 ++++++ ...ement.AppInsightsTelemetryPublisher.csproj | 19 +++++++ .../ServiceCollectionExtensions.cs | 31 +++++++++++ .../TelemetryPublisherAppInsights.cs | 54 +++++++++++++++++++ .../ConfigurationFeatureDefinitionProvider.cs | 21 +++++++- .../FeatureDefinition.cs | 22 ++++++++ .../FeatureManager.cs | 24 +++++++++ .../IsExternalInit.cs | 21 ++++++++ .../Microsoft.FeatureManagement.csproj | 2 +- .../ServiceCollectionExtensions.cs | 15 +++++- .../Targeting/ContextualTargetingFilter.cs | 1 - .../Telemetry/EvaluationEvent.cs | 21 ++++++++ .../Telemetry/ITelemetryPublisher.cs | 23 ++++++++ .../FeatureManagement.cs | 47 +++++++++++++++- .../TestTelemetryPublisher.cs | 24 +++++++++ .../Tests.FeatureManagement.csproj | 1 + .../Tests.FeatureManagement/appsettings.json | 19 +++++++ 17 files changed, 355 insertions(+), 6 deletions(-) create mode 100644 src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher.csproj create mode 100644 src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/ServiceCollectionExtensions.cs create mode 100644 src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/TelemetryPublisherAppInsights.cs create mode 100644 src/Microsoft.FeatureManagement/IsExternalInit.cs create mode 100644 src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs create mode 100644 src/Microsoft.FeatureManagement/Telemetry/ITelemetryPublisher.cs create mode 100644 tests/Tests.FeatureManagement/TestTelemetryPublisher.cs diff --git a/Microsoft.FeatureManagement.sln b/Microsoft.FeatureManagement.sln index d4c7ce05..c6170de0 100644 --- a/Microsoft.FeatureManagement.sln +++ b/Microsoft.FeatureManagement.sln @@ -21,6 +21,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TargetingConsoleApp", "exam EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RazorPages", "examples\RazorPages\RazorPages.csproj", "{BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.FeatureManagement.AppInsightsTelemetryPublisher", "src\Microsoft.FeatureManagement.AppInsightsTelemetryPublisher\Microsoft.FeatureManagement.AppInsightsTelemetryPublisher.csproj", "{F7CAA3C0-BBAE-4271-B08C-29C9A279DED1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -55,6 +57,18 @@ Global {BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}.Debug|Any CPU.Build.0 = Debug|Any CPU {BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}.Release|Any CPU.ActiveCfg = Release|Any CPU {BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}.Release|Any CPU.Build.0 = Release|Any CPU + {A7F4F8CE-50FD-4450-83F6-30384335000C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A7F4F8CE-50FD-4450-83F6-30384335000C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A7F4F8CE-50FD-4450-83F6-30384335000C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A7F4F8CE-50FD-4450-83F6-30384335000C}.Release|Any CPU.Build.0 = Release|Any CPU + {F7CAA3C0-BBAE-4271-B08C-29C9A279DED1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7CAA3C0-BBAE-4271-B08C-29C9A279DED1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7CAA3C0-BBAE-4271-B08C-29C9A279DED1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7CAA3C0-BBAE-4271-B08C-29C9A279DED1}.Release|Any CPU.Build.0 = Release|Any CPU + {9F21A42B-99B7-4AEB-A7E2-D90DB62A5050}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F21A42B-99B7-4AEB-A7E2-D90DB62A5050}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F21A42B-99B7-4AEB-A7E2-D90DB62A5050}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F21A42B-99B7-4AEB-A7E2-D90DB62A5050}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -65,6 +79,8 @@ Global {E50FB931-7A42-440E-AC47-B8DFE5E15394} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} {6558C21E-CF20-4278-AA08-EB9D1DF29D66} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} {BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} + {A7F4F8CE-50FD-4450-83F6-30384335000C} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} + {9F21A42B-99B7-4AEB-A7E2-D90DB62A5050} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {84DA6C54-F140-4518-A1B4-E4CF42117FBD} diff --git a/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher.csproj b/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher.csproj new file mode 100644 index 00000000..040f1c86 --- /dev/null +++ b/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher.csproj @@ -0,0 +1,19 @@ + + + + netstandard2.0;netcoreapp3.1;net5.0;net6.0 + true + false + ..\..\build\Microsoft.FeatureManagement.snk + 8.0 + + + + + + + + + + + diff --git a/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/ServiceCollectionExtensions.cs b/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..869ae195 --- /dev/null +++ b/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/ServiceCollectionExtensions.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +using Microsoft.ApplicationInsights; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.FeatureManagement.Telemetry; + +namespace Microsoft.FeatureManagement.AppInsightsTelemetryPublisher +{ + /// + /// Extensions used to add feature management publisher functionality. + /// + public static class ServiceCollectionExtensions + { + /// + /// Adds an event publisher that publishes feature evaluation events to Application Insights. + /// + /// The service collection that feature management services are added to. + /// The that was given as a parameter, with the publisher added. + public static IServiceCollection AddFeatureManagementTelemetryPublisherAppInsights(this IServiceCollection services) + { + // + // Add required services + services.AddSingleton(serviceProvider => + new TelemetryPublisherAppInsights(serviceProvider.GetRequiredService()) + ); + + return services; + } + } +} diff --git a/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/TelemetryPublisherAppInsights.cs b/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/TelemetryPublisherAppInsights.cs new file mode 100644 index 00000000..56ba9eb1 --- /dev/null +++ b/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/TelemetryPublisherAppInsights.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +using Microsoft.ApplicationInsights; +using Microsoft.FeatureManagement.Telemetry; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.FeatureManagement.AppInsightsTelemetryPublisher +{ + /// + /// Used to publish data from evaluation events to Application Insights + /// + public class TelemetryPublisherAppInsights : ITelemetryPublisher + { + private readonly string _eventName = "FeatureEvaluation"; + private readonly TelemetryClient _telemetryClient; + + public TelemetryPublisherAppInsights(TelemetryClient telemetryClient) + { + _telemetryClient = telemetryClient; + } + + /// + /// Publishes a custom event to Application Insights using data from the given evaluation event. + /// + /// The event to publish. + /// A cancellation token. + /// Returns a ValueTask that represents the asynchronous operation + public ValueTask PublishEvent(EvaluationEvent evaluationEvent, CancellationToken cancellationToken) + { + FeatureDefinition featureDefinition = evaluationEvent.FeatureDefinition; + + Dictionary properties = new Dictionary() + { + { "FeatureName", featureDefinition.Name }, + { "IsEnabled", evaluationEvent.IsEnabled.ToString() }, + { "Label", featureDefinition.Label }, + { "ETag", featureDefinition.ETag }, + }; + + foreach (KeyValuePair kvp in featureDefinition.Tags) + { + properties["Tag." + kvp.Key] = kvp.Value; + } + + _telemetryClient.TrackEvent(_eventName, properties); + + + return new ValueTask(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs index 4774fd5e..743aca4a 100644 --- a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs +++ b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs @@ -135,6 +135,10 @@ We support */ RequirementType requirementType = RequirementType.Any; + string label = null; + string eTag = null; + bool enableTelemetry = false; + IReadOnlyDictionary tags = null; var enabledFor = new List(); @@ -167,7 +171,7 @@ We support if (!string.IsNullOrEmpty(rawRequirementType) && !Enum.TryParse(rawRequirementType, ignoreCase: true, out requirementType)) { throw new FeatureManagementException( - FeatureManagementError.InvalidConfigurationSetting, + FeatureManagementError.InvalidConfigurationSetting, $"Invalid requirement type '{rawRequirementType}' for feature '{configurationSection.Key}'."); } @@ -187,13 +191,26 @@ We support }); } } + + IConfigurationSection tagsSection = configurationSection.GetSection("Tags"); + if (tagsSection.Exists()) { + tags = tagsSection.GetChildren().ToDictionary(s => s.Key, s => s.Value); + } + + label = configurationSection["Label"]; + eTag = configurationSection["ETag"]; + enableTelemetry = configurationSection.GetValue("EnableTelemetry"); } return new FeatureDefinition() { Name = configurationSection.Key, EnabledFor = enabledFor, - RequirementType = requirementType + RequirementType = requirementType, + Label = label, + ETag = eTag, + EnableTelemetry = enableTelemetry, + Tags = tags }; } diff --git a/src/Microsoft.FeatureManagement/FeatureDefinition.cs b/src/Microsoft.FeatureManagement/FeatureDefinition.cs index 6736314f..73d8de34 100644 --- a/src/Microsoft.FeatureManagement/FeatureDefinition.cs +++ b/src/Microsoft.FeatureManagement/FeatureDefinition.cs @@ -25,5 +25,27 @@ public class FeatureDefinition /// The default value is . /// public RequirementType RequirementType { get; set; } = RequirementType.Any; + + /// + /// A value used to group configuration settings. + /// A is used together with a to uniquely identify a feature. + /// + public string Label { get; set; } + + /// + /// An ETag indicating the state of a feature within a configuration store. + /// + public string ETag { get; set; } + + /// + /// A dictionary of tags used to assign additional properties to a feature. + /// These can be used to indicate how a feature may be applied. + /// + public IReadOnlyDictionary Tags { get; set; } + + /// + /// A flag to enable or disable sending telemetry events to the ITelemetryProvider implementation. + /// + public bool EnableTelemetry { get; set; } } } diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index 17b94c47..00969d5f 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -5,10 +5,12 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Microsoft.FeatureManagement.Telemetry; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.FeatureManagement @@ -54,6 +56,8 @@ public FeatureManager( _parametersCache = new MemoryCache(new MemoryCacheOptions()); } + public ITelemetryPublisher _telemetryPublisher { get; init; } + public Task IsEnabledAsync(string feature) { return IsEnabledAsync(feature, null, false); @@ -214,6 +218,26 @@ await contextualFilter.EvaluateAsync(context, appContext).ConfigureAwait(false) await sessionManager.SetAsync(feature, enabled).ConfigureAwait(false); } + if (featureDefinition.EnableTelemetry) + { + if (_telemetryPublisher == null) + { + string errorMessage = $"The feature declaration enabled telemetry but no instance of ITelemetryPublisher was initialized."; + + _logger.LogWarning(errorMessage); + } + else + { + EvaluationEvent evaluationEvent = new EvaluationEvent() + { + FeatureDefinition = featureDefinition, + IsEnabled = enabled + }; + + await _telemetryPublisher.PublishEvent(evaluationEvent, CancellationToken.None); + } + } + return enabled; } diff --git a/src/Microsoft.FeatureManagement/IsExternalInit.cs b/src/Microsoft.FeatureManagement/IsExternalInit.cs new file mode 100644 index 00000000..279b080b --- /dev/null +++ b/src/Microsoft.FeatureManagement/IsExternalInit.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// + +// The init accessor for properties is supported in C# 9.0 and later. +// This class is used to compile .NET frameworks that don't support C# 9.0 or later while still using the init accessor for a property. +// The code referenced for this file can be found here: https://github.com/dotnet/roslyn/issues/45510#issuecomment-725091019 + +#if NETSTANDARD2_0 || NETCOREAPP2_1 || NETCOREAPP3_1 + +using System.ComponentModel; + +namespace System.Runtime.CompilerServices +{ + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class IsExternalInit + { + } +} + +#endif \ No newline at end of file diff --git a/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj b/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj index beb3ceb4..dbfacdce 100644 --- a/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj +++ b/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj @@ -15,7 +15,7 @@ true false ..\..\build\Microsoft.FeatureManagement.snk - 8.0 + 9.0 diff --git a/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs b/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs index 507f1394..6df0ed45 100644 --- a/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs +++ b/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs @@ -4,7 +4,11 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.FeatureManagement.Telemetry; using System; +using System.Collections.Generic; namespace Microsoft.FeatureManagement { @@ -26,7 +30,16 @@ public static IFeatureManagementBuilder AddFeatureManagement(this IServiceCollec // Add required services services.TryAddSingleton(); - services.AddSingleton(); + services.TryAddSingleton(sp => + new FeatureManager( + sp.GetRequiredService(), + sp.GetRequiredService>(), + sp.GetRequiredService>(), + sp.GetRequiredService(), + sp.GetRequiredService>()) + { + _telemetryPublisher = sp.GetService(), + }); services.AddSingleton(); diff --git a/src/Microsoft.FeatureManagement/Targeting/ContextualTargetingFilter.cs b/src/Microsoft.FeatureManagement/Targeting/ContextualTargetingFilter.cs index d29cafcd..790d0449 100644 --- a/src/Microsoft.FeatureManagement/Targeting/ContextualTargetingFilter.cs +++ b/src/Microsoft.FeatureManagement/Targeting/ContextualTargetingFilter.cs @@ -5,7 +5,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; -using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; diff --git a/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs b/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs new file mode 100644 index 00000000..ea3439db --- /dev/null +++ b/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +namespace Microsoft.FeatureManagement.Telemetry +{ + /// + /// An event representing the evaluation of a feature. + /// + public class EvaluationEvent + { + /// + /// The definition of the feature that was evaluated. + /// + public FeatureDefinition FeatureDefinition { get; set; } + + /// + /// The enabled state of the feature after evaluation. + /// + public bool IsEnabled { get; set; } + } +} diff --git a/src/Microsoft.FeatureManagement/Telemetry/ITelemetryPublisher.cs b/src/Microsoft.FeatureManagement/Telemetry/ITelemetryPublisher.cs new file mode 100644 index 00000000..8728b1d9 --- /dev/null +++ b/src/Microsoft.FeatureManagement/Telemetry/ITelemetryPublisher.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.FeatureManagement.Telemetry +{ + /// + /// A publisher of telemetry events. + /// + public interface ITelemetryPublisher + { + /// + /// Handles an EvaluationEvent and publishes it to the configured telemetry channel. + /// + /// The event to publish. + /// A cancellation token. + /// ValueTask + public ValueTask PublishEvent(EvaluationEvent evaluationEvent, CancellationToken cancellationToken); + } + +} diff --git a/tests/Tests.FeatureManagement/FeatureManagement.cs b/tests/Tests.FeatureManagement/FeatureManagement.cs index 9ecd3e7f..f8de1226 100644 --- a/tests/Tests.FeatureManagement/FeatureManagement.cs +++ b/tests/Tests.FeatureManagement/FeatureManagement.cs @@ -10,6 +10,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.FeatureManagement; using Microsoft.FeatureManagement.FeatureFilters; +using Microsoft.FeatureManagement.Telemetry; +using Microsoft.FeatureManagement.Tests; using System; using System.Collections.Generic; using System.Linq; @@ -23,7 +25,7 @@ namespace Tests.FeatureManagement public class FeatureManagement { private const string OnFeature = "OnTestFeature"; - private const string OffFeature = "OffFeature"; + private const string OffFeature = "OffTestFeature"; private const string ConditionalFeature = "ConditionalFeature"; private const string ContextualFeature = "ContextualFeature"; @@ -961,6 +963,49 @@ public async Task BindsFeatureFlagSettings() Assert.True(called); } + [Fact] + public async Task TelemetryPublishing() + { + IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); + + var services = new ServiceCollection(); + + TestTelemetryPublisher testPublisher = new TestTelemetryPublisher(); + + services + .AddSingleton(config) + .AddSingleton(testPublisher) + .AddFeatureManagement() + .AddFeatureFilter(); + + ServiceProvider serviceProvider = services.BuildServiceProvider(); + + IFeatureManager featureManager = serviceProvider.GetRequiredService(); + + // Test a feature with telemetry disabled + bool result = featureManager.IsEnabledAsync(OnFeature).Result; + + Assert.True(result); + Assert.Null(testPublisher.evalationEventCache); + + // Test telemetry cases + string onFeature = "AlwaysOnTestFeature"; + + result = featureManager.IsEnabledAsync(onFeature).Result; + + Assert.True(result); + Assert.Equal(onFeature, testPublisher.evalationEventCache.FeatureDefinition.Name); + Assert.Equal(result, testPublisher.evalationEventCache.IsEnabled); + + string offFeature = "OffTimeTestFeature"; + + result = featureManager.IsEnabledAsync(offFeature).Result; + + Assert.False(result); + Assert.Equal(offFeature, testPublisher.evalationEventCache.FeatureDefinition.Name); + Assert.Equal(result, testPublisher.evalationEventCache.IsEnabled); + } + private static void DisableEndpointRouting(MvcOptions options) { #if NET6_0 || NET5_0 || NETCOREAPP3_1 diff --git a/tests/Tests.FeatureManagement/TestTelemetryPublisher.cs b/tests/Tests.FeatureManagement/TestTelemetryPublisher.cs new file mode 100644 index 00000000..fe9d483d --- /dev/null +++ b/tests/Tests.FeatureManagement/TestTelemetryPublisher.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +using Microsoft.FeatureManagement.Telemetry; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.FeatureManagement.Tests +{ + public class TestTelemetryPublisher : ITelemetryPublisher + { + private readonly string _eventName = "FeatureEvaluation"; + + public EvaluationEvent evalationEventCache { get; private set; } + + public ValueTask PublishEvent(EvaluationEvent evaluationEvent, CancellationToken cancellationToken) + { + evalationEventCache = evaluationEvent; + + return new ValueTask(); + } + } +} \ No newline at end of file diff --git a/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj b/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj index 7501c259..2e62c685 100644 --- a/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj +++ b/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj @@ -9,6 +9,7 @@ + diff --git a/tests/Tests.FeatureManagement/appsettings.json b/tests/Tests.FeatureManagement/appsettings.json index 2eeff873..cc8b54b5 100644 --- a/tests/Tests.FeatureManagement/appsettings.json +++ b/tests/Tests.FeatureManagement/appsettings.json @@ -9,6 +9,25 @@ "FeatureManagement": { "OnTestFeature": true, "OffTestFeature": false, + "AlwaysOnTestFeature": { + "EnableTelemetry": true, + "EnabledFor": [ + { + "Name": "AlwaysOn" + } + ] + }, + "OffTimeTestFeature": { + "EnableTelemetry": true, + "EnabledFor": [ + { + "Name": "TimeWindow", + "Parameters": { + "End": "2023-09-11T22:02:11.000Z" + } + } + ] + }, "TargetingTestFeature": { "EnabledFor": [ { From 614e9afdd773d326a1f67cc32630ac939efaa6d7 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Tue, 12 Sep 2023 15:57:17 -0700 Subject: [PATCH 02/40] Use await insead of .result --- tests/Tests.FeatureManagement/FeatureManagement.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Tests.FeatureManagement/FeatureManagement.cs b/tests/Tests.FeatureManagement/FeatureManagement.cs index f8de1226..399878d7 100644 --- a/tests/Tests.FeatureManagement/FeatureManagement.cs +++ b/tests/Tests.FeatureManagement/FeatureManagement.cs @@ -983,7 +983,7 @@ public async Task TelemetryPublishing() IFeatureManager featureManager = serviceProvider.GetRequiredService(); // Test a feature with telemetry disabled - bool result = featureManager.IsEnabledAsync(OnFeature).Result; + bool result = await featureManager.IsEnabledAsync(OnFeature); Assert.True(result); Assert.Null(testPublisher.evalationEventCache); @@ -991,7 +991,7 @@ public async Task TelemetryPublishing() // Test telemetry cases string onFeature = "AlwaysOnTestFeature"; - result = featureManager.IsEnabledAsync(onFeature).Result; + result = await featureManager.IsEnabledAsync(onFeature); Assert.True(result); Assert.Equal(onFeature, testPublisher.evalationEventCache.FeatureDefinition.Name); @@ -999,7 +999,7 @@ public async Task TelemetryPublishing() string offFeature = "OffTimeTestFeature"; - result = featureManager.IsEnabledAsync(offFeature).Result; + result = await featureManager.IsEnabledAsync(offFeature); Assert.False(result); Assert.Equal(offFeature, testPublisher.evalationEventCache.FeatureDefinition.Name); From 4db49a31b50629bb098e83876ff8ddfdb51bd6ca Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Tue, 12 Sep 2023 16:05:38 -0700 Subject: [PATCH 03/40] Reverting unused dependency removal --- .../Targeting/ContextualTargetingFilter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.FeatureManagement/Targeting/ContextualTargetingFilter.cs b/src/Microsoft.FeatureManagement/Targeting/ContextualTargetingFilter.cs index 790d0449..d29cafcd 100644 --- a/src/Microsoft.FeatureManagement/Targeting/ContextualTargetingFilter.cs +++ b/src/Microsoft.FeatureManagement/Targeting/ContextualTargetingFilter.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; +using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; From 75cab57524104e9b7178c5f7b9b376eb8b840e9a Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Tue, 12 Sep 2023 16:18:17 -0700 Subject: [PATCH 04/40] Update src/Microsoft.FeatureManagement/FeatureDefinition.cs Co-authored-by: Jimmy Campbell --- src/Microsoft.FeatureManagement/FeatureDefinition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.FeatureManagement/FeatureDefinition.cs b/src/Microsoft.FeatureManagement/FeatureDefinition.cs index 73d8de34..39b9be7a 100644 --- a/src/Microsoft.FeatureManagement/FeatureDefinition.cs +++ b/src/Microsoft.FeatureManagement/FeatureDefinition.cs @@ -41,7 +41,7 @@ public class FeatureDefinition /// A dictionary of tags used to assign additional properties to a feature. /// These can be used to indicate how a feature may be applied. /// - public IReadOnlyDictionary Tags { get; set; } + public IReadOnlyDictionary Tags { get; set; } /// /// A flag to enable or disable sending telemetry events to the ITelemetryProvider implementation. From f8e4f7b3ec3ad4a3c192766ca137f67212c1aac0 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Tue, 12 Sep 2023 16:18:38 -0700 Subject: [PATCH 05/40] Update src/Microsoft.FeatureManagement/FeatureDefinition.cs Co-authored-by: Jimmy Campbell --- src/Microsoft.FeatureManagement/FeatureDefinition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.FeatureManagement/FeatureDefinition.cs b/src/Microsoft.FeatureManagement/FeatureDefinition.cs index 39b9be7a..c40b2073 100644 --- a/src/Microsoft.FeatureManagement/FeatureDefinition.cs +++ b/src/Microsoft.FeatureManagement/FeatureDefinition.cs @@ -44,7 +44,7 @@ public class FeatureDefinition public IReadOnlyDictionary Tags { get; set; } /// - /// A flag to enable or disable sending telemetry events to the ITelemetryProvider implementation. + /// A flag to enable or disable sending telemetry events to the registered . /// public bool EnableTelemetry { get; set; } } From 7c77718e166a511ca715385d2f816c36304d81d8 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Tue, 12 Sep 2023 16:19:31 -0700 Subject: [PATCH 06/40] Update src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/TelemetryPublisherAppInsights.cs Co-authored-by: Jimmy Campbell --- .../TelemetryPublisherAppInsights.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/TelemetryPublisherAppInsights.cs b/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/TelemetryPublisherAppInsights.cs index 56ba9eb1..a3089d12 100644 --- a/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/TelemetryPublisherAppInsights.cs +++ b/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/TelemetryPublisherAppInsights.cs @@ -42,7 +42,7 @@ public ValueTask PublishEvent(EvaluationEvent evaluationEvent, CancellationToken foreach (KeyValuePair kvp in featureDefinition.Tags) { - properties["Tag." + kvp.Key] = kvp.Value; + properties["Tags." + kvp.Key] = kvp.Value; } _telemetryClient.TrackEvent(_eventName, properties); From 52930c010522cb7bf0663db9bfed3521668589c0 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Tue, 12 Sep 2023 16:46:38 -0700 Subject: [PATCH 07/40] Removed factory pattern for publisher DI --- .../ServiceCollectionExtensions.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/ServiceCollectionExtensions.cs b/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/ServiceCollectionExtensions.cs index 869ae195..fd234c05 100644 --- a/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/ServiceCollectionExtensions.cs +++ b/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/ServiceCollectionExtensions.cs @@ -21,9 +21,7 @@ public static IServiceCollection AddFeatureManagementTelemetryPublisherAppInsigh { // // Add required services - services.AddSingleton(serviceProvider => - new TelemetryPublisherAppInsights(serviceProvider.GetRequiredService()) - ); + services.AddSingleton(); return services; } From cb1858688ab73043162ef6533444d99db9639d3f Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Tue, 12 Sep 2023 17:21:36 -0700 Subject: [PATCH 08/40] Update src/Microsoft.FeatureManagement/FeatureManager.cs Co-authored-by: Jimmy Campbell --- src/Microsoft.FeatureManagement/FeatureManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index 00969d5f..b1fc2b20 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -222,7 +222,7 @@ await contextualFilter.EvaluateAsync(context, appContext).ConfigureAwait(false) { if (_telemetryPublisher == null) { - string errorMessage = $"The feature declaration enabled telemetry but no instance of ITelemetryPublisher was initialized."; + string errorMessage = $"The feature declaration enabled telemetry but no telemetry publisher was registered."; _logger.LogWarning(errorMessage); } From 121ca358d39e01a0552115913c7f934d01fab1e5 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Tue, 12 Sep 2023 17:25:27 -0700 Subject: [PATCH 09/40] Resolve misc. comments --- Microsoft.FeatureManagement.sln | 14 ++------------ ...FeatureManagement.Telemetry.AppInsights.csproj} | 2 +- .../ServiceCollectionExtensions.cs | 4 +--- .../TelemetryPublisherAppInsights.cs | 3 +-- src/Microsoft.FeatureManagement/FeatureManager.cs | 14 +++++++------- tests/Tests.FeatureManagement/appsettings.json | 2 +- 6 files changed, 13 insertions(+), 26 deletions(-) rename src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/{Microsoft.FeatureManagement.AppInsightsTelemetryPublisher.csproj => Microsoft.FeatureManagement.Telemetry.AppInsights.csproj} (94%) diff --git a/Microsoft.FeatureManagement.sln b/Microsoft.FeatureManagement.sln index c6170de0..2618f0e3 100644 --- a/Microsoft.FeatureManagement.sln +++ b/Microsoft.FeatureManagement.sln @@ -19,9 +19,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleApp", "examples\Cons EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TargetingConsoleApp", "examples\TargetingConsoleApp\TargetingConsoleApp.csproj", "{6558C21E-CF20-4278-AA08-EB9D1DF29D66}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RazorPages", "examples\RazorPages\RazorPages.csproj", "{BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorPages", "examples\RazorPages\RazorPages.csproj", "{BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.FeatureManagement.AppInsightsTelemetryPublisher", "src\Microsoft.FeatureManagement.AppInsightsTelemetryPublisher\Microsoft.FeatureManagement.AppInsightsTelemetryPublisher.csproj", "{F7CAA3C0-BBAE-4271-B08C-29C9A279DED1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.FeatureManagement.Telemetry.AppInsights", "src\Microsoft.FeatureManagement.AppInsightsTelemetryPublisher\Microsoft.FeatureManagement.Telemetry.AppInsights.csproj", "{F7CAA3C0-BBAE-4271-B08C-29C9A279DED1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -57,18 +57,10 @@ Global {BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}.Debug|Any CPU.Build.0 = Debug|Any CPU {BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}.Release|Any CPU.ActiveCfg = Release|Any CPU {BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}.Release|Any CPU.Build.0 = Release|Any CPU - {A7F4F8CE-50FD-4450-83F6-30384335000C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A7F4F8CE-50FD-4450-83F6-30384335000C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A7F4F8CE-50FD-4450-83F6-30384335000C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A7F4F8CE-50FD-4450-83F6-30384335000C}.Release|Any CPU.Build.0 = Release|Any CPU {F7CAA3C0-BBAE-4271-B08C-29C9A279DED1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F7CAA3C0-BBAE-4271-B08C-29C9A279DED1}.Debug|Any CPU.Build.0 = Debug|Any CPU {F7CAA3C0-BBAE-4271-B08C-29C9A279DED1}.Release|Any CPU.ActiveCfg = Release|Any CPU {F7CAA3C0-BBAE-4271-B08C-29C9A279DED1}.Release|Any CPU.Build.0 = Release|Any CPU - {9F21A42B-99B7-4AEB-A7E2-D90DB62A5050}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9F21A42B-99B7-4AEB-A7E2-D90DB62A5050}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9F21A42B-99B7-4AEB-A7E2-D90DB62A5050}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9F21A42B-99B7-4AEB-A7E2-D90DB62A5050}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -79,8 +71,6 @@ Global {E50FB931-7A42-440E-AC47-B8DFE5E15394} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} {6558C21E-CF20-4278-AA08-EB9D1DF29D66} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} {BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} - {A7F4F8CE-50FD-4450-83F6-30384335000C} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} - {9F21A42B-99B7-4AEB-A7E2-D90DB62A5050} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {84DA6C54-F140-4518-A1B4-E4CF42117FBD} diff --git a/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher.csproj b/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/Microsoft.FeatureManagement.Telemetry.AppInsights.csproj similarity index 94% rename from src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher.csproj rename to src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/Microsoft.FeatureManagement.Telemetry.AppInsights.csproj index 040f1c86..0b1eddb6 100644 --- a/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher.csproj +++ b/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/Microsoft.FeatureManagement.Telemetry.AppInsights.csproj @@ -1,4 +1,4 @@ - + netstandard2.0;netcoreapp3.1;net5.0;net6.0 diff --git a/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/ServiceCollectionExtensions.cs b/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/ServiceCollectionExtensions.cs index fd234c05..88a454c7 100644 --- a/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/ServiceCollectionExtensions.cs +++ b/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/ServiceCollectionExtensions.cs @@ -1,11 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // -using Microsoft.ApplicationInsights; using Microsoft.Extensions.DependencyInjection; -using Microsoft.FeatureManagement.Telemetry; -namespace Microsoft.FeatureManagement.AppInsightsTelemetryPublisher +namespace Microsoft.FeatureManagement.Telemetry.AppInsights { /// /// Extensions used to add feature management publisher functionality. diff --git a/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/TelemetryPublisherAppInsights.cs b/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/TelemetryPublisherAppInsights.cs index 56ba9eb1..576ba6dd 100644 --- a/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/TelemetryPublisherAppInsights.cs +++ b/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/TelemetryPublisherAppInsights.cs @@ -2,12 +2,11 @@ // Licensed under the MIT license. // using Microsoft.ApplicationInsights; -using Microsoft.FeatureManagement.Telemetry; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.FeatureManagement.AppInsightsTelemetryPublisher +namespace Microsoft.FeatureManagement.Telemetry.AppInsights { /// /// Used to publish data from evaluation events to Application Insights diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index 00969d5f..b9040e21 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -228,13 +228,13 @@ await contextualFilter.EvaluateAsync(context, appContext).ConfigureAwait(false) } else { - EvaluationEvent evaluationEvent = new EvaluationEvent() - { - FeatureDefinition = featureDefinition, - IsEnabled = enabled - }; - - await _telemetryPublisher.PublishEvent(evaluationEvent, CancellationToken.None); + await _telemetryPublisher.PublishEvent( + new EvaluationEvent + { + FeatureDefinition = featureDefinition, + IsEnabled = enabled + }, + CancellationToken.None); } } diff --git a/tests/Tests.FeatureManagement/appsettings.json b/tests/Tests.FeatureManagement/appsettings.json index cc8b54b5..080dccff 100644 --- a/tests/Tests.FeatureManagement/appsettings.json +++ b/tests/Tests.FeatureManagement/appsettings.json @@ -23,7 +23,7 @@ { "Name": "TimeWindow", "Parameters": { - "End": "2023-09-11T22:02:11.000Z" + "End": "1970-01-01T00:00:00Z" } } ] From e0256421f3d35fa6a159fc5c5042e4cbe92c96e7 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Tue, 12 Sep 2023 17:29:58 -0700 Subject: [PATCH 10/40] Adjusts descriptions for FeatureDefinition properties --- src/Microsoft.FeatureManagement/FeatureDefinition.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.FeatureManagement/FeatureDefinition.cs b/src/Microsoft.FeatureManagement/FeatureDefinition.cs index c40b2073..30bcbea6 100644 --- a/src/Microsoft.FeatureManagement/FeatureDefinition.cs +++ b/src/Microsoft.FeatureManagement/FeatureDefinition.cs @@ -27,13 +27,13 @@ public class FeatureDefinition public RequirementType RequirementType { get; set; } = RequirementType.Any; /// - /// A value used to group configuration settings. + /// A value used to group feature flags. /// A is used together with a to uniquely identify a feature. /// public string Label { get; set; } /// - /// An ETag indicating the state of a feature within a configuration store. + /// An ETag indicating the state of a feature. This value is used to determine whether a feature has changed. /// public string ETag { get; set; } From 8ad67b7abc40fd793b2dd9adaa695f97a8020cae Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Thu, 14 Sep 2023 17:53:03 -0700 Subject: [PATCH 11/40] Rename AppInsights to ApplicationInsights --- ....FeatureManagement.Telemetry.ApplicationInsights.csproj} | 0 .../ServiceCollectionExtensions.cs | 6 +++--- .../TelemetryPublisherApplicationInsights.cs} | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) rename src/{Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/Microsoft.FeatureManagement.Telemetry.AppInsights.csproj => Microsoft.FeatureManagement.Telemetry.ApplicationInsights/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj} (100%) rename src/{Microsoft.FeatureManagement.AppInsightsTelemetryPublisher => Microsoft.FeatureManagement.Telemetry.ApplicationInsights}/ServiceCollectionExtensions.cs (84%) rename src/{Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/TelemetryPublisherAppInsights.cs => Microsoft.FeatureManagement.Telemetry.ApplicationInsights/TelemetryPublisherApplicationInsights.cs} (88%) diff --git a/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/Microsoft.FeatureManagement.Telemetry.AppInsights.csproj b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj similarity index 100% rename from src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/Microsoft.FeatureManagement.Telemetry.AppInsights.csproj rename to src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj diff --git a/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/ServiceCollectionExtensions.cs b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/ServiceCollectionExtensions.cs similarity index 84% rename from src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/ServiceCollectionExtensions.cs rename to src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/ServiceCollectionExtensions.cs index 88a454c7..c82023d1 100644 --- a/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/ServiceCollectionExtensions.cs +++ b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/ServiceCollectionExtensions.cs @@ -3,7 +3,7 @@ // using Microsoft.Extensions.DependencyInjection; -namespace Microsoft.FeatureManagement.Telemetry.AppInsights +namespace Microsoft.FeatureManagement.Telemetry.ApplicationInsights { /// /// Extensions used to add feature management publisher functionality. @@ -15,11 +15,11 @@ public static class ServiceCollectionExtensions /// /// The service collection that feature management services are added to. /// The that was given as a parameter, with the publisher added. - public static IServiceCollection AddFeatureManagementTelemetryPublisherAppInsights(this IServiceCollection services) + public static IServiceCollection AddFeatureManagementTelemetryPublisherApplicationInsights(this IServiceCollection services) { // // Add required services - services.AddSingleton(); + services.AddSingleton(); return services; } diff --git a/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/TelemetryPublisherAppInsights.cs b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/TelemetryPublisherApplicationInsights.cs similarity index 88% rename from src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/TelemetryPublisherAppInsights.cs rename to src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/TelemetryPublisherApplicationInsights.cs index b7b04e0d..c7d144fa 100644 --- a/src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/TelemetryPublisherAppInsights.cs +++ b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/TelemetryPublisherApplicationInsights.cs @@ -6,17 +6,17 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.FeatureManagement.Telemetry.AppInsights +namespace Microsoft.FeatureManagement.Telemetry.ApplicationInsights { /// /// Used to publish data from evaluation events to Application Insights /// - public class TelemetryPublisherAppInsights : ITelemetryPublisher + public class TelemetryPublisherApplicationInsights : ITelemetryPublisher { private readonly string _eventName = "FeatureEvaluation"; private readonly TelemetryClient _telemetryClient; - public TelemetryPublisherAppInsights(TelemetryClient telemetryClient) + public TelemetryPublisherApplicationInsights(TelemetryClient telemetryClient) { _telemetryClient = telemetryClient; } From fce72745e1cc6f46516019b3c26c5edf108abe82 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Thu, 14 Sep 2023 18:19:01 -0700 Subject: [PATCH 12/40] Resolves project reference in sln --- Microsoft.FeatureManagement.sln | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Microsoft.FeatureManagement.sln b/Microsoft.FeatureManagement.sln index 2618f0e3..96cb6431 100644 --- a/Microsoft.FeatureManagement.sln +++ b/Microsoft.FeatureManagement.sln @@ -21,7 +21,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TargetingConsoleApp", "exam EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorPages", "examples\RazorPages\RazorPages.csproj", "{BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.FeatureManagement.Telemetry.AppInsights", "src\Microsoft.FeatureManagement.AppInsightsTelemetryPublisher\Microsoft.FeatureManagement.Telemetry.AppInsights.csproj", "{F7CAA3C0-BBAE-4271-B08C-29C9A279DED1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.FeatureManagement.Telemetry.ApplicationInsights", "src\Microsoft.FeatureManagement.Telemetry.ApplicationInsights\Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj", "{0ADB5CFC-D3FA-48E5-910A-51770C8E608C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -57,10 +57,10 @@ Global {BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}.Debug|Any CPU.Build.0 = Debug|Any CPU {BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}.Release|Any CPU.ActiveCfg = Release|Any CPU {BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}.Release|Any CPU.Build.0 = Release|Any CPU - {F7CAA3C0-BBAE-4271-B08C-29C9A279DED1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F7CAA3C0-BBAE-4271-B08C-29C9A279DED1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F7CAA3C0-BBAE-4271-B08C-29C9A279DED1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F7CAA3C0-BBAE-4271-B08C-29C9A279DED1}.Release|Any CPU.Build.0 = Release|Any CPU + {0ADB5CFC-D3FA-48E5-910A-51770C8E608C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0ADB5CFC-D3FA-48E5-910A-51770C8E608C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0ADB5CFC-D3FA-48E5-910A-51770C8E608C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0ADB5CFC-D3FA-48E5-910A-51770C8E608C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From d23357cfe8d394136c48cd4ab23a2b19b0cea9a2 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Thu, 14 Sep 2023 18:20:50 -0700 Subject: [PATCH 13/40] Adjusts ServiceCollectionExtensions to use TryAddSingleton --- .../ServiceCollectionExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/ServiceCollectionExtensions.cs b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/ServiceCollectionExtensions.cs index c82023d1..9dd7cf49 100644 --- a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/ServiceCollectionExtensions.cs +++ b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/ServiceCollectionExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. // using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; namespace Microsoft.FeatureManagement.Telemetry.ApplicationInsights { @@ -19,7 +20,7 @@ public static IServiceCollection AddFeatureManagementTelemetryPublisherApplicati { // // Add required services - services.AddSingleton(); + services.TryAddSingleton(); return services; } From e43e1143df789a65e6fe7d6b632a6602af3f0b31 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Thu, 14 Sep 2023 18:21:52 -0700 Subject: [PATCH 14/40] Removes langversion from .csproj --- ...rosoft.FeatureManagement.Telemetry.ApplicationInsights.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj index 0b1eddb6..213559f5 100644 --- a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj +++ b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj @@ -5,7 +5,6 @@ true false ..\..\build\Microsoft.FeatureManagement.snk - 8.0 From 93a88a322a54b04e4cf2941f9dcea1025f6d2d65 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Thu, 14 Sep 2023 18:50:06 -0700 Subject: [PATCH 15/40] Temp --- Microsoft.FeatureManagement.sln | 7 +++++++ .../AppInsightsWithEvaluationData.csproj | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 examples/AppInsightsWithEvaluationData/AppInsightsWithEvaluationData.csproj diff --git a/Microsoft.FeatureManagement.sln b/Microsoft.FeatureManagement.sln index 96cb6431..fdb499cc 100644 --- a/Microsoft.FeatureManagement.sln +++ b/Microsoft.FeatureManagement.sln @@ -23,6 +23,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorPages", "examples\Razo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.FeatureManagement.Telemetry.ApplicationInsights", "src\Microsoft.FeatureManagement.Telemetry.ApplicationInsights\Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj", "{0ADB5CFC-D3FA-48E5-910A-51770C8E608C}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppInsightsWithEvaluationData", "examples\AppInsightsWithEvaluationData\AppInsightsWithEvaluationData.csproj", "{9717561C-E561-4F89-93CA-07FF0C3786D7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -61,6 +63,10 @@ Global {0ADB5CFC-D3FA-48E5-910A-51770C8E608C}.Debug|Any CPU.Build.0 = Debug|Any CPU {0ADB5CFC-D3FA-48E5-910A-51770C8E608C}.Release|Any CPU.ActiveCfg = Release|Any CPU {0ADB5CFC-D3FA-48E5-910A-51770C8E608C}.Release|Any CPU.Build.0 = Release|Any CPU + {9717561C-E561-4F89-93CA-07FF0C3786D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9717561C-E561-4F89-93CA-07FF0C3786D7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9717561C-E561-4F89-93CA-07FF0C3786D7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9717561C-E561-4F89-93CA-07FF0C3786D7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -71,6 +77,7 @@ Global {E50FB931-7A42-440E-AC47-B8DFE5E15394} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} {6558C21E-CF20-4278-AA08-EB9D1DF29D66} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} {BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} + {9717561C-E561-4F89-93CA-07FF0C3786D7} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {84DA6C54-F140-4518-A1B4-E4CF42117FBD} diff --git a/examples/AppInsightsWithEvaluationData/AppInsightsWithEvaluationData.csproj b/examples/AppInsightsWithEvaluationData/AppInsightsWithEvaluationData.csproj new file mode 100644 index 00000000..1d7179ee --- /dev/null +++ b/examples/AppInsightsWithEvaluationData/AppInsightsWithEvaluationData.csproj @@ -0,0 +1,18 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + From ba22ed15d62733d07aa0dc84d89acdb9d32bbac3 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Mon, 18 Sep 2023 13:40:58 -0700 Subject: [PATCH 16/40] Adds explicit null checkts before publishing fields to app insights --- .../TelemetryPublisherApplicationInsights.cs | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/TelemetryPublisherApplicationInsights.cs b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/TelemetryPublisherApplicationInsights.cs index c7d144fa..ace98ac6 100644 --- a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/TelemetryPublisherApplicationInsights.cs +++ b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/TelemetryPublisherApplicationInsights.cs @@ -34,14 +34,25 @@ public ValueTask PublishEvent(EvaluationEvent evaluationEvent, CancellationToken Dictionary properties = new Dictionary() { { "FeatureName", featureDefinition.Name }, - { "IsEnabled", evaluationEvent.IsEnabled.ToString() }, - { "Label", featureDefinition.Label }, - { "ETag", featureDefinition.ETag }, + { "IsEnabled", evaluationEvent.IsEnabled.ToString() } }; - foreach (KeyValuePair kvp in featureDefinition.Tags) + if (featureDefinition.ETag != null) { - properties["Tags." + kvp.Key] = kvp.Value; + properties.Add("ETag", featureDefinition.ETag); + } + + if (featureDefinition.Label != null) + { + properties.Add("Label", featureDefinition.Label); + } + + if (featureDefinition.Tags != null) + { + foreach (KeyValuePair kvp in featureDefinition.Tags) + { + properties["Tags." + kvp.Key] = kvp.Value; + } } _telemetryClient.TrackEvent(_eventName, properties); From 0b5bea4d86a42f307c7b890253a817316ced4094 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Mon, 18 Sep 2023 13:43:21 -0700 Subject: [PATCH 17/40] Addressing misc. comments --- src/Microsoft.FeatureManagement/FeatureManager.cs | 10 ++++------ .../ServiceCollectionExtensions.cs | 2 +- tests/Tests.FeatureManagement/FeatureManagement.cs | 10 +++++----- .../Tests.FeatureManagement/TestTelemetryPublisher.cs | 6 ++---- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index 80387ea4..5a40e2cd 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -56,7 +56,7 @@ public FeatureManager( _parametersCache = new MemoryCache(new MemoryCacheOptions()); } - public ITelemetryPublisher _telemetryPublisher { get; init; } + public ITelemetryPublisher TelemetryPublisher { get; init; } public Task IsEnabledAsync(string feature) { @@ -220,15 +220,13 @@ await contextualFilter.EvaluateAsync(context, appContext).ConfigureAwait(false) if (featureDefinition.EnableTelemetry) { - if (_telemetryPublisher == null) + if (TelemetryPublisher == null) { - string errorMessage = $"The feature declaration enabled telemetry but no telemetry publisher was registered."; - - _logger.LogWarning(errorMessage); + _logger.LogWarning("The feature declaration enabled telemetry but no telemetry publisher was registered."); } else { - await _telemetryPublisher.PublishEvent( + await TelemetryPublisher.PublishEvent( new EvaluationEvent { FeatureDefinition = featureDefinition, diff --git a/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs b/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs index 6df0ed45..f53ceb0b 100644 --- a/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs +++ b/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs @@ -38,7 +38,7 @@ public static IFeatureManagementBuilder AddFeatureManagement(this IServiceCollec sp.GetRequiredService(), sp.GetRequiredService>()) { - _telemetryPublisher = sp.GetService(), + TelemetryPublisher = sp.GetService(), }); services.AddSingleton(); diff --git a/tests/Tests.FeatureManagement/FeatureManagement.cs b/tests/Tests.FeatureManagement/FeatureManagement.cs index 399878d7..c1ef0d3e 100644 --- a/tests/Tests.FeatureManagement/FeatureManagement.cs +++ b/tests/Tests.FeatureManagement/FeatureManagement.cs @@ -986,7 +986,7 @@ public async Task TelemetryPublishing() bool result = await featureManager.IsEnabledAsync(OnFeature); Assert.True(result); - Assert.Null(testPublisher.evalationEventCache); + Assert.Null(testPublisher.evaluationEventCache); // Test telemetry cases string onFeature = "AlwaysOnTestFeature"; @@ -994,16 +994,16 @@ public async Task TelemetryPublishing() result = await featureManager.IsEnabledAsync(onFeature); Assert.True(result); - Assert.Equal(onFeature, testPublisher.evalationEventCache.FeatureDefinition.Name); - Assert.Equal(result, testPublisher.evalationEventCache.IsEnabled); + Assert.Equal(onFeature, testPublisher.evaluationEventCache.FeatureDefinition.Name); + Assert.Equal(result, testPublisher.evaluationEventCache.IsEnabled); string offFeature = "OffTimeTestFeature"; result = await featureManager.IsEnabledAsync(offFeature); Assert.False(result); - Assert.Equal(offFeature, testPublisher.evalationEventCache.FeatureDefinition.Name); - Assert.Equal(result, testPublisher.evalationEventCache.IsEnabled); + Assert.Equal(offFeature, testPublisher.evaluationEventCache.FeatureDefinition.Name); + Assert.Equal(result, testPublisher.evaluationEventCache.IsEnabled); } private static void DisableEndpointRouting(MvcOptions options) diff --git a/tests/Tests.FeatureManagement/TestTelemetryPublisher.cs b/tests/Tests.FeatureManagement/TestTelemetryPublisher.cs index fe9d483d..f829a5d6 100644 --- a/tests/Tests.FeatureManagement/TestTelemetryPublisher.cs +++ b/tests/Tests.FeatureManagement/TestTelemetryPublisher.cs @@ -10,13 +10,11 @@ namespace Microsoft.FeatureManagement.Tests { public class TestTelemetryPublisher : ITelemetryPublisher { - private readonly string _eventName = "FeatureEvaluation"; - - public EvaluationEvent evalationEventCache { get; private set; } + public EvaluationEvent evaluationEventCache { get; private set; } public ValueTask PublishEvent(EvaluationEvent evaluationEvent, CancellationToken cancellationToken) { - evalationEventCache = evaluationEvent; + evaluationEventCache = evaluationEvent; return new ValueTask(); } From 2bfcbc86dfaa851c86dc675273d7e3f50eb7d7bc Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Mon, 18 Sep 2023 14:04:26 -0700 Subject: [PATCH 18/40] Removing project reference for example project --- Microsoft.FeatureManagement.sln | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Microsoft.FeatureManagement.sln b/Microsoft.FeatureManagement.sln index fdb499cc..96cb6431 100644 --- a/Microsoft.FeatureManagement.sln +++ b/Microsoft.FeatureManagement.sln @@ -23,8 +23,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorPages", "examples\Razo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.FeatureManagement.Telemetry.ApplicationInsights", "src\Microsoft.FeatureManagement.Telemetry.ApplicationInsights\Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj", "{0ADB5CFC-D3FA-48E5-910A-51770C8E608C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppInsightsWithEvaluationData", "examples\AppInsightsWithEvaluationData\AppInsightsWithEvaluationData.csproj", "{9717561C-E561-4F89-93CA-07FF0C3786D7}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -63,10 +61,6 @@ Global {0ADB5CFC-D3FA-48E5-910A-51770C8E608C}.Debug|Any CPU.Build.0 = Debug|Any CPU {0ADB5CFC-D3FA-48E5-910A-51770C8E608C}.Release|Any CPU.ActiveCfg = Release|Any CPU {0ADB5CFC-D3FA-48E5-910A-51770C8E608C}.Release|Any CPU.Build.0 = Release|Any CPU - {9717561C-E561-4F89-93CA-07FF0C3786D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9717561C-E561-4F89-93CA-07FF0C3786D7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9717561C-E561-4F89-93CA-07FF0C3786D7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9717561C-E561-4F89-93CA-07FF0C3786D7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -77,7 +71,6 @@ Global {E50FB931-7A42-440E-AC47-B8DFE5E15394} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} {6558C21E-CF20-4278-AA08-EB9D1DF29D66} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} {BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} - {9717561C-E561-4F89-93CA-07FF0C3786D7} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {84DA6C54-F140-4518-A1B4-E4CF42117FBD} From 5b1842f9b2264f3133e88c7826eac2de2236a1b6 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Wed, 20 Sep 2023 16:47:44 -0700 Subject: [PATCH 19/40] Syncing telemetry and variants --- .../AppInsightsWithEvaluationData.csproj | 18 - ... ApplicationInsightsTelemetryPublisher.cs} | 25 +- .../ServiceCollectionExtensions.cs | 28 -- .../FeatureManagementBuilder.cs | 9 + .../FeatureManager.cs | 336 +++++++++--------- .../FeatureManagerSnapshot.cs | 22 +- .../IFeatureDefinitionProvider.cs | 1 + .../IFeatureManagementBuilder.cs | 7 + .../IVariantFeatureManager.cs | 9 +- .../Microsoft.FeatureManagement.csproj | 1 + .../ServiceCollectionExtensions.cs | 22 +- .../Telemetry/EvaluationEvent.cs | 5 + .../FeatureManagement.cs | 46 ++- .../Tests.FeatureManagement/appsettings.json | 2 + 14 files changed, 287 insertions(+), 244 deletions(-) delete mode 100644 examples/AppInsightsWithEvaluationData/AppInsightsWithEvaluationData.csproj rename src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/{TelemetryPublisherApplicationInsights.cs => ApplicationInsightsTelemetryPublisher.cs} (72%) delete mode 100644 src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/ServiceCollectionExtensions.cs diff --git a/examples/AppInsightsWithEvaluationData/AppInsightsWithEvaluationData.csproj b/examples/AppInsightsWithEvaluationData/AppInsightsWithEvaluationData.csproj deleted file mode 100644 index 1d7179ee..00000000 --- a/examples/AppInsightsWithEvaluationData/AppInsightsWithEvaluationData.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - net6.0 - enable - enable - - - - - - - - - - - - diff --git a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/TelemetryPublisherApplicationInsights.cs b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/ApplicationInsightsTelemetryPublisher.cs similarity index 72% rename from src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/TelemetryPublisherApplicationInsights.cs rename to src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/ApplicationInsightsTelemetryPublisher.cs index ace98ac6..303c8e31 100644 --- a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/TelemetryPublisherApplicationInsights.cs +++ b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/ApplicationInsightsTelemetryPublisher.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. // using Microsoft.ApplicationInsights; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -11,14 +12,14 @@ namespace Microsoft.FeatureManagement.Telemetry.ApplicationInsights /// /// Used to publish data from evaluation events to Application Insights /// - public class TelemetryPublisherApplicationInsights : ITelemetryPublisher + public class ApplicationInsightsTelemetryPublisher : ITelemetryPublisher { private readonly string _eventName = "FeatureEvaluation"; private readonly TelemetryClient _telemetryClient; - public TelemetryPublisherApplicationInsights(TelemetryClient telemetryClient) + public ApplicationInsightsTelemetryPublisher(TelemetryClient telemetryClient) { - _telemetryClient = telemetryClient; + _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); } /// @@ -29,12 +30,15 @@ public TelemetryPublisherApplicationInsights(TelemetryClient telemetryClient) /// Returns a ValueTask that represents the asynchronous operation public ValueTask PublishEvent(EvaluationEvent evaluationEvent, CancellationToken cancellationToken) { + ValidateEvent(evaluationEvent); + FeatureDefinition featureDefinition = evaluationEvent.FeatureDefinition; Dictionary properties = new Dictionary() { { "FeatureName", featureDefinition.Name }, - { "IsEnabled", evaluationEvent.IsEnabled.ToString() } + { "IsEnabled", evaluationEvent.IsEnabled.ToString() }, + { "Variant", evaluationEvent.Variant?.Name } }; if (featureDefinition.ETag != null) @@ -60,5 +64,18 @@ public ValueTask PublishEvent(EvaluationEvent evaluationEvent, CancellationToken return new ValueTask(); } + + private void ValidateEvent(EvaluationEvent evaluationEvent) + { + if (evaluationEvent == null) + { + throw new ArgumentNullException(nameof(evaluationEvent)); + } + + if (evaluationEvent.FeatureDefinition == null) + { + throw new ArgumentNullException(nameof(evaluationEvent.FeatureDefinition)); + } + } } } \ No newline at end of file diff --git a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/ServiceCollectionExtensions.cs b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/ServiceCollectionExtensions.cs deleted file mode 100644 index 9dd7cf49..00000000 --- a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; - -namespace Microsoft.FeatureManagement.Telemetry.ApplicationInsights -{ - /// - /// Extensions used to add feature management publisher functionality. - /// - public static class ServiceCollectionExtensions - { - /// - /// Adds an event publisher that publishes feature evaluation events to Application Insights. - /// - /// The service collection that feature management services are added to. - /// The that was given as a parameter, with the publisher added. - public static IServiceCollection AddFeatureManagementTelemetryPublisherApplicationInsights(this IServiceCollection services) - { - // - // Add required services - services.TryAddSingleton(); - - return services; - } - } -} diff --git a/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs b/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs index a433b2bc..95e4adc7 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.FeatureManagement.Telemetry; namespace Microsoft.FeatureManagement { @@ -49,5 +51,12 @@ public IFeatureManagementBuilder AddSessionManager() where T : ISessionManage return this; } + + public IFeatureManagementBuilder AddFeatureManagementTelemetry() where T : ITelemetryPublisher + { + Services.TryAddSingleton(typeof(ITelemetryPublisher), typeof(T)); + + return this; + } } } diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index d88b9fd8..4e69d080 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -4,14 +4,10 @@ using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; -<<<<<<< HEAD using Microsoft.FeatureManagement.Telemetry; -======= using Microsoft.FeatureManagement.FeatureFilters; using Microsoft.FeatureManagement.Targeting; ->>>>>>> preview using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -19,6 +15,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Runtime.CompilerServices; namespace Microsoft.FeatureManagement { @@ -66,13 +63,11 @@ public FeatureManager( _parametersCache = new MemoryCache(new MemoryCacheOptions()); } -<<<<<<< HEAD public ITelemetryPublisher TelemetryPublisher { get; init; } -======= + public IConfiguration Configuration { get; init; } public ITargetingContextAccessor TargetingContextAccessor { get; init; } ->>>>>>> preview public Task IsEnabledAsync(string feature) { @@ -104,10 +99,11 @@ private async Task IsEnabledWithVariantsAsync(string feature, TC { isFeatureEnabled = false; } - else if ((featureDefinition.Variants?.Any() ?? false) && featureDefinition.Allocation != null) - { - VariantDefinition variantDefinition; + VariantDefinition variantDefinition = null; + + if ((featureDefinition.Variants?.Any() ?? false) && featureDefinition.Allocation != null) + { if (!isFeatureEnabled) { variantDefinition = featureDefinition.Variants.FirstOrDefault((variant) => variant.Name == featureDefinition.Allocation.DefaultWhenDisabled); @@ -132,7 +128,7 @@ private async Task IsEnabledWithVariantsAsync(string feature, TC .ConfigureAwait(false); } - if (variantDefinition != null) + if (variantDefinition != null && featureDefinition.Status != FeatureStatus.Disabled) { if (variantDefinition.StatusOverride == StatusOverride.Enabled) { @@ -150,14 +146,28 @@ private async Task IsEnabledWithVariantsAsync(string feature, TC await sessionManager.SetAsync(feature, isFeatureEnabled).ConfigureAwait(false); } + PublishTelemetry(new EvaluationEvent + { + FeatureDefinition = featureDefinition, + IsEnabled = isFeatureEnabled, + Variant = GetVariantFromVariantDefinition(variantDefinition) + }); + return isFeatureEnabled; } - public async IAsyncEnumerable GetFeatureNamesAsync() + public IAsyncEnumerable GetFeatureNamesAsync() + { + return GetFeatureNamesAsync(CancellationToken.None); + } + + public async IAsyncEnumerable GetFeatureNamesAsync([EnumeratorCancellation] CancellationToken cancellationToken) { - await foreach (FeatureDefinition featureDefintion in _featureDefinitionProvider.GetAllFeatureDefinitionsAsync().ConfigureAwait(false)) + await foreach (FeatureDefinition featureDefinition in _featureDefinitionProvider.GetAllFeatureDefinitionsAsync().ConfigureAwait(false)) { - yield return featureDefintion.Name; + cancellationToken.ThrowIfCancellationRequested(); + + yield return featureDefinition.Name; } } @@ -178,126 +188,110 @@ private async Task IsEnabledAsync(string feature, TContext appCo } } + FeatureDefinition featureDefinition = await GetFeatureDefinition(feature); + bool enabled; - FeatureDefinition featureDefinition = await _featureDefinitionProvider.GetFeatureDefinitionAsync(feature).ConfigureAwait(false); + if (featureDefinition.RequirementType == RequirementType.All && _options.IgnoreMissingFeatureFilters) + { + throw new FeatureManagementException( + FeatureManagementError.Conflict, + $"The 'IgnoreMissingFeatureFilters' flag cannot use used in combination with a feature of requirement type 'All'."); + } - if (featureDefinition != null) + // + // Treat an empty list of enabled filters or if status is disabled as a disabled feature + if (featureDefinition.EnabledFor == null || !featureDefinition.EnabledFor.Any() || featureDefinition.Status == FeatureStatus.Disabled) { - if (featureDefinition.RequirementType == RequirementType.All && _options.IgnoreMissingFeatureFilters) - { - throw new FeatureManagementException( - FeatureManagementError.Conflict, - $"The 'IgnoreMissingFeatureFilters' flag cannot use used in combination with a feature of requirement type 'All'."); - } + enabled = false; + } + else + { + // + // If the requirement type is all, we default to true. Requirement type All will end on a false + enabled = featureDefinition.RequirementType == RequirementType.All; // - // Treat an empty list of enabled filters or if status is disabled as a disabled feature - if (featureDefinition.EnabledFor == null || !featureDefinition.EnabledFor.Any() || featureDefinition.Status == FeatureStatus.Disabled) - { - enabled = false; - } - else - { - // - // If the requirement type is all, we default to true. Requirement type All will end on a false - enabled = featureDefinition.RequirementType == RequirementType.All; + // We iterate until we hit our target evaluation + bool targetEvaluation = !enabled; - // - // We iterate until we hit our target evaluation - bool targetEvaluation = !enabled; + // + // Keep track of the index of the filter we are evaluating + int filterIndex = -1; - // - // Keep track of the index of the filter we are evaluating - int filterIndex = -1; + // + // For all enabling filters listed in the feature's state, evaluate them according to requirement type + foreach (FeatureFilterConfiguration featureFilterConfiguration in featureDefinition.EnabledFor) + { + filterIndex++; // - // For all enabling filters listed in the feature's state, evaluate them according to requirement type - foreach (FeatureFilterConfiguration featureFilterConfiguration in featureDefinition.EnabledFor) + // Handle AlwaysOn and On filters + if (string.Equals(featureFilterConfiguration.Name, "AlwaysOn", StringComparison.OrdinalIgnoreCase) || + string.Equals(featureFilterConfiguration.Name, "On", StringComparison.OrdinalIgnoreCase)) { - filterIndex++; - - // - // Handle AlwaysOn and On filters - if (string.Equals(featureFilterConfiguration.Name, "AlwaysOn", StringComparison.OrdinalIgnoreCase) || - string.Equals(featureFilterConfiguration.Name, "On", StringComparison.OrdinalIgnoreCase)) + if (featureDefinition.RequirementType == RequirementType.Any) { - if (featureDefinition.RequirementType == RequirementType.Any) - { - enabled = true; - break; - } - - continue; + enabled = true; + break; } - IFeatureFilterMetadata filter = GetFeatureFilterMetadata(featureFilterConfiguration.Name); - - if (filter == null) - { - string errorMessage = $"The feature filter '{featureFilterConfiguration.Name}' specified for feature '{feature}' was not found."; + continue; + } - if (!_options.IgnoreMissingFeatureFilters) - { - throw new FeatureManagementException(FeatureManagementError.MissingFeatureFilter, errorMessage); - } + IFeatureFilterMetadata filter = GetFeatureFilterMetadata(featureFilterConfiguration.Name); - _logger.LogWarning(errorMessage); + if (filter == null) + { + string errorMessage = $"The feature filter '{featureFilterConfiguration.Name}' specified for feature '{feature}' was not found."; - continue; + if (!_options.IgnoreMissingFeatureFilters) + { + throw new FeatureManagementException(FeatureManagementError.MissingFeatureFilter, errorMessage); } - var context = new FeatureFilterEvaluationContext() - { - FeatureName = feature, - Parameters = featureFilterConfiguration.Parameters - }; + _logger.LogWarning(errorMessage); - // - // IContextualFeatureFilter - if (useAppContext) - { - ContextualFeatureFilterEvaluator contextualFilter = GetContextualFeatureFilter(featureFilterConfiguration.Name, typeof(TContext)); + continue; + } - BindSettings(filter, context, filterIndex); + var context = new FeatureFilterEvaluationContext() + { + FeatureName = feature, + Parameters = featureFilterConfiguration.Parameters + }; - if (contextualFilter != null && - await contextualFilter.EvaluateAsync(context, appContext).ConfigureAwait(false) == targetEvaluation) - { - enabled = targetEvaluation; + // + // IContextualFeatureFilter + if (useAppContext) + { + ContextualFeatureFilterEvaluator contextualFilter = GetContextualFeatureFilter(featureFilterConfiguration.Name, typeof(TContext)); - break; - } - } + BindSettings(filter, context, filterIndex); - // - // IFeatureFilter - if (filter is IFeatureFilter featureFilter) + if (contextualFilter != null && + await contextualFilter.EvaluateAsync(context, appContext).ConfigureAwait(false) == targetEvaluation) { - BindSettings(filter, context, filterIndex); - - if (await featureFilter.EvaluateAsync(context).ConfigureAwait(false) == targetEvaluation) - { - enabled = targetEvaluation; + enabled = targetEvaluation; - break; - } + break; } } - } - } - else - { - enabled = false; - string errorMessage = $"The feature declaration for the feature '{feature}' was not found."; + // + // IFeatureFilter + if (filter is IFeatureFilter featureFilter) + { + BindSettings(filter, context, filterIndex); - if (!_options.IgnoreMissingFeatures) - { - throw new FeatureManagementException(FeatureManagementError.MissingFeature, errorMessage); + if (await featureFilter.EvaluateAsync(context).ConfigureAwait(false) == targetEvaluation) + { + enabled = targetEvaluation; + + break; + } + } } - - _logger.LogWarning(errorMessage); } return enabled; @@ -310,27 +304,6 @@ public ValueTask GetVariantAsync(string feature, CancellationToken canc throw new ArgumentNullException(nameof(feature)); } -<<<<<<< HEAD - if (featureDefinition.EnableTelemetry) - { - if (TelemetryPublisher == null) - { - _logger.LogWarning("The feature declaration enabled telemetry but no telemetry publisher was registered."); - } - else - { - await TelemetryPublisher.PublishEvent( - new EvaluationEvent - { - FeatureDefinition = featureDefinition, - IsEnabled = enabled - }, - CancellationToken.None); - } - } - - return enabled; -======= return GetVariantAsync(feature, context: null, useContext: false, cancellationToken); } @@ -351,21 +324,7 @@ public ValueTask GetVariantAsync(string feature, TargetingContext conte private async ValueTask GetVariantAsync(string feature, TargetingContext context, bool useContext, CancellationToken cancellationToken) { - FeatureDefinition featureDefinition = await _featureDefinitionProvider - .GetFeatureDefinitionAsync(feature) - .ConfigureAwait(false); - - if (featureDefinition == null) - { - string errorMessage = $"The feature declaration for the feature '{feature}' was not found."; - - if (!_options.IgnoreMissingFeatures) - { - throw new FeatureManagementException(FeatureManagementError.MissingFeature, errorMessage); - } - - _logger.LogWarning(errorMessage); - } + FeatureDefinition featureDefinition = await GetFeatureDefinition(feature).ConfigureAwait(false); if (featureDefinition?.Allocation == null || (!featureDefinition.Variants?.Any() ?? false)) { @@ -390,39 +349,37 @@ private async ValueTask GetVariantAsync(string feature, TargetingContex variantDefinition = await GetAssignedVariantAsync(featureDefinition, context, cancellationToken).ConfigureAwait(false); } - if (variantDefinition == null) + Variant variant = GetVariantFromVariantDefinition(variantDefinition); + + PublishTelemetry(new EvaluationEvent { - return null; - } + FeatureDefinition = featureDefinition, + IsEnabled = isFeatureEnabled, + Variant = variant + }); - IConfigurationSection variantConfiguration = null; + return variant; + } - bool configValueSet = variantDefinition.ConfigurationValue.Exists(); - bool configReferenceSet = !string.IsNullOrEmpty(variantDefinition.ConfigurationReference); + private async ValueTask GetFeatureDefinition(string feature) + { + FeatureDefinition featureDefinition = await _featureDefinitionProvider + .GetFeatureDefinitionAsync(feature) + .ConfigureAwait(false); - if (configValueSet) - { - variantConfiguration = variantDefinition.ConfigurationValue; - } - else if (configReferenceSet) + if (featureDefinition == null) { - if (Configuration == null) - { - _logger.LogWarning($"Cannot use {nameof(variantDefinition.ConfigurationReference)} as no instance of {nameof(IConfiguration)} is present."); + string errorMessage = $"The feature declaration for the feature '{feature}' was not found."; - return null; - } - else + if (!_options.IgnoreMissingFeatures) { - variantConfiguration = Configuration.GetSection(variantDefinition.ConfigurationReference); + throw new FeatureManagementException(FeatureManagementError.MissingFeature, errorMessage); } + + _logger.LogWarning(errorMessage); } - return new Variant() - { - Name = variantDefinition.Name, - Configuration = variantConfiguration - }; + return featureDefinition; } private async ValueTask ResolveTargetingContextAsync(CancellationToken cancellationToken) @@ -544,7 +501,6 @@ private ValueTask AssignVariantAsync(FeatureDefinition featur } return new ValueTask(variant); ->>>>>>> preview } private void BindSettings(IFeatureFilterMetadata filter, FeatureFilterEvaluationContext context, int filterIndex) @@ -669,5 +625,59 @@ private ContextualFeatureFilterEvaluator GetContextualFeatureFilter(string filte return filter; } + + private async void PublishTelemetry(EvaluationEvent evaluationEvent) + { + if (evaluationEvent.FeatureDefinition.EnableTelemetry) + { + if (TelemetryPublisher == null) + { + _logger.LogWarning("The feature declaration enabled telemetry but no telemetry publisher was registered."); + } + else + { + await TelemetryPublisher.PublishEvent( + evaluationEvent, + CancellationToken.None); + } + } + } + + private Variant GetVariantFromVariantDefinition(VariantDefinition variantDefinition) + { + if (variantDefinition == null) + { + return null; + } + + IConfigurationSection variantConfiguration = null; + + bool configValueSet = variantDefinition.ConfigurationValue.Exists(); + bool configReferenceSet = !string.IsNullOrEmpty(variantDefinition.ConfigurationReference); + + if (configValueSet) + { + variantConfiguration = variantDefinition.ConfigurationValue; + } + else if (configReferenceSet) + { + if (Configuration == null) + { + _logger.LogWarning($"Cannot use {nameof(variantDefinition.ConfigurationReference)} as no instance of {nameof(IConfiguration)} is present."); + + return null; + } + else + { + variantConfiguration = Configuration.GetSection(variantDefinition.ConfigurationReference); + } + } + + return new Variant() + { + Name = variantDefinition.Name, + Configuration = variantConfiguration + }; + } } } diff --git a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs index 9e20e8c6..f1904560 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -15,23 +16,28 @@ namespace Microsoft.FeatureManagement /// class FeatureManagerSnapshot : IFeatureManagerSnapshot, IVariantFeatureManagerSnapshot { - private readonly FeatureManager _featureManager; + private readonly IVariantFeatureManager _featureManager; private readonly ConcurrentDictionary> _flagCache = new ConcurrentDictionary>(); private readonly ConcurrentDictionary _variantCache = new ConcurrentDictionary(); private IEnumerable _featureNames; - public FeatureManagerSnapshot(FeatureManager featureManager) + public FeatureManagerSnapshot(IVariantFeatureManager featureManager) { _featureManager = featureManager ?? throw new ArgumentNullException(nameof(featureManager)); } - public async IAsyncEnumerable GetFeatureNamesAsync() + public IAsyncEnumerable GetFeatureNamesAsync() + { + return GetFeatureNamesAsync(CancellationToken.None); + } + + public async IAsyncEnumerable GetFeatureNamesAsync([EnumeratorCancellation] CancellationToken cancellationToken) { if (_featureNames == null) { var featureNames = new List(); - await foreach (string featureName in _featureManager.GetFeatureNamesAsync().ConfigureAwait(false)) + await foreach (string featureName in _featureManager.GetFeatureNamesAsync(cancellationToken).ConfigureAwait(false)) { featureNames.Add(featureName); } @@ -49,28 +55,28 @@ public Task IsEnabledAsync(string feature) { return _flagCache.GetOrAdd( feature, - (key) => _featureManager.IsEnabledAsync(key)); + (key) => _featureManager.IsEnabledAsync(key, CancellationToken.None)); } public Task IsEnabledAsync(string feature, TContext context) { return _flagCache.GetOrAdd( feature, - (key) => _featureManager.IsEnabledAsync(key, context)); + (key) => _featureManager.IsEnabledAsync(key, context, CancellationToken.None)); } public Task IsEnabledAsync(string feature, CancellationToken cancellationToken) { return _flagCache.GetOrAdd( feature, - (key) => _featureManager.IsEnabledAsync(key)); + (key) => _featureManager.IsEnabledAsync(key, CancellationToken.None)); } public Task IsEnabledAsync(string feature, TContext context, CancellationToken cancellationToken) { return _flagCache.GetOrAdd( feature, - (key) => _featureManager.IsEnabledAsync(key, context)); + (key) => _featureManager.IsEnabledAsync(key, context, CancellationToken.None)); } public async ValueTask GetVariantAsync(string feature, CancellationToken cancellationToken) diff --git a/src/Microsoft.FeatureManagement/IFeatureDefinitionProvider.cs b/src/Microsoft.FeatureManagement/IFeatureDefinitionProvider.cs index bc4895b9..36ac2e76 100644 --- a/src/Microsoft.FeatureManagement/IFeatureDefinitionProvider.cs +++ b/src/Microsoft.FeatureManagement/IFeatureDefinitionProvider.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. // using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.FeatureManagement diff --git a/src/Microsoft.FeatureManagement/IFeatureManagementBuilder.cs b/src/Microsoft.FeatureManagement/IFeatureManagementBuilder.cs index 6365c098..096fe7a6 100644 --- a/src/Microsoft.FeatureManagement/IFeatureManagementBuilder.cs +++ b/src/Microsoft.FeatureManagement/IFeatureManagementBuilder.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. // using Microsoft.Extensions.DependencyInjection; +using Microsoft.FeatureManagement.Telemetry; namespace Microsoft.FeatureManagement { @@ -30,5 +31,11 @@ public interface IFeatureManagementBuilder /// An implementation of /// The feature management builder. IFeatureManagementBuilder AddSessionManager() where T : ISessionManager; + + /// + /// Adds an event publisher that publishes feature evaluation events. + /// + /// The that the publisher was added to. + IFeatureManagementBuilder AddFeatureManagementTelemetry() where T : ITelemetryPublisher; } } diff --git a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs index 10395327..87594ddf 100644 --- a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs +++ b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using System.Threading; using Microsoft.FeatureManagement.FeatureFilters; +using System.Collections.Generic; namespace Microsoft.FeatureManagement { @@ -12,6 +13,12 @@ namespace Microsoft.FeatureManagement /// public interface IVariantFeatureManager { + /// + /// Retrieves a list of feature names registered in the feature manager. + /// + /// An enumerator which provides asynchronous iteration over the feature names registered in the feature manager. + IAsyncEnumerable GetFeatureNamesAsync(CancellationToken cancellationToken); + /// /// Checks whether a given feature is enabled. /// @@ -30,7 +37,7 @@ public interface IVariantFeatureManager Task IsEnabledAsync(string feature, TContext context, CancellationToken cancellationToken); /// - /// Gets the assigned variant for a specfic feature. + /// Gets the assigned variant for a specific feature. /// /// The name of the feature to evaluate. /// The cancellation token to cancel the operation. diff --git a/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj b/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj index 383956c9..c83e0832 100644 --- a/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj +++ b/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj @@ -37,6 +37,7 @@ + diff --git a/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs b/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs index 2416a9f3..f38cb9d0 100644 --- a/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs +++ b/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs @@ -31,22 +31,7 @@ public static IFeatureManagementBuilder AddFeatureManagement(this IServiceCollec // Add required services services.TryAddSingleton(); - services.TryAddSingleton(sp => - new FeatureManager( - sp.GetRequiredService(), - sp.GetRequiredService>(), - sp.GetRequiredService>(), - sp.GetRequiredService(), - sp.GetRequiredService>(), - sp.GetRequiredService>()) - { - Configuration = sp.GetService(), - TargetingContextAccessor = sp.GetService(), - TelemetryPublisher = sp.GetService() - }); - - services.TryAddSingleton(sp => - new FeatureManager( + services.AddSingleton(sp => new FeatureManager( sp.GetRequiredService(), sp.GetRequiredService>(), sp.GetRequiredService>(), @@ -58,6 +43,11 @@ public static IFeatureManagementBuilder AddFeatureManagement(this IServiceCollec TargetingContextAccessor = sp.GetService(), TelemetryPublisher = sp.GetService() }); + + services.TryAddSingleton(sp => sp.GetRequiredService()); + + services.TryAddSingleton(sp => sp.GetRequiredService()); + services.AddSingleton(); services.AddScoped(); diff --git a/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs b/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs index ea3439db..8731776e 100644 --- a/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs +++ b/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs @@ -17,5 +17,10 @@ public class EvaluationEvent /// The enabled state of the feature after evaluation. /// public bool IsEnabled { get; set; } + + /// + /// The variant given after evaluation. + /// + public Variant Variant { get; set; } } } diff --git a/tests/Tests.FeatureManagement/FeatureManagement.cs b/tests/Tests.FeatureManagement/FeatureManagement.cs index 9cc54eaf..e6967c7f 100644 --- a/tests/Tests.FeatureManagement/FeatureManagement.cs +++ b/tests/Tests.FeatureManagement/FeatureManagement.cs @@ -966,20 +966,23 @@ public async Task BindsFeatureFlagSettings() [Fact] public async Task TelemetryPublishing() { - TestTelemetryPublisher testPublisher = new TestTelemetryPublisher(); + IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); + + var services = new ServiceCollection(); services .AddSingleton(config) - .AddSingleton(testPublisher) .AddFeatureManagement() + .AddFeatureManagementTelemetry() .AddFeatureFilter(); ServiceProvider serviceProvider = services.BuildServiceProvider(); - IFeatureManager featureManager = serviceProvider.GetRequiredService(); + IVariantFeatureManager featureManager = serviceProvider.GetRequiredService(); + TestTelemetryPublisher testPublisher = (TestTelemetryPublisher) serviceProvider.GetRequiredService(); // Test a feature with telemetry disabled - bool result = await featureManager.IsEnabledAsync(OnFeature); + bool result = await featureManager.IsEnabledAsync(OnFeature, CancellationToken.None); Assert.True(result); Assert.Null(testPublisher.evaluationEventCache); @@ -987,7 +990,7 @@ public async Task TelemetryPublishing() // Test telemetry cases string onFeature = "AlwaysOnTestFeature"; - result = await featureManager.IsEnabledAsync(onFeature); + result = await featureManager.IsEnabledAsync(onFeature, CancellationToken.None); Assert.True(result); Assert.Equal(onFeature, testPublisher.evaluationEventCache.FeatureDefinition.Name); @@ -995,11 +998,42 @@ public async Task TelemetryPublishing() string offFeature = "OffTimeTestFeature"; - result = await featureManager.IsEnabledAsync(offFeature); + result = await featureManager.IsEnabledAsync(offFeature, CancellationToken.None); Assert.False(result); Assert.Equal(offFeature, testPublisher.evaluationEventCache.FeatureDefinition.Name); Assert.Equal(result, testPublisher.evaluationEventCache.IsEnabled); + + // Test variant cases + string variantDefaultEnabledFeature = "VariantFeatureDefaultEnabled"; + + result = await featureManager.IsEnabledAsync(variantDefaultEnabledFeature, CancellationToken.None); + + Assert.True(result); + Assert.Equal(variantDefaultEnabledFeature, testPublisher.evaluationEventCache.FeatureDefinition.Name); + Assert.Equal(result, testPublisher.evaluationEventCache.IsEnabled); + Assert.Equal("Medium", testPublisher.evaluationEventCache.Variant.Name); + + Variant variantResult = await featureManager.GetVariantAsync(variantDefaultEnabledFeature, CancellationToken.None); + + Assert.True(testPublisher.evaluationEventCache.IsEnabled); + Assert.Equal(variantDefaultEnabledFeature, testPublisher.evaluationEventCache.FeatureDefinition.Name); + Assert.Equal(variantResult.Name, testPublisher.evaluationEventCache.Variant.Name); + + string variantFeatureStatusDisabled = "VariantFeatureStatusDisabled"; + + result = await featureManager.IsEnabledAsync(variantFeatureStatusDisabled, CancellationToken.None); + + Assert.False(result); + Assert.Equal(variantFeatureStatusDisabled, testPublisher.evaluationEventCache.FeatureDefinition.Name); + Assert.Equal(result, testPublisher.evaluationEventCache.IsEnabled); + Assert.Equal("Small", testPublisher.evaluationEventCache.Variant.Name); + + variantResult = await featureManager.GetVariantAsync(variantFeatureStatusDisabled, CancellationToken.None); + + Assert.False(testPublisher.evaluationEventCache.IsEnabled); + Assert.Equal(variantFeatureStatusDisabled, testPublisher.evaluationEventCache.FeatureDefinition.Name); + Assert.Equal(variantResult.Name, testPublisher.evaluationEventCache.Variant.Name); } public async Task UsesVariants() diff --git a/tests/Tests.FeatureManagement/appsettings.json b/tests/Tests.FeatureManagement/appsettings.json index c9ffdad1..096c6253 100644 --- a/tests/Tests.FeatureManagement/appsettings.json +++ b/tests/Tests.FeatureManagement/appsettings.json @@ -228,6 +228,7 @@ }, "VariantFeatureStatusDisabled": { "Status": "Disabled", + "EnableTelemetry": true, "Allocation": { "DefaultWhenDisabled": "Small" }, @@ -244,6 +245,7 @@ ] }, "VariantFeatureDefaultEnabled": { + "EnableTelemetry": true, "Allocation": { "DefaultWhenEnabled": "Medium", "User": [ From ecc898da456d2f88bf97df3a1e96d760d26d6f2f Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Wed, 20 Sep 2023 17:16:05 -0700 Subject: [PATCH 20/40] Adds null check for feature definitions --- src/Microsoft.FeatureManagement/FeatureManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index 4e69d080..fadf01cc 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -102,7 +102,7 @@ private async Task IsEnabledWithVariantsAsync(string feature, TC VariantDefinition variantDefinition = null; - if ((featureDefinition.Variants?.Any() ?? false) && featureDefinition.Allocation != null) + if (featureDefinition != null && (featureDefinition.Variants?.Any() ?? false) && featureDefinition.Allocation != null) { if (!isFeatureEnabled) { From 39dd606fb262ddb5e8a1e3aebf1a188408c0dcfb Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Fri, 29 Sep 2023 14:35:44 -0700 Subject: [PATCH 21/40] Adjusted TelemetryPublishers to no longer be inserted into DI --- .../Program.cs | 41 +++++++++++++++++++ .../FeatureManagementBuilder.cs | 20 +++++++-- .../FeatureManagementOptions.cs | 6 +++ .../FeatureManager.cs | 13 +++--- .../IFeatureManagementBuilder.cs | 2 +- .../ServiceCollectionExtensions.cs | 7 +++- .../FeatureManagement.cs | 6 +-- .../TestTelemetryPublisher.cs | 1 - 8 files changed, 81 insertions(+), 15 deletions(-) create mode 100644 examples/EvaluationDataToApplicationInsights/Program.cs diff --git a/examples/EvaluationDataToApplicationInsights/Program.cs b/examples/EvaluationDataToApplicationInsights/Program.cs new file mode 100644 index 00000000..ea6523ec --- /dev/null +++ b/examples/EvaluationDataToApplicationInsights/Program.cs @@ -0,0 +1,41 @@ +using Microsoft.FeatureManagement.Telemetry.ApplicationInsights; +using EvaluationDataToApplicationInsights.Middlewares; +using Microsoft.FeatureManagement; +using Microsoft.FeatureManagement.FeatureFilters; +using EvaluationDataToApplicationInsights; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddRazorPages(); +builder.Services.AddApplicationInsightsTelemetry(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddFeatureManagement() + .AddFeatureFilter() + .AddTelemetryPublisher(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); +} + +app.UseHttpsRedirection(); +app.UseStaticFiles(); + +// +// App Insights User Tagging +app.UseMetricsMiddleware(); + +app.UseRouting(); + +app.UseAuthorization(); + +app.MapRazorPages(); + +app.Run(); diff --git a/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs b/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs index 95e4adc7..3f3a1cf8 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.FeatureManagement.Telemetry; namespace Microsoft.FeatureManagement @@ -52,9 +51,24 @@ public IFeatureManagementBuilder AddSessionManager() where T : ISessionManage return this; } - public IFeatureManagementBuilder AddFeatureManagementTelemetry() where T : ITelemetryPublisher + public IFeatureManagementBuilder AddTelemetryPublisher() where T : ITelemetryPublisher { - Services.TryAddSingleton(typeof(ITelemetryPublisher), typeof(T)); + AddTelemetryPublisher(sp => ActivatorUtilities.CreateInstance(sp, typeof(T)) as ITelemetryPublisher); + + return this; + } + + private IFeatureManagementBuilder AddTelemetryPublisher(Func factory) where T : ITelemetryPublisher + { + Services.Configure(options => + { + if (options.telemetryPublisherFactories == null) + { + options.telemetryPublisherFactories = new List>(); + } + + options.telemetryPublisherFactories.Add(factory); + }); return this; } diff --git a/src/Microsoft.FeatureManagement/FeatureManagementOptions.cs b/src/Microsoft.FeatureManagement/FeatureManagementOptions.cs index 46658786..8a9e8e88 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagementOptions.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagementOptions.cs @@ -1,6 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // +using Microsoft.FeatureManagement.Telemetry; +using System; +using System.Collections.Generic; + namespace Microsoft.FeatureManagement { /// @@ -22,5 +26,7 @@ public class FeatureManagementOptions /// The default value is true. /// public bool IgnoreMissingFeatures { get; set; } = true; + + internal ICollection> telemetryPublisherFactories; } } diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index fadf01cc..eb2e8186 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -63,7 +63,7 @@ public FeatureManager( _parametersCache = new MemoryCache(new MemoryCacheOptions()); } - public ITelemetryPublisher TelemetryPublisher { get; init; } + public ICollection TelemetryPublishers { get; init; } public IConfiguration Configuration { get; init; } @@ -630,15 +630,18 @@ private async void PublishTelemetry(EvaluationEvent evaluationEvent) { if (evaluationEvent.FeatureDefinition.EnableTelemetry) { - if (TelemetryPublisher == null) + if (!TelemetryPublishers.Any()) { _logger.LogWarning("The feature declaration enabled telemetry but no telemetry publisher was registered."); } else { - await TelemetryPublisher.PublishEvent( - evaluationEvent, - CancellationToken.None); + foreach (ITelemetryPublisher telemetryPublisher in TelemetryPublishers) + { + await telemetryPublisher.PublishEvent( + evaluationEvent, + CancellationToken.None); + } } } } diff --git a/src/Microsoft.FeatureManagement/IFeatureManagementBuilder.cs b/src/Microsoft.FeatureManagement/IFeatureManagementBuilder.cs index 096fe7a6..ded16b23 100644 --- a/src/Microsoft.FeatureManagement/IFeatureManagementBuilder.cs +++ b/src/Microsoft.FeatureManagement/IFeatureManagementBuilder.cs @@ -36,6 +36,6 @@ public interface IFeatureManagementBuilder /// Adds an event publisher that publishes feature evaluation events. /// /// The that the publisher was added to. - IFeatureManagementBuilder AddFeatureManagementTelemetry() where T : ITelemetryPublisher; + IFeatureManagementBuilder AddTelemetryPublisher() where T : ITelemetryPublisher; } } diff --git a/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs b/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs index f38cb9d0..41c160d3 100644 --- a/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs +++ b/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs @@ -10,6 +10,7 @@ using Microsoft.FeatureManagement.FeatureFilters; using System; using System.Collections.Generic; +using System.Linq; namespace Microsoft.FeatureManagement { @@ -41,8 +42,10 @@ public static IFeatureManagementBuilder AddFeatureManagement(this IServiceCollec { Configuration = sp.GetService(), TargetingContextAccessor = sp.GetService(), - TelemetryPublisher = sp.GetService() - }); + TelemetryPublishers = sp.GetService>()?.Value.telemetryPublisherFactories? + .Select(factory => factory(sp)) + .ToList() + });; services.TryAddSingleton(sp => sp.GetRequiredService()); diff --git a/tests/Tests.FeatureManagement/FeatureManagement.cs b/tests/Tests.FeatureManagement/FeatureManagement.cs index e6967c7f..26957a1f 100644 --- a/tests/Tests.FeatureManagement/FeatureManagement.cs +++ b/tests/Tests.FeatureManagement/FeatureManagement.cs @@ -973,13 +973,13 @@ public async Task TelemetryPublishing() services .AddSingleton(config) .AddFeatureManagement() - .AddFeatureManagementTelemetry() + .AddTelemetryPublisher() .AddFeatureFilter(); ServiceProvider serviceProvider = services.BuildServiceProvider(); - IVariantFeatureManager featureManager = serviceProvider.GetRequiredService(); - TestTelemetryPublisher testPublisher = (TestTelemetryPublisher) serviceProvider.GetRequiredService(); + FeatureManager featureManager = (FeatureManager) serviceProvider.GetRequiredService(); + TestTelemetryPublisher testPublisher = (TestTelemetryPublisher) featureManager.TelemetryPublishers.First(); // Test a feature with telemetry disabled bool result = await featureManager.IsEnabledAsync(OnFeature, CancellationToken.None); diff --git a/tests/Tests.FeatureManagement/TestTelemetryPublisher.cs b/tests/Tests.FeatureManagement/TestTelemetryPublisher.cs index f829a5d6..088941b5 100644 --- a/tests/Tests.FeatureManagement/TestTelemetryPublisher.cs +++ b/tests/Tests.FeatureManagement/TestTelemetryPublisher.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. // using Microsoft.FeatureManagement.Telemetry; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; From 9cf586a507cd939c9515aae8a38ab3131751ff75 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Fri, 29 Sep 2023 14:51:05 -0700 Subject: [PATCH 22/40] Resolving comments --- .../FeatureDefinition.cs | 2 +- .../FeatureManager.cs | 23 +++++++++++-------- .../FeatureManagerSnapshot.cs | 4 ++-- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.FeatureManagement/FeatureDefinition.cs b/src/Microsoft.FeatureManagement/FeatureDefinition.cs index dacaea30..1d521d04 100644 --- a/src/Microsoft.FeatureManagement/FeatureDefinition.cs +++ b/src/Microsoft.FeatureManagement/FeatureDefinition.cs @@ -61,7 +61,7 @@ public class FeatureDefinition public IReadOnlyDictionary Tags { get; set; } /// - /// A flag to enable or disable sending telemetry events to the registered . + /// A flag to enable or disable sending telemetry events to the registered . /// public bool EnableTelemetry { get; set; } } diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index eb2e8186..50045d78 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -192,21 +192,26 @@ private async Task IsEnabledAsync(string feature, TContext appCo bool enabled; - if (featureDefinition.RequirementType == RequirementType.All && _options.IgnoreMissingFeatureFilters) - { - throw new FeatureManagementException( - FeatureManagementError.Conflict, - $"The 'IgnoreMissingFeatureFilters' flag cannot use used in combination with a feature of requirement type 'All'."); - } - // - // Treat an empty list of enabled filters or if status is disabled as a disabled feature - if (featureDefinition.EnabledFor == null || !featureDefinition.EnabledFor.Any() || featureDefinition.Status == FeatureStatus.Disabled) + // Treat a null, empty, or status disabled feature as disabled + if (featureDefinition == null || + featureDefinition.EnabledFor == null || + !featureDefinition.EnabledFor.Any() || + featureDefinition.Status == FeatureStatus.Disabled) { enabled = false; } else { + // + // Ensure no conflicts in the feature definition + if (featureDefinition.RequirementType == RequirementType.All && _options.IgnoreMissingFeatureFilters) + { + throw new FeatureManagementException( + FeatureManagementError.Conflict, + $"The 'IgnoreMissingFeatureFilters' flag cannot be used in combination with a feature of requirement type 'All'."); + } + // // If the requirement type is all, we default to true. Requirement type All will end on a false enabled = featureDefinition.RequirementType == RequirementType.All; diff --git a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs index f1904560..35c4b0ec 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs @@ -69,14 +69,14 @@ public Task IsEnabledAsync(string feature, CancellationToken cancellationT { return _flagCache.GetOrAdd( feature, - (key) => _featureManager.IsEnabledAsync(key, CancellationToken.None)); + (key) => _featureManager.IsEnabledAsync(key, cancellationToken)); } public Task IsEnabledAsync(string feature, TContext context, CancellationToken cancellationToken) { return _flagCache.GetOrAdd( feature, - (key) => _featureManager.IsEnabledAsync(key, context, CancellationToken.None)); + (key) => _featureManager.IsEnabledAsync(key, context, cancellationToken)); } public async ValueTask GetVariantAsync(string feature, CancellationToken cancellationToken) From 78d3ba2dbf98b90cf8acd53d691ccf45c3b62077 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Fri, 29 Sep 2023 14:54:32 -0700 Subject: [PATCH 23/40] Removing Application Insights project --- Microsoft.FeatureManagement.sln | 6 -- .../ApplicationInsightsTelemetryPublisher.cs | 81 ------------------- ...ement.Telemetry.ApplicationInsights.csproj | 18 ----- .../Tests.FeatureManagement.csproj | 1 - 4 files changed, 106 deletions(-) delete mode 100644 src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/ApplicationInsightsTelemetryPublisher.cs delete mode 100644 src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj diff --git a/Microsoft.FeatureManagement.sln b/Microsoft.FeatureManagement.sln index 96cb6431..6eed8deb 100644 --- a/Microsoft.FeatureManagement.sln +++ b/Microsoft.FeatureManagement.sln @@ -21,8 +21,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TargetingConsoleApp", "exam EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorPages", "examples\RazorPages\RazorPages.csproj", "{BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.FeatureManagement.Telemetry.ApplicationInsights", "src\Microsoft.FeatureManagement.Telemetry.ApplicationInsights\Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj", "{0ADB5CFC-D3FA-48E5-910A-51770C8E608C}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -57,10 +55,6 @@ Global {BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}.Debug|Any CPU.Build.0 = Debug|Any CPU {BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}.Release|Any CPU.ActiveCfg = Release|Any CPU {BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}.Release|Any CPU.Build.0 = Release|Any CPU - {0ADB5CFC-D3FA-48E5-910A-51770C8E608C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0ADB5CFC-D3FA-48E5-910A-51770C8E608C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0ADB5CFC-D3FA-48E5-910A-51770C8E608C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0ADB5CFC-D3FA-48E5-910A-51770C8E608C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/ApplicationInsightsTelemetryPublisher.cs b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/ApplicationInsightsTelemetryPublisher.cs deleted file mode 100644 index 303c8e31..00000000 --- a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/ApplicationInsightsTelemetryPublisher.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// -using Microsoft.ApplicationInsights; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.FeatureManagement.Telemetry.ApplicationInsights -{ - /// - /// Used to publish data from evaluation events to Application Insights - /// - public class ApplicationInsightsTelemetryPublisher : ITelemetryPublisher - { - private readonly string _eventName = "FeatureEvaluation"; - private readonly TelemetryClient _telemetryClient; - - public ApplicationInsightsTelemetryPublisher(TelemetryClient telemetryClient) - { - _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - } - - /// - /// Publishes a custom event to Application Insights using data from the given evaluation event. - /// - /// The event to publish. - /// A cancellation token. - /// Returns a ValueTask that represents the asynchronous operation - public ValueTask PublishEvent(EvaluationEvent evaluationEvent, CancellationToken cancellationToken) - { - ValidateEvent(evaluationEvent); - - FeatureDefinition featureDefinition = evaluationEvent.FeatureDefinition; - - Dictionary properties = new Dictionary() - { - { "FeatureName", featureDefinition.Name }, - { "IsEnabled", evaluationEvent.IsEnabled.ToString() }, - { "Variant", evaluationEvent.Variant?.Name } - }; - - if (featureDefinition.ETag != null) - { - properties.Add("ETag", featureDefinition.ETag); - } - - if (featureDefinition.Label != null) - { - properties.Add("Label", featureDefinition.Label); - } - - if (featureDefinition.Tags != null) - { - foreach (KeyValuePair kvp in featureDefinition.Tags) - { - properties["Tags." + kvp.Key] = kvp.Value; - } - } - - _telemetryClient.TrackEvent(_eventName, properties); - - - return new ValueTask(); - } - - private void ValidateEvent(EvaluationEvent evaluationEvent) - { - if (evaluationEvent == null) - { - throw new ArgumentNullException(nameof(evaluationEvent)); - } - - if (evaluationEvent.FeatureDefinition == null) - { - throw new ArgumentNullException(nameof(evaluationEvent.FeatureDefinition)); - } - } - } -} \ No newline at end of file diff --git a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj deleted file mode 100644 index 213559f5..00000000 --- a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - netstandard2.0;netcoreapp3.1;net5.0;net6.0 - true - false - ..\..\build\Microsoft.FeatureManagement.snk - - - - - - - - - - - diff --git a/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj b/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj index 91945600..cbeed357 100644 --- a/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj +++ b/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj @@ -9,7 +9,6 @@ - From ac3630ca07084f1ed90b33ff21660265d7f41aab Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Fri, 29 Sep 2023 18:09:25 -0700 Subject: [PATCH 24/40] Converts some variables to inline --- src/Microsoft.FeatureManagement/FeatureManager.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index 50045d78..7a082c0d 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -660,14 +660,11 @@ private Variant GetVariantFromVariantDefinition(VariantDefinition variantDefinit IConfigurationSection variantConfiguration = null; - bool configValueSet = variantDefinition.ConfigurationValue.Exists(); - bool configReferenceSet = !string.IsNullOrEmpty(variantDefinition.ConfigurationReference); - - if (configValueSet) + if (variantDefinition.ConfigurationValue.Exists()) { variantConfiguration = variantDefinition.ConfigurationValue; } - else if (configReferenceSet) + else if (!string.IsNullOrEmpty(variantDefinition.ConfigurationReference)) { if (Configuration == null) { From 03881340b5f9a59c6a3148310e27e767b664f663 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Mon, 2 Oct 2023 10:18:00 -0700 Subject: [PATCH 25/40] Resolving misc. comments --- .../Program.cs | 41 ---------- .../ConfigurationFeatureDefinitionProvider.cs | 13 ++-- .../FeatureDefinition.cs | 2 +- .../FeatureManagementBuilder.cs | 10 +-- .../FeatureManagementOptions.cs | 6 +- .../FeatureManager.cs | 78 +++++++++---------- .../ServiceCollectionExtensions.cs | 5 +- .../Tests.FeatureManagement/appsettings.json | 8 +- 8 files changed, 63 insertions(+), 100 deletions(-) delete mode 100644 examples/EvaluationDataToApplicationInsights/Program.cs diff --git a/examples/EvaluationDataToApplicationInsights/Program.cs b/examples/EvaluationDataToApplicationInsights/Program.cs deleted file mode 100644 index ea6523ec..00000000 --- a/examples/EvaluationDataToApplicationInsights/Program.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Microsoft.FeatureManagement.Telemetry.ApplicationInsights; -using EvaluationDataToApplicationInsights.Middlewares; -using Microsoft.FeatureManagement; -using Microsoft.FeatureManagement.FeatureFilters; -using EvaluationDataToApplicationInsights; - -var builder = WebApplication.CreateBuilder(args); - -// Add services to the container. -builder.Services.AddRazorPages(); -builder.Services.AddApplicationInsightsTelemetry(); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); -builder.Services.AddFeatureManagement() - .AddFeatureFilter() - .AddTelemetryPublisher(); - -var app = builder.Build(); - -// Configure the HTTP request pipeline. -if (!app.Environment.IsDevelopment()) -{ - app.UseExceptionHandler("/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); -} - -app.UseHttpsRedirection(); -app.UseStaticFiles(); - -// -// App Insights User Tagging -app.UseMetricsMiddleware(); - -app.UseRouting(); - -app.UseAuthorization(); - -app.MapRazorPages(); - -app.Run(); diff --git a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs index fd847efb..dbb72abc 100644 --- a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs +++ b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs @@ -138,8 +138,8 @@ We support RequirementType requirementType = RequirementType.Any; string label = null; string eTag = null; - bool enableTelemetry = false; - IReadOnlyDictionary tags = null; + bool telemetryEnabled = false; + Dictionary tags = new Dictionary(); FeatureStatus featureStatus = FeatureStatus.Conditional; @@ -288,12 +288,15 @@ We support IConfigurationSection tagsSection = configurationSection.GetSection("Tags"); if (tagsSection.Exists()) { - tags = tagsSection.GetChildren().ToDictionary(s => s.Key, s => s.Value); + foreach (IConfigurationSection tag in tagsSection.GetChildren()) + { + tags.Add(tag.Key, tag.Value); + } } label = configurationSection["Label"]; eTag = configurationSection["ETag"]; - enableTelemetry = configurationSection.GetValue("EnableTelemetry"); + telemetryEnabled = configurationSection.GetValue("TelemetryEnabled"); } return new FeatureDefinition() @@ -306,7 +309,7 @@ We support Variants = variants, Label = label, ETag = eTag, - EnableTelemetry = enableTelemetry, + TelemetryEnabled = telemetryEnabled, Tags = tags }; } diff --git a/src/Microsoft.FeatureManagement/FeatureDefinition.cs b/src/Microsoft.FeatureManagement/FeatureDefinition.cs index 1d521d04..daf398ce 100644 --- a/src/Microsoft.FeatureManagement/FeatureDefinition.cs +++ b/src/Microsoft.FeatureManagement/FeatureDefinition.cs @@ -63,6 +63,6 @@ public class FeatureDefinition /// /// A flag to enable or disable sending telemetry events to the registered . /// - public bool EnableTelemetry { get; set; } + public bool TelemetryEnabled { get; set; } } } diff --git a/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs b/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs index 3f3a1cf8..2eca4994 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs @@ -53,21 +53,21 @@ public IFeatureManagementBuilder AddSessionManager() where T : ISessionManage public IFeatureManagementBuilder AddTelemetryPublisher() where T : ITelemetryPublisher { - AddTelemetryPublisher(sp => ActivatorUtilities.CreateInstance(sp, typeof(T)) as ITelemetryPublisher); + AddTelemetryPublisher(sp => ActivatorUtilities.CreateInstance(sp, typeof(T)) as ITelemetryPublisher); return this; } - private IFeatureManagementBuilder AddTelemetryPublisher(Func factory) where T : ITelemetryPublisher + private IFeatureManagementBuilder AddTelemetryPublisher(Func factory) { Services.Configure(options => { - if (options.telemetryPublisherFactories == null) + if (options.TelemetryPublisherFactories == null) { - options.telemetryPublisherFactories = new List>(); + options.TelemetryPublisherFactories = new List>(); } - options.telemetryPublisherFactories.Add(factory); + options.TelemetryPublisherFactories.Add(factory); }); return this; diff --git a/src/Microsoft.FeatureManagement/FeatureManagementOptions.cs b/src/Microsoft.FeatureManagement/FeatureManagementOptions.cs index 8a9e8e88..0e2c8eaf 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagementOptions.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagementOptions.cs @@ -27,6 +27,10 @@ public class FeatureManagementOptions /// public bool IgnoreMissingFeatures { get; set; } = true; - internal ICollection> telemetryPublisherFactories; + /// + /// Holds a collection of factories that can be used to create instances. + /// This avoids the need to add the publishers to the service collection. + /// + internal ICollection> TelemetryPublisherFactories { get; set; } } } diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index 7a082c0d..d4abbbed 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -63,7 +63,7 @@ public FeatureManager( _parametersCache = new MemoryCache(new MemoryCacheOptions()); } - public ICollection TelemetryPublishers { get; init; } + public IEnumerable TelemetryPublishers { get; init; } public IConfiguration Configuration { get; init; } @@ -91,52 +91,52 @@ public Task IsEnabledAsync(string feature, TContext appContext, private async Task IsEnabledWithVariantsAsync(string feature, TContext appContext, bool useAppContext, CancellationToken cancellationToken) { - bool isFeatureEnabled = await IsEnabledAsync(feature, appContext, useAppContext, cancellationToken).ConfigureAwait(false); + bool isFeatureEnabled = false; - FeatureDefinition featureDefinition = await _featureDefinitionProvider.GetFeatureDefinitionAsync(feature).ConfigureAwait(false); - - if (featureDefinition == null || featureDefinition.Status == FeatureStatus.Disabled) - { - isFeatureEnabled = false; - } + FeatureDefinition featureDefinition = await GetFeatureDefinition(feature).ConfigureAwait(false); VariantDefinition variantDefinition = null; - if (featureDefinition != null && (featureDefinition.Variants?.Any() ?? false) && featureDefinition.Allocation != null) + if (featureDefinition != null) { - if (!isFeatureEnabled) - { - variantDefinition = featureDefinition.Variants.FirstOrDefault((variant) => variant.Name == featureDefinition.Allocation.DefaultWhenDisabled); - } - else - { - TargetingContext targetingContext; + isFeatureEnabled = await IsEnabledAsync(feature, appContext, useAppContext, featureDefinition, cancellationToken).ConfigureAwait(false); - if (useAppContext) + if (featureDefinition.Variants != null && featureDefinition.Variants.Any() && featureDefinition.Allocation != null) + { + if (!isFeatureEnabled) { - targetingContext = appContext as TargetingContext; + variantDefinition = featureDefinition.Variants.FirstOrDefault((variant) => variant.Name == featureDefinition.Allocation.DefaultWhenDisabled); } else { - targetingContext = await ResolveTargetingContextAsync(cancellationToken).ConfigureAwait(false); - } + TargetingContext targetingContext; - variantDefinition = await GetAssignedVariantAsync( - featureDefinition, - targetingContext, - cancellationToken) - .ConfigureAwait(false); - } + if (useAppContext) + { + targetingContext = appContext as TargetingContext; + } + else + { + targetingContext = await ResolveTargetingContextAsync(cancellationToken).ConfigureAwait(false); + } - if (variantDefinition != null && featureDefinition.Status != FeatureStatus.Disabled) - { - if (variantDefinition.StatusOverride == StatusOverride.Enabled) - { - isFeatureEnabled = true; + variantDefinition = await GetAssignedVariantAsync( + featureDefinition, + targetingContext, + cancellationToken) + .ConfigureAwait(false); } - else if (variantDefinition.StatusOverride == StatusOverride.Disabled) + + if (variantDefinition != null && featureDefinition.Status != FeatureStatus.Disabled) { - isFeatureEnabled = false; + if (variantDefinition.StatusOverride == StatusOverride.Enabled) + { + isFeatureEnabled = true; + } + else if (variantDefinition.StatusOverride == StatusOverride.Disabled) + { + isFeatureEnabled = false; + } } } } @@ -150,7 +150,7 @@ private async Task IsEnabledWithVariantsAsync(string feature, TC { FeatureDefinition = featureDefinition, IsEnabled = isFeatureEnabled, - Variant = GetVariantFromVariantDefinition(variantDefinition) + Variant = variantDefinition != null ? GetVariantFromVariantDefinition(variantDefinition) : null }); return isFeatureEnabled; @@ -176,7 +176,7 @@ public void Dispose() _parametersCache.Dispose(); } - private async Task IsEnabledAsync(string feature, TContext appContext, bool useAppContext, CancellationToken cancellationToken) + private async Task IsEnabledAsync(string feature, TContext appContext, bool useAppContext, FeatureDefinition featureDefinition, CancellationToken cancellationToken) { foreach (ISessionManager sessionManager in _sessionManagers) { @@ -188,8 +188,6 @@ private async Task IsEnabledAsync(string feature, TContext appCo } } - FeatureDefinition featureDefinition = await GetFeatureDefinition(feature); - bool enabled; // @@ -338,7 +336,7 @@ private async ValueTask GetVariantAsync(string feature, TargetingContex VariantDefinition variantDefinition = null; - bool isFeatureEnabled = await IsEnabledAsync(feature, context, useContext, cancellationToken).ConfigureAwait(false); + bool isFeatureEnabled = await IsEnabledAsync(feature, context, useContext, featureDefinition, cancellationToken).ConfigureAwait(false); if (!isFeatureEnabled) { @@ -354,7 +352,7 @@ private async ValueTask GetVariantAsync(string feature, TargetingContex variantDefinition = await GetAssignedVariantAsync(featureDefinition, context, cancellationToken).ConfigureAwait(false); } - Variant variant = GetVariantFromVariantDefinition(variantDefinition); + Variant variant = variantDefinition != null ? GetVariantFromVariantDefinition(variantDefinition) : null; PublishTelemetry(new EvaluationEvent { @@ -633,7 +631,7 @@ private ContextualFeatureFilterEvaluator GetContextualFeatureFilter(string filte private async void PublishTelemetry(EvaluationEvent evaluationEvent) { - if (evaluationEvent.FeatureDefinition.EnableTelemetry) + if (evaluationEvent.FeatureDefinition.TelemetryEnabled) { if (!TelemetryPublishers.Any()) { diff --git a/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs b/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs index 41c160d3..0a7fcdb2 100644 --- a/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs +++ b/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs @@ -6,7 +6,6 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Microsoft.FeatureManagement.Telemetry; using Microsoft.FeatureManagement.FeatureFilters; using System; using System.Collections.Generic; @@ -42,10 +41,10 @@ public static IFeatureManagementBuilder AddFeatureManagement(this IServiceCollec { Configuration = sp.GetService(), TargetingContextAccessor = sp.GetService(), - TelemetryPublishers = sp.GetService>()?.Value.telemetryPublisherFactories? + TelemetryPublishers = sp.GetService>()?.Value.TelemetryPublisherFactories? .Select(factory => factory(sp)) .ToList() - });; + }); services.TryAddSingleton(sp => sp.GetRequiredService()); diff --git a/tests/Tests.FeatureManagement/appsettings.json b/tests/Tests.FeatureManagement/appsettings.json index 096c6253..11010571 100644 --- a/tests/Tests.FeatureManagement/appsettings.json +++ b/tests/Tests.FeatureManagement/appsettings.json @@ -21,7 +21,7 @@ "OnTestFeature": true, "OffTestFeature": false, "AlwaysOnTestFeature": { - "EnableTelemetry": true, + "TelemetryEnabled": true, "EnabledFor": [ { "Name": "AlwaysOn" @@ -29,7 +29,7 @@ ] }, "OffTimeTestFeature": { - "EnableTelemetry": true, + "TelemetryEnabled": true, "EnabledFor": [ { "Name": "TimeWindow", @@ -228,7 +228,7 @@ }, "VariantFeatureStatusDisabled": { "Status": "Disabled", - "EnableTelemetry": true, + "TelemetryEnabled": true, "Allocation": { "DefaultWhenDisabled": "Small" }, @@ -245,7 +245,7 @@ ] }, "VariantFeatureDefaultEnabled": { - "EnableTelemetry": true, + "TelemetryEnabled": true, "Allocation": { "DefaultWhenEnabled": "Medium", "User": [ From 2ff83acd9f77593ce589bf3c0ea763d6852606d4 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Mon, 2 Oct 2023 10:49:15 -0700 Subject: [PATCH 26/40] Moves AddTelemetryPublisher to be an extension method --- .../FeatureManagementBuilder.cs | 22 ---------- .../FeatureManagementBuilderExtensions.cs | 40 +++++++++++++++++++ .../IFeatureManagementBuilder.cs | 6 --- 3 files changed, 40 insertions(+), 28 deletions(-) create mode 100644 src/Microsoft.FeatureManagement/FeatureManagementBuilderExtensions.cs diff --git a/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs b/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs index 2eca4994..c4aaceb1 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs @@ -50,27 +50,5 @@ public IFeatureManagementBuilder AddSessionManager() where T : ISessionManage return this; } - - public IFeatureManagementBuilder AddTelemetryPublisher() where T : ITelemetryPublisher - { - AddTelemetryPublisher(sp => ActivatorUtilities.CreateInstance(sp, typeof(T)) as ITelemetryPublisher); - - return this; - } - - private IFeatureManagementBuilder AddTelemetryPublisher(Func factory) - { - Services.Configure(options => - { - if (options.TelemetryPublisherFactories == null) - { - options.TelemetryPublisherFactories = new List>(); - } - - options.TelemetryPublisherFactories.Add(factory); - }); - - return this; - } } } diff --git a/src/Microsoft.FeatureManagement/FeatureManagementBuilderExtensions.cs b/src/Microsoft.FeatureManagement/FeatureManagementBuilderExtensions.cs new file mode 100644 index 00000000..72de9c40 --- /dev/null +++ b/src/Microsoft.FeatureManagement/FeatureManagementBuilderExtensions.cs @@ -0,0 +1,40 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.FeatureManagement.Telemetry; +using System; +using System.Collections.Generic; + +namespace Microsoft.FeatureManagement +{ + /// + /// Extensions used to add feature management functionality. + /// + public static class FeatureManagementBuilderExtensions + { + /// + /// Adds a telemetry publisher to the feature management system. + /// + /// The used to customize feature management functionality. + /// A that can be used to customize feature management functionality. + public static IFeatureManagementBuilder AddTelemetryPublisher(this IFeatureManagementBuilder builder) where T : ITelemetryPublisher + { + builder.AddTelemetryPublisher(sp => ActivatorUtilities.CreateInstance(sp, typeof(T)) as ITelemetryPublisher); + + return builder; + } + + private static IFeatureManagementBuilder AddTelemetryPublisher(this IFeatureManagementBuilder builder, Func factory) + { + builder.Services.Configure(options => + { + if (options.TelemetryPublisherFactories == null) + { + options.TelemetryPublisherFactories = new List>(); + } + + options.TelemetryPublisherFactories.Add(factory); + }); + + return builder; + } + } +} diff --git a/src/Microsoft.FeatureManagement/IFeatureManagementBuilder.cs b/src/Microsoft.FeatureManagement/IFeatureManagementBuilder.cs index ded16b23..c4adf31d 100644 --- a/src/Microsoft.FeatureManagement/IFeatureManagementBuilder.cs +++ b/src/Microsoft.FeatureManagement/IFeatureManagementBuilder.cs @@ -31,11 +31,5 @@ public interface IFeatureManagementBuilder /// An implementation of /// The feature management builder. IFeatureManagementBuilder AddSessionManager() where T : ISessionManager; - - /// - /// Adds an event publisher that publishes feature evaluation events. - /// - /// The that the publisher was added to. - IFeatureManagementBuilder AddTelemetryPublisher() where T : ITelemetryPublisher; } } From 4a5b135535413eedacc3836da87a8f6e2c9a19c5 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Tue, 3 Oct 2023 13:40:47 -0700 Subject: [PATCH 27/40] Removing IVariantFeatureManager GetFeatureNamesAsync definition, as it should it it's own PR --- .../FeatureManager.cs | 16 ++++--------- .../FeatureManagerSnapshot.cs | 24 +++++++------------ .../IVariantFeatureManager.cs | 10 ++------ 3 files changed, 15 insertions(+), 35 deletions(-) diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index d4abbbed..17941274 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // using Microsoft.Extensions.Caching.Memory; @@ -15,7 +15,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using System.Runtime.CompilerServices; namespace Microsoft.FeatureManagement { @@ -156,18 +155,11 @@ private async Task IsEnabledWithVariantsAsync(string feature, TC return isFeatureEnabled; } - public IAsyncEnumerable GetFeatureNamesAsync() + public async IAsyncEnumerable GetFeatureNamesAsync() { - return GetFeatureNamesAsync(CancellationToken.None); - } - - public async IAsyncEnumerable GetFeatureNamesAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - await foreach (FeatureDefinition featureDefinition in _featureDefinitionProvider.GetAllFeatureDefinitionsAsync().ConfigureAwait(false)) + await foreach (FeatureDefinition featureDefintion in _featureDefinitionProvider.GetAllFeatureDefinitionsAsync().ConfigureAwait(false)) { - cancellationToken.ThrowIfCancellationRequested(); - - yield return featureDefinition.Name; + yield return featureDefintion.Name; } } diff --git a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs index 35c4b0ec..b32030d4 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs @@ -1,11 +1,10 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // using Microsoft.FeatureManagement.FeatureFilters; using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -16,28 +15,23 @@ namespace Microsoft.FeatureManagement /// class FeatureManagerSnapshot : IFeatureManagerSnapshot, IVariantFeatureManagerSnapshot { - private readonly IVariantFeatureManager _featureManager; + private readonly FeatureManager _featureManager; private readonly ConcurrentDictionary> _flagCache = new ConcurrentDictionary>(); private readonly ConcurrentDictionary _variantCache = new ConcurrentDictionary(); private IEnumerable _featureNames; - public FeatureManagerSnapshot(IVariantFeatureManager featureManager) + public FeatureManagerSnapshot(FeatureManager featureManager) { _featureManager = featureManager ?? throw new ArgumentNullException(nameof(featureManager)); } - public IAsyncEnumerable GetFeatureNamesAsync() - { - return GetFeatureNamesAsync(CancellationToken.None); - } - - public async IAsyncEnumerable GetFeatureNamesAsync([EnumeratorCancellation] CancellationToken cancellationToken) + public async IAsyncEnumerable GetFeatureNamesAsync() { if (_featureNames == null) { var featureNames = new List(); - await foreach (string featureName in _featureManager.GetFeatureNamesAsync(cancellationToken).ConfigureAwait(false)) + await foreach (string featureName in _featureManager.GetFeatureNamesAsync().ConfigureAwait(false)) { featureNames.Add(featureName); } @@ -55,28 +49,28 @@ public Task IsEnabledAsync(string feature) { return _flagCache.GetOrAdd( feature, - (key) => _featureManager.IsEnabledAsync(key, CancellationToken.None)); + (key) => _featureManager.IsEnabledAsync(key)); } public Task IsEnabledAsync(string feature, TContext context) { return _flagCache.GetOrAdd( feature, - (key) => _featureManager.IsEnabledAsync(key, context, CancellationToken.None)); + (key) => _featureManager.IsEnabledAsync(key, context)); } public Task IsEnabledAsync(string feature, CancellationToken cancellationToken) { return _flagCache.GetOrAdd( feature, - (key) => _featureManager.IsEnabledAsync(key, cancellationToken)); + (key) => _featureManager.IsEnabledAsync(key)); } public Task IsEnabledAsync(string feature, TContext context, CancellationToken cancellationToken) { return _flagCache.GetOrAdd( feature, - (key) => _featureManager.IsEnabledAsync(key, context, cancellationToken)); + (key) => _featureManager.IsEnabledAsync(key, context)); } public async ValueTask GetVariantAsync(string feature, CancellationToken cancellationToken) diff --git a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs index 87594ddf..e0276f51 100644 --- a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs +++ b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // using System.Threading.Tasks; @@ -13,12 +13,6 @@ namespace Microsoft.FeatureManagement /// public interface IVariantFeatureManager { - /// - /// Retrieves a list of feature names registered in the feature manager. - /// - /// An enumerator which provides asynchronous iteration over the feature names registered in the feature manager. - IAsyncEnumerable GetFeatureNamesAsync(CancellationToken cancellationToken); - /// /// Checks whether a given feature is enabled. /// @@ -37,7 +31,7 @@ public interface IVariantFeatureManager Task IsEnabledAsync(string feature, TContext context, CancellationToken cancellationToken); /// - /// Gets the assigned variant for a specific feature. + /// Gets the assigned variant for a specfic feature. /// /// The name of the feature to evaluate. /// The cancellation token to cancel the operation. From b6e8df0c72df561cb339cd82a30e75c415207eed Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Tue, 3 Oct 2023 13:42:11 -0700 Subject: [PATCH 28/40] Remove unused dependency --- src/Microsoft.FeatureManagement/IVariantFeatureManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs index e0276f51..43525d4a 100644 --- a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs +++ b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using System.Threading; using Microsoft.FeatureManagement.FeatureFilters; -using System.Collections.Generic; namespace Microsoft.FeatureManagement { From f70513ff1d25896c26b02a75ebaf890ecc69122c Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Tue, 3 Oct 2023 16:48:12 -0700 Subject: [PATCH 29/40] Resolving comments --- .../FeatureManagementBuilder.cs | 3 +-- .../FeatureManager.cs | 19 +++++++++---------- .../IFeatureDefinitionProvider.cs | 3 +-- .../IFeatureManagementBuilder.cs | 3 +-- .../IVariantFeatureManager.cs | 2 +- .../Microsoft.FeatureManagement.csproj | 3 +-- .../Telemetry/ITelemetryPublisher.cs | 3 +-- 7 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs b/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs index c4aaceb1..02f088c9 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs @@ -1,11 +1,10 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.DependencyInjection; -using Microsoft.FeatureManagement.Telemetry; namespace Microsoft.FeatureManagement { diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index 17941274..b51a9ec1 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -98,7 +98,7 @@ private async Task IsEnabledWithVariantsAsync(string feature, TC if (featureDefinition != null) { - isFeatureEnabled = await IsEnabledAsync(feature, appContext, useAppContext, featureDefinition, cancellationToken).ConfigureAwait(false); + isFeatureEnabled = await IsEnabledAsync(featureDefinition, appContext, useAppContext, cancellationToken).ConfigureAwait(false); if (featureDefinition.Variants != null && featureDefinition.Variants.Any() && featureDefinition.Allocation != null) { @@ -168,11 +168,11 @@ public void Dispose() _parametersCache.Dispose(); } - private async Task IsEnabledAsync(string feature, TContext appContext, bool useAppContext, FeatureDefinition featureDefinition, CancellationToken cancellationToken) + private async Task IsEnabledAsync(FeatureDefinition featureDefinition, TContext appContext, bool useAppContext, CancellationToken cancellationToken) { foreach (ISessionManager sessionManager in _sessionManagers) { - bool? readSessionResult = await sessionManager.GetAsync(feature).ConfigureAwait(false); + bool? readSessionResult = await sessionManager.GetAsync(featureDefinition.Name).ConfigureAwait(false); if (readSessionResult.HasValue) { @@ -183,9 +183,8 @@ private async Task IsEnabledAsync(string feature, TContext appCo bool enabled; // - // Treat a null, empty, or status disabled feature as disabled - if (featureDefinition == null || - featureDefinition.EnabledFor == null || + // Treat an empty or status disabled feature as disabled + if (featureDefinition.EnabledFor == null || !featureDefinition.EnabledFor.Any() || featureDefinition.Status == FeatureStatus.Disabled) { @@ -238,7 +237,7 @@ private async Task IsEnabledAsync(string feature, TContext appCo if (filter == null) { - string errorMessage = $"The feature filter '{featureFilterConfiguration.Name}' specified for feature '{feature}' was not found."; + string errorMessage = $"The feature filter '{featureFilterConfiguration.Name}' specified for feature '{featureDefinition.Name}' was not found."; if (!_options.IgnoreMissingFeatureFilters) { @@ -252,7 +251,7 @@ private async Task IsEnabledAsync(string feature, TContext appCo var context = new FeatureFilterEvaluationContext() { - FeatureName = feature, + FeatureName = featureDefinition.Name, Parameters = featureFilterConfiguration.Parameters }; @@ -321,14 +320,14 @@ private async ValueTask GetVariantAsync(string feature, TargetingContex { FeatureDefinition featureDefinition = await GetFeatureDefinition(feature).ConfigureAwait(false); - if (featureDefinition?.Allocation == null || (!featureDefinition.Variants?.Any() ?? false)) + if (featureDefinition == null || featureDefinition.Allocation == null || (!featureDefinition.Variants?.Any() ?? false)) { return null; } VariantDefinition variantDefinition = null; - bool isFeatureEnabled = await IsEnabledAsync(feature, context, useContext, featureDefinition, cancellationToken).ConfigureAwait(false); + bool isFeatureEnabled = await IsEnabledAsync(featureDefinition, context, useContext, cancellationToken).ConfigureAwait(false); if (!isFeatureEnabled) { diff --git a/src/Microsoft.FeatureManagement/IFeatureDefinitionProvider.cs b/src/Microsoft.FeatureManagement/IFeatureDefinitionProvider.cs index 36ac2e76..caca909e 100644 --- a/src/Microsoft.FeatureManagement/IFeatureDefinitionProvider.cs +++ b/src/Microsoft.FeatureManagement/IFeatureDefinitionProvider.cs @@ -1,8 +1,7 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // using System.Collections.Generic; -using System.Threading; using System.Threading.Tasks; namespace Microsoft.FeatureManagement diff --git a/src/Microsoft.FeatureManagement/IFeatureManagementBuilder.cs b/src/Microsoft.FeatureManagement/IFeatureManagementBuilder.cs index c4adf31d..ba84e71e 100644 --- a/src/Microsoft.FeatureManagement/IFeatureManagementBuilder.cs +++ b/src/Microsoft.FeatureManagement/IFeatureManagementBuilder.cs @@ -1,8 +1,7 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // using Microsoft.Extensions.DependencyInjection; -using Microsoft.FeatureManagement.Telemetry; namespace Microsoft.FeatureManagement { diff --git a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs index 43525d4a..10395327 100644 --- a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs +++ b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // using System.Threading.Tasks; diff --git a/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj b/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj index c83e0832..b2320f47 100644 --- a/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj +++ b/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj @@ -1,4 +1,4 @@ - + @@ -37,7 +37,6 @@ - diff --git a/src/Microsoft.FeatureManagement/Telemetry/ITelemetryPublisher.cs b/src/Microsoft.FeatureManagement/Telemetry/ITelemetryPublisher.cs index 8728b1d9..5eb83e68 100644 --- a/src/Microsoft.FeatureManagement/Telemetry/ITelemetryPublisher.cs +++ b/src/Microsoft.FeatureManagement/Telemetry/ITelemetryPublisher.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // using System.Threading; @@ -19,5 +19,4 @@ public interface ITelemetryPublisher /// ValueTask public ValueTask PublishEvent(EvaluationEvent evaluationEvent, CancellationToken cancellationToken); } - } From bf151acf2cadc5372c70b511626e2bb388325d38 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Wed, 4 Oct 2023 08:58:22 -0700 Subject: [PATCH 30/40] Remove invisible character changes --- src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs | 2 +- src/Microsoft.FeatureManagement/IFeatureDefinitionProvider.cs | 2 +- src/Microsoft.FeatureManagement/IFeatureManagementBuilder.cs | 2 +- src/Microsoft.FeatureManagement/IVariantFeatureManager.cs | 2 +- .../Microsoft.FeatureManagement.csproj | 3 ++- .../Telemetry/ITelemetryPublisher.cs | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs b/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs index 02f088c9..a433b2bc 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // using System; diff --git a/src/Microsoft.FeatureManagement/IFeatureDefinitionProvider.cs b/src/Microsoft.FeatureManagement/IFeatureDefinitionProvider.cs index caca909e..bc4895b9 100644 --- a/src/Microsoft.FeatureManagement/IFeatureDefinitionProvider.cs +++ b/src/Microsoft.FeatureManagement/IFeatureDefinitionProvider.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // using System.Collections.Generic; diff --git a/src/Microsoft.FeatureManagement/IFeatureManagementBuilder.cs b/src/Microsoft.FeatureManagement/IFeatureManagementBuilder.cs index ba84e71e..6365c098 100644 --- a/src/Microsoft.FeatureManagement/IFeatureManagementBuilder.cs +++ b/src/Microsoft.FeatureManagement/IFeatureManagementBuilder.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // using Microsoft.Extensions.DependencyInjection; diff --git a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs index 10395327..43525d4a 100644 --- a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs +++ b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // using System.Threading.Tasks; diff --git a/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj b/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj index b2320f47..c83e0832 100644 --- a/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj +++ b/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj @@ -1,4 +1,4 @@ - + @@ -37,6 +37,7 @@ + diff --git a/src/Microsoft.FeatureManagement/Telemetry/ITelemetryPublisher.cs b/src/Microsoft.FeatureManagement/Telemetry/ITelemetryPublisher.cs index 5eb83e68..89f7a931 100644 --- a/src/Microsoft.FeatureManagement/Telemetry/ITelemetryPublisher.cs +++ b/src/Microsoft.FeatureManagement/Telemetry/ITelemetryPublisher.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // using System.Threading; From edbb8d5be0cfe243dd1d82fe0819f710ddb7118e Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Wed, 4 Oct 2023 09:01:32 -0700 Subject: [PATCH 31/40] Remove Dependency Injection package --- .../Microsoft.FeatureManagement.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj b/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj index c83e0832..383956c9 100644 --- a/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj +++ b/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj @@ -37,7 +37,6 @@ - From e94ad7ffe3625950714a986c3b02d5c295ee438d Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Wed, 4 Oct 2023 09:27:08 -0700 Subject: [PATCH 32/40] Update src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs --- src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs index b32030d4..9e20e8c6 100644 --- a/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs +++ b/src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // using Microsoft.FeatureManagement.FeatureFilters; From 8b0f3ba05ad9623cf2a93c2441886d9ac214d78f Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Wed, 4 Oct 2023 09:27:42 -0700 Subject: [PATCH 33/40] Update src/Microsoft.FeatureManagement/IVariantFeatureManager.cs --- src/Microsoft.FeatureManagement/IVariantFeatureManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs index 43525d4a..10395327 100644 --- a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs +++ b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // using System.Threading.Tasks; From 5df39c8529afd70d7e351e47e39399e0cfdba49a Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Wed, 4 Oct 2023 09:47:05 -0700 Subject: [PATCH 34/40] Persists cancellation token to PublishEvent and adds null check for null publisher collection --- .../FeatureManager.cs | 10 ++++---- .../FeatureManagement.cs | 24 +++++++++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index b51a9ec1..dec16138 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -150,7 +150,7 @@ private async Task IsEnabledWithVariantsAsync(string feature, TC FeatureDefinition = featureDefinition, IsEnabled = isFeatureEnabled, Variant = variantDefinition != null ? GetVariantFromVariantDefinition(variantDefinition) : null - }); + }, cancellationToken); return isFeatureEnabled; } @@ -350,7 +350,7 @@ private async ValueTask GetVariantAsync(string feature, TargetingContex FeatureDefinition = featureDefinition, IsEnabled = isFeatureEnabled, Variant = variant - }); + }, cancellationToken); return variant; } @@ -620,11 +620,11 @@ private ContextualFeatureFilterEvaluator GetContextualFeatureFilter(string filte return filter; } - private async void PublishTelemetry(EvaluationEvent evaluationEvent) + private async void PublishTelemetry(EvaluationEvent evaluationEvent, CancellationToken cancellationToken) { if (evaluationEvent.FeatureDefinition.TelemetryEnabled) { - if (!TelemetryPublishers.Any()) + if (TelemetryPublishers == null || !TelemetryPublishers.Any()) { _logger.LogWarning("The feature declaration enabled telemetry but no telemetry publisher was registered."); } @@ -634,7 +634,7 @@ private async void PublishTelemetry(EvaluationEvent evaluationEvent) { await telemetryPublisher.PublishEvent( evaluationEvent, - CancellationToken.None); + cancellationToken); } } } diff --git a/tests/Tests.FeatureManagement/FeatureManagement.cs b/tests/Tests.FeatureManagement/FeatureManagement.cs index 26957a1f..7a01fed6 100644 --- a/tests/Tests.FeatureManagement/FeatureManagement.cs +++ b/tests/Tests.FeatureManagement/FeatureManagement.cs @@ -1036,6 +1036,30 @@ public async Task TelemetryPublishing() Assert.Equal(variantResult.Name, testPublisher.evaluationEventCache.Variant.Name); } + [Fact] + public async Task TelemetryPublishingNullPublisher() + { + IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); + + var services = new ServiceCollection(); + + services + .AddSingleton(config) + .AddFeatureManagement() + .AddFeatureFilter(); + + ServiceProvider serviceProvider = services.BuildServiceProvider(); + + FeatureManager featureManager = (FeatureManager)serviceProvider.GetRequiredService(); + + // Test telemetry enabled feature with no telemetry publisher + string onFeature = "AlwaysOnTestFeature"; + + bool result = await featureManager.IsEnabledAsync(onFeature, CancellationToken.None); + + Assert.True(result); + } + public async Task UsesVariants() { IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); From 982f3130603fead31dcb57b0b69a24bb529ba727 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Wed, 4 Oct 2023 12:03:14 -0700 Subject: [PATCH 35/40] Adds TargetingContext to the EvaluationResult --- src/Microsoft.FeatureManagement/FeatureManager.cs | 5 +++-- .../Telemetry/EvaluationEvent.cs | 9 ++++++++- tests/Tests.FeatureManagement/FeatureManagement.cs | 14 ++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index dec16138..82aa1d14 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -95,6 +95,7 @@ private async Task IsEnabledWithVariantsAsync(string feature, TC FeatureDefinition featureDefinition = await GetFeatureDefinition(feature).ConfigureAwait(false); VariantDefinition variantDefinition = null; + TargetingContext targetingContext = null; if (featureDefinition != null) { @@ -108,8 +109,6 @@ private async Task IsEnabledWithVariantsAsync(string feature, TC } else { - TargetingContext targetingContext; - if (useAppContext) { targetingContext = appContext as TargetingContext; @@ -148,6 +147,7 @@ private async Task IsEnabledWithVariantsAsync(string feature, TC PublishTelemetry(new EvaluationEvent { FeatureDefinition = featureDefinition, + TargetingContext = targetingContext, IsEnabled = isFeatureEnabled, Variant = variantDefinition != null ? GetVariantFromVariantDefinition(variantDefinition) : null }, cancellationToken); @@ -348,6 +348,7 @@ private async ValueTask GetVariantAsync(string feature, TargetingContex PublishTelemetry(new EvaluationEvent { FeatureDefinition = featureDefinition, + TargetingContext = context, IsEnabled = isFeatureEnabled, Variant = variant }, cancellationToken); diff --git a/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs b/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs index 8731776e..1c254c2a 100644 --- a/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs +++ b/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // +using Microsoft.FeatureManagement.FeatureFilters; + namespace Microsoft.FeatureManagement.Telemetry { /// @@ -12,7 +14,12 @@ public class EvaluationEvent /// The definition of the feature that was evaluated. /// public FeatureDefinition FeatureDefinition { get; set; } - + + /// + /// The targeting context used for the evaluation. + /// + public ITargetingContext TargetingContext { get; set; } + /// /// The enabled state of the feature after evaluation. /// diff --git a/tests/Tests.FeatureManagement/FeatureManagement.cs b/tests/Tests.FeatureManagement/FeatureManagement.cs index 7a01fed6..805aecb7 100644 --- a/tests/Tests.FeatureManagement/FeatureManagement.cs +++ b/tests/Tests.FeatureManagement/FeatureManagement.cs @@ -1034,6 +1034,20 @@ public async Task TelemetryPublishing() Assert.False(testPublisher.evaluationEventCache.IsEnabled); Assert.Equal(variantFeatureStatusDisabled, testPublisher.evaluationEventCache.FeatureDefinition.Name); Assert.Equal(variantResult.Name, testPublisher.evaluationEventCache.Variant.Name); + + TargetingContext targetingContext = new TargetingContext + { + UserId = "Jeff" + }; + variantResult = await featureManager.GetVariantAsync( + variantDefaultEnabledFeature, + targetingContext, + CancellationToken.None); + + Assert.True(testPublisher.evaluationEventCache.IsEnabled); + Assert.Equal(testPublisher.evaluationEventCache.TargetingContext.UserId, targetingContext.UserId); + Assert.Equal(variantDefaultEnabledFeature, testPublisher.evaluationEventCache.FeatureDefinition.Name); + Assert.Equal(variantResult.Name, testPublisher.evaluationEventCache.Variant.Name); } [Fact] From 505d834889543de3c19b13ce4fe50898b5599439 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Thu, 5 Oct 2023 10:26:16 -0700 Subject: [PATCH 36/40] Resolving comments --- .../FeatureDefinition.cs | 8 +- .../FeatureManager.cs | 77 ++++++++++--------- 2 files changed, 42 insertions(+), 43 deletions(-) diff --git a/src/Microsoft.FeatureManagement/FeatureDefinition.cs b/src/Microsoft.FeatureManagement/FeatureDefinition.cs index daf398ce..c158e5fb 100644 --- a/src/Microsoft.FeatureManagement/FeatureDefinition.cs +++ b/src/Microsoft.FeatureManagement/FeatureDefinition.cs @@ -44,19 +44,17 @@ public class FeatureDefinition public IEnumerable Variants { get; set; } = Enumerable.Empty(); /// - /// A value used to group feature flags. - /// A is used together with a to uniquely identify a feature. + /// Metadata that can be used to group feature flags. /// public string Label { get; set; } /// - /// An ETag indicating the state of a feature. This value is used to determine whether a feature has changed. + /// An ETag that is used to track when the feature definiton has changed. /// public string ETag { get; set; } /// - /// A dictionary of tags used to assign additional properties to a feature. - /// These can be used to indicate how a feature may be applied. + /// A dictionary of tags used to assign additional metadata to a feature. /// public IReadOnlyDictionary Tags { get; set; } diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index 82aa1d14..af460f0d 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -95,8 +95,18 @@ private async Task IsEnabledWithVariantsAsync(string feature, TC FeatureDefinition featureDefinition = await GetFeatureDefinition(feature).ConfigureAwait(false); VariantDefinition variantDefinition = null; + TargetingContext targetingContext = null; + if (useAppContext) + { + targetingContext = appContext as TargetingContext; + } + else + { + targetingContext = await ResolveTargetingContextAsync(cancellationToken).ConfigureAwait(false); + } + if (featureDefinition != null) { isFeatureEnabled = await IsEnabledAsync(featureDefinition, appContext, useAppContext, cancellationToken).ConfigureAwait(false); @@ -109,15 +119,6 @@ private async Task IsEnabledWithVariantsAsync(string feature, TC } else { - if (useAppContext) - { - targetingContext = appContext as TargetingContext; - } - else - { - targetingContext = await ResolveTargetingContextAsync(cancellationToken).ConfigureAwait(false); - } - variantDefinition = await GetAssignedVariantAsync( featureDefinition, targetingContext, @@ -144,13 +145,16 @@ private async Task IsEnabledWithVariantsAsync(string feature, TC await sessionManager.SetAsync(feature, isFeatureEnabled).ConfigureAwait(false); } - PublishTelemetry(new EvaluationEvent + if (featureDefinition.TelemetryEnabled) { - FeatureDefinition = featureDefinition, - TargetingContext = targetingContext, - IsEnabled = isFeatureEnabled, - Variant = variantDefinition != null ? GetVariantFromVariantDefinition(variantDefinition) : null - }, cancellationToken); + PublishTelemetry(new EvaluationEvent + { + FeatureDefinition = featureDefinition, + TargetingContext = targetingContext, + IsEnabled = isFeatureEnabled, + Variant = variantDefinition != null ? GetVariantFromVariantDefinition(variantDefinition) : null + }, cancellationToken); + } return isFeatureEnabled; } @@ -170,6 +174,8 @@ public void Dispose() private async Task IsEnabledAsync(FeatureDefinition featureDefinition, TContext appContext, bool useAppContext, CancellationToken cancellationToken) { + Debug.Assert(featureDefinition != null); + foreach (ISessionManager sessionManager in _sessionManagers) { bool? readSessionResult = await sessionManager.GetAsync(featureDefinition.Name).ConfigureAwait(false); @@ -345,13 +351,16 @@ private async ValueTask GetVariantAsync(string feature, TargetingContex Variant variant = variantDefinition != null ? GetVariantFromVariantDefinition(variantDefinition) : null; - PublishTelemetry(new EvaluationEvent + if (featureDefinition.TelemetryEnabled) { - FeatureDefinition = featureDefinition, - TargetingContext = context, - IsEnabled = isFeatureEnabled, - Variant = variant - }, cancellationToken); + PublishTelemetry(new EvaluationEvent + { + FeatureDefinition = featureDefinition, + TargetingContext = context, + IsEnabled = isFeatureEnabled, + Variant = variant + }, cancellationToken); + } return variant; } @@ -623,31 +632,23 @@ private ContextualFeatureFilterEvaluator GetContextualFeatureFilter(string filte private async void PublishTelemetry(EvaluationEvent evaluationEvent, CancellationToken cancellationToken) { - if (evaluationEvent.FeatureDefinition.TelemetryEnabled) + if (TelemetryPublishers == null || !TelemetryPublishers.Any()) + { + _logger.LogWarning("The feature declaration enabled telemetry but no telemetry publisher was registered."); + } + else { - if (TelemetryPublishers == null || !TelemetryPublishers.Any()) + foreach (ITelemetryPublisher telemetryPublisher in TelemetryPublishers) { - _logger.LogWarning("The feature declaration enabled telemetry but no telemetry publisher was registered."); - } - else - { - foreach (ITelemetryPublisher telemetryPublisher in TelemetryPublishers) - { - await telemetryPublisher.PublishEvent( - evaluationEvent, - cancellationToken); - } + await telemetryPublisher.PublishEvent( + evaluationEvent, + cancellationToken); } } } private Variant GetVariantFromVariantDefinition(VariantDefinition variantDefinition) { - if (variantDefinition == null) - { - return null; - } - IConfigurationSection variantConfiguration = null; if (variantDefinition.ConfigurationValue.Exists()) From fb055fcd47ebb41792b9e059fc1ffcdd21ae1ced Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Thu, 5 Oct 2023 13:25:52 -0700 Subject: [PATCH 37/40] Removes TargetingContext from EvaluationEvent for now --- .../FeatureManager.cs | 24 +++++++++---------- .../Telemetry/EvaluationEvent.cs | 5 ---- .../FeatureManagement.cs | 15 ------------ 3 files changed, 11 insertions(+), 33 deletions(-) diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index af460f0d..ccd5e77a 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -96,17 +96,6 @@ private async Task IsEnabledWithVariantsAsync(string feature, TC VariantDefinition variantDefinition = null; - TargetingContext targetingContext = null; - - if (useAppContext) - { - targetingContext = appContext as TargetingContext; - } - else - { - targetingContext = await ResolveTargetingContextAsync(cancellationToken).ConfigureAwait(false); - } - if (featureDefinition != null) { isFeatureEnabled = await IsEnabledAsync(featureDefinition, appContext, useAppContext, cancellationToken).ConfigureAwait(false); @@ -119,6 +108,17 @@ private async Task IsEnabledWithVariantsAsync(string feature, TC } else { + TargetingContext targetingContext; + + if (useAppContext) + { + targetingContext = appContext as TargetingContext; + } + else + { + targetingContext = await ResolveTargetingContextAsync(cancellationToken).ConfigureAwait(false); + } + variantDefinition = await GetAssignedVariantAsync( featureDefinition, targetingContext, @@ -150,7 +150,6 @@ private async Task IsEnabledWithVariantsAsync(string feature, TC PublishTelemetry(new EvaluationEvent { FeatureDefinition = featureDefinition, - TargetingContext = targetingContext, IsEnabled = isFeatureEnabled, Variant = variantDefinition != null ? GetVariantFromVariantDefinition(variantDefinition) : null }, cancellationToken); @@ -356,7 +355,6 @@ private async ValueTask GetVariantAsync(string feature, TargetingContex PublishTelemetry(new EvaluationEvent { FeatureDefinition = featureDefinition, - TargetingContext = context, IsEnabled = isFeatureEnabled, Variant = variant }, cancellationToken); diff --git a/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs b/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs index 1c254c2a..a425c290 100644 --- a/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs +++ b/src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs @@ -15,11 +15,6 @@ public class EvaluationEvent /// public FeatureDefinition FeatureDefinition { get; set; } - /// - /// The targeting context used for the evaluation. - /// - public ITargetingContext TargetingContext { get; set; } - /// /// The enabled state of the feature after evaluation. /// diff --git a/tests/Tests.FeatureManagement/FeatureManagement.cs b/tests/Tests.FeatureManagement/FeatureManagement.cs index 805aecb7..9f076778 100644 --- a/tests/Tests.FeatureManagement/FeatureManagement.cs +++ b/tests/Tests.FeatureManagement/FeatureManagement.cs @@ -10,7 +10,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.FeatureManagement; using Microsoft.FeatureManagement.FeatureFilters; -using Microsoft.FeatureManagement.Telemetry; using Microsoft.FeatureManagement.Tests; using System; using System.Collections.Generic; @@ -1034,20 +1033,6 @@ public async Task TelemetryPublishing() Assert.False(testPublisher.evaluationEventCache.IsEnabled); Assert.Equal(variantFeatureStatusDisabled, testPublisher.evaluationEventCache.FeatureDefinition.Name); Assert.Equal(variantResult.Name, testPublisher.evaluationEventCache.Variant.Name); - - TargetingContext targetingContext = new TargetingContext - { - UserId = "Jeff" - }; - variantResult = await featureManager.GetVariantAsync( - variantDefaultEnabledFeature, - targetingContext, - CancellationToken.None); - - Assert.True(testPublisher.evaluationEventCache.IsEnabled); - Assert.Equal(testPublisher.evaluationEventCache.TargetingContext.UserId, targetingContext.UserId); - Assert.Equal(variantDefaultEnabledFeature, testPublisher.evaluationEventCache.FeatureDefinition.Name); - Assert.Equal(variantResult.Name, testPublisher.evaluationEventCache.Variant.Name); } [Fact] From 7d00c6dd5654b68e7a37fb0673c8e42057eff052 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Fri, 6 Oct 2023 13:44:48 -0700 Subject: [PATCH 38/40] Moves tags, etag, and label under 'TelemetryMetadata' --- .../ConfigurationFeatureDefinitionProvider.cs | 31 ++++++++++++------- .../FeatureDefinition.cs | 19 +++--------- .../Telemetry/TelemetryMetadata.cs | 28 +++++++++++++++++ .../FeatureManagement.cs | 3 ++ .../Tests.FeatureManagement/appsettings.json | 9 +++++- 5 files changed, 64 insertions(+), 26 deletions(-) create mode 100644 src/Microsoft.FeatureManagement/Telemetry/TelemetryMetadata.cs diff --git a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs index bb5afb35..f090d8b1 100644 --- a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs +++ b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; +using Microsoft.FeatureManagement.Telemetry; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -288,18 +289,23 @@ We support } } - IConfigurationSection tagsSection = configurationSection.GetSection("Tags"); - if (tagsSection.Exists()) + telemetryEnabled = configurationSection.GetValue("TelemetryEnabled"); + + IConfigurationSection telemetryMetadataSection = configurationSection.GetSection("TelemetryMetadata"); + if (telemetryMetadataSection.Exists()) { - foreach (IConfigurationSection tag in tagsSection.GetChildren()) + IConfigurationSection tagsSection = telemetryMetadataSection.GetSection("Tags"); + if (tagsSection.Exists()) { - tags.Add(tag.Key, tag.Value); + foreach (IConfigurationSection tag in tagsSection.GetChildren()) + { + tags.Add(tag.Key, tag.Value); + } } - } - label = configurationSection["Label"]; - eTag = configurationSection["ETag"]; - telemetryEnabled = configurationSection.GetValue("TelemetryEnabled"); + label = telemetryMetadataSection["Label"]; + eTag = telemetryMetadataSection["ETag"]; + } } return new FeatureDefinition() @@ -310,10 +316,13 @@ We support Status = featureStatus, Allocation = allocation, Variants = variants, - Label = label, - ETag = eTag, TelemetryEnabled = telemetryEnabled, - Tags = tags + TelemetryMetadata = new TelemetryMetadata() + { + Label = label, + ETag = eTag, + Tags = tags + } }; } diff --git a/src/Microsoft.FeatureManagement/FeatureDefinition.cs b/src/Microsoft.FeatureManagement/FeatureDefinition.cs index c158e5fb..5af0e5b9 100644 --- a/src/Microsoft.FeatureManagement/FeatureDefinition.cs +++ b/src/Microsoft.FeatureManagement/FeatureDefinition.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // +using Microsoft.FeatureManagement.Telemetry; using System.Collections.Generic; using System.Linq; @@ -44,23 +45,13 @@ public class FeatureDefinition public IEnumerable Variants { get; set; } = Enumerable.Empty(); /// - /// Metadata that can be used to group feature flags. - /// - public string Label { get; set; } - - /// - /// An ETag that is used to track when the feature definiton has changed. - /// - public string ETag { get; set; } - - /// - /// A dictionary of tags used to assign additional metadata to a feature. + /// A flag to enable or disable sending telemetry events to the registered . /// - public IReadOnlyDictionary Tags { get; set; } + public bool TelemetryEnabled { get; set; } /// - /// A flag to enable or disable sending telemetry events to the registered . + /// A container for metadata relevant to telemetry. /// - public bool TelemetryEnabled { get; set; } + public TelemetryMetadata TelemetryMetadata { get; set; } } } diff --git a/src/Microsoft.FeatureManagement/Telemetry/TelemetryMetadata.cs b/src/Microsoft.FeatureManagement/Telemetry/TelemetryMetadata.cs new file mode 100644 index 00000000..02a217e4 --- /dev/null +++ b/src/Microsoft.FeatureManagement/Telemetry/TelemetryMetadata.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +using System.Collections.Generic; + +namespace Microsoft.FeatureManagement.Telemetry +{ + /// + /// A container for metadata relevant to telemetry. + /// + public class TelemetryMetadata + { + /// + /// Metadata that can be used to group feature flags. + /// + public string Label { get; set; } + + /// + /// An ETag that is used to track when the feature definiton has changed. + /// + public string ETag { get; set; } + + /// + /// A dictionary of tags used to assign additional metadata to a feature. + /// + public IReadOnlyDictionary Tags { get; set; } + } +} diff --git a/tests/Tests.FeatureManagement/FeatureManagement.cs b/tests/Tests.FeatureManagement/FeatureManagement.cs index c7f6039d..bcb14103 100644 --- a/tests/Tests.FeatureManagement/FeatureManagement.cs +++ b/tests/Tests.FeatureManagement/FeatureManagement.cs @@ -856,6 +856,9 @@ public async Task TelemetryPublishing() Assert.True(result); Assert.Equal(onFeature, testPublisher.evaluationEventCache.FeatureDefinition.Name); Assert.Equal(result, testPublisher.evaluationEventCache.IsEnabled); + Assert.Equal("EtagValue", testPublisher.evaluationEventCache.FeatureDefinition.TelemetryMetadata.ETag); + Assert.Equal("LabelValue", testPublisher.evaluationEventCache.FeatureDefinition.TelemetryMetadata.Label); + Assert.Equal("Tag1Value", testPublisher.evaluationEventCache.FeatureDefinition.TelemetryMetadata.Tags["Tag1"]); string offFeature = "OffTimeTestFeature"; diff --git a/tests/Tests.FeatureManagement/appsettings.json b/tests/Tests.FeatureManagement/appsettings.json index 11010571..01f148ee 100644 --- a/tests/Tests.FeatureManagement/appsettings.json +++ b/tests/Tests.FeatureManagement/appsettings.json @@ -26,7 +26,14 @@ { "Name": "AlwaysOn" } - ] + ], + "TelemetryMetadata": { + "Tags": { + "Tag1": "Tag1Value" + }, + "Etag": "EtagValue", + "Label": "LabelValue" + } }, "OffTimeTestFeature": { "TelemetryEnabled": true, From 9386f6a111aa40c12246e18e7797022eb0cea98c Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Fri, 6 Oct 2023 16:54:00 -0700 Subject: [PATCH 39/40] Adjusts telemetry metadata to already be a flattened dictionary --- .../ConfigurationFeatureDefinitionProvider.cs | 29 +++++-------------- .../FeatureDefinition.cs | 2 +- .../Telemetry/TelemetryMetadata.cs | 28 ------------------ .../FeatureManagement.cs | 6 ++-- .../Tests.FeatureManagement/appsettings.json | 5 ++-- 5 files changed, 14 insertions(+), 56 deletions(-) delete mode 100644 src/Microsoft.FeatureManagement/Telemetry/TelemetryMetadata.cs diff --git a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs index f090d8b1..b01c77cc 100644 --- a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs +++ b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; -using Microsoft.FeatureManagement.Telemetry; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -140,10 +139,6 @@ We support */ RequirementType requirementType = RequirementType.Any; - string label = null; - string eTag = null; - bool telemetryEnabled = false; - Dictionary tags = new Dictionary(); FeatureStatus featureStatus = FeatureStatus.Conditional; @@ -153,6 +148,10 @@ We support var enabledFor = new List(); + bool telemetryEnabled = false; + + Dictionary telemetryMetadata = null; + string val = configurationSection.Value; // configuration[$"{featureName}"]; if (string.IsNullOrEmpty(val)) @@ -292,19 +291,12 @@ We support telemetryEnabled = configurationSection.GetValue("TelemetryEnabled"); IConfigurationSection telemetryMetadataSection = configurationSection.GetSection("TelemetryMetadata"); + if (telemetryMetadataSection.Exists()) { - IConfigurationSection tagsSection = telemetryMetadataSection.GetSection("Tags"); - if (tagsSection.Exists()) - { - foreach (IConfigurationSection tag in tagsSection.GetChildren()) - { - tags.Add(tag.Key, tag.Value); - } - } + telemetryMetadata = new Dictionary(); - label = telemetryMetadataSection["Label"]; - eTag = telemetryMetadataSection["ETag"]; + telemetryMetadataSection.Bind(telemetryMetadata); } } @@ -317,12 +309,7 @@ We support Allocation = allocation, Variants = variants, TelemetryEnabled = telemetryEnabled, - TelemetryMetadata = new TelemetryMetadata() - { - Label = label, - ETag = eTag, - Tags = tags - } + TelemetryMetadata = telemetryMetadata }; } diff --git a/src/Microsoft.FeatureManagement/FeatureDefinition.cs b/src/Microsoft.FeatureManagement/FeatureDefinition.cs index 5af0e5b9..226c45e1 100644 --- a/src/Microsoft.FeatureManagement/FeatureDefinition.cs +++ b/src/Microsoft.FeatureManagement/FeatureDefinition.cs @@ -52,6 +52,6 @@ public class FeatureDefinition /// /// A container for metadata relevant to telemetry. /// - public TelemetryMetadata TelemetryMetadata { get; set; } + public IReadOnlyDictionary TelemetryMetadata { get; set; } } } diff --git a/src/Microsoft.FeatureManagement/Telemetry/TelemetryMetadata.cs b/src/Microsoft.FeatureManagement/Telemetry/TelemetryMetadata.cs deleted file mode 100644 index 02a217e4..00000000 --- a/src/Microsoft.FeatureManagement/Telemetry/TelemetryMetadata.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// -using System.Collections.Generic; - -namespace Microsoft.FeatureManagement.Telemetry -{ - /// - /// A container for metadata relevant to telemetry. - /// - public class TelemetryMetadata - { - /// - /// Metadata that can be used to group feature flags. - /// - public string Label { get; set; } - - /// - /// An ETag that is used to track when the feature definiton has changed. - /// - public string ETag { get; set; } - - /// - /// A dictionary of tags used to assign additional metadata to a feature. - /// - public IReadOnlyDictionary Tags { get; set; } - } -} diff --git a/tests/Tests.FeatureManagement/FeatureManagement.cs b/tests/Tests.FeatureManagement/FeatureManagement.cs index bcb14103..935bf9ae 100644 --- a/tests/Tests.FeatureManagement/FeatureManagement.cs +++ b/tests/Tests.FeatureManagement/FeatureManagement.cs @@ -856,9 +856,9 @@ public async Task TelemetryPublishing() Assert.True(result); Assert.Equal(onFeature, testPublisher.evaluationEventCache.FeatureDefinition.Name); Assert.Equal(result, testPublisher.evaluationEventCache.IsEnabled); - Assert.Equal("EtagValue", testPublisher.evaluationEventCache.FeatureDefinition.TelemetryMetadata.ETag); - Assert.Equal("LabelValue", testPublisher.evaluationEventCache.FeatureDefinition.TelemetryMetadata.Label); - Assert.Equal("Tag1Value", testPublisher.evaluationEventCache.FeatureDefinition.TelemetryMetadata.Tags["Tag1"]); + Assert.Equal("EtagValue", testPublisher.evaluationEventCache.FeatureDefinition.TelemetryMetadata["Etag"]); + Assert.Equal("LabelValue", testPublisher.evaluationEventCache.FeatureDefinition.TelemetryMetadata["Label"]); + Assert.Equal("Tag1Value", testPublisher.evaluationEventCache.FeatureDefinition.TelemetryMetadata["Tags.Tag1"]); string offFeature = "OffTimeTestFeature"; diff --git a/tests/Tests.FeatureManagement/appsettings.json b/tests/Tests.FeatureManagement/appsettings.json index 01f148ee..0bdac1d5 100644 --- a/tests/Tests.FeatureManagement/appsettings.json +++ b/tests/Tests.FeatureManagement/appsettings.json @@ -28,9 +28,8 @@ } ], "TelemetryMetadata": { - "Tags": { - "Tag1": "Tag1Value" - }, + "Tags.Tag1": "Tag1Value", + "Tags.Tag2": "Tag2Value", "Etag": "EtagValue", "Label": "LabelValue" } From 8b8cb74d5c3dbaaf8d38c714c13b35fcce152862 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Mon, 9 Oct 2023 15:10:48 -0700 Subject: [PATCH 40/40] Removes bind in favor of ToDictionary. Removes unused using --- .../ConfigurationFeatureDefinitionProvider.cs | 2 +- src/Microsoft.FeatureManagement/FeatureDefinition.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs index b01c77cc..5359d060 100644 --- a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs +++ b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs @@ -296,7 +296,7 @@ We support { telemetryMetadata = new Dictionary(); - telemetryMetadataSection.Bind(telemetryMetadata); + telemetryMetadata = telemetryMetadataSection.GetChildren().ToDictionary(x => x.Key, x => x.Value); } } diff --git a/src/Microsoft.FeatureManagement/FeatureDefinition.cs b/src/Microsoft.FeatureManagement/FeatureDefinition.cs index 226c45e1..48ebeb00 100644 --- a/src/Microsoft.FeatureManagement/FeatureDefinition.cs +++ b/src/Microsoft.FeatureManagement/FeatureDefinition.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // -using Microsoft.FeatureManagement.Telemetry; using System.Collections.Generic; using System.Linq;