Skip to content

Commit

Permalink
feat: relative weights in fractional, fix injected props (#208)
Browse files Browse the repository at this point in the history
Signed-off-by: Florian Bacher <florian.bacher@dynatrace.com>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
  • Loading branch information
bacherfl and toddbaert authored Jul 4, 2024
1 parent 941d506 commit 7cccc8d
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 43 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ jobs:
services:
# flagd-testbed for flagd RPC provider e2e tests
flagd:
image: ghcr.io/open-feature/flagd-testbed:v0.5.4
image: ghcr.io/open-feature/flagd-testbed:v0.5.6
ports:
- 8013:8013
# sync-testbed for flagd in-process provider e2e tests
sync:
image: ghcr.io/open-feature/sync-testbed:v0.5.4
image: ghcr.io/open-feature/sync-testbed:v0.5.6
ports:
- 9090:9090
steps:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ internal class FlagdProperties
internal FlagdProperties(object from)
{
//object value;
if (from is Dictionary<string, object> dict)
if (from is IDictionary<string, object> dict)
{
if (dict.TryGetValue(TargetingKeyKey, out object targetingKeyValue)
&& targetingKeyValue is string targetingKeyString)
{
TargetingKey = targetingKeyString;
}
if (dict.TryGetValue(FlagdPropertiesKey, out object flagdPropertiesObj)
&& flagdPropertiesObj is Dictionary<string, object> flagdProperties)
&& flagdPropertiesObj is IDictionary<string, object> flagdProperties)
{
if (flagdProperties.TryGetValue(FlagKeyKey, out object flagKeyObj)
&& flagKeyObj is string flagKey)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ internal FractionalEvaluator()
class FractionalEvaluationDistribution
{
public string variant;
public int percentage;
public int weight;
}

internal object Evaluate(IProcessJsonLogic p, JToken[] args, object data)
Expand All @@ -49,17 +49,20 @@ internal object Evaluate(IProcessJsonLogic p, JToken[] args, object data)

var flagdProperties = new FlagdProperties(data);

// check if the first argument is a string (i.e. the property to base the distribution on
var propertyValue = flagdProperties.TargetingKey;
var bucketStartIndex = 0;

var arg0 = p.Apply(args[0], data);

string propertyValue;
if (arg0 is string stringValue)
{
propertyValue = stringValue;
bucketStartIndex = 1;
}
else
{
propertyValue = flagdProperties.FlagKey + flagdProperties.TargetingKey;
}

var distributions = new List<FractionalEvaluationDistribution>();
var distributionSum = 0;
Expand All @@ -75,46 +78,40 @@ internal object Evaluate(IProcessJsonLogic p, JToken[] args, object data)

var bucketArr = bucket.MakeEnumerable().ToArray();

if (bucketArr.Count() < 2)
if (!bucketArr.Any())
{
continue;
}

if (!bucketArr.ElementAt(1).IsNumeric())
var weight = 1;

if (bucketArr.Length >= 2 && bucketArr.ElementAt(1).IsNumeric())
{
continue;
weight = Convert.ToInt32(bucketArr.ElementAt(1));
}


var percentage = Convert.ToInt32(bucketArr.ElementAt(1));
distributions.Add(new FractionalEvaluationDistribution
{
variant = bucketArr.ElementAt(0).ToString(),
percentage = percentage
weight = weight
});

distributionSum += percentage;
}

if (distributionSum != 100)
{
Logger.LogDebug("Sum of distribution values is not eqyal to 100");
return null;
distributionSum += weight;
}

var valueToDistribute = flagdProperties.FlagKey + propertyValue;
var valueToDistribute = propertyValue;
var murmur32 = MurmurHash.Create32();
var bytes = Encoding.ASCII.GetBytes(valueToDistribute);
var hashBytes = murmur32.ComputeHash(bytes);
var hash = BitConverter.ToInt32(hashBytes, 0);

var bucketValue = (int)(Math.Abs((float)hash) / Int32.MaxValue * 100);

var rangeEnd = 0;
var rangeEnd = 0.0;

foreach (var dist in distributions)
{
rangeEnd += dist.percentage;
rangeEnd += 100 * (dist.weight / (float)distributionSum);
if (bucketValue < rangeEnd)
{
return dist.variant;
Expand Down
16 changes: 4 additions & 12 deletions src/OpenFeature.Contrib.Providers.Flagd/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,25 +1,17 @@
services:
flagd:
build:
context: flagd-testbed
dockerfile: flagd/Dockerfile
image: ghcr.io/open-feature/flagd-testbed:v0.5.6
ports:
- 8013:8013
flagd-unstable:
build:
context: flagd-testbed
dockerfile: flagd/Dockerfile.unstable
image: ghcr.io/open-feature/flagd-testbed-unstable:v0.5.6
ports:
- 8014:8013
flagd-sync:
build:
context: flagd-testbed
dockerfile: sync/Dockerfile
image: ghcr.io/open-feature/sync-testbed:v0.5.6
ports:
- 9090:9090
flagd-sync-unstable:
build:
context: flagd-testbed
dockerfile: sync/Dockerfile.unstable
image: ghcr.io/open-feature/sync-testbed-unstable:v0.5.6
ports:
- 9091:9090
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
using System.ComponentModel;
using System.Reflection;
using System.Threading.Tasks;
using OpenFeature.Constant;
using OpenFeature.Model;
Expand Down Expand Up @@ -245,7 +244,7 @@ public void Thenthedefaultstringvalueshouldbereturned()
public void Giventhereasonshouldindicateanerrorandtheerrorcodeshouldindicateamissingflagwith(string errorCode)
{
Assert.Equal(Reason.Error.ToString(), notFoundDetails.Reason);
Assert.Contains(errorCode, notFoundDetails.ErrorMessage);
Assert.Contains(errorCode, GetErrorTypeDescription(notFoundDetails.ErrorType));
}

[When(@"a string flag with key ""(.*)"" is evaluated as an integer, with details and a default value (.*)")]
Expand All @@ -266,7 +265,15 @@ public void Thenthedefaultintegervalueshouldbereturned()
public void Giventhereasonshouldindicateanerrorandtheerrorcodeshouldindicateatypemismatchwith(string errorCode)
{
Assert.Equal(Reason.Error.ToString(), typeErrorDetails.Reason);
Assert.Contains(errorCode, this.typeErrorDetails.ErrorMessage);
Assert.Contains(errorCode, GetErrorTypeDescription(typeErrorDetails.ErrorType));
}

// convenience method to get the enum description.
private string GetErrorTypeDescription(Enum value)
{
FieldInfo info = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes = (DescriptionAttribute[])info.GetCustomAttributes(typeof(DescriptionAttribute));
return attributes[0].Description;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ public void Evaluate(string email, string flagKey, string expected)

var targetingString = @"{""fractional"": [
{
""var"": [
""email""
""cat"": [
{ ""var"":""$flagd.flagKey"" },
{ ""var"":""email"" }
]
},
[""red"", 25], [""blue"", 25], [""green"", 25], [""yellow"", 25],
Expand All @@ -55,7 +56,70 @@ public void Evaluate(string email, string flagKey, string expected)
// Act & Assert
var result = evaluator.Apply(rule, data);
Assert.Equal(expected, result.ToString());
}

[Theory]
[MemberData(nameof(FractionalEvaluationTestData.FractionalEvaluationContext), MemberType = typeof(FractionalEvaluationTestData))]
public void EvaluateUsingRelativeWeights(string email, string flagKey, string expected)
{
// Arrange
var evaluator = new JsonLogicEvaluator(EvaluateOperators.Default);
var fractionalEvaluator = new FractionalEvaluator();
EvaluateOperators.Default.AddOperator("fractional", fractionalEvaluator.Evaluate);

var targetingString = @"{""fractional"": [
{
""cat"": [
{ ""var"":""$flagd.flagKey"" },
{ ""var"":""email"" }
]
},
[""red"", 5], [""blue"", 5], [""green"", 5], [""yellow"", 5],
]}";

// Parse json into hierarchical structure
var rule = JObject.Parse(targetingString);

var data = new Dictionary<string, object> {
{ "email", email },
{"$flagd", new Dictionary<string, object> { {"flagKey", flagKey } } }
};

// Act & Assert
var result = evaluator.Apply(rule, data);
Assert.Equal(expected, result.ToString());
}

[Theory]
[MemberData(nameof(FractionalEvaluationTestData.FractionalEvaluationContext), MemberType = typeof(FractionalEvaluationTestData))]
public void EvaluateUsingDefaultWeights(string email, string flagKey, string expected)
{
// Arrange
var evaluator = new JsonLogicEvaluator(EvaluateOperators.Default);
var fractionalEvaluator = new FractionalEvaluator();
EvaluateOperators.Default.AddOperator("fractional", fractionalEvaluator.Evaluate);

var targetingString = @"{""fractional"": [
{
""cat"": [
{ ""var"":""$flagd.flagKey"" },
{ ""var"":""email"" }
]
},
[""red""], [""blue""], [""green""], [""yellow""],
]}";

// Parse json into hierarchical structure
var rule = JObject.Parse(targetingString);

var data = new Dictionary<string, object> {
{ "email", email },
{"$flagd", new Dictionary<string, object> { {"flagKey", flagKey } } }
};

// Act & Assert
var result = evaluator.Apply(rule, data);
Assert.Equal(expected, result.ToString());
}

[Theory]
Expand Down

0 comments on commit 7cccc8d

Please sign in to comment.