Skip to content

Commit

Permalink
chore: deprecate fractionalEvaluation for fractional (#873)
Browse files Browse the repository at this point in the history
* deprecates fractionalEvaluation in favor of fractional
* adds evaluation names to their implementations to bind them more
closely

Signed-off-by: Todd Baert <[email protected]>
Co-authored-by: Kavindu Dodanduwa <[email protected]>
  • Loading branch information
toddbaert and Kavindu-Dodan authored Aug 29, 2023
1 parent da30b7b commit 243fef9
Show file tree
Hide file tree
Showing 12 changed files with 544 additions and 31 deletions.
2 changes: 2 additions & 0 deletions core/pkg/eval/fractional_evaluation.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"github.com/twmb/murmur3"
)

const FractionEvaluationName = "fractional"

type FractionalEvaluator struct {
Logger *logger.Logger
}
Expand Down
20 changes: 10 additions & 10 deletions core/pkg/eval/fractional_evaluation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func TestFractionalEvaluation(t *testing.T) {
}]
},
{
"fractionalEvaluation": [
"fractional": [
{"var": "email"},
[
"red",
Expand Down Expand Up @@ -123,7 +123,7 @@ func TestFractionalEvaluation(t *testing.T) {
}]
},
{
"fractionalEvaluation": [
"fractional": [
{"var": "email"},
[
"red",
Expand Down Expand Up @@ -176,7 +176,7 @@ func TestFractionalEvaluation(t *testing.T) {
}]
},
{
"fractionalEvaluation": [
"fractional": [
"email",
[
"red",
Expand Down Expand Up @@ -218,7 +218,7 @@ func TestFractionalEvaluation(t *testing.T) {
"yellow": "#FFFF00",
},
Targeting: []byte(`{
"fractionalEvaluation": [
"fractional": [
{"var": "email"},
[
"red",
Expand Down Expand Up @@ -260,7 +260,7 @@ func TestFractionalEvaluation(t *testing.T) {
"yellow": "#FFFF00",
},
Targeting: []byte(`{
"fractionalEvaluation": [
"fractional": [
{"var": "email"},
[
"black",
Expand Down Expand Up @@ -292,7 +292,7 @@ func TestFractionalEvaluation(t *testing.T) {
"yellow": "#FFFF00",
},
Targeting: []byte(`{
"fractionalEvaluation": [
"fractional": [
{"var": "email"},
[
"red",
Expand Down Expand Up @@ -328,7 +328,7 @@ func TestFractionalEvaluation(t *testing.T) {
"yellow": "#FFFF00",
},
Targeting: []byte(`{
"fractionalEvaluation": [
"fractional": [
[
"blue",
50
Expand Down Expand Up @@ -359,7 +359,7 @@ func TestFractionalEvaluation(t *testing.T) {
log,
store.NewFlags(),
WithEvaluator(
"fractionalEvaluation",
FractionEvaluationName,
NewFractionalEvaluator(log).FractionalEvaluation,
),
)
Expand Down Expand Up @@ -406,7 +406,7 @@ func BenchmarkFractionalEvaluation(b *testing.B) {
}]
},
{
"fractionalEvaluation": [
"fractional": [
"email",
[
"red",
Expand Down Expand Up @@ -490,7 +490,7 @@ func BenchmarkFractionalEvaluation(b *testing.B) {
log,
&store.Flags{Flags: tt.flags.Flags},
WithEvaluator(
"fractionalEvaluation",
FractionEvaluationName,
NewFractionalEvaluator(log).FractionalEvaluation,
),
)
Expand Down
2 changes: 1 addition & 1 deletion core/pkg/eval/json_evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ func (je *JSONEvaluator) evaluateVariant(reqID string, flagKey string, context m
}

var result bytes.Buffer
// evaluate json-logic rules to determine the variant
// evaluate JsonLogic rules to determine the variant
err = jsonlogic.Apply(bytes.NewReader(targetingBytes), bytes.NewReader(b), &result)
if err != nil {
je.Logger.ErrorWithID(reqID, fmt.Sprintf("error applying rules: %s", err))
Expand Down
145 changes: 145 additions & 0 deletions core/pkg/eval/legacy_fractional_evaluation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// This evaluation type is deprecated and will be removed before v1.
// Do not enhance it or use it for reference.

package eval

import (
"errors"
"fmt"
"math"

"github.com/open-feature/flagd/core/pkg/logger"
"github.com/zeebo/xxh3"
)

const (
LegacyFractionEvaluationName = "fractionalEvaluation"
LegacyFractionEvaluationLink = "https://flagd.dev/concepts/#migrating-from-legacy-fractionalevaluation"
)

// Deprecated: LegacyFractionalEvaluator is deprecated. This will be removed prior to v1 release.
type LegacyFractionalEvaluator struct {
Logger *logger.Logger
}

type legacyFractionalEvaluationDistribution struct {
variant string
percentage int
}

func NewLegacyFractionalEvaluator(logger *logger.Logger) *LegacyFractionalEvaluator {
return &LegacyFractionalEvaluator{Logger: logger}
}

func (fe *LegacyFractionalEvaluator) LegacyFractionalEvaluation(values, data interface{}) interface{} {
fe.Logger.Warn(
fmt.Sprintf("%s is deprecated, please use %s, see: %s",
LegacyFractionEvaluationName,
FractionEvaluationName,
LegacyFractionEvaluationLink))

valueToDistribute, feDistributions, err := parseLegacyFractionalEvaluationData(values, data)
if err != nil {
fe.Logger.Error(fmt.Sprintf("parse fractional evaluation data: %v", err))
return nil
}

return distributeLegacyValue(valueToDistribute, feDistributions)
}

func parseLegacyFractionalEvaluationData(values, data interface{}) (string,
[]legacyFractionalEvaluationDistribution, error,
) {
valuesArray, ok := values.([]interface{})
if !ok {
return "", nil, errors.New("fractional evaluation data is not an array")
}
if len(valuesArray) < 2 {
return "", nil, errors.New("fractional evaluation data has length under 2")
}

bucketBy, ok := valuesArray[0].(string)
if !ok {
return "", nil, errors.New("first element of fractional evaluation data isn't of type string")
}

dataMap, ok := data.(map[string]interface{})
if !ok {
return "", nil, errors.New("data isn't of type map[string]interface{}")
}

v, ok := dataMap[bucketBy]
if !ok {
return "", nil, nil
}

valueToDistribute, ok := v.(string)
if !ok {
return "", nil, fmt.Errorf("var: %s isn't of type string", bucketBy)
}

feDistributions, err := parseLegacyFractionalEvaluationDistributions(valuesArray)
if err != nil {
return "", nil, err
}

return valueToDistribute, feDistributions, nil
}

func parseLegacyFractionalEvaluationDistributions(values []interface{}) (
[]legacyFractionalEvaluationDistribution, error,
) {
sumOfPercentages := 0
var feDistributions []legacyFractionalEvaluationDistribution
for i := 1; i < len(values); i++ {
distributionArray, ok := values[i].([]interface{})
if !ok {
return nil, errors.New("distribution elements aren't of type []interface{}")
}

if len(distributionArray) != 2 {
return nil, errors.New("distribution element isn't length 2")
}

variant, ok := distributionArray[0].(string)
if !ok {
return nil, errors.New("first element of distribution element isn't string")
}

percentage, ok := distributionArray[1].(float64)
if !ok {
return nil, errors.New("second element of distribution element isn't float")
}

sumOfPercentages += int(percentage)

feDistributions = append(feDistributions, legacyFractionalEvaluationDistribution{
variant: variant,
percentage: int(percentage),
})
}

if sumOfPercentages != 100 {
return nil, fmt.Errorf("percentages must sum to 100, got: %d", sumOfPercentages)
}

return feDistributions, nil
}

func distributeLegacyValue(value string, feDistribution []legacyFractionalEvaluationDistribution) string {
hashValue := xxh3.HashString(value)

hashRatio := float64(hashValue) / math.Pow(2, 64) // divide the hash value by the largest possible value, integer 2^64

bucket := int(hashRatio * 100) // integer in range [0, 99]

rangeEnd := 0
for _, dist := range feDistribution {
rangeEnd += dist.percentage
if bucket < rangeEnd {
return dist.variant
}
}

return ""
}
Loading

0 comments on commit 243fef9

Please sign in to comment.