Skip to content

Commit

Permalink
feat: Change fractional custom op from percentage-based to relative w…
Browse files Browse the repository at this point in the history
…eighting. open-feature#828

Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
  • Loading branch information
aepfli committed Jun 19, 2024
1 parent cf77d56 commit ddbcc84
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,37 +50,32 @@ public Object evaluate(List arguments, Object data) throws JsonLogicEvaluationEx
}

final List<FractionProperty> propertyList = new ArrayList<>();
int totalWeight = 0;

double distribution = 0;
try {
for (Object dist : distibutions) {
FractionProperty fractionProperty = new FractionProperty(dist);
propertyList.add(fractionProperty);
distribution += fractionProperty.getPercentage();
totalWeight += fractionProperty.getWeight();
}
} catch (JsonLogicException e) {
log.debug("Error parsing fractional targeting rule", e);
return null;
}

if (distribution != 100) {
log.debug("Fractional properties do not sum to 100");
return null;
}

// find distribution
return distributeValue(bucketBy, propertyList);
return distributeValue(bucketBy, propertyList, totalWeight);
}

private static String distributeValue(final String hashKey, final List<FractionProperty> propertyList)
private static String distributeValue(final String hashKey, final List<FractionProperty> propertyList, int totalWeight)
throws JsonLogicEvaluationException {
byte[] bytes = hashKey.getBytes(StandardCharsets.UTF_8);
int mmrHash = MurmurHash3.hash32x86(bytes, 0, bytes.length, 0);
int bucket = (int) ((Math.abs(mmrHash) * 1.0f / Integer.MAX_VALUE) * 100);
float bucket = (Math.abs(mmrHash) * 1.0f / Integer.MAX_VALUE) * 100;

int bucketSum = 0;
float bucketSum = 0;
for (FractionProperty p : propertyList) {
bucketSum += p.getPercentage();
bucketSum += p.getPercentage(totalWeight);

if (bucket < bucketSum) {
return p.getVariant();
Expand All @@ -95,7 +90,7 @@ private static String distributeValue(final String hashKey, final List<FractionP
@SuppressWarnings({"checkstyle:NoFinalizer"})
private static class FractionProperty {
private final String variant;
private final int percentage;
private final int weight;

protected final void finalize() {
// DO NOT REMOVE, spotbugs: CT_CONSTRUCTOR_THROW
Expand All @@ -108,23 +103,32 @@ protected final void finalize() {

final List<?> array = (List) from;

if (array.size() != 2) {
throw new JsonLogicException("Fraction property does not have two elements");
if (array.isEmpty()) {
throw new JsonLogicException("Fraction property needs at least one element");
}

// first must be a string
if (!(array.get(0) instanceof String)) {
throw new JsonLogicException("First element of the fraction property is not a string variant");
}

// second element must be a number
if (!(array.get(1) instanceof Number)) {
throw new JsonLogicException("Second element of the fraction property is not a number");
}

variant = (String) array.get(0);
percentage = ((Number) array.get(1)).intValue();
if(array.size() >= 2) {
// second element must be a number
if (!(array.get(1) instanceof Number)) {
throw new JsonLogicException("Second element of the fraction property is not a number");
}
weight = ((Number) array.get(1)).intValue();
} else {
weight = 1;
}
}

float getPercentage(int totalWeight) {
if (weight == 0) {
return 0;
}
return (float) (weight * 100) / totalWeight;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ void targetingBackedFractional() throws JsonLogicEvaluationException {


@Test
void invalidRuleSumNot100() throws JsonLogicEvaluationException {
void invalidRuleSumGreater100() throws JsonLogicEvaluationException {
// given
Fractional fractional = new Fractional();

Expand Down Expand Up @@ -189,7 +189,49 @@ void invalidRuleSumNot100() throws JsonLogicEvaluationException {
Object evaluate = fractional.evaluate(rule, data);

// then
assertNull(evaluate);
assertEquals("blue", evaluate);
}

@Test
void invalidRuleSumlower100() throws JsonLogicEvaluationException {
// given
Fractional fractional = new Fractional();

/* Rule
* [
* [
* "blue",
* 50
* ],
* [
* "green",
* 30
* ]
* ]
* */

final List<Object> rule = new ArrayList<>();

final List<Object> bucket1 = new ArrayList<>();
bucket1.add("blue");
bucket1.add(50);

final List<Object> bucket2 = new ArrayList<>();
bucket2.add("green");
bucket2.add(70);

rule.add(bucket1);
rule.add(bucket2);

Map<String, String> data = new HashMap<>();
data.put(FLAG_KEY, "headerColor");
data.put(TARGET_KEY, "foo@foo.com");

// when
Object evaluate = fractional.evaluate(rule, data);

// then
assertEquals("blue", evaluate);
}

@Test
Expand Down Expand Up @@ -227,7 +269,7 @@ void notEnoughBuckets() throws JsonLogicEvaluationException {


@Test
void invalidRule() throws JsonLogicEvaluationException {
void prefillingRuleWithPlaceHolderValue() throws JsonLogicEvaluationException {
// given
Fractional fractional = new Fractional();

Expand Down Expand Up @@ -263,7 +305,7 @@ void invalidRule() throws JsonLogicEvaluationException {
Object evaluate = fractional.evaluate(rule, data);

// then
assertNull(evaluate);
assertEquals("blue", evaluate);
}

}
}

0 comments on commit ddbcc84

Please sign in to comment.