From 898630f5c01c5f0cf33231ae51e9489625833885 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Tue, 19 Sep 2023 17:11:01 -0700 Subject: [PATCH 01/14] update readme with variants, first draft --- README.md | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 148 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 49c2ec6f..6832cf16 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ The feature management library supports appsettings.json as a feature flag sourc } ``` -The `FeatureManagement` section of the json document is used by convention to load feature flag settings. In the section above, we see that we have provided three different features. Features define their feature filters using the `EnabledFor` property. In the feature filters for `FeatureT` we see `AlwaysOn`. This feature filter is built-in and if specified will always enable the feature. The `AlwaysOn` feature filter does not require any configuration so it only has the `Name` property. `FeatureU` has no filters in its `EnabledFor` property and thus will never be enabled. Any functionality that relies on this feature being enabled will not be accessible as long as the feature filters remain empty. However, as soon as a feature filter is added that enables the feature it can begin working. `FeatureV` specifies a feature filter named `TimeWindow`. This is an example of a configurable feature filter. We can see in the example that the filter has a `Parameters` property. This is used to configure the filter. In this case, the start and end times for the feature to be active are configured. +The `FeatureManagement` section of the json document is used by convention to load feature flag settings. In the section above, we see that we have provided three different features. Features define their feature filters using the `EnabledFor` property. In the feature filters for `FeatureT` we see `AlwaysOn`. This feature filter is built-in and if specified will always enable the feature. The `AlwaysOn` feature filter does not require any configuration so it only has the `Name` property. The alternative name for this filter is `On`. `FeatureU` has no filters in its `EnabledFor` property and thus will never be enabled. Any functionality that relies on this feature being enabled will not be accessible as long as the feature filters remain empty. However, as soon as a feature filter is added that enables the feature it can begin working. `FeatureV` specifies a feature filter named `TimeWindow`. This is an example of a configurable feature filter. We can see in the example that the filter has a `Parameters` property. This is used to configure the filter. In this case, the start and end times for the feature to be active are configured. ### On/Off Declaration @@ -139,6 +139,23 @@ A `RequirementType` of `All` changes the traversal. First, if there are no filte In the above example, `FeatureW` specifies a `RequirementType` of `All`, meaning all of it's filters must evaluate to true for the feature to be enabled. In this case, the feature will be enabled for 50% of users during the specified time window. +### Status + +The `Status` property of a feature flag determines whether the remaining feature definition should be evaluated. `Status` is equal to `Conditional` by default, so the feature definition will determine whether the flag is enabled or which variant to return. If `Status` is equal to `Disabled`, then the feature flag is always considered disabled and will always return the `DefaultWhenDisabled` variant if specified. + +``` +"FeatureX": { + "Status": "Disabled", + "EnabledFor": [ + { + "Name": "On" + } + ] +} +``` + +In this example, even though the `On` filter would normally always make the feature enabled, the `Status` property is set to `Disabled`, so this feature will always be disabled. + ### Referencing To make it easier to reference these feature flags in code, we recommend to define feature flag variables like below. @@ -636,6 +653,136 @@ When defining an Audience, users and groups can be excluded from the audience. T In the above example, the feature will be enabled for users named `Jeff` and `Alicia`. It will also be enabled for users in the group named `Ring0`. However, if the user is named `Mark`, the feature will be disabled, regardless if they are in the group `Ring0` or not. Exclusions take priority over the rest of the targeting filter. +## Variants + +Variants represent a configuration of a feature. A feature flag with variants contains a list of variants and assignment parameters dictating under what conditions each variant should be used. A variant's configuration can be a complex object, but it can also be as simple as a string or number. + +``` C# +public class Variant +{ + /// + /// The name of the variant. + /// + public string Name { get; set; } + + /// + /// The configuration of the variant. + /// + public IConfigurationSection Configuration { get; set; } +} +``` + +### Getting a Feature's Variant + +Variants expand on the basic flow of feature management and enable getting a feature's variant to later perform actions based on the name or configuration. This is done through the `IVariantFeatureManager`'s `GetVariantAsync` method. + +``` C# +… +IVariantFeatureManager featureManager; +… +Variant variant = await featureManager.GetVariantAsync(nameof(MyFeatureFlags.FeatureU)); + +IConfigurationSection variantConfiguration = variant.Configuration; + +// Do something with the resulting variant and its configuration +``` + +### Setting a Variant's Configuration + +For each of the variants in the `Variants` property of a feature, there is a specified configuration. This can be set using either the `ConfigurationReference` or `ConfigurationValue` properties. `ConfigurationReference` is a string path that references a section of the current configuration. `ConfigurationValue` is an inline configuration. If both are specified, `ConfigurationValue` is used. + +``` +"Variants": [ + { + "Name": "Big", + "ConfigurationReference": "ShoppingCart:Big" + }, + { + "Name": "Small", + "ConfigurationValue": { + "Size": 300 + } + } +] +``` + +### Assigning a Variant + +The process of deciding which variant to return for a feature is called assignment. Assignment is determined by the `Allocation` property of the feature. Allocation logic is similar to the [Microsoft.Targeting](./README.md#MicrosoftTargeting) feature filter, with some additional parameters to allow for more specific variant assignment. + +``` +"Allocation": { + "DefaultWhenEnabled": "Small", + "DefaultWhenDisabled": "Small", + "User": [ + { + "Variant": "Big", + "Users": [ + "Marsha" + ] + } + ], + "Group": [ + { + "Variant": "Big", + "Groups": [ + "Ring1" + ] + } + ], + "Percentile": [ + { + "Variant": "Big", + "From": 0, + "To": 10 + } + ], + "Seed": "13973240" +}, +"Variants": [ + { + "Name": "Big", + "ConfigurationReference": "ShoppingCart:Big" + }, + { + "Name": "Small", + "ConfigurationValue": { + "Size": 300 + } + } +] +``` + +In the above example, if the feature is not enabled, `GetVariantAsync` would return the variant allocated by `DefaultWhenDisabled`, which is `Small` in this case. + +If the feature is enabled, the feature manager will check the `User`, `Group`, and `Percentile` allocations in that order to see if they match the targeting context or calculated percentile for that context. If they match, then the specified variant is returned for that allocation. If none of these allocations match the targeting context, the `DefaultWhenEnabled` variant is returned. + +### StatusOverride + +The `StatusOverride` property, if set for the assigned variant, overrides a feature's state. By default, this property is equal to `None`, which does not affect the feature state. If `StatusOverride` is equal to `Enabled` for the assigned variant, then the feature is enabled. If `StatusOverride` is equal to `Disabled` for the assigned variant, then the feature is disabled. + +``` +"Allocation": { + "DefaultWhenEnabled": "OffVariant" +}, +"Variants": [ + { + "Name": "OffVariant", + "ConfigurationValue": { + "Size": 300 + }, + "StatusOverride": "Disabled" + } +], +"EnabledFor": [ + { + "Name": "On" + } +] +``` + +In the above example, the feature is enabled by the `On` filter and assigns the variant set for `DefaultWhenEnabled`, which is the `OffVariant` variant. The `OffVariant` variant has the `StatusOverride` property set to `Disabled`, so calling `IsEnabledAsync` for this feature will always return disabled, even though the feature would otherwise be enabled by its filters. + ## Caching Feature state is provided by the IConfiguration system. Any caching and dynamic updating is expected to be handled by configuration providers. The feature manager asks IConfiguration for the latest value of a feature's state whenever a feature is checked to be enabled. From 89c13ad5e1b92e2d0cb3fed77589b01cb901fa3c Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Wed, 20 Sep 2023 10:36:21 -0700 Subject: [PATCH 02/14] small summary revisions --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6832cf16..3bcd491e 100644 --- a/README.md +++ b/README.md @@ -689,7 +689,7 @@ IConfigurationSection variantConfiguration = variant.Configuration; ### Setting a Variant's Configuration -For each of the variants in the `Variants` property of a feature, there is a specified configuration. This can be set using either the `ConfigurationReference` or `ConfigurationValue` properties. `ConfigurationReference` is a string path that references a section of the current configuration. `ConfigurationValue` is an inline configuration. If both are specified, `ConfigurationValue` is used. +For each of the variants in the `Variants` property of a feature, there is a specified configuration. This can be set using either the `ConfigurationReference` or `ConfigurationValue` properties. `ConfigurationReference` is a string path that references a section of the current configuration that contains the feature flag declaration. `ConfigurationValue` is an inline configuration. If both are specified, `ConfigurationValue` is used. ``` "Variants": [ @@ -755,7 +755,7 @@ The process of deciding which variant to return for a feature is called assignme In the above example, if the feature is not enabled, `GetVariantAsync` would return the variant allocated by `DefaultWhenDisabled`, which is `Small` in this case. -If the feature is enabled, the feature manager will check the `User`, `Group`, and `Percentile` allocations in that order to see if they match the targeting context or calculated percentile for that context. If they match, then the specified variant is returned for that allocation. If none of these allocations match the targeting context, the `DefaultWhenEnabled` variant is returned. +If the feature is enabled, the feature manager will check the `User`, `Group`, and `Percentile` allocations in that order to see if they match the targeting context or calculated percentile for that context. If the user is named `Marsha`, in the group named `Ring1`, or the user happens to fall between the 0 and 10th percentile calculated with the given `Seed`, then the specified variant is returned for that allocation. If none of these allocations match the targeting context, the `DefaultWhenEnabled` variant is returned. ### StatusOverride From 12728da3c9adc2a66726e82c37203fce4534aa2e Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Wed, 20 Sep 2023 10:37:31 -0700 Subject: [PATCH 03/14] fix wording of summary --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3bcd491e..4f59c6f2 100644 --- a/README.md +++ b/README.md @@ -781,7 +781,7 @@ The `StatusOverride` property, if set for the assigned variant, overrides a feat ] ``` -In the above example, the feature is enabled by the `On` filter and assigns the variant set for `DefaultWhenEnabled`, which is the `OffVariant` variant. The `OffVariant` variant has the `StatusOverride` property set to `Disabled`, so calling `IsEnabledAsync` for this feature will always return disabled, even though the feature would otherwise be enabled by its filters. +In the above example, the feature is enabled by the `On` filter and assigns the variant set for `DefaultWhenEnabled`, which is the `OffVariant` variant. The `OffVariant` variant has the `StatusOverride` property set to `Disabled`, so calling `IsEnabledAsync` for this feature will return disabled, even though the feature would otherwise be enabled by its filters. ## Caching From 18d63eaef72c58ead57a3b3148f7478c26774cea Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Wed, 20 Sep 2023 10:39:24 -0700 Subject: [PATCH 04/14] fix code description --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4f59c6f2..4dfec3c9 100644 --- a/README.md +++ b/README.md @@ -755,7 +755,7 @@ The process of deciding which variant to return for a feature is called assignme In the above example, if the feature is not enabled, `GetVariantAsync` would return the variant allocated by `DefaultWhenDisabled`, which is `Small` in this case. -If the feature is enabled, the feature manager will check the `User`, `Group`, and `Percentile` allocations in that order to see if they match the targeting context or calculated percentile for that context. If the user is named `Marsha`, in the group named `Ring1`, or the user happens to fall between the 0 and 10th percentile calculated with the given `Seed`, then the specified variant is returned for that allocation. If none of these allocations match the targeting context, the `DefaultWhenEnabled` variant is returned. +If the feature is enabled, the feature manager will check the `User`, `Group`, and `Percentile` allocations in that order to see if they match the targeting context or calculated percentile for that context. If the user is named `Marsha`, in the group named `Ring1`, or the user happens to fall between the 0 and 10th percentile calculated with the given `Seed`, then the specified variant is returned for that allocation. In this case, all of these would return the `Big` variant. If none of these allocations match the targeting context, the `DefaultWhenEnabled` variant is returned. ### StatusOverride From 57373e12425254c54241f8607f352a588a11d2c7 Mon Sep 17 00:00:00 2001 From: Amer Jusupovic <32405726+amerjusupovic@users.noreply.github.com> Date: Thu, 28 Sep 2023 12:03:33 -0700 Subject: [PATCH 05/14] Apply suggestions from code review Co-authored-by: Jimmy Campbell --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4dfec3c9..414ff444 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,8 @@ In the above example, `FeatureW` specifies a `RequirementType` of `All`, meaning ### Status -The `Status` property of a feature flag determines whether the remaining feature definition should be evaluated. `Status` is equal to `Conditional` by default, so the feature definition will determine whether the flag is enabled or which variant to return. If `Status` is equal to `Disabled`, then the feature flag is always considered disabled and will always return the `DefaultWhenDisabled` variant if specified. + +`Status` is an optional property of a feature flag that controls how a flags enabled state is evaluated. By default, the status of a flag is `Conditional`, meaning that feature filters should be evaluated to determine if the flag is enabled. If the `Status` of a flag is set to `Disabled` then feature filters are not evaluated and the flag is always considered to be disabled. ``` "FeatureX": { @@ -655,7 +656,7 @@ In the above example, the feature will be enabled for users named `Jeff` and `Al ## Variants -Variants represent a configuration of a feature. A feature flag with variants contains a list of variants and assignment parameters dictating under what conditions each variant should be used. A variant's configuration can be a complex object, but it can also be as simple as a string or number. +Variants enable a feature flag to become more than a simple on/off flag. A variant represents a value of a feature flag that can be a string, a number, or even a configuration object. A feature flag that declares variants should define under what circumstances each variant should be used which is covered in greater detail in the 'Assigning a variant' section. ``` C# public class Variant @@ -680,7 +681,7 @@ Variants expand on the basic flow of feature management and enable getting a fea … IVariantFeatureManager featureManager; … -Variant variant = await featureManager.GetVariantAsync(nameof(MyFeatureFlags.FeatureU)); +Variant variant = await featureManager.GetVariantAsync(MyFeatureFlags.FeatureU); IConfigurationSection variantConfiguration = variant.Configuration; From 6ee9de881248d8300e04924a51b5904558ca5cf8 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Thu, 28 Sep 2023 12:04:20 -0700 Subject: [PATCH 06/14] revisions --- README.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4dfec3c9..81b48bf5 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Here are some of the benefits of using this library: * [Built-in Feature Filters](#built-in-Feature-Filters) * [Targeting](#targeting) * [Targeting Exclusion](#targeting-exclusion) +* [Variants](#variants) * [Caching](#caching) * [Custom Feature Providers](#custom-feature-providers) @@ -64,7 +65,7 @@ The feature management library supports appsettings.json as a feature flag sourc "FeatureT": { "EnabledFor": [ { - "Name": "AlwaysOn" + "Name": "On" } ] }, @@ -86,7 +87,7 @@ The feature management library supports appsettings.json as a feature flag sourc } ``` -The `FeatureManagement` section of the json document is used by convention to load feature flag settings. In the section above, we see that we have provided three different features. Features define their feature filters using the `EnabledFor` property. In the feature filters for `FeatureT` we see `AlwaysOn`. This feature filter is built-in and if specified will always enable the feature. The `AlwaysOn` feature filter does not require any configuration so it only has the `Name` property. The alternative name for this filter is `On`. `FeatureU` has no filters in its `EnabledFor` property and thus will never be enabled. Any functionality that relies on this feature being enabled will not be accessible as long as the feature filters remain empty. However, as soon as a feature filter is added that enables the feature it can begin working. `FeatureV` specifies a feature filter named `TimeWindow`. This is an example of a configurable feature filter. We can see in the example that the filter has a `Parameters` property. This is used to configure the filter. In this case, the start and end times for the feature to be active are configured. +The `FeatureManagement` section of the json document is used by convention to load feature flag settings. In the section above, we see that we have provided three different features. Features define their feature filters using the `EnabledFor` property. In the feature filters for `FeatureT` we see `On`. This feature filter is built-in and if specified will always enable the feature. The `On` feature filter does not require any configuration so it only has the `Name` property. `FeatureU` has no filters in its `EnabledFor` property and thus will never be enabled. Any functionality that relies on this feature being enabled will not be accessible as long as the feature filters remain empty. However, as soon as a feature filter is added that enables the feature it can begin working. `FeatureV` specifies a feature filter named `TimeWindow`. This is an example of a configurable feature filter. We can see in the example that the filter has a `Parameters` property. This is used to configure the filter. In this case, the start and end times for the feature to be active are configured. ### On/Off Declaration @@ -689,7 +690,7 @@ IConfigurationSection variantConfiguration = variant.Configuration; ### Setting a Variant's Configuration -For each of the variants in the `Variants` property of a feature, there is a specified configuration. This can be set using either the `ConfigurationReference` or `ConfigurationValue` properties. `ConfigurationReference` is a string path that references a section of the current configuration that contains the feature flag declaration. `ConfigurationValue` is an inline configuration. If both are specified, `ConfigurationValue` is used. +For each of the variants in the `Variants` property of a feature, there is a specified configuration. This can be set using either the `ConfigurationReference` or `ConfigurationValue` properties. `ConfigurationReference` is a string path that references a section of the current configuration that contains the feature flag declaration. `ConfigurationValue` is an inline configuration that can be a string, number, boolean or a JSON. If both are specified, `ConfigurationValue` is used. ``` "Variants": [ @@ -706,9 +707,9 @@ For each of the variants in the `Variants` property of a feature, there is a spe ] ``` -### Assigning a Variant +### Allocating a Variant -The process of deciding which variant to return for a feature is called assignment. Assignment is determined by the `Allocation` property of the feature. Allocation logic is similar to the [Microsoft.Targeting](./README.md#MicrosoftTargeting) feature filter, with some additional parameters to allow for more specific variant assignment. +The process of allocating a variant to a specific feature is determined by the `Allocation` property of the feature. ``` "Allocation": { @@ -755,11 +756,13 @@ The process of deciding which variant to return for a feature is called assignme In the above example, if the feature is not enabled, `GetVariantAsync` would return the variant allocated by `DefaultWhenDisabled`, which is `Small` in this case. -If the feature is enabled, the feature manager will check the `User`, `Group`, and `Percentile` allocations in that order to see if they match the targeting context or calculated percentile for that context. If the user is named `Marsha`, in the group named `Ring1`, or the user happens to fall between the 0 and 10th percentile calculated with the given `Seed`, then the specified variant is returned for that allocation. In this case, all of these would return the `Big` variant. If none of these allocations match the targeting context, the `DefaultWhenEnabled` variant is returned. +If the feature is enabled, the feature manager will check the `User`, `Group`, and `Percentile` allocations in that order to see if they match the targeting context or calculated percentile for that context. If the user is named `Marsha`, in the group named `Ring1`, or the user happens to fall between the 0 and 10th percentile calculated with the given `Seed`, then the specified variant is returned for that allocation. If no `Seed` is specified, then a default seed is created based on the feature name. In this case, all of these would return the `Big` variant. If none of these allocations match the targeting context, the `DefaultWhenEnabled` variant is returned. + +Allocation logic is similar to the [Microsoft.Targeting](./README.md#MicrosoftTargeting) feature filter, but there are some parameters that are present in targeting that aren't in allocation, and vice versa. The outcomes of targeting and allocation are not related. ### StatusOverride -The `StatusOverride` property, if set for the assigned variant, overrides a feature's state. By default, this property is equal to `None`, which does not affect the feature state. If `StatusOverride` is equal to `Enabled` for the assigned variant, then the feature is enabled. If `StatusOverride` is equal to `Disabled` for the assigned variant, then the feature is disabled. +The `StatusOverride` property, if set for the assigned variant, overrides a feature's state. By default, this property is equal to `None`, which does not affect the feature state. If `StatusOverride` is equal to `Enabled` for the assigned variant, then the feature is enabled. If `StatusOverride` is equal to `Disabled` for the assigned variant, then the feature is disabled. A feature with a `Status` of `Disabled` cannot be overridden. ``` "Allocation": { From ce9d6674670cbb5be0588b668bfc50e73d8d7fbd Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Thu, 28 Sep 2023 13:00:05 -0700 Subject: [PATCH 07/14] PR revisions, describe Allocation properties, fix descriptions and titles --- README.md | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2d9cbd33..fadee937 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ The feature management library supports appsettings.json as a feature flag sourc "FeatureT": { "EnabledFor": [ { - "Name": "On" + "Name": "AlwaysOn" } ] }, @@ -87,7 +87,7 @@ The feature management library supports appsettings.json as a feature flag sourc } ``` -The `FeatureManagement` section of the json document is used by convention to load feature flag settings. In the section above, we see that we have provided three different features. Features define their feature filters using the `EnabledFor` property. In the feature filters for `FeatureT` we see `On`. This feature filter is built-in and if specified will always enable the feature. The `On` feature filter does not require any configuration so it only has the `Name` property. `FeatureU` has no filters in its `EnabledFor` property and thus will never be enabled. Any functionality that relies on this feature being enabled will not be accessible as long as the feature filters remain empty. However, as soon as a feature filter is added that enables the feature it can begin working. `FeatureV` specifies a feature filter named `TimeWindow`. This is an example of a configurable feature filter. We can see in the example that the filter has a `Parameters` property. This is used to configure the filter. In this case, the start and end times for the feature to be active are configured. +The `FeatureManagement` section of the json document is used by convention to load feature flag settings. In the section above, we see that we have provided three different features. Features define their feature filters using the `EnabledFor` property. In the feature filters for `FeatureT` we see `AlwaysOn`. This feature filter is built-in and if specified will always enable the feature. The `AlwaysOn` feature filter does not require any configuration so it only has the `Name` property. `FeatureU` has no filters in its `EnabledFor` property and thus will never be enabled. Any functionality that relies on this feature being enabled will not be accessible as long as the feature filters remain empty. However, as soon as a feature filter is added that enables the feature it can begin working. `FeatureV` specifies a feature filter named `TimeWindow`. This is an example of a configurable feature filter. We can see in the example that the filter has a `Parameters` property. This is used to configure the filter. In this case, the start and end times for the feature to be active are configured. ### On/Off Declaration @@ -657,7 +657,7 @@ In the above example, the feature will be enabled for users named `Jeff` and `Al ## Variants -Variants enable a feature flag to become more than a simple on/off flag. A variant represents a value of a feature flag that can be a string, a number, or even a configuration object. A feature flag that declares variants should define under what circumstances each variant should be used which is covered in greater detail in the 'Assigning a variant' section. +Variants enable a feature flag to become more than a simple on/off flag. A variant represents a value of a feature flag that can be a string, a number, or even a configuration object. A feature flag that declares variants should define under what circumstances each variant should be used, which is covered in greater detail in the [Allocating a Variant](./README.md#allocating-a-variant) section. ``` C# public class Variant @@ -755,15 +755,26 @@ The process of allocating a variant to a specific feature is determined by the ` ] ``` +The `Allocation` setting of a feature flag has the following properties: + +| Property | Description | +| ---------------- | ---------------- | +| `DefaultWhenDisabled` | Specifies which variant should be used when a variant is requested while the feature is considered disabled. | +| `DefaultWhenEnabled` | Specifies which variant should be used when a variant is requested while the feature is considered enabled and no other allocation properties matched the current user. | +| `User` | Specifies a variant and a list of users for which that variant should be used. | +| `Group` | Specifies a variant and a list of groups the current user has to be in for that variant to be used. | +| `Percentile` | Specifies a variant and a percentage range the user's calculated percentage has to fit into for that variant to be used. | +| `Seed` | The value which percentage calculations are based on. The percentage calculation for a specific user will be the same across all features if the same `Seed` value is used. If no `Seed` is specified, then a default seed is created based on the feature name. | + In the above example, if the feature is not enabled, `GetVariantAsync` would return the variant allocated by `DefaultWhenDisabled`, which is `Small` in this case. -If the feature is enabled, the feature manager will check the `User`, `Group`, and `Percentile` allocations in that order to see if they match the targeting context or calculated percentile for that context. If the user is named `Marsha`, in the group named `Ring1`, or the user happens to fall between the 0 and 10th percentile calculated with the given `Seed`, then the specified variant is returned for that allocation. If no `Seed` is specified, then a default seed is created based on the feature name. In this case, all of these would return the `Big` variant. If none of these allocations match the targeting context, the `DefaultWhenEnabled` variant is returned. +If the feature is enabled, the feature manager will check the `User`, `Group`, and `Percentile` allocations in that order to allocate a variant for this feature. If the user being evaluated is named `Marsha`, in the group named `Ring1`, or the user happens to fall between the 0 and 10th percentile calculated with the given `Seed`, then the specified variant is returned for that allocation. In this case, all of these would return the `Big` variant. If none of these allocations match the targeting context, the `DefaultWhenEnabled` variant is returned, which is `Small`. Allocation logic is similar to the [Microsoft.Targeting](./README.md#MicrosoftTargeting) feature filter, but there are some parameters that are present in targeting that aren't in allocation, and vice versa. The outcomes of targeting and allocation are not related. -### StatusOverride +### Overriding Enabled State with a Variant -The `StatusOverride` property, if set for the assigned variant, overrides a feature's state. By default, this property is equal to `None`, which does not affect the feature state. If `StatusOverride` is equal to `Enabled` for the assigned variant, then the feature is enabled. If `StatusOverride` is equal to `Disabled` for the assigned variant, then the feature is disabled. A feature with a `Status` of `Disabled` cannot be overridden. +You can use variants to override the enabled or disabled state of a feature flag. This gives variants an opportunity to extend the evaluation of a feature flag. If a caller is checking whether a flag that has variants is enabled, then variant allocation will be performed to see if an allocated variant is set up to override the result. This is done using the optional variant property `StatusOverride`. By default, this property is set to `None`, which means the variant doesn't affect whether the flag is considered enabled or disabled. Setting `StatusOverride` to `Enabled` allows the variant, when chosen, to override a flag to be enabled. Setting `StatusOverride` to `Disabled` provides the opposite functionality, therefore disabling the flag when the variant is chosen. A feature with a `Status` of `Disabled` cannot be overridden. ``` "Allocation": { From 8bd7e87a9fd98f76910bde823a113de466c836d6 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Thu, 28 Sep 2023 13:05:57 -0700 Subject: [PATCH 08/14] clarify configurationvalue possible values --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fadee937..55bd7872 100644 --- a/README.md +++ b/README.md @@ -657,7 +657,7 @@ In the above example, the feature will be enabled for users named `Jeff` and `Al ## Variants -Variants enable a feature flag to become more than a simple on/off flag. A variant represents a value of a feature flag that can be a string, a number, or even a configuration object. A feature flag that declares variants should define under what circumstances each variant should be used, which is covered in greater detail in the [Allocating a Variant](./README.md#allocating-a-variant) section. +Variants enable a feature flag to become more than a simple on/off flag. A variant represents a value of a feature flag that can be a string, a number, a boolean, or even a configuration object. A feature flag that declares variants should define under what circumstances each variant should be used, which is covered in greater detail in the [Allocating a Variant](./README.md#allocating-a-variant) section. ``` C# public class Variant @@ -691,7 +691,7 @@ IConfigurationSection variantConfiguration = variant.Configuration; ### Setting a Variant's Configuration -For each of the variants in the `Variants` property of a feature, there is a specified configuration. This can be set using either the `ConfigurationReference` or `ConfigurationValue` properties. `ConfigurationReference` is a string path that references a section of the current configuration that contains the feature flag declaration. `ConfigurationValue` is an inline configuration that can be a string, number, boolean or a JSON. If both are specified, `ConfigurationValue` is used. +For each of the variants in the `Variants` property of a feature, there is a specified configuration. This can be set using either the `ConfigurationReference` or `ConfigurationValue` properties. `ConfigurationReference` is a string path that references a section of the current configuration that contains the feature flag declaration. `ConfigurationValue` is an inline configuration that can be a string, number, boolean, or configuration object. If both are specified, `ConfigurationValue` is used. ``` "Variants": [ @@ -748,9 +748,7 @@ The process of allocating a variant to a specific feature is determined by the ` }, { "Name": "Small", - "ConfigurationValue": { - "Size": 300 - } + "ConfigurationValue": "300px" } ] ``` From 43932ff88aa9d8a0cbed95c473a6ebd41b66d1ab Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Thu, 28 Sep 2023 13:07:20 -0700 Subject: [PATCH 09/14] clarify seed description --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 55bd7872..12fd530b 100644 --- a/README.md +++ b/README.md @@ -762,7 +762,7 @@ The `Allocation` setting of a feature flag has the following properties: | `User` | Specifies a variant and a list of users for which that variant should be used. | | `Group` | Specifies a variant and a list of groups the current user has to be in for that variant to be used. | | `Percentile` | Specifies a variant and a percentage range the user's calculated percentage has to fit into for that variant to be used. | -| `Seed` | The value which percentage calculations are based on. The percentage calculation for a specific user will be the same across all features if the same `Seed` value is used. If no `Seed` is specified, then a default seed is created based on the feature name. | +| `Seed` | The value which percentage calculations for `Percentile` are based on. The percentage calculation for a specific user will be the same across all features if the same `Seed` value is used. If no `Seed` is specified, then a default seed is created based on the feature name. | In the above example, if the feature is not enabled, `GetVariantAsync` would return the variant allocated by `DefaultWhenDisabled`, which is `Small` in this case. From 7157af13b2ba636cabca96b30b7c463f12f43e69 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Thu, 28 Sep 2023 13:10:56 -0700 Subject: [PATCH 10/14] remove all mentions of On filter --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 12fd530b..75524e74 100644 --- a/README.md +++ b/README.md @@ -150,13 +150,13 @@ In the above example, `FeatureW` specifies a `RequirementType` of `All`, meaning "Status": "Disabled", "EnabledFor": [ { - "Name": "On" + "Name": "AlwaysOn" } ] } ``` -In this example, even though the `On` filter would normally always make the feature enabled, the `Status` property is set to `Disabled`, so this feature will always be disabled. +In this example, even though the `AlwaysOn` filter would normally always make the feature enabled, the `Status` property is set to `Disabled`, so this feature will always be disabled. ### Referencing @@ -789,12 +789,12 @@ You can use variants to override the enabled or disabled state of a feature flag ], "EnabledFor": [ { - "Name": "On" + "Name": "AlwaysOn" } ] ``` -In the above example, the feature is enabled by the `On` filter and assigns the variant set for `DefaultWhenEnabled`, which is the `OffVariant` variant. The `OffVariant` variant has the `StatusOverride` property set to `Disabled`, so calling `IsEnabledAsync` for this feature will return disabled, even though the feature would otherwise be enabled by its filters. +In the above example, the feature is enabled by the `AlwaysOn` filter and assigns the variant set for `DefaultWhenEnabled`, which is the `OffVariant` variant. The `OffVariant` variant has the `StatusOverride` property set to `Disabled`, so calling `IsEnabledAsync` for this feature will return disabled, even though the feature would otherwise be enabled by its filters. ## Caching From 808d015154403fb957150801da1cde04961e05c5 Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Wed, 4 Oct 2023 15:12:00 -0700 Subject: [PATCH 11/14] fix example for override, clarify allowing no configurationvalue or reference --- README.md | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 75524e74..2bf67ee8 100644 --- a/README.md +++ b/README.md @@ -691,7 +691,7 @@ IConfigurationSection variantConfiguration = variant.Configuration; ### Setting a Variant's Configuration -For each of the variants in the `Variants` property of a feature, there is a specified configuration. This can be set using either the `ConfigurationReference` or `ConfigurationValue` properties. `ConfigurationReference` is a string path that references a section of the current configuration that contains the feature flag declaration. `ConfigurationValue` is an inline configuration that can be a string, number, boolean, or configuration object. If both are specified, `ConfigurationValue` is used. +For each of the variants in the `Variants` property of a feature, there is a specified configuration. This can be set using either the `ConfigurationReference` or `ConfigurationValue` properties. `ConfigurationReference` is a string path that references a section of the current configuration that contains the feature flag declaration. `ConfigurationValue` is an inline configuration that can be a string, number, boolean, or configuration object. If both are specified, `ConfigurationValue` is used. If neither are specified, the returned variant's `Configuration` property will be null. ``` "Variants": [ @@ -775,17 +775,23 @@ Allocation logic is similar to the [Microsoft.Targeting](./README.md#MicrosoftTa You can use variants to override the enabled or disabled state of a feature flag. This gives variants an opportunity to extend the evaluation of a feature flag. If a caller is checking whether a flag that has variants is enabled, then variant allocation will be performed to see if an allocated variant is set up to override the result. This is done using the optional variant property `StatusOverride`. By default, this property is set to `None`, which means the variant doesn't affect whether the flag is considered enabled or disabled. Setting `StatusOverride` to `Enabled` allows the variant, when chosen, to override a flag to be enabled. Setting `StatusOverride` to `Disabled` provides the opposite functionality, therefore disabling the flag when the variant is chosen. A feature with a `Status` of `Disabled` cannot be overridden. ``` -"Allocation": { - "DefaultWhenEnabled": "OffVariant" +"Allocation": { + "Percentile": [{ + "Variant": "On", + "From": 10, + "To": 20 + }], + "DefaultWhenEnabled": "Off", + "Seed": "Black-Friday-Feature-Group" }, "Variants": [ { - "Name": "OffVariant", - "ConfigurationValue": { - "Size": 300 - }, + "Name": "On" + }, + { + "Name": "Off", "StatusOverride": "Disabled" - } + } ], "EnabledFor": [ { @@ -794,7 +800,7 @@ You can use variants to override the enabled or disabled state of a feature flag ] ``` -In the above example, the feature is enabled by the `AlwaysOn` filter and assigns the variant set for `DefaultWhenEnabled`, which is the `OffVariant` variant. The `OffVariant` variant has the `StatusOverride` property set to `Disabled`, so calling `IsEnabledAsync` for this feature will return disabled, even though the feature would otherwise be enabled by its filters. +In the above example, the feature is enabled by the `AlwaysOn` filter. If the current user is in the calculated percentile range of 10 to 20, then the `On` variant is returned. Otherwise, the `Off` variant is returned and because `StatusOverride` is equal to `Disabled`, the feature will now be considered disabled. ## Caching From 91a3299fc64bfad85fef821fcb6b7967c7bed15e Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Tue, 10 Oct 2023 11:14:52 -0700 Subject: [PATCH 12/14] change statusoverride example --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2bf67ee8..2de51dd7 100644 --- a/README.md +++ b/README.md @@ -782,14 +782,16 @@ You can use variants to override the enabled or disabled state of a feature flag "To": 20 }], "DefaultWhenEnabled": "Off", - "Seed": "Black-Friday-Feature-Group" + "Seed": "Enhanced-Feature-Group" }, "Variants": [ { - "Name": "On" + "Name": "On", + "ConfigurationValue": true }, { "Name": "Off", + "ConfigurationValue": false, "StatusOverride": "Disabled" } ], From c9ce607aae01941ad9e68a329c22a7aaa1d7d315 Mon Sep 17 00:00:00 2001 From: Amer Jusupovic <32405726+amerjusupovic@users.noreply.github.com> Date: Thu, 12 Oct 2023 15:08:27 -0700 Subject: [PATCH 13/14] Apply suggestions from code review Co-authored-by: Jimmy Campbell --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2de51dd7..3a564410 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,8 @@ In the above example, `FeatureW` specifies a `RequirementType` of `All`, meaning ### Status -`Status` is an optional property of a feature flag that controls how a flags enabled state is evaluated. By default, the status of a flag is `Conditional`, meaning that feature filters should be evaluated to determine if the flag is enabled. If the `Status` of a flag is set to `Disabled` then feature filters are not evaluated and the flag is always considered to be disabled. +`Status` is an optional property of a feature flag that controls how a flag's enabled state is evaluated. By default, the status of a flag is `Conditional`, meaning that feature filters should be evaluated to determine if the flag is enabled. If the `Status` of a flag is set to `Disabled` then feature filters are not evaluated and the flag is always considered to be disabled. + ``` "FeatureX": { @@ -758,7 +759,8 @@ The `Allocation` setting of a feature flag has the following properties: | Property | Description | | ---------------- | ---------------- | | `DefaultWhenDisabled` | Specifies which variant should be used when a variant is requested while the feature is considered disabled. | -| `DefaultWhenEnabled` | Specifies which variant should be used when a variant is requested while the feature is considered enabled and no other allocation properties matched the current user. | +| `DefaultWhenEnabled` | Specifies which variant should be used when a variant is requested while the feature is considered enabled and no variant was allocated to the user. | + | `User` | Specifies a variant and a list of users for which that variant should be used. | | `Group` | Specifies a variant and a list of groups the current user has to be in for that variant to be used. | | `Percentile` | Specifies a variant and a percentage range the user's calculated percentage has to fit into for that variant to be used. | From c21b4a1f2fb3237b6e85fb06cd39b57f3ed0014b Mon Sep 17 00:00:00 2001 From: AMER JUSUPOVIC Date: Thu, 12 Oct 2023 16:40:42 -0700 Subject: [PATCH 14/14] revisions to summaries, fix allocation table --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3a564410..f26c87c6 100644 --- a/README.md +++ b/README.md @@ -658,6 +658,8 @@ In the above example, the feature will be enabled for users named `Jeff` and `Al ## Variants +When new features are added to an application, there may come a time when a feature has multiple different proposed design options. A common solution for deciding on a design is some form of A/B testing, which involves providing a different version of the feature to different segments of the user base and choosing a version based on user interaction. In this library, this functionality is enabled by representing different configurations of a feature with variants. + Variants enable a feature flag to become more than a simple on/off flag. A variant represents a value of a feature flag that can be a string, a number, a boolean, or even a configuration object. A feature flag that declares variants should define under what circumstances each variant should be used, which is covered in greater detail in the [Allocating a Variant](./README.md#allocating-a-variant) section. ``` C# @@ -677,7 +679,7 @@ public class Variant ### Getting a Feature's Variant -Variants expand on the basic flow of feature management and enable getting a feature's variant to later perform actions based on the name or configuration. This is done through the `IVariantFeatureManager`'s `GetVariantAsync` method. +A feature's variant can be retrieved using the `IVariantFeatureManager`'s `GetVariantAsync` method. ``` C# … @@ -760,7 +762,6 @@ The `Allocation` setting of a feature flag has the following properties: | ---------------- | ---------------- | | `DefaultWhenDisabled` | Specifies which variant should be used when a variant is requested while the feature is considered disabled. | | `DefaultWhenEnabled` | Specifies which variant should be used when a variant is requested while the feature is considered enabled and no variant was allocated to the user. | - | `User` | Specifies a variant and a list of users for which that variant should be used. | | `Group` | Specifies a variant and a list of groups the current user has to be in for that variant to be used. | | `Percentile` | Specifies a variant and a percentage range the user's calculated percentage has to fit into for that variant to be used. | @@ -768,13 +769,13 @@ The `Allocation` setting of a feature flag has the following properties: In the above example, if the feature is not enabled, `GetVariantAsync` would return the variant allocated by `DefaultWhenDisabled`, which is `Small` in this case. -If the feature is enabled, the feature manager will check the `User`, `Group`, and `Percentile` allocations in that order to allocate a variant for this feature. If the user being evaluated is named `Marsha`, in the group named `Ring1`, or the user happens to fall between the 0 and 10th percentile calculated with the given `Seed`, then the specified variant is returned for that allocation. In this case, all of these would return the `Big` variant. If none of these allocations match the targeting context, the `DefaultWhenEnabled` variant is returned, which is `Small`. +If the feature is enabled, the feature manager will check the `User`, `Group`, and `Percentile` allocations in that order to allocate a variant for this feature. If the user being evaluated is named `Marsha`, in the group named `Ring1`, or the user happens to fall between the 0 and 10th percentile calculated with the given `Seed`, then the specified variant is returned for that allocation. In this case, all of these would return the `Big` variant. If none of these allocations match, the `DefaultWhenEnabled` variant is returned, which is `Small`. Allocation logic is similar to the [Microsoft.Targeting](./README.md#MicrosoftTargeting) feature filter, but there are some parameters that are present in targeting that aren't in allocation, and vice versa. The outcomes of targeting and allocation are not related. ### Overriding Enabled State with a Variant -You can use variants to override the enabled or disabled state of a feature flag. This gives variants an opportunity to extend the evaluation of a feature flag. If a caller is checking whether a flag that has variants is enabled, then variant allocation will be performed to see if an allocated variant is set up to override the result. This is done using the optional variant property `StatusOverride`. By default, this property is set to `None`, which means the variant doesn't affect whether the flag is considered enabled or disabled. Setting `StatusOverride` to `Enabled` allows the variant, when chosen, to override a flag to be enabled. Setting `StatusOverride` to `Disabled` provides the opposite functionality, therefore disabling the flag when the variant is chosen. A feature with a `Status` of `Disabled` cannot be overridden. +You can use variants to override the enabled state of a feature flag. This gives variants an opportunity to extend the evaluation of a feature flag. If a caller is checking whether a flag that has variants is enabled, then variant allocation will be performed to see if an allocated variant is set up to override the result. This is done using the optional variant property `StatusOverride`. By default, this property is set to `None`, which means the variant doesn't affect whether the flag is considered enabled or disabled. Setting `StatusOverride` to `Enabled` allows the variant, when chosen, to override a flag to be enabled. Setting `StatusOverride` to `Disabled` provides the opposite functionality, therefore disabling the flag when the variant is chosen. A feature with a `Status` of `Disabled` cannot be overridden. ``` "Allocation": {