Skip to content

Commit b90eb4d

Browse files
authored
feat: improve evaluation context to Statsig user conversion (#520)
Signed-off-by: liran2000 <[email protected]>
1 parent c6b5183 commit b90eb4d

File tree

3 files changed

+103
-8
lines changed

3 files changed

+103
-8
lines changed

providers/statsig/README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
# Installation
66

7-
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
7+
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
88

99
```shell
1010
go get github.com/statsig-io/go-sdk
@@ -53,7 +53,7 @@ featureConfig := statsigProvider.FeatureConfig{
5353
evalCtx := of.NewEvaluationContext(
5454
"",
5555
map[string]interface{}{
56-
"UserID": "123",
56+
"UserID": "123", // can use "UserID" or of.TargetingKey ("targetingKey")
5757
"Email": "[email protected]",
5858
"feature_config": featureConfig,
5959
},

providers/statsig/pkg/provider.go

+18-6
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func (p *Provider) BooleanEvaluation(ctx context.Context, flag string, defaultVa
7676
}
7777
}
7878

79-
statsigUser, err := toStatsigUser(evalCtx)
79+
statsigUser, err := ToStatsigUser(evalCtx)
8080
if err != nil {
8181
return of.BoolResolutionDetail{
8282
Value: defaultValue,
@@ -191,7 +191,7 @@ func (p *Provider) ObjectEvaluation(ctx context.Context, flag string, defaultVal
191191
}
192192
}
193193

194-
statsigUser, err := toStatsigUser(evalCtx)
194+
statsigUser, err := ToStatsigUser(evalCtx)
195195
if err != nil {
196196
return of.InterfaceResolutionDetail{
197197
Value: defaultValue,
@@ -312,15 +312,15 @@ func (p *Provider) ObjectEvaluation(ctx context.Context, flag string, defaultVal
312312
}
313313
}
314314

315-
func toStatsigUser(evalCtx of.FlattenedContext) (*statsig.User, error) {
315+
func ToStatsigUser(evalCtx of.FlattenedContext) (*statsig.User, error) {
316316
if len(evalCtx) == 0 {
317317
return &statsig.User{}, nil
318318
}
319319

320320
statsigUser := statsig.User{}
321321
for key, origVal := range evalCtx {
322322
switch key {
323-
case "UserID":
323+
case of.TargetingKey, "UserID":
324324
val, ok := toStr(origVal)
325325
if !ok {
326326
return nil, fmt.Errorf("key `%s` can not be converted to string", key)
@@ -364,7 +364,13 @@ func toStatsigUser(evalCtx of.FlattenedContext) (*statsig.User, error) {
364364
statsigUser.AppVersion = val
365365
case "Custom":
366366
if val, ok := origVal.(map[string]interface{}); ok {
367-
statsigUser.Custom = val
367+
if statsigUser.Custom == nil {
368+
statsigUser.Custom = val
369+
} else {
370+
for k, v := range val {
371+
statsigUser.Custom[k] = v
372+
}
373+
}
368374
} else {
369375
return nil, fmt.Errorf("key `%s` can not be converted to map", key)
370376
}
@@ -388,9 +394,15 @@ func toStatsigUser(evalCtx of.FlattenedContext) (*statsig.User, error) {
388394
}
389395
case featureConfigKey:
390396
default:
391-
return nil, fmt.Errorf("key `%s` is not mapped", key)
397+
if statsigUser.Custom == nil {
398+
statsigUser.Custom = make(map[string]interface{})
399+
}
400+
statsigUser.Custom[key] = origVal
392401
}
393402
}
403+
if statsigUser.UserID == "" {
404+
return nil, of.NewTargetingKeyMissingResolutionError("UserID/targetingKey is missing")
405+
}
394406

395407
return &statsigUser, nil
396408
}

providers/statsig/pkg/provider_test.go

+83
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"reflect"
88
"testing"
99

10+
"github.com/stretchr/testify/assert"
1011
"github.com/stretchr/testify/require"
1112

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

92+
func TestConvertsValidUserIDToString(t *testing.T) {
93+
evalCtx := of.FlattenedContext{
94+
"UserID": "test_user",
95+
}
96+
97+
user, err := statsigProvider.ToStatsigUser(evalCtx)
98+
assert.NoError(t, err)
99+
assert.Equal(t, "test_user", user.UserID)
100+
}
101+
102+
// Converts valid EvaluationContext with all fields correctly to statsig.User
103+
func TestConvertsValidEvaluationContextToStatsigUser(t *testing.T) {
104+
evalCtx := of.FlattenedContext{
105+
of.TargetingKey: "test-key",
106+
"Email": "[email protected]",
107+
"IpAddress": "192.168.1.1",
108+
"UserAgent": "Mozilla/5.0",
109+
"Country": "US",
110+
"Locale": "en-US",
111+
"AppVersion": "1.0.0",
112+
"Custom": map[string]interface{}{"customKey": "customValue"},
113+
"PrivateAttributes": map[string]interface{}{"privateKey": "privateValue"},
114+
"StatsigEnvironment": map[string]string{"envKey": "envValue"},
115+
"CustomIDs": map[string]string{"customIDKey": "customIDValue"},
116+
"custom-key": "custom-value",
117+
}
118+
119+
user, err := statsigProvider.ToStatsigUser(evalCtx)
120+
if err != nil {
121+
t.Fatalf("expected no error, got %v", err)
122+
}
123+
124+
if user.UserID != "test-key" {
125+
t.Errorf("expected UserID to be 'test-key', got %v", user.UserID)
126+
}
127+
if user.Email != "[email protected]" {
128+
t.Errorf("expected Email to be '[email protected]', got %v", user.Email)
129+
}
130+
if user.IpAddress != "192.168.1.1" {
131+
t.Errorf("expected IpAddress to be '192.168.1.1', got %v", user.IpAddress)
132+
}
133+
if user.UserAgent != "Mozilla/5.0" {
134+
t.Errorf("expected UserAgent to be 'Mozilla/5.0', got %v", user.UserAgent)
135+
}
136+
if user.Country != "US" {
137+
t.Errorf("expected Country to be 'US', got %v", user.Country)
138+
}
139+
if user.Locale != "en-US" {
140+
t.Errorf("expected Locale to be 'en-US', got %v", user.Locale)
141+
}
142+
if user.AppVersion != "1.0.0" {
143+
t.Errorf("expected AppVersion to be '1.0.0', got %v", user.AppVersion)
144+
}
145+
if user.Custom["customKey"] != "customValue" {
146+
t.Errorf("expected Custom['customKey'] to be 'customValue', got %v", user.Custom["customKey"])
147+
}
148+
if user.PrivateAttributes["privateKey"] != "privateValue" {
149+
t.Errorf("expected PrivateAttributes['privateKey'] to be 'privateValue', got %v", user.PrivateAttributes["privateKey"])
150+
}
151+
if user.StatsigEnvironment["envKey"] != "envValue" {
152+
t.Errorf("expected StatsigEnvironment['envKey'] to be 'envValue', got %v", user.StatsigEnvironment["envKey"])
153+
}
154+
if user.CustomIDs["customIDKey"] != "customIDValue" {
155+
t.Errorf("expected CustomIDs['customIDKey'] to be 'customIDValue', got %v", user.CustomIDs["customIDKey"])
156+
}
157+
if user.Custom["custom-key"] != "custom-value" {
158+
t.Errorf("expected CustomIDs['custom-key'] to be 'custom_value', got %v", user.Custom["custom-key"])
159+
}
160+
}
161+
162+
// Handles missing TargetingKey by checking for "key" in EvaluationContext
163+
func TestHandlesMissingTargetingKey(t *testing.T) {
164+
evalCtx := of.FlattenedContext{
165+
"dummy-key": "test-key",
166+
}
167+
168+
_, err := statsigProvider.ToStatsigUser(evalCtx)
169+
if err == nil {
170+
t.Fatalf("expected error")
171+
}
172+
}
173+
91174
func cleanup() {
92175
provider.Shutdown()
93176
}

0 commit comments

Comments
 (0)