Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for feature management variants and telemetry #476

Merged
merged 18 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
3a30965
in progress set up feature flag structure for variants and allocation
amerjusupovic Oct 4, 2023
fdc508c
complete structure for feature flag variants and allocation, parsing …
amerjusupovic Oct 5, 2023
d1e25b1
upgrade version of sdk package, update to new names for each variable
amerjusupovic Oct 20, 2023
67fffe1
Merge branch 'preview' of https://github.com/Azure/AppConfiguration-D…
amerjusupovic Oct 20, 2023
6c70f9c
fix json property names
amerjusupovic Oct 20, 2023
f857c28
fix constants to match variable names
amerjusupovic Oct 23, 2023
770263f
add json properties for telemetry
amerjusupovic Oct 23, 2023
ea88139
add telemetry metadata parsing, add tests for both variants and telem…
amerjusupovic Oct 23, 2023
d2f1cf0
don't add telemetry enabled value when false
amerjusupovic Oct 23, 2023
5c8f349
Merge branch 'preview' of https://github.com/Azure/AppConfiguration-D…
amerjusupovic Oct 24, 2023
b4b11b6
Merge branch 'preview' into ajusupovic/support-variants
amerjusupovic Nov 21, 2023
0f271f6
update to include telemetry section from new schema
amerjusupovic Jan 10, 2024
b11b588
Merge branch 'preview' into ajusupovic/support-variants
amerjusupovic Jan 10, 2024
86fc7a2
use constants for properties
amerjusupovic Jan 11, 2024
0ed38f0
rename variables for clarity
amerjusupovic Jan 11, 2024
ed93adb
use variable for reused paths
amerjusupovic Jan 16, 2024
4434b83
use ienumerable over list
amerjusupovic Jan 17, 2024
442cd51
Merge branch 'preview' into ajusupovic/support-variants
amerjusupovic Jan 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using System.Collections.Generic;
using System.Text.Json.Serialization;

namespace Microsoft.Extensions.Configuration.AzureAppConfiguration.FeatureManagement
{
internal class FeatureAllocation
{
[JsonPropertyName("default_when_disabled")]
public string DefaultWhenDisabled { get; set; }

[JsonPropertyName("default_when_enabled")]
public string DefaultWhenEnabled { get; set; }

[JsonPropertyName("user")]
public IEnumerable<FeatureUserAllocation> User { get; set; }

[JsonPropertyName("group")]
public IEnumerable<FeatureGroupAllocation> Group { get; set; }

[JsonPropertyName("percentile")]
public IEnumerable<FeaturePercentileAllocation> Percentile { get; set; }

[JsonPropertyName("seed")]
public string Seed { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using System.Collections.Generic;
using System.Text.Json.Serialization;

namespace Microsoft.Extensions.Configuration.AzureAppConfiguration.FeatureManagement
Expand All @@ -15,5 +16,14 @@ internal class FeatureFlag

[JsonPropertyName("conditions")]
public FeatureConditions Conditions { get; set; }

[JsonPropertyName("variants")]
public IEnumerable<FeatureVariant> Variants { get; set; }

[JsonPropertyName("allocation")]
public FeatureAllocation Allocation { get; set; }

[JsonPropertyName("telemetry")]
public FeatureTelemetry Telemetry { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using System.Collections.Generic;
using System.Text.Json.Serialization;

namespace Microsoft.Extensions.Configuration.AzureAppConfiguration.FeatureManagement
{
internal class FeatureGroupAllocation
{
[JsonPropertyName("variant")]
public string Variant { get; set; }

[JsonPropertyName("groups")]
public IEnumerable<string> Groups { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,27 @@ internal class FeatureManagementConstants
public const string ContentType = "application/vnd.microsoft.appconfig.ff+json";
public const string SectionName = "FeatureManagement";
public const string EnabledFor = "EnabledFor";
public const string Variants = "Variants";
public const string Allocation = "Allocation";
public const string User = "User";
public const string Group = "Group";
public const string Percentile = "Percentile";
public const string Telemetry = "Telemetry";
public const string Enabled = "Enabled";
public const string Metadata = "Metadata";
public const string RequirementType = "RequirementType";
public const string Name = "Name";
public const string Parameters = "Parameters";
public const string Variant = "Variant";
public const string ConfigurationValue = "ConfigurationValue";
public const string ConfigurationReference = "ConfigurationReference";
public const string StatusOverride = "StatusOverride";
public const string DefaultWhenDisabled = "DefaultWhenDisabled";
public const string DefaultWhenEnabled = "DefaultWhenEnabled";
public const string Users = "Users";
public const string Groups = "Groups";
public const string From = "From";
public const string To = "To";
public const string Seed = "Seed";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ public Task<IEnumerable<KeyValuePair<string, string>>> ProcessKeyValue(Configura

var keyValues = new List<KeyValuePair<string, string>>();

string featureFlagPath = $"{FeatureManagementConstants.SectionName}:{featureFlag.Id}";

if (featureFlag.Enabled)
{
//if (featureFlag.Conditions?.ClientFilters == null)
if (featureFlag.Conditions?.ClientFilters == null || !featureFlag.Conditions.ClientFilters.Any()) // workaround since we are not yet setting client filters to null
{
//
// Always on
keyValues.Add(new KeyValuePair<string, string>($"{FeatureManagementConstants.SectionName}:{featureFlag.Id}", true.ToString()));
keyValues.Add(new KeyValuePair<string, string>(featureFlagPath, true.ToString()));
}
else
{
Expand All @@ -44,11 +46,13 @@ public Task<IEnumerable<KeyValuePair<string, string>>> ProcessKeyValue(Configura
{
ClientFilter clientFilter = featureFlag.Conditions.ClientFilters[i];

keyValues.Add(new KeyValuePair<string, string>($"{FeatureManagementConstants.SectionName}:{featureFlag.Id}:{FeatureManagementConstants.EnabledFor}:{i}:Name", clientFilter.Name));
string clientFiltersPath = $"{featureFlagPath}:{FeatureManagementConstants.EnabledFor}:{i}";

keyValues.Add(new KeyValuePair<string, string>($"{clientFiltersPath}:{FeatureManagementConstants.Name}", clientFilter.Name));

foreach (KeyValuePair<string, string> kvp in new JsonFlattener().FlattenJson(clientFilter.Parameters))
{
keyValues.Add(new KeyValuePair<string, string>($"{FeatureManagementConstants.SectionName}:{featureFlag.Id}:{FeatureManagementConstants.EnabledFor}:{i}:Parameters:{kvp.Key}", kvp.Value));
keyValues.Add(new KeyValuePair<string, string>($"{clientFiltersPath}:{FeatureManagementConstants.Parameters}:{kvp.Key}", kvp.Value));
}
}

Expand All @@ -57,7 +61,7 @@ public Task<IEnumerable<KeyValuePair<string, string>>> ProcessKeyValue(Configura
if (featureFlag.Conditions.RequirementType != null)
{
keyValues.Add(new KeyValuePair<string, string>(
$"{FeatureManagementConstants.SectionName}:{featureFlag.Id}:{FeatureManagementConstants.RequirementType}",
$"{featureFlagPath}:{FeatureManagementConstants.RequirementType}",
featureFlag.Conditions.RequirementType));
}
}
Expand All @@ -67,6 +71,135 @@ public Task<IEnumerable<KeyValuePair<string, string>>> ProcessKeyValue(Configura
keyValues.Add(new KeyValuePair<string, string>($"{FeatureManagementConstants.SectionName}:{featureFlag.Id}", false.ToString()));
}

if (featureFlag.Variants != null)
{
int i = 0;

foreach (FeatureVariant featureVariant in featureFlag.Variants)
{
string variantsPath = $"{featureFlagPath}:{FeatureManagementConstants.Variants}:{i}";

keyValues.Add(new KeyValuePair<string, string>($"{variantsPath}:{FeatureManagementConstants.Name}", featureVariant.Name));

if (featureVariant.ConfigurationValue != null)
{
keyValues.Add(new KeyValuePair<string, string>($"{variantsPath}:{FeatureManagementConstants.ConfigurationValue}", featureVariant.ConfigurationValue));
}

if (featureVariant.ConfigurationReference != null)
{
keyValues.Add(new KeyValuePair<string, string>($"{variantsPath}:{FeatureManagementConstants.ConfigurationReference}", featureVariant.ConfigurationReference));
}

if (featureVariant.StatusOverride != null)
{
keyValues.Add(new KeyValuePair<string, string>($"{variantsPath}:{FeatureManagementConstants.StatusOverride}", featureVariant.StatusOverride));
}

i++;
}
}

if (featureFlag.Allocation != null)
{
FeatureAllocation allocation = featureFlag.Allocation;

string allocationPath = $"{featureFlagPath}:{FeatureManagementConstants.Allocation}";

if (allocation.DefaultWhenDisabled != null)
{
keyValues.Add(new KeyValuePair<string, string>($"{allocationPath}:{FeatureManagementConstants.DefaultWhenDisabled}", allocation.DefaultWhenDisabled));
}

if (allocation.DefaultWhenEnabled != null)
{
keyValues.Add(new KeyValuePair<string, string>($"{allocationPath}:{FeatureManagementConstants.DefaultWhenEnabled}", allocation.DefaultWhenEnabled));
}

if (allocation.User != null)
{
int i = 0;

foreach (FeatureUserAllocation userAllocation in allocation.User)
{
keyValues.Add(new KeyValuePair<string, string>($"{allocationPath}:{FeatureManagementConstants.User}:{i}:{FeatureManagementConstants.Variant}", userAllocation.Variant));

int j = 0;

foreach (string user in userAllocation.Users)
{
keyValues.Add(new KeyValuePair<string, string>($"{allocationPath}:{FeatureManagementConstants.User}:{i}:{FeatureManagementConstants.Users}:{j}", user));

j++;
}

i++;
}
}

if (allocation.Group != null)
{
int i = 0;

foreach (FeatureGroupAllocation groupAllocation in allocation.Group)
{
keyValues.Add(new KeyValuePair<string, string>($"{allocationPath}:{FeatureManagementConstants.Group}:{i}:{FeatureManagementConstants.Variant}", groupAllocation.Variant));

int j = 0;

foreach (string group in groupAllocation.Groups)
{
keyValues.Add(new KeyValuePair<string, string>($"{allocationPath}:{FeatureManagementConstants.Group}:{i}:{FeatureManagementConstants.Groups}:{j}", group));

j++;
}

i++;
}
}

if (allocation.Percentile != null)
{
int i = 0;

foreach (FeaturePercentileAllocation percentileAllocation in allocation.Percentile)
{
keyValues.Add(new KeyValuePair<string, string>($"{allocationPath}:{FeatureManagementConstants.Percentile}:{i}:{FeatureManagementConstants.Variant}", percentileAllocation.Variant));

keyValues.Add(new KeyValuePair<string, string>($"{allocationPath}:{FeatureManagementConstants.Percentile}:{i}:{FeatureManagementConstants.From}", percentileAllocation.From.ToString()));

keyValues.Add(new KeyValuePair<string, string>($"{allocationPath}:{FeatureManagementConstants.Percentile}:{i}:{FeatureManagementConstants.To}", percentileAllocation.To.ToString()));

i++;
}
}

if (allocation.Seed != null)
{
keyValues.Add(new KeyValuePair<string, string>($"{allocationPath}:{FeatureManagementConstants.Seed}", allocation.Seed));
}
}

if (featureFlag.Telemetry != null)
{
FeatureTelemetry telemetry = featureFlag.Telemetry;

string telemetryPath = $"{featureFlagPath}:{FeatureManagementConstants.Telemetry}";

if (telemetry.Enabled)
{
keyValues.Add(new KeyValuePair<string, string>($"{telemetryPath}:{FeatureManagementConstants.Enabled}", telemetry.Enabled.ToString()));
}

if (telemetry.Metadata != null)
{
foreach (KeyValuePair<string, string> kvp in telemetry.Metadata)
{
keyValues.Add(new KeyValuePair<string, string>($"{telemetryPath}:{FeatureManagementConstants.Metadata}:{kvp.Key}", kvp.Value));
}
}
}

return Task.FromResult<IEnumerable<KeyValuePair<string, string>>>(keyValues);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using System.Collections.Generic;
using System.Text.Json.Serialization;

namespace Microsoft.Extensions.Configuration.AzureAppConfiguration.FeatureManagement
{
internal class FeaturePercentileAllocation
{
[JsonPropertyName("variant")]
public string Variant { get; set; }

[JsonPropertyName("from")]
public double From { get; set; }

[JsonPropertyName("to")]
public double To { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//

using System.Collections.Generic;
using System.Text.Json.Serialization;

namespace Microsoft.Extensions.Configuration.AzureAppConfiguration.FeatureManagement
{
internal class FeatureTelemetry
{
[JsonPropertyName("enabled")]
public bool Enabled { get; set; }

[JsonPropertyName("metadata")]
public IReadOnlyDictionary<string, string> Metadata { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using System.Collections.Generic;
using System.Text.Json.Serialization;

namespace Microsoft.Extensions.Configuration.AzureAppConfiguration.FeatureManagement
{
internal class FeatureUserAllocation
{
[JsonPropertyName("variant")]
public string Variant { get; set; }

[JsonPropertyName("users")]
public IEnumerable<string> Users { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using System.Text.Json.Serialization;

namespace Microsoft.Extensions.Configuration.AzureAppConfiguration.FeatureManagement
{
internal class FeatureVariant
{
[JsonPropertyName("name")]
public string Name { get; set; }

[JsonPropertyName("configuration_value")]
public string ConfigurationValue { get; set; }

[JsonPropertyName("configuration_reference")]
public string ConfigurationReference { get; set; }

[JsonPropertyName("status_override")]
public string StatusOverride { get; set; }
}
}
Loading