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 environment parameter in config #246

Merged
merged 13 commits into from
Jun 21, 2022
7 changes: 6 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package ffclient
import (
"context"
"errors"
"github.com/thomaspoignant/go-feature-flag/ffnotifier"
"log"
"time"

"github.com/thomaspoignant/go-feature-flag/ffnotifier"

"github.com/thomaspoignant/go-feature-flag/internal"
"github.com/thomaspoignant/go-feature-flag/internal/notifier"
)
Expand All @@ -27,6 +28,10 @@ type Config struct {
// Default: context.Background()
Context context.Context

// Environment (optional), can be checked in feature flag rules
// Default: ""
Environment string

// Retriever is the component in charge to retrieve your flag file
Retriever Retriever

Expand Down
4 changes: 2 additions & 2 deletions internal/flag/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (

type Flag interface {
// Value is returning the Value associate to the flag (True / False / Default ) based
// if the flag apply to the user or not.
Value(flagName string, user ffuser.User) (interface{}, string)
// if the flag apply to the user and environment or not.
Value(flagName string, user ffuser.User, environment string) (interface{}, string)

// String display correctly a flag with the right formatting
String() string
Expand Down
22 changes: 12 additions & 10 deletions internal/flagv1/flag_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package flagv1

import (
"fmt"
"github.com/nikunjy/rules/parser"
"github.com/thomaspoignant/go-feature-flag/ffuser"
"github.com/thomaspoignant/go-feature-flag/internal/utils"
"math"
"strconv"
"strings"
"time"

"github.com/nikunjy/rules/parser"
"github.com/thomaspoignant/go-feature-flag/ffuser"
"github.com/thomaspoignant/go-feature-flag/internal/utils"
)

// percentageMultiplier is the multiplier used to have a bigger range of possibility.
Expand Down Expand Up @@ -55,14 +56,14 @@ type FlagData struct {

// Value is returning the Value associate to the flag (True / False / Default ) based
// if the toggle apply to the user or not.
func (f *FlagData) Value(flagName string, user ffuser.User) (interface{}, string) {
func (f *FlagData) Value(flagName string, user ffuser.User, environment string) (interface{}, string) {
f.updateFlagStage()
if f.isExperimentationOver() {
// if we have an experimentation that has not started or that is finished we use the default value.
return f.getDefault(), VariationDefault
}

if f.evaluateRule(user) {
if f.evaluateRule(user, environment) {
if f.isInPercentage(flagName, user) {
// Rule applied and user in the cohort.
return f.getTrue(), VariationTrue
Expand All @@ -77,9 +78,8 @@ func (f *FlagData) Value(flagName string, user ffuser.User) (interface{}, string

func (f *FlagData) isExperimentationOver() bool {
now := time.Now()
return f.Rollout != nil && f.Rollout.Experimentation != nil && (
(f.Rollout.Experimentation.Start != nil && now.Before(*f.Rollout.Experimentation.Start)) ||
(f.Rollout.Experimentation.End != nil && now.After(*f.Rollout.Experimentation.End)))
return f.Rollout != nil && f.Rollout.Experimentation != nil && ((f.Rollout.Experimentation.Start != nil && now.Before(*f.Rollout.Experimentation.Start)) ||
(f.Rollout.Experimentation.End != nil && now.After(*f.Rollout.Experimentation.End)))
}

// isInPercentage check if the user is in the cohort for the toggle.
Expand All @@ -101,7 +101,7 @@ func (f *FlagData) isInPercentage(flagName string, user ffuser.User) bool {
}

// evaluateRule is checking if the rule can apply to a specific user.
func (f *FlagData) evaluateRule(user ffuser.User) bool {
func (f *FlagData) evaluateRule(user ffuser.User, environment string) bool {
// Flag disable we cannot apply it.
if f.GetDisable() {
return false
Expand All @@ -113,7 +113,9 @@ func (f *FlagData) evaluateRule(user ffuser.User) bool {
}

// Evaluate the rule on the user.
return parser.Evaluate(f.getRule(), utils.UserToMap(user))
userMap := utils.UserToMap(user)
userMap["env"] = environment
return parser.Evaluate(f.getRule(), userMap)
}

// string display correctly a flag
Expand Down
5 changes: 3 additions & 2 deletions internal/flagv1/flag_priv_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package flagv1

import (
"github.com/stretchr/testify/assert"
"testing"
"time"

"github.com/stretchr/testify/assert"

"github.com/thomaspoignant/go-feature-flag/ffuser"
"github.com/thomaspoignant/go-feature-flag/testutils/testconvert"
)
Expand Down Expand Up @@ -87,7 +88,7 @@ func TestFlag_evaluateRule(t *testing.T) {
False: testconvert.Interface(tt.fields.False),
}

got := f.evaluateRule(tt.args.user)
got := f.evaluateRule(tt.args.user, "")
assert.Equal(t, tt.want, got)
})
}
Expand Down
29 changes: 15 additions & 14 deletions internal/flagv1/flag_pub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ package flagv1_test

import (
"fmt"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/thomaspoignant/go-feature-flag/internal/flag"
flagv1 "github.com/thomaspoignant/go-feature-flag/internal/flagv1"
"testing"
"time"

"github.com/thomaspoignant/go-feature-flag/ffuser"
"github.com/thomaspoignant/go-feature-flag/testutils/testconvert"
Expand Down Expand Up @@ -336,7 +337,7 @@ func TestFlag_value(t *testing.T) {
Rollout: &tt.fields.Rollout,
}

got, variationType := f.Value(tt.args.flagName, tt.args.user)
got, variationType := f.Value(tt.args.flagName, tt.args.user, "")
assert.Equal(t, tt.want.value, got)
assert.Equal(t, tt.want.variationType, variationType)
})
Expand All @@ -361,15 +362,15 @@ func TestFlag_ProgressiveRollout(t *testing.T) {
flagName := "test-flag"

// We evaluate the same flag multiple time overtime.
v, _ := f.Value(flagName, user)
v, _ := f.Value(flagName, user, "")
assert.Equal(t, f.GetVariationValue(flagv1.VariationFalse), v)

time.Sleep(1 * time.Second)
v2, _ := f.Value(flagName, user)
v2, _ := f.Value(flagName, user, "")
assert.Equal(t, f.GetVariationValue(flagv1.VariationFalse), v2)

time.Sleep(1 * time.Second)
v3, _ := f.Value(flagName, user)
v3, _ := f.Value(flagName, user, "")
assert.Equal(t, f.GetVariationValue(flagv1.VariationTrue), v3)
}

Expand Down Expand Up @@ -446,43 +447,43 @@ func TestFlag_ScheduledRollout(t *testing.T) {
flagName := "test-flag"

// We evaluate the same flag multiple time overtime.
v, _ := f.Value(flagName, user)
v, _ := f.Value(flagName, user, "")
assert.Equal(t, f.GetVariationValue(flagv1.VariationFalse), v)

time.Sleep(1 * time.Second)

v, _ = f.Value(flagName, user)
v, _ = f.Value(flagName, user, "")
assert.Equal(t, "True", v)
assert.Equal(t, 1.1, f.GetVersion())

time.Sleep(1 * time.Second)

v, _ = f.Value(flagName, user)
v, _ = f.Value(flagName, user, "")
assert.Equal(t, "Default2", v)

time.Sleep(1 * time.Second)

v, _ = f.Value(flagName, user)
v, _ = f.Value(flagName, user, "")
assert.Equal(t, "True2", v)

time.Sleep(1 * time.Second)

v, _ = f.Value(flagName, user)
v, _ = f.Value(flagName, user, "")
assert.Equal(t, "Default2", v)

time.Sleep(1 * time.Second)

v, _ = f.Value(flagName, user)
v, _ = f.Value(flagName, user, "")
assert.Equal(t, "Default2", v)

time.Sleep(1 * time.Second)

v, _ = f.Value(flagName, user)
v, _ = f.Value(flagName, user, "")
assert.Equal(t, "True2", v)

time.Sleep(1 * time.Second)

v, _ = f.Value(flagName, user)
v, _ = f.Value(flagName, user, "")
assert.Equal(t, "Default2", v)
}

Expand Down
17 changes: 9 additions & 8 deletions variation.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ffclient

import (
"fmt"

"github.com/thomaspoignant/go-feature-flag/ffexporter"
"github.com/thomaspoignant/go-feature-flag/ffuser"
"github.com/thomaspoignant/go-feature-flag/internal/flag"
Expand Down Expand Up @@ -140,7 +141,7 @@ func (g *GoFeatureFlag) AllFlagsState(user ffuser.User) flagstate.AllFlags {

allFlags := flagstate.NewAllFlags()
for key, currentFlag := range flags {
flagValue, varType := currentFlag.Value(key, user)
flagValue, varType := currentFlag.Value(key, user, g.config.Environment)
switch v := flagValue; v.(type) {
case int, float64, bool, string, []interface{}, map[string]interface{}:
allFlags.AddFlag(key, flagstate.NewFlagState(currentFlag.GetTrackEvents(), v, varType, false))
Expand Down Expand Up @@ -171,7 +172,7 @@ func (g *GoFeatureFlag) boolVariation(flagKey string, user ffuser.User, sdkDefau
}, err
}

flagValue, variationType := f.Value(flagKey, user)
flagValue, variationType := f.Value(flagKey, user, g.config.Environment)
res, ok := flagValue.(bool)
if !ok {
return model.BoolVarResult{
Expand Down Expand Up @@ -199,7 +200,7 @@ func (g *GoFeatureFlag) intVariation(flagKey string, user ffuser.User, sdkDefaul
}, err
}

flagValue, variationType := f.Value(flagKey, user)
flagValue, variationType := f.Value(flagKey, user, g.config.Environment)
res, ok := flagValue.(int)
if !ok {
// if this is a float64 we convert it to int
Expand Down Expand Up @@ -236,7 +237,7 @@ func (g *GoFeatureFlag) float64Variation(flagKey string, user ffuser.User, sdkDe
}, err
}

flagValue, variationType := f.Value(flagKey, user)
flagValue, variationType := f.Value(flagKey, user, g.config.Environment)
res, ok := flagValue.(float64)
if !ok {
return model.Float64VarResult{
Expand Down Expand Up @@ -266,7 +267,7 @@ func (g *GoFeatureFlag) stringVariation(flagKey string, user ffuser.User, sdkDef
}, err
}

flagValue, variationType := f.Value(flagKey, user)
flagValue, variationType := f.Value(flagKey, user, g.config.Environment)
res, ok := flagValue.(string)
if !ok {
return model.StringVarResult{
Expand Down Expand Up @@ -296,7 +297,7 @@ func (g *GoFeatureFlag) jsonArrayVariation(flagKey string, user ffuser.User, sdk
}, err
}

flagValue, variationType := f.Value(flagKey, user)
flagValue, variationType := f.Value(flagKey, user, g.config.Environment)
res, ok := flagValue.([]interface{})
if !ok {
return model.JSONArrayVarResult{
Expand Down Expand Up @@ -326,7 +327,7 @@ func (g *GoFeatureFlag) jsonVariation(flagKey string, user ffuser.User, sdkDefau
}, err
}

flagValue, variationType := f.Value(flagKey, user)
flagValue, variationType := f.Value(flagKey, user, g.config.Environment)
res, ok := flagValue.(map[string]interface{})
if !ok {
return model.JSONVarResult{
Expand Down Expand Up @@ -376,7 +377,7 @@ func (g *GoFeatureFlag) RawVariation(flagKey string, user ffuser.User, sdkDefaul
return res, err
}

flagValue, variationType := f.Value(flagKey, user)
flagValue, variationType := f.Value(flagKey, user, g.config.Environment)
res := model.RawVarResult{
Value: flagValue,
VariationResult: computeVariationResult(f, variationType, false),
Expand Down