Skip to content

Commit

Permalink
feat: improve evaluation context to Statsig user conversion (#520)
Browse files Browse the repository at this point in the history
Signed-off-by: liran2000 <[email protected]>
  • Loading branch information
liran2000 authored Jun 4, 2024
1 parent c6b5183 commit b90eb4d
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 8 deletions.
4 changes: 2 additions & 2 deletions providers/statsig/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# Installation

To use the provider, you'll need to install [Statsig Go client](github.com/statsig-io/go-sdk) and Statsig provider. You can install the packages using the following command
To use the provider, you'll need to install [Statsig Go client](https://github.com/statsig-io/go-sdk) and Statsig provider. You can install the packages using the following command

```shell
go get github.com/statsig-io/go-sdk
Expand Down Expand Up @@ -53,7 +53,7 @@ featureConfig := statsigProvider.FeatureConfig{
evalCtx := of.NewEvaluationContext(
"",
map[string]interface{}{
"UserID": "123",
"UserID": "123", // can use "UserID" or of.TargetingKey ("targetingKey")
"Email": "[email protected]",
"feature_config": featureConfig,
},
Expand Down
24 changes: 18 additions & 6 deletions providers/statsig/pkg/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func (p *Provider) BooleanEvaluation(ctx context.Context, flag string, defaultVa
}
}

statsigUser, err := toStatsigUser(evalCtx)
statsigUser, err := ToStatsigUser(evalCtx)
if err != nil {
return of.BoolResolutionDetail{
Value: defaultValue,
Expand Down Expand Up @@ -191,7 +191,7 @@ func (p *Provider) ObjectEvaluation(ctx context.Context, flag string, defaultVal
}
}

statsigUser, err := toStatsigUser(evalCtx)
statsigUser, err := ToStatsigUser(evalCtx)
if err != nil {
return of.InterfaceResolutionDetail{
Value: defaultValue,
Expand Down Expand Up @@ -312,15 +312,15 @@ func (p *Provider) ObjectEvaluation(ctx context.Context, flag string, defaultVal
}
}

func toStatsigUser(evalCtx of.FlattenedContext) (*statsig.User, error) {
func ToStatsigUser(evalCtx of.FlattenedContext) (*statsig.User, error) {
if len(evalCtx) == 0 {
return &statsig.User{}, nil
}

statsigUser := statsig.User{}
for key, origVal := range evalCtx {
switch key {
case "UserID":
case of.TargetingKey, "UserID":
val, ok := toStr(origVal)
if !ok {
return nil, fmt.Errorf("key `%s` can not be converted to string", key)
Expand Down Expand Up @@ -364,7 +364,13 @@ func toStatsigUser(evalCtx of.FlattenedContext) (*statsig.User, error) {
statsigUser.AppVersion = val
case "Custom":
if val, ok := origVal.(map[string]interface{}); ok {
statsigUser.Custom = val
if statsigUser.Custom == nil {
statsigUser.Custom = val
} else {
for k, v := range val {
statsigUser.Custom[k] = v
}
}
} else {
return nil, fmt.Errorf("key `%s` can not be converted to map", key)
}
Expand All @@ -388,9 +394,15 @@ func toStatsigUser(evalCtx of.FlattenedContext) (*statsig.User, error) {
}
case featureConfigKey:
default:
return nil, fmt.Errorf("key `%s` is not mapped", key)
if statsigUser.Custom == nil {
statsigUser.Custom = make(map[string]interface{})
}
statsigUser.Custom[key] = origVal
}
}
if statsigUser.UserID == "" {
return nil, of.NewTargetingKeyMissingResolutionError("UserID/targetingKey is missing")
}

return &statsigUser, nil
}
Expand Down
83 changes: 83 additions & 0 deletions providers/statsig/pkg/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"reflect"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

statsigProvider "github.com/open-feature/go-sdk-contrib/providers/statsig/pkg"
Expand Down Expand Up @@ -88,6 +89,88 @@ func TestBoolLayerEvaluation(t *testing.T) {
}
}

func TestConvertsValidUserIDToString(t *testing.T) {
evalCtx := of.FlattenedContext{
"UserID": "test_user",
}

user, err := statsigProvider.ToStatsigUser(evalCtx)
assert.NoError(t, err)
assert.Equal(t, "test_user", user.UserID)
}

// Converts valid EvaluationContext with all fields correctly to statsig.User
func TestConvertsValidEvaluationContextToStatsigUser(t *testing.T) {
evalCtx := of.FlattenedContext{
of.TargetingKey: "test-key",
"Email": "[email protected]",
"IpAddress": "192.168.1.1",
"UserAgent": "Mozilla/5.0",
"Country": "US",
"Locale": "en-US",
"AppVersion": "1.0.0",
"Custom": map[string]interface{}{"customKey": "customValue"},
"PrivateAttributes": map[string]interface{}{"privateKey": "privateValue"},
"StatsigEnvironment": map[string]string{"envKey": "envValue"},
"CustomIDs": map[string]string{"customIDKey": "customIDValue"},
"custom-key": "custom-value",
}

user, err := statsigProvider.ToStatsigUser(evalCtx)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}

if user.UserID != "test-key" {
t.Errorf("expected UserID to be 'test-key', got %v", user.UserID)
}
if user.Email != "[email protected]" {
t.Errorf("expected Email to be '[email protected]', got %v", user.Email)
}
if user.IpAddress != "192.168.1.1" {
t.Errorf("expected IpAddress to be '192.168.1.1', got %v", user.IpAddress)
}
if user.UserAgent != "Mozilla/5.0" {
t.Errorf("expected UserAgent to be 'Mozilla/5.0', got %v", user.UserAgent)
}
if user.Country != "US" {
t.Errorf("expected Country to be 'US', got %v", user.Country)
}
if user.Locale != "en-US" {
t.Errorf("expected Locale to be 'en-US', got %v", user.Locale)
}
if user.AppVersion != "1.0.0" {
t.Errorf("expected AppVersion to be '1.0.0', got %v", user.AppVersion)
}
if user.Custom["customKey"] != "customValue" {
t.Errorf("expected Custom['customKey'] to be 'customValue', got %v", user.Custom["customKey"])
}
if user.PrivateAttributes["privateKey"] != "privateValue" {
t.Errorf("expected PrivateAttributes['privateKey'] to be 'privateValue', got %v", user.PrivateAttributes["privateKey"])
}
if user.StatsigEnvironment["envKey"] != "envValue" {
t.Errorf("expected StatsigEnvironment['envKey'] to be 'envValue', got %v", user.StatsigEnvironment["envKey"])
}
if user.CustomIDs["customIDKey"] != "customIDValue" {
t.Errorf("expected CustomIDs['customIDKey'] to be 'customIDValue', got %v", user.CustomIDs["customIDKey"])
}
if user.Custom["custom-key"] != "custom-value" {
t.Errorf("expected CustomIDs['custom-key'] to be 'custom_value', got %v", user.Custom["custom-key"])
}
}

// Handles missing TargetingKey by checking for "key" in EvaluationContext
func TestHandlesMissingTargetingKey(t *testing.T) {
evalCtx := of.FlattenedContext{
"dummy-key": "test-key",
}

_, err := statsigProvider.ToStatsigUser(evalCtx)
if err == nil {
t.Fatalf("expected error")
}
}

func cleanup() {
provider.Shutdown()
}
Expand Down

0 comments on commit b90eb4d

Please sign in to comment.