From 475f4e4e57a1f8d3cb186538844c988dd3d91aab Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Wed, 24 Jan 2024 15:43:39 +0800 Subject: [PATCH 1/4] add schema field name --- .../ConfigurationFields.cs | 2 +- .../MicrosoftFeatureFlagFields.cs | 27 ++- .../FeatureManagement.cs | 197 ---------------- .../MicrosoftFeatureFlagSchema.cs | 214 ++++++++++++++++++ 4 files changed, 241 insertions(+), 199 deletions(-) create mode 100644 tests/Tests.FeatureManagement/MicrosoftFeatureFlagSchema.cs diff --git a/src/Microsoft.FeatureManagement/ConfigurationFields.cs b/src/Microsoft.FeatureManagement/ConfigurationFields.cs index 01a1f698..47d0c6a4 100644 --- a/src/Microsoft.FeatureManagement/ConfigurationFields.cs +++ b/src/Microsoft.FeatureManagement/ConfigurationFields.cs @@ -17,8 +17,8 @@ internal static class ConfigurationFields public const string AllocationSectionName = "Allocation"; public const string AllocationDefaultWhenDisabled = "DefaultWhenDisabled"; public const string AllocationDefaultWhenEnabled = "DefaultWhenEnabled"; - public const string UserAllocationSectionName = "User"; public const string AllocationVariantKeyword = "Variant"; + public const string UserAllocationSectionName = "User"; public const string UserAllocationUsers = "Users"; public const string GroupAllocationSectionName = "Group"; public const string GroupAllocationGroups = "Groups"; diff --git a/src/Microsoft.FeatureManagement/MicrosoftFeatureFlagFields.cs b/src/Microsoft.FeatureManagement/MicrosoftFeatureFlagFields.cs index 21286e5c..a1a7a1fe 100644 --- a/src/Microsoft.FeatureManagement/MicrosoftFeatureFlagFields.cs +++ b/src/Microsoft.FeatureManagement/MicrosoftFeatureFlagFields.cs @@ -5,7 +5,7 @@ namespace Microsoft.FeatureManagement { // - // Microsoft feature flag schema: https://github.com/Azure/AppConfiguration/blob/main/docs/FeatureManagement/FeatureFlag.v1.1.0.schema.json + // Microsoft feature flag schema: https://github.com/Azure/AppConfiguration/blob/main/docs/FeatureManagement/FeatureFlag.v2.0.0.schema.json internal static class MicrosoftFeatureFlagFields { public const string FeatureFlagsSectionName = "FeatureFlags"; @@ -18,9 +18,34 @@ internal static class MicrosoftFeatureFlagFields public const string ClientFilters = "client_filters"; public const string RequirementType = "requirement_type"; + // + // Allocation keywords + public const string AllocationSectionName = "allocation"; + public const string AllocationDefaultWhenDisabled = "default_when_disabled"; + public const string AllocationDefaultWhenEnabled = "default_when_enabled"; + public const string AllocationVariantKeyword = "variant"; + public const string UserAllocationSectionName = "user"; + public const string UserAllocationUsers = "users"; + public const string GroupAllocationSectionName = "group"; + public const string GroupAllocationGroups = "groups"; + public const string PercentileAllocationSectionName = "percentile"; + public const string PercentileAllocationFrom = "from"; + public const string PercentileAllocationTo = "to"; + public const string AllocationSeed = "seed"; + // // Client filter keywords public const string Name = "name"; public const string Parameters = "parameters"; + + // Variants keywords + public const string VariantsSectionName = "variants"; + public const string VariantDefinitionConfigurationValue = "configuration_value"; + public const string VariantDefinitionConfigurationReference = "configuration_reference"; + public const string VariantDefinitionStatusOverride = "status_override"; + + // Telemetry keywords + public const string Telemetry = "telemetry"; + public const string Metadata = "metadata"; } } \ No newline at end of file diff --git a/tests/Tests.FeatureManagement/FeatureManagement.cs b/tests/Tests.FeatureManagement/FeatureManagement.cs index 9db2a34e..3b130ae5 100644 --- a/tests/Tests.FeatureManagement/FeatureManagement.cs +++ b/tests/Tests.FeatureManagement/FeatureManagement.cs @@ -109,203 +109,6 @@ public async Task ReadsTopLevelConfiguration() Assert.True(await featureManager.IsEnabledAsync(feature)); } - [Fact] - public async Task ReadsFeatureFlagsArraySchema() - { - string json = @" - { - ""AllowedHosts"": ""*"", - ""FeatureManagement"": { - ""MyFeature"": true, - ""FeatureFlags"": [ - { - ""id"": ""Alpha"", - ""enabled"": true, - ""conditions"": { - ""client_filters"": [] - } - }, - { - ""id"": ""Beta"", - ""enabled"": true, - ""conditions"": { - ""client_filters"": [ - { - ""name"": ""Percentage"", - ""parameters"": { - ""Value"": 100 - } - }, - { - ""name"": ""Targeting"", - ""parameters"": { - ""Audience"": { - ""Users"": [""Jeff""], - ""Groups"": [], - ""DefaultRolloutPercentage"": 0 - } - } - } - ], - ""requirement_type"" : ""all"" - } - }, - { - ""id"": ""Sigma"", - ""enabled"": false, - ""conditions"": { - ""client_filters"": [ - { - ""name"": ""Percentage"", - ""parameters"": { - ""Value"": 100 - } - } - ] - } - }, - { - ""id"": ""Omega"", - ""enabled"": true, - ""conditions"": { - ""client_filters"": [ - { - ""name"": ""Percentage"", - ""parameters"": { - ""Value"": 100 - } - }, - { - ""name"": ""Percentage"", - ""parameters"": { - ""Value"": 0 - } - } - ] - } - } - ] - } - }"; - - var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); - - IConfiguration config = new ConfigurationBuilder().AddJsonStream(stream).Build(); - - var services = new ServiceCollection(); - - services.AddSingleton(config) - .AddFeatureManagement(); - - ServiceProvider serviceProvider = services.BuildServiceProvider(); - - IFeatureManager featureManager = serviceProvider.GetRequiredService(); - - Assert.False(await featureManager.IsEnabledAsync("MyFeature")); - - Assert.True(await featureManager.IsEnabledAsync("Alpha")); - - Assert.True(await featureManager.IsEnabledAsync("Beta", new TargetingContext - { - UserId = "Jeff" - })); - - Assert.False(await featureManager.IsEnabledAsync("Beta", new TargetingContext - { - UserId = "Sam" - })); - - Assert.False(await featureManager.IsEnabledAsync("Sigma")); - - Assert.True(await featureManager.IsEnabledAsync("Omega")); - - json = @" - { - ""AllowedHosts"": ""*"", - ""FeatureManagement"": { - ""MyFeature"": true, - ""FeatureFlags"": [ - { - ""id"": ""Alpha"", - ""enabled"": true - } - ] - } - }"; - - stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); - - config = new ConfigurationBuilder().AddJsonStream(stream).Build(); - - services = new ServiceCollection(); - - services.AddFeatureManagement(config.GetSection("FeatureManagement")); - - serviceProvider = services.BuildServiceProvider(); - - featureManager = serviceProvider.GetRequiredService(); - - Assert.False(await featureManager.IsEnabledAsync("MyFeature")); - - Assert.True(await featureManager.IsEnabledAsync("Alpha")); - - json = @" - { - ""AllowedHosts"": ""*"", - ""FeatureManagement"": { - ""MyFeature"": true, - ""FeatureFlags"": true - } - }"; - - stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); - - config = new ConfigurationBuilder().AddJsonStream(stream).Build(); - - services = new ServiceCollection(); - - services.AddFeatureManagement(config.GetSection("FeatureManagement")); - - serviceProvider = services.BuildServiceProvider(); - - featureManager = serviceProvider.GetRequiredService(); - - Assert.True(await featureManager.IsEnabledAsync("MyFeature")); - - Assert.True(await featureManager.IsEnabledAsync("FeatureFlags")); - - json = @" - { - ""AllowedHosts"": ""*"", - ""FeatureManagement"": { - ""MyFeature"": true, - ""FeatureFlags"": { - ""EnabledFor"": [ - { - ""Name"": ""AlwaysOn"" - } - ] - } - } - }"; - - stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); - - config = new ConfigurationBuilder().AddJsonStream(stream).Build(); - - services = new ServiceCollection(); - - services.AddFeatureManagement(config.GetSection("FeatureManagement")); - - serviceProvider = services.BuildServiceProvider(); - - featureManager = serviceProvider.GetRequiredService(); - - Assert.True(await featureManager.IsEnabledAsync("MyFeature")); - - Assert.True(await featureManager.IsEnabledAsync("FeatureFlags")); - } - [Fact] public void AddsScopedFeatureManagement() { diff --git a/tests/Tests.FeatureManagement/MicrosoftFeatureFlagSchema.cs b/tests/Tests.FeatureManagement/MicrosoftFeatureFlagSchema.cs new file mode 100644 index 00000000..1b49cd60 --- /dev/null +++ b/tests/Tests.FeatureManagement/MicrosoftFeatureFlagSchema.cs @@ -0,0 +1,214 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.FeatureManagement; +using Microsoft.FeatureManagement.FeatureFilters; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Tests.FeatureManagement +{ + public class MicrosoftFeatureFlagSchemaTest + { + [Fact] + public async Task ReadsMicrosoftFeatureFlagSchema() + { + string json = @" + { + ""AllowedHosts"": ""*"", + ""FeatureManagement"": { + ""MyFeature"": true, + ""FeatureFlags"": [ + { + ""id"": ""Alpha"", + ""enabled"": true, + ""conditions"": { + ""client_filters"": [] + } + }, + { + ""id"": ""Beta"", + ""enabled"": true, + ""conditions"": { + ""client_filters"": [ + { + ""name"": ""Percentage"", + ""parameters"": { + ""Value"": 100 + } + }, + { + ""name"": ""Targeting"", + ""parameters"": { + ""Audience"": { + ""Users"": [""Jeff""], + ""Groups"": [], + ""DefaultRolloutPercentage"": 0 + } + } + } + ], + ""requirement_type"" : ""all"" + } + }, + { + ""id"": ""Sigma"", + ""enabled"": false, + ""conditions"": { + ""client_filters"": [ + { + ""name"": ""Percentage"", + ""parameters"": { + ""Value"": 100 + } + } + ] + } + }, + { + ""id"": ""Omega"", + ""enabled"": true, + ""conditions"": { + ""client_filters"": [ + { + ""name"": ""Percentage"", + ""parameters"": { + ""Value"": 100 + } + }, + { + ""name"": ""Percentage"", + ""parameters"": { + ""Value"": 0 + } + } + ] + } + } + ] + } + }"; + + var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); + + IConfiguration config = new ConfigurationBuilder().AddJsonStream(stream).Build(); + + var services = new ServiceCollection(); + + services.AddSingleton(config) + .AddFeatureManagement(); + + ServiceProvider serviceProvider = services.BuildServiceProvider(); + + IFeatureManager featureManager = serviceProvider.GetRequiredService(); + + Assert.False(await featureManager.IsEnabledAsync("MyFeature")); + + Assert.True(await featureManager.IsEnabledAsync("Alpha")); + + Assert.True(await featureManager.IsEnabledAsync("Beta", new TargetingContext + { + UserId = "Jeff" + })); + + Assert.False(await featureManager.IsEnabledAsync("Beta", new TargetingContext + { + UserId = "Sam" + })); + + Assert.False(await featureManager.IsEnabledAsync("Sigma")); + + Assert.True(await featureManager.IsEnabledAsync("Omega")); + + json = @" + { + ""AllowedHosts"": ""*"", + ""FeatureManagement"": { + ""MyFeature"": true, + ""FeatureFlags"": [ + { + ""id"": ""Alpha"", + ""enabled"": true + } + ] + } + }"; + + stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); + + config = new ConfigurationBuilder().AddJsonStream(stream).Build(); + + services = new ServiceCollection(); + + services.AddFeatureManagement(config.GetSection("FeatureManagement")); + + serviceProvider = services.BuildServiceProvider(); + + featureManager = serviceProvider.GetRequiredService(); + + Assert.False(await featureManager.IsEnabledAsync("MyFeature")); + + Assert.True(await featureManager.IsEnabledAsync("Alpha")); + + json = @" + { + ""AllowedHosts"": ""*"", + ""FeatureManagement"": { + ""MyFeature"": true, + ""FeatureFlags"": true + } + }"; + + stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); + + config = new ConfigurationBuilder().AddJsonStream(stream).Build(); + + services = new ServiceCollection(); + + services.AddFeatureManagement(config.GetSection("FeatureManagement")); + + serviceProvider = services.BuildServiceProvider(); + + featureManager = serviceProvider.GetRequiredService(); + + Assert.True(await featureManager.IsEnabledAsync("MyFeature")); + + Assert.True(await featureManager.IsEnabledAsync("FeatureFlags")); + + json = @" + { + ""AllowedHosts"": ""*"", + ""FeatureManagement"": { + ""MyFeature"": true, + ""FeatureFlags"": { + ""EnabledFor"": [ + { + ""Name"": ""AlwaysOn"" + } + ] + } + } + }"; + + stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); + + config = new ConfigurationBuilder().AddJsonStream(stream).Build(); + + services = new ServiceCollection(); + + services.AddFeatureManagement(config.GetSection("FeatureManagement")); + + serviceProvider = services.BuildServiceProvider(); + + featureManager = serviceProvider.GetRequiredService(); + + Assert.True(await featureManager.IsEnabledAsync("MyFeature")); + + Assert.True(await featureManager.IsEnabledAsync("FeatureFlags")); + } + } +} From 4eb7b8b8eb5264285c308f2f94eddbd4ede6c117 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Wed, 24 Jan 2024 16:42:50 +0800 Subject: [PATCH 2/4] support microsoft feature flag schema v2.0.0 --- .../ConfigurationFeatureDefinitionProvider.cs | 135 +++++++++++++++++- 1 file changed, 128 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs index d87a135b..dc1be39b 100644 --- a/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs +++ b/src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs @@ -233,7 +233,7 @@ We support Allocation allocation = null; - List variants = null; + var variants = new List(); bool telemetryEnabled = false; @@ -349,8 +349,6 @@ We support IEnumerable variantsSections = configurationSection.GetSection(ConfigurationFields.VariantsSectionName).GetChildren(); - variants = new List(); - foreach (IConfigurationSection section in variantsSections) { if (int.TryParse(section.Key, out int _) && !string.IsNullOrEmpty(section[ConfigurationFields.NameKeyword])) @@ -452,9 +450,19 @@ private FeatureDefinition ParseMicrosoftFeatureDefinition(IConfigurationSection bool enabled = false; - IConfigurationSection conditions = configurationSection.GetSection(MicrosoftFeatureFlagFields.Conditions); + FeatureStatus featureStatus = FeatureStatus.Disabled; + + Allocation allocation = null; + + var variants = new List(); + + bool telemetryEnabled = false; + + Dictionary telemetryMetadata = null; + + IConfigurationSection conditionsSection = configurationSection.GetSection(MicrosoftFeatureFlagFields.Conditions); - string rawRequirementType = conditions[MicrosoftFeatureFlagFields.RequirementType]; + string rawRequirementType = conditionsSection[MicrosoftFeatureFlagFields.RequirementType]; string rawEnabled = configurationSection[MicrosoftFeatureFlagFields.Enabled]; @@ -470,7 +478,9 @@ private FeatureDefinition ParseMicrosoftFeatureDefinition(IConfigurationSection if (enabled) { - IEnumerable filterSections = conditions.GetSection(MicrosoftFeatureFlagFields.ClientFilters).GetChildren(); + featureStatus = FeatureStatus.Conditional; + + IEnumerable filterSections = conditionsSection.GetSection(MicrosoftFeatureFlagFields.ClientFilters).GetChildren(); if (filterSections.Any()) { @@ -498,11 +508,122 @@ private FeatureDefinition ParseMicrosoftFeatureDefinition(IConfigurationSection } } + IConfigurationSection allocationSection = configurationSection.GetSection(MicrosoftFeatureFlagFields.AllocationSectionName); + + if (allocationSection.Exists()) + { + allocation = new Allocation() + { + DefaultWhenDisabled = allocationSection[MicrosoftFeatureFlagFields.AllocationDefaultWhenDisabled], + DefaultWhenEnabled = allocationSection[MicrosoftFeatureFlagFields.AllocationDefaultWhenEnabled], + User = allocationSection.GetSection(MicrosoftFeatureFlagFields.UserAllocationSectionName).GetChildren().Select(userAllocation => + { + return new UserAllocation() + { + Variant = userAllocation[MicrosoftFeatureFlagFields.AllocationVariantKeyword], + Users = userAllocation.GetSection(MicrosoftFeatureFlagFields.UserAllocationUsers).Get>() + }; + }), + Group = allocationSection.GetSection(MicrosoftFeatureFlagFields.GroupAllocationSectionName).GetChildren().Select(groupAllocation => + { + return new GroupAllocation() + { + Variant = groupAllocation[MicrosoftFeatureFlagFields.AllocationVariantKeyword], + Groups = groupAllocation.GetSection(MicrosoftFeatureFlagFields.GroupAllocationGroups).Get>() + }; + }), + Percentile = allocationSection.GetSection(MicrosoftFeatureFlagFields.PercentileAllocationSectionName).GetChildren().Select(percentileAllocation => + { + double from = 0; + + double to = 0; + + string rawFrom = percentileAllocation[MicrosoftFeatureFlagFields.PercentileAllocationFrom]; + + string rawTo = percentileAllocation[MicrosoftFeatureFlagFields.PercentileAllocationTo]; + + if (!string.IsNullOrEmpty(rawFrom)) + { + from = ParseDouble(featureName, rawFrom, MicrosoftFeatureFlagFields.PercentileAllocationFrom); + } + + if (!string.IsNullOrEmpty(rawTo)) + { + to = ParseDouble(featureName, rawTo, MicrosoftFeatureFlagFields.PercentileAllocationTo); + } + + return new PercentileAllocation() + { + Variant = percentileAllocation[MicrosoftFeatureFlagFields.AllocationVariantKeyword], + From = from, + To = to + }; + }), + Seed = allocationSection[MicrosoftFeatureFlagFields.AllocationSeed] + }; + } + + IEnumerable variantsSections = configurationSection.GetSection(MicrosoftFeatureFlagFields.VariantsSectionName).GetChildren(); + + foreach (IConfigurationSection section in variantsSections) + { + if (int.TryParse(section.Key, out int _) && !string.IsNullOrEmpty(section[MicrosoftFeatureFlagFields.Name])) + { + StatusOverride statusOverride = StatusOverride.None; + + string rawStatusOverride = section[MicrosoftFeatureFlagFields.VariantDefinitionStatusOverride]; + + if (!string.IsNullOrEmpty(rawStatusOverride)) + { + statusOverride = ParseEnum(configurationSection.Key, rawStatusOverride, MicrosoftFeatureFlagFields.VariantDefinitionStatusOverride); + } + + var variant = new VariantDefinition() + { + Name = section[MicrosoftFeatureFlagFields.Name], + ConfigurationValue = section.GetSection(MicrosoftFeatureFlagFields.VariantDefinitionConfigurationValue), + ConfigurationReference = section[MicrosoftFeatureFlagFields.VariantDefinitionConfigurationReference], + StatusOverride = statusOverride + }; + + variants.Add(variant); + } + } + + IConfigurationSection telemetrySection = configurationSection.GetSection(MicrosoftFeatureFlagFields.Telemetry); + + if (telemetrySection.Exists()) + { + string rawTelemetryEnabled = telemetrySection[MicrosoftFeatureFlagFields.Enabled]; + + if (!string.IsNullOrEmpty(rawTelemetryEnabled)) + { + telemetryEnabled = ParseBool(featureName, rawTelemetryEnabled, MicrosoftFeatureFlagFields.Enabled); + } + + IConfigurationSection telemetryMetadataSection = telemetrySection.GetSection(MicrosoftFeatureFlagFields.Metadata); + + if (telemetryMetadataSection.Exists()) + { + telemetryMetadata = new Dictionary(); + + telemetryMetadata = telemetryMetadataSection.GetChildren().ToDictionary(x => x.Key, x => x.Value); + } + } + return new FeatureDefinition() { Name = featureName, EnabledFor = enabledFor, - RequirementType = requirementType + RequirementType = requirementType, + Status = featureStatus, + Allocation = allocation, + Variants = variants, + Telemetry = new TelemetryConfiguration + { + Enabled = telemetryEnabled, + Metadata = telemetryMetadata + } }; } From dbccdc2a3ae615c68fe3f573218b40a35d41f844 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Wed, 24 Jan 2024 17:27:28 +0800 Subject: [PATCH 3/4] testcase added --- .../MicrosoftFeatureFlagSchema.cs | 312 +++++++++++++----- 1 file changed, 234 insertions(+), 78 deletions(-) diff --git a/tests/Tests.FeatureManagement/MicrosoftFeatureFlagSchema.cs b/tests/Tests.FeatureManagement/MicrosoftFeatureFlagSchema.cs index 1b49cd60..0a2d896a 100644 --- a/tests/Tests.FeatureManagement/MicrosoftFeatureFlagSchema.cs +++ b/tests/Tests.FeatureManagement/MicrosoftFeatureFlagSchema.cs @@ -5,7 +5,9 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.FeatureManagement; using Microsoft.FeatureManagement.FeatureFilters; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using System.Threading.Tasks; using Xunit; @@ -15,7 +17,7 @@ namespace Tests.FeatureManagement public class MicrosoftFeatureFlagSchemaTest { [Fact] - public async Task ReadsMicrosoftFeatureFlagSchema() + public async Task ReadsMicrosoftFeatureFlagSchemaIfAny() { string json = @" { @@ -25,69 +27,7 @@ public async Task ReadsMicrosoftFeatureFlagSchema() ""FeatureFlags"": [ { ""id"": ""Alpha"", - ""enabled"": true, - ""conditions"": { - ""client_filters"": [] - } - }, - { - ""id"": ""Beta"", - ""enabled"": true, - ""conditions"": { - ""client_filters"": [ - { - ""name"": ""Percentage"", - ""parameters"": { - ""Value"": 100 - } - }, - { - ""name"": ""Targeting"", - ""parameters"": { - ""Audience"": { - ""Users"": [""Jeff""], - ""Groups"": [], - ""DefaultRolloutPercentage"": 0 - } - } - } - ], - ""requirement_type"" : ""all"" - } - }, - { - ""id"": ""Sigma"", - ""enabled"": false, - ""conditions"": { - ""client_filters"": [ - { - ""name"": ""Percentage"", - ""parameters"": { - ""Value"": 100 - } - } - ] - } - }, - { - ""id"": ""Omega"", - ""enabled"": true, - ""conditions"": { - ""client_filters"": [ - { - ""name"": ""Percentage"", - ""parameters"": { - ""Value"": 100 - } - }, - { - ""name"": ""Percentage"", - ""parameters"": { - ""Value"": 0 - } - } - ] - } + ""enabled"": true } ] } @@ -110,21 +50,69 @@ public async Task ReadsMicrosoftFeatureFlagSchema() Assert.True(await featureManager.IsEnabledAsync("Alpha")); - Assert.True(await featureManager.IsEnabledAsync("Beta", new TargetingContext + json = @" { - UserId = "Jeff" - })); + ""AllowedHosts"": ""*"", + ""FeatureManagement"": { + ""MyFeature"": true, + ""FeatureFlags"": true + } + }"; - Assert.False(await featureManager.IsEnabledAsync("Beta", new TargetingContext - { - UserId = "Sam" - })); + stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); - Assert.False(await featureManager.IsEnabledAsync("Sigma")); + config = new ConfigurationBuilder().AddJsonStream(stream).Build(); - Assert.True(await featureManager.IsEnabledAsync("Omega")); + services = new ServiceCollection(); + + services.AddSingleton(config) + .AddFeatureManagement(); + + serviceProvider = services.BuildServiceProvider(); + + featureManager = serviceProvider.GetRequiredService(); + + Assert.True(await featureManager.IsEnabledAsync("MyFeature")); + + Assert.True(await featureManager.IsEnabledAsync("FeatureFlags")); json = @" + { + ""AllowedHosts"": ""*"", + ""FeatureManagement"": { + ""MyFeature"": true, + ""FeatureFlags"": { + ""EnabledFor"": [ + { + ""Name"": ""AlwaysOn"" + } + ] + } + } + }"; + + stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); + + config = new ConfigurationBuilder().AddJsonStream(stream).Build(); + + services = new ServiceCollection(); + + services.AddSingleton(config) + .AddFeatureManagement(); + + serviceProvider = services.BuildServiceProvider(); + + featureManager = serviceProvider.GetRequiredService(); + + Assert.True(await featureManager.IsEnabledAsync("MyFeature")); + + Assert.True(await featureManager.IsEnabledAsync("FeatureFlags")); + } + + [Fact] + public async Task ReadsTopLevelConfiguration() + { + string json = @" { ""AllowedHosts"": ""*"", ""FeatureManagement"": { @@ -138,17 +126,17 @@ public async Task ReadsMicrosoftFeatureFlagSchema() } }"; - stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); + var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); - config = new ConfigurationBuilder().AddJsonStream(stream).Build(); + IConfiguration config = new ConfigurationBuilder().AddJsonStream(stream).Build(); - services = new ServiceCollection(); + var services = new ServiceCollection(); services.AddFeatureManagement(config.GetSection("FeatureManagement")); - serviceProvider = services.BuildServiceProvider(); + ServiceProvider serviceProvider = services.BuildServiceProvider(); - featureManager = serviceProvider.GetRequiredService(); + IFeatureManager featureManager = serviceProvider.GetRequiredService(); Assert.False(await featureManager.IsEnabledAsync("MyFeature")); @@ -210,5 +198,173 @@ public async Task ReadsMicrosoftFeatureFlagSchema() Assert.True(await featureManager.IsEnabledAsync("FeatureFlags")); } + + [Fact] + public async Task ReadsFeatureFilterConfiguration() + { + string json = @" + { + ""AllowedHosts"": ""*"", + ""FeatureManagement"": { + ""FeatureFlags"": [ + { + ""id"": ""ConditionalFeature"", + ""enabled"": true, + ""conditions"": { + ""client_filters"": [ + { + ""name"": ""Test"", + ""parameters"": { + ""P1"": ""V1"" + } + } + ] + } + }, + ] + } + }"; + + var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); + + IConfiguration config = new ConfigurationBuilder().AddJsonStream(stream).Build(); + + var services = new ServiceCollection(); + + services.AddSingleton(config) + .AddFeatureManagement() + .AddFeatureFilter(); + + ServiceProvider serviceProvider = services.BuildServiceProvider(); + + IFeatureManager featureManager = serviceProvider.GetRequiredService(); + + IEnumerable featureFilters = serviceProvider.GetRequiredService>(); + + // + // Sync filter + TestFilter testFeatureFilter = (TestFilter)featureFilters.First(f => f is TestFilter); + + bool called = false; + + testFeatureFilter.Callback = (evaluationContext) => + { + called = true; + + Assert.Equal("V1", evaluationContext.Parameters["P1"]); + + Assert.Equal(Features.ConditionalFeature, evaluationContext.FeatureName); + + return Task.FromResult(true); + }; + + await featureManager.IsEnabledAsync(Features.ConditionalFeature); + + Assert.True(called); + + json = @" + { + ""AllowedHosts"": ""*"", + ""FeatureManagement"": { + ""FeatureFlags"": [ + { + ""id"": ""Alpha"", + ""enabled"": true, + ""conditions"": { + ""client_filters"": [] + } + }, + { + ""id"": ""Beta"", + ""enabled"": true, + ""conditions"": { + ""client_filters"": [ + { + ""name"": ""Percentage"", + ""parameters"": { + ""Value"": 100 + } + }, + { + ""name"": ""Targeting"", + ""parameters"": { + ""Audience"": { + ""Users"": [""Jeff""], + ""Groups"": [], + ""DefaultRolloutPercentage"": 0 + } + } + } + ], + ""requirement_type"" : ""all"" + } + }, + { + ""id"": ""Sigma"", + ""enabled"": false, + ""conditions"": { + ""client_filters"": [ + { + ""name"": ""Percentage"", + ""parameters"": { + ""Value"": 100 + } + } + ] + } + }, + { + ""id"": ""Omega"", + ""enabled"": true, + ""conditions"": { + ""client_filters"": [ + { + ""name"": ""Percentage"", + ""parameters"": { + ""Value"": 100 + } + }, + { + ""name"": ""Percentage"", + ""parameters"": { + ""Value"": 0 + } + } + ] + } + } + ] + } + }"; + + stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); + + config = new ConfigurationBuilder().AddJsonStream(stream).Build(); + + services = new ServiceCollection(); + + services.AddSingleton(config) + .AddFeatureManagement(); + + serviceProvider = services.BuildServiceProvider(); + + featureManager = serviceProvider.GetRequiredService(); + + Assert.True(await featureManager.IsEnabledAsync("Alpha")); + + Assert.True(await featureManager.IsEnabledAsync("Beta", new TargetingContext + { + UserId = "Jeff" + })); + + Assert.False(await featureManager.IsEnabledAsync("Beta", new TargetingContext + { + UserId = "Sam" + })); + + Assert.False(await featureManager.IsEnabledAsync("Sigma")); + + Assert.True(await featureManager.IsEnabledAsync("Omega")); + } } } From 7dfe6ca7516729d8d0c919337293fabdb497fbae Mon Sep 17 00:00:00 2001 From: zhiyuanliang Date: Mon, 29 Jan 2024 19:35:29 +0800 Subject: [PATCH 4/4] add testcase --- .../MicrosoftFeatureFlag.json | 72 ++ .../MicrosoftFeatureFlagSchema.cs | 156 +-- .../Tests.FeatureManagement.csproj | 3 + .../Tests.FeatureManagement/appsettings.json | 916 +++++++++--------- 4 files changed, 585 insertions(+), 562 deletions(-) create mode 100644 tests/Tests.FeatureManagement/MicrosoftFeatureFlag.json diff --git a/tests/Tests.FeatureManagement/MicrosoftFeatureFlag.json b/tests/Tests.FeatureManagement/MicrosoftFeatureFlag.json new file mode 100644 index 00000000..a3267450 --- /dev/null +++ b/tests/Tests.FeatureManagement/MicrosoftFeatureFlag.json @@ -0,0 +1,72 @@ +{ + "ShoppingCart": { + "Big": { + "Size": 600, + "Color": "green" + } + }, + "FeatureManagement": { + "FeatureFlags": [ + { + "id": "AlwaysOnTestFeature", + "enabled": true, + "telemetry": { + "enabled": true, + "metadata": { + "Tags.Tag1": "Tag1Value", + "Tags.Tag2": "Tag2Value", + "Etag": "EtagValue", + "Label": "LabelValue" + } + }, + "conditions": { + "client_filters": [ + { + "name": "AlwaysOn" + } + ], + "requirement_type": "All" + }, + "variants": [ + { + "name": "Small", + "configuration_value": "300px" + }, + { + "name": "Big", + "configuration_reference": "ShoppingCart:Big", + "status_override": "Disabled" + } + ], + "allocation": { + "default_when_enabled": "Small", + "default_when_disabled": "Big", + "percentile": [ + { + "variant": "Small", + "from": 0, + "to": 50 + } + ], + "user": [ + { + "variant": "Small", + "users": [ + "Jeff" + ] + } + ], + "group": [ + { + "variant": "Big", + "groups": [ + "Group1" + ] + } + ], + "seed": 12345 + } + } + ] + } +} \ No newline at end of file diff --git a/tests/Tests.FeatureManagement/MicrosoftFeatureFlagSchema.cs b/tests/Tests.FeatureManagement/MicrosoftFeatureFlagSchema.cs index 0a2d896a..c9f90407 100644 --- a/tests/Tests.FeatureManagement/MicrosoftFeatureFlagSchema.cs +++ b/tests/Tests.FeatureManagement/MicrosoftFeatureFlagSchema.cs @@ -199,6 +199,58 @@ public async Task ReadsTopLevelConfiguration() Assert.True(await featureManager.IsEnabledAsync("FeatureFlags")); } + [Fact] + public async Task ReadsFeatureDefinition() + { + IConfiguration config = new ConfigurationBuilder().AddJsonFile("MicrosoftFeatureFlag.json").Build(); + + var featureDefinitionProvider = new ConfigurationFeatureDefinitionProvider(config); + + FeatureDefinition featureDefinition = await featureDefinitionProvider.GetFeatureDefinitionAsync(Features.AlwaysOnTestFeature); + + Assert.NotNull(featureDefinition); + + Assert.Equal(RequirementType.All, featureDefinition.RequirementType); + + Assert.Equal(FeatureStatus.Conditional, featureDefinition.Status); + + Assert.Equal("Small", featureDefinition.Allocation.DefaultWhenEnabled); + + Assert.Equal("Big", featureDefinition.Allocation.DefaultWhenDisabled); + + Assert.Equal("Small", featureDefinition.Allocation.User.First().Variant); + + Assert.Equal("Jeff", featureDefinition.Allocation.User.First().Users.First()); + + Assert.Equal("Big", featureDefinition.Allocation.Group.First().Variant); + + Assert.Equal("Group1", featureDefinition.Allocation.Group.First().Groups.First()); + + Assert.Equal("Small", featureDefinition.Allocation.Percentile.First().Variant); + + Assert.Equal(0, featureDefinition.Allocation.Percentile.First().From); + + Assert.Equal(50, featureDefinition.Allocation.Percentile.First().To); + + Assert.Equal("12345", featureDefinition.Allocation.Seed); + + VariantDefinition smallVariant = featureDefinition.Variants.FirstOrDefault(variant => string.Equals(variant.Name, "Small")); + + Assert.NotNull(smallVariant); + + Assert.Equal("300px", smallVariant.ConfigurationValue.Value); + + Assert.Equal(StatusOverride.None, smallVariant.StatusOverride); + + VariantDefinition bigVariant = featureDefinition.Variants.FirstOrDefault(variant => string.Equals(variant.Name, "Big")); + + Assert.NotNull(bigVariant); + + Assert.Equal("ShoppingCart:Big", bigVariant.ConfigurationReference); + + Assert.Equal(StatusOverride.Disabled, bigVariant.StatusOverride); + } + [Fact] public async Task ReadsFeatureFilterConfiguration() { @@ -261,110 +313,6 @@ public async Task ReadsFeatureFilterConfiguration() await featureManager.IsEnabledAsync(Features.ConditionalFeature); Assert.True(called); - - json = @" - { - ""AllowedHosts"": ""*"", - ""FeatureManagement"": { - ""FeatureFlags"": [ - { - ""id"": ""Alpha"", - ""enabled"": true, - ""conditions"": { - ""client_filters"": [] - } - }, - { - ""id"": ""Beta"", - ""enabled"": true, - ""conditions"": { - ""client_filters"": [ - { - ""name"": ""Percentage"", - ""parameters"": { - ""Value"": 100 - } - }, - { - ""name"": ""Targeting"", - ""parameters"": { - ""Audience"": { - ""Users"": [""Jeff""], - ""Groups"": [], - ""DefaultRolloutPercentage"": 0 - } - } - } - ], - ""requirement_type"" : ""all"" - } - }, - { - ""id"": ""Sigma"", - ""enabled"": false, - ""conditions"": { - ""client_filters"": [ - { - ""name"": ""Percentage"", - ""parameters"": { - ""Value"": 100 - } - } - ] - } - }, - { - ""id"": ""Omega"", - ""enabled"": true, - ""conditions"": { - ""client_filters"": [ - { - ""name"": ""Percentage"", - ""parameters"": { - ""Value"": 100 - } - }, - { - ""name"": ""Percentage"", - ""parameters"": { - ""Value"": 0 - } - } - ] - } - } - ] - } - }"; - - stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); - - config = new ConfigurationBuilder().AddJsonStream(stream).Build(); - - services = new ServiceCollection(); - - services.AddSingleton(config) - .AddFeatureManagement(); - - serviceProvider = services.BuildServiceProvider(); - - featureManager = serviceProvider.GetRequiredService(); - - Assert.True(await featureManager.IsEnabledAsync("Alpha")); - - Assert.True(await featureManager.IsEnabledAsync("Beta", new TargetingContext - { - UserId = "Jeff" - })); - - Assert.False(await featureManager.IsEnabledAsync("Beta", new TargetingContext - { - UserId = "Sam" - })); - - Assert.False(await featureManager.IsEnabledAsync("Sigma")); - - Assert.True(await featureManager.IsEnabledAsync("Omega")); } } } diff --git a/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj b/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj index 4d8697ad..35a3e69d 100644 --- a/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj +++ b/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj @@ -38,6 +38,9 @@ Always + + Always + diff --git a/tests/Tests.FeatureManagement/appsettings.json b/tests/Tests.FeatureManagement/appsettings.json index 4bfa426e..d74bfbb0 100644 --- a/tests/Tests.FeatureManagement/appsettings.json +++ b/tests/Tests.FeatureManagement/appsettings.json @@ -17,482 +17,482 @@ } }, - "FeatureManagement": { - "OnTestFeature": true, - "OffTestFeature": false, - "AlwaysOnTestFeature": { - "Telemetry": { - "Enabled": true, - "Metadata": { - "Tags.Tag1": "Tag1Value", - "Tags.Tag2": "Tag2Value", - "Etag": "EtagValue", - "Label": "LabelValue" - } - }, - "EnabledFor": [ - { - "Name": "AlwaysOn" - } - ] - }, - "OffTimeTestFeature": { - "Telemetry": { - "Enabled": true - }, - "EnabledFor": [ - { - "Name": "TimeWindow", - "Parameters": { - "End": "1970-01-01T00:00:00Z" - } - } - ] - }, - "FeatureUsesFiltersWithDuplicatedAlias": { - "RequirementType": "all", - "EnabledFor": [ - { - "Name": "DuplicatedFilterName" - }, - { - "Name": "Percentage", - "Parameters": { - "Value": 100 - } - } - ] - }, - "TargetingTestFeature": { - "EnabledFor": [ - { - "Name": "Targeting", - "Parameters": { - "Audience": { - "Users": [ - "Jeff", - "Alicia" - ], - "Groups": [ - { - "Name": "Ring0", - "RolloutPercentage": 100 - }, - { - "Name": "Ring1", - "RolloutPercentage": 50 - } - ], - "DefaultRolloutPercentage": 20 - } - } - } - ] - }, - "TargetingTestFeatureWithExclusion": { - "EnabledFor": [ - { - "Name": "Targeting", - "Parameters": { - "Audience": { - "Users": [ - "Jeff", - "Alicia" - ], - "Groups": [ - { - "Name": "Ring0", - "RolloutPercentage": 100 - }, - { - "Name": "Ring1", - "RolloutPercentage": 50 - } - ], - "DefaultRolloutPercentage": 20, - "Exclusion": { - "Users": [ - "Jeff" - ], - "Groups": [ - "Ring0", - "Ring2" - ] - } - } - } - } - ] - }, - "CustomFilterFeature": { - "EnabledFor": [ - { - "Name": "CustomTargetingFilter", - "Parameters": { - "Audience": { - "Users": [ - "Jeff" - ] - } - } - } - ] - }, - "ConditionalFeature": { - "EnabledFor": [ - { - "Name": "Test", - "Parameters": { - "P1": "V1" - } - } - ] - }, - "ConditionalFeature2": { - "EnabledFor": [ - { - "Name": "Test" - } - ] - }, - "ContextualFeature": { - "EnabledFor": [ - { - "Name": "ContextualTest", - "Parameters": { - "AllowedAccounts": [ - "abc" - ] - } - } - ] + "FeatureManagement": { + "OnTestFeature": true, + "OffTestFeature": false, + "AlwaysOnTestFeature": { + "Telemetry": { + "Enabled": true, + "Metadata": { + "Tags.Tag1": "Tag1Value", + "Tags.Tag2": "Tag2Value", + "Etag": "EtagValue", + "Label": "LabelValue" + } + }, + "EnabledFor": [ + { + "Name": "AlwaysOn" + } + ] + }, + "OffTimeTestFeature": { + "Telemetry": { + "Enabled": true + }, + "EnabledFor": [ + { + "Name": "TimeWindow", + "Parameters": { + "End": "1970-01-01T00:00:00Z" + } + } + ] + }, + "FeatureUsesFiltersWithDuplicatedAlias": { + "RequirementType": "all", + "EnabledFor": [ + { + "Name": "DuplicatedFilterName" }, - "AnyFilterFeature": { - "RequirementType": "Any", - "EnabledFor": [ - { - "Name": "Test", - "Parameters": { - "Id": "1" - } + { + "Name": "Percentage", + "Parameters": { + "Value": 100 + } + } + ] + }, + "TargetingTestFeature": { + "EnabledFor": [ + { + "Name": "Targeting", + "Parameters": { + "Audience": { + "Users": [ + "Jeff", + "Alicia" + ], + "Groups": [ + { + "Name": "Ring0", + "RolloutPercentage": 100 }, { - "Name": "Test", - "Parameters": { - "Id": "2" - } + "Name": "Ring1", + "RolloutPercentage": 50 } - ] - }, - "AllFilterFeature": { - "RequirementType": "all", - "EnabledFor": [ - { - "Name": "Test", - "Parameters": { - "Id": "1" - } + ], + "DefaultRolloutPercentage": 20 + } + } + } + ] + }, + "TargetingTestFeatureWithExclusion": { + "EnabledFor": [ + { + "Name": "Targeting", + "Parameters": { + "Audience": { + "Users": [ + "Jeff", + "Alicia" + ], + "Groups": [ + { + "Name": "Ring0", + "RolloutPercentage": 100 }, { - "Name": "Test", - "Parameters": { - "Id": "2" - } - } - ] - - }, - "VariantFeaturePercentileOn": { - "Telemetry": { - "Enabled": true - }, - "Allocation": { - "Percentile": [ - { - "Variant": "Big", - "From": 0, - "To": 50 - } - ], - "Seed": 1234 - }, - "Variants": [ - { - "Name": "Big", - "ConfigurationReference": "ShoppingCart:Big", - "StatusOverride": "Disabled" - } - ], - "EnabledFor": [ - { - "Name": "On" + "Name": "Ring1", + "RolloutPercentage": 50 } - ] - }, - "VariantFeaturePercentileOff": { - "Telemetry": { - "Enabled": true - }, - "Allocation": { - "Percentile": [ - { - "Variant": "Big", - "From": 0, - "To": 50 - } + ], + "DefaultRolloutPercentage": 20, + "Exclusion": { + "Users": [ + "Jeff" ], - "Seed": 12345 - }, - "Variants": [ - { - "Name": "Big", - "ConfigurationReference": "ShoppingCart:Big" - } - ], - "EnabledFor": [ - { - "Name": "On" - } - ] - }, - "VariantFeatureAlwaysOff": { - "Telemetry": { - "Enabled": true - }, - "Allocation": { - "Percentile": [ - { - "Variant": "Big", - "From": 0, - "To": 100 - } - ], - "Seed": 12345 - }, - "Variants": [ - { - "Name": "Big", - "ConfigurationReference": "ShoppingCart:Big" - } - ], - "EnabledFor": [] - }, - "VariantFeatureStatusDisabled": { - "Status": "Disabled", - "Telemetry": { - "Enabled": true - }, - "Allocation": { - "DefaultWhenDisabled": "Small" - }, - "Variants": [ - { - "Name": "Small", - "ConfigurationValue": "300px" - } - ], - "EnabledFor": [ - { - "Name": "On" - } - ] - }, - "VariantFeatureDefaultEnabled": { - "Telemetry": { - "Enabled": true - }, - "Allocation": { - "DefaultWhenEnabled": "Medium", - "User": [ - { - "Variant": "Small", - "Users": [ - "Jeff" - ] - } + "Groups": [ + "Ring0", + "Ring2" ] - }, - "Variants": [ - { - "Name": "Medium", - "ConfigurationValue": { - "Size": "450px", - "Color": "Purple" - } - }, - { - "Name": "Small", - "ConfigurationValue": "300px" - } - ], - "EnabledFor": [ - { - "Name": "On" - } - ] - }, - "VariantFeatureUser": { - "Telemetry": { - "Enabled": true - }, - "Allocation": { - "User": [ - { - "Variant": "Small", - "Users": [ - "Marsha" - ] - } - ] - }, - "Variants": [ - { - "Name": "Small", - "ConfigurationValue": "300px" - } - ], - "EnabledFor": [ - { - "Name": "On" - } - ] - }, - "VariantFeatureGroup": { - "Telemetry": { - "Enabled": true - }, - "Allocation": { - "User": [ - { - "Variant": "Small", - "Users": [ - "Jeff" - ] - } - ], - "Group": [ - { - "Variant": "Small", - "Groups": [ - "Group1" - ] - } - ] - }, - "Variants": [ - { - "Name": "Small", - "ConfigurationValue": "300px" - } - ], - "EnabledFor": [ - { - "Name": "On" - } + } + } + } + } + ] + }, + "CustomFilterFeature": { + "EnabledFor": [ + { + "Name": "CustomTargetingFilter", + "Parameters": { + "Audience": { + "Users": [ + "Jeff" + ] + } + } + } + ] + }, + "ConditionalFeature": { + "EnabledFor": [ + { + "Name": "Test", + "Parameters": { + "P1": "V1" + } + } + ] + }, + "ConditionalFeature2": { + "EnabledFor": [ + { + "Name": "Test" + } + ] + }, + "ContextualFeature": { + "EnabledFor": [ + { + "Name": "ContextualTest", + "Parameters": { + "AllowedAccounts": [ + "abc" ] + } + } + ] + }, + "AnyFilterFeature": { + "RequirementType": "Any", + "EnabledFor": [ + { + "Name": "Test", + "Parameters": { + "Id": "1" + } }, - "VariantFeatureNoVariants": { - "Allocation": { - "User": [ - { - "Variant": "Small", - "Users": [ - "Marsha" - ] - } - ] - }, - "Variants": [], - "EnabledFor": [ - { - "Name": "On" - } - ] + { + "Name": "Test", + "Parameters": { + "Id": "2" + } + } + ] + }, + "AllFilterFeature": { + "RequirementType": "all", + "EnabledFor": [ + { + "Name": "Test", + "Parameters": { + "Id": "1" + } }, - "VariantFeatureBothConfigurations": { - "Allocation": { - "DefaultWhenEnabled": "Small" - }, - "Variants": [ - { - "Name": "Small", - "ConfigurationValue": "600px", - "ConfigurationReference": "ShoppingCart:Small" - } - ], - "EnabledFor": [ - { - "Name": "On" - } + { + "Name": "Test", + "Parameters": { + "Id": "2" + } + } + ] + + }, + "VariantFeaturePercentileOn": { + "Telemetry": { + "Enabled": true + }, + "Allocation": { + "Percentile": [ + { + "Variant": "Big", + "From": 0, + "To": 50 + } + ], + "Seed": 1234 + }, + "Variants": [ + { + "Name": "Big", + "ConfigurationReference": "ShoppingCart:Big", + "StatusOverride": "Disabled" + } + ], + "EnabledFor": [ + { + "Name": "On" + } + ] + }, + "VariantFeaturePercentileOff": { + "Telemetry": { + "Enabled": true + }, + "Allocation": { + "Percentile": [ + { + "Variant": "Big", + "From": 0, + "To": 50 + } + ], + "Seed": 12345 + }, + "Variants": [ + { + "Name": "Big", + "ConfigurationReference": "ShoppingCart:Big" + } + ], + "EnabledFor": [ + { + "Name": "On" + } + ] + }, + "VariantFeatureAlwaysOff": { + "Telemetry": { + "Enabled": true + }, + "Allocation": { + "Percentile": [ + { + "Variant": "Big", + "From": 0, + "To": 100 + } + ], + "Seed": 12345 + }, + "Variants": [ + { + "Name": "Big", + "ConfigurationReference": "ShoppingCart:Big" + } + ], + "EnabledFor": [] + }, + "VariantFeatureStatusDisabled": { + "Status": "Disabled", + "Telemetry": { + "Enabled": true + }, + "Allocation": { + "DefaultWhenDisabled": "Small" + }, + "Variants": [ + { + "Name": "Small", + "ConfigurationValue": "300px" + } + ], + "EnabledFor": [ + { + "Name": "On" + } + ] + }, + "VariantFeatureDefaultEnabled": { + "Telemetry": { + "Enabled": true + }, + "Allocation": { + "DefaultWhenEnabled": "Medium", + "User": [ + { + "Variant": "Small", + "Users": [ + "Jeff" ] + } + ] + }, + "Variants": [ + { + "Name": "Medium", + "ConfigurationValue": { + "Size": "450px", + "Color": "Purple" + } }, - "VariantFeatureNoAllocation": { - "Telemetry": { - "Enabled": true - }, - "Variants": [ - { - "Name": "Small", - "ConfigurationValue": "300px" - } - ], - "EnabledFor": [ - { - "Name": "On" - } + { + "Name": "Small", + "ConfigurationValue": "300px" + } + ], + "EnabledFor": [ + { + "Name": "On" + } + ] + }, + "VariantFeatureUser": { + "Telemetry": { + "Enabled": true + }, + "Allocation": { + "User": [ + { + "Variant": "Small", + "Users": [ + "Marsha" ] - }, - "VariantFeatureAlwaysOffNoAllocation": { - "Telemetry": { - "Enabled": true - }, - "Variants": [ - { - "Name": "Small", - "ConfigurationValue": "300px" - } - ], - "EnabledFor": [ + } + ] + }, + "Variants": [ + { + "Name": "Small", + "ConfigurationValue": "300px" + } + ], + "EnabledFor": [ + { + "Name": "On" + } + ] + }, + "VariantFeatureGroup": { + "Telemetry": { + "Enabled": true + }, + "Allocation": { + "User": [ + { + "Variant": "Small", + "Users": [ + "Jeff" ] - }, - "VariantFeatureInvalidStatusOverride": { - "Allocation": { - "DefaultWhenEnabled": "Small" - }, - "Variants": [ - { - "Name": "Small", - "ConfigurationValue": "300px", - "StatusOverride": "InvalidValue" - } - ], - "EnabledFor": [ - { - "Name": "On" - } + } + ], + "Group": [ + { + "Variant": "Small", + "Groups": [ + "Group1" ] - }, - "VariantFeatureInvalidFromTo": { - "Allocation": { - "Percentile": [ - { - "Variant": "Small", - "From": "Invalid", - "To": "Invalid" - } - ] - }, - "Variants": [ - { - "Name": "Small", - "ConfigurationReference": "ShoppingCart:Small" - } - ], - "EnabledFor": [ - { - "Name": "On" - } + } + ] + }, + "Variants": [ + { + "Name": "Small", + "ConfigurationValue": "300px" + } + ], + "EnabledFor": [ + { + "Name": "On" + } + ] + }, + "VariantFeatureNoVariants": { + "Allocation": { + "User": [ + { + "Variant": "Small", + "Users": [ + "Marsha" ] + } + ] + }, + "Variants": [], + "EnabledFor": [ + { + "Name": "On" + } + ] + }, + "VariantFeatureBothConfigurations": { + "Allocation": { + "DefaultWhenEnabled": "Small" + }, + "Variants": [ + { + "Name": "Small", + "ConfigurationValue": "600px", + "ConfigurationReference": "ShoppingCart:Small" + } + ], + "EnabledFor": [ + { + "Name": "On" + } + ] + }, + "VariantFeatureNoAllocation": { + "Telemetry": { + "Enabled": true + }, + "Variants": [ + { + "Name": "Small", + "ConfigurationValue": "300px" + } + ], + "EnabledFor": [ + { + "Name": "On" + } + ] + }, + "VariantFeatureAlwaysOffNoAllocation": { + "Telemetry": { + "Enabled": true + }, + "Variants": [ + { + "Name": "Small", + "ConfigurationValue": "300px" + } + ], + "EnabledFor": [ + ] + }, + "VariantFeatureInvalidStatusOverride": { + "Allocation": { + "DefaultWhenEnabled": "Small" + }, + "Variants": [ + { + "Name": "Small", + "ConfigurationValue": "300px", + "StatusOverride": "InvalidValue" + } + ], + "EnabledFor": [ + { + "Name": "On" + } + ] + }, + "VariantFeatureInvalidFromTo": { + "Allocation": { + "Percentile": [ + { + "Variant": "Small", + "From": "Invalid", + "To": "Invalid" + } + ] + }, + "Variants": [ + { + "Name": "Small", + "ConfigurationReference": "ShoppingCart:Small" + } + ], + "EnabledFor": [ + { + "Name": "On" } + ] } + } } \ No newline at end of file