Skip to content

Commit eb22398

Browse files
author
Michael Ng
authored
feat: Hook up event processor to IsFeatureEnabled. (#67)
1 parent f0b69af commit eb22398

File tree

12 files changed

+105
-68
lines changed

12 files changed

+105
-68
lines changed

examples/main.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77

88
"github.com/optimizely/go-sdk/optimizely/client"
99
"github.com/optimizely/go-sdk/optimizely/entities"
10-
"github.com/optimizely/go-sdk/optimizely/event"
1110
"github.com/optimizely/go-sdk/optimizely/logging"
1211
)
1312

@@ -37,18 +36,6 @@ func main() {
3736
enabled, _ := app.IsFeatureEnabled("mutext_feat", user)
3837
fmt.Printf("Is feature enabled? %v\n", enabled)
3938

40-
processor := event.NewEventProcessor(100, 100)
41-
42-
impression := event.UserEvent{}
43-
44-
processor.ProcessEvent(impression)
45-
46-
_, ok := processor.(*event.QueueingEventProcessor)
47-
48-
if ok {
49-
time.Sleep(1000 * time.Millisecond)
50-
fmt.Println("\nending")
51-
}
5239
fmt.Println()
5340

5441
/************* ClientWithOptions - custom context ********************/

optimizely/client/client.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
"reflect"
2424
"runtime/debug"
2525

26+
"github.com/optimizely/go-sdk/optimizely/event"
27+
2628
"github.com/optimizely/go-sdk/optimizely"
2729
"github.com/optimizely/go-sdk/optimizely/decision"
2830
"github.com/optimizely/go-sdk/optimizely/entities"
@@ -35,6 +37,7 @@ var logger = logging.GetLogger("Client")
3537
type OptimizelyClient struct {
3638
configManager optimizely.ProjectConfigManager
3739
decisionService decision.DecisionService
40+
eventProcessor event.Processor
3841
isValid bool
3942

4043
cancelFunc context.CancelFunc
@@ -80,16 +83,18 @@ func (o *OptimizelyClient) IsFeatureEnabled(featureKey string, userContext entit
8083
return result, err
8184
}
8285

83-
logger.Debug(fmt.Sprintf(`Decision made for feature "%s" for user "%s" with the following reason: "%s". Source: "%s".`, featureKey, userID, featureDecision.Reason, featureDecision.Source))
84-
8586
if featureDecision.Variation.FeatureEnabled == true {
8687
result = true
8788
logger.Info(fmt.Sprintf(`Feature "%s" is enabled for user "%s".`, featureKey, userID))
8889
} else {
8990
logger.Info(fmt.Sprintf(`Feature "%s" is not enabled for user "%s".`, featureKey, userID))
9091
}
9192

92-
// @TODO(mng): send impression event
93+
if featureDecision.Source == decision.FeatureTest {
94+
// send impression event for feature tests
95+
impressionEvent := event.CreateImpressionUserEvent(projectConfig, featureDecision.Experiment, featureDecision.Variation, userContext)
96+
o.eventProcessor.ProcessEvent(impressionEvent)
97+
}
9398
return result, nil
9499
}
95100

optimizely/client/factory.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ import (
2020
"context"
2121
"errors"
2222
"fmt"
23+
"time"
24+
25+
"github.com/optimizely/go-sdk/optimizely/event"
2326

2427
"github.com/optimizely/go-sdk/optimizely/notification"
2528

@@ -41,6 +44,9 @@ type OptimizelyFactory struct {
4144
Datafile []byte
4245
}
4346

47+
const defaultEventQueueSize = 10
48+
const defaultEventFlushInterval = 30 * time.Second
49+
4450
// StaticClient returns a client initialized with a static project config
4551
func (f OptimizelyFactory) StaticClient() (*OptimizelyClient, error) {
4652
var configManager optimizely.ProjectConfigManager
@@ -111,6 +117,9 @@ func (f OptimizelyFactory) ClientWithOptions(clientOptions Options) (*Optimizely
111117
client.decisionService = decision.NewCompositeService(notificationCenter)
112118
}
113119

120+
// @TODO: allow event processor to be passed in
121+
// @TODO: pass the context object to the event processor
122+
client.eventProcessor = event.NewEventProcessor(defaultEventQueueSize, defaultEventFlushInterval)
114123
client.isValid = true
115124
return client, nil
116125
}

optimizely/config/datafileprojectconfig/config.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,17 +84,17 @@ func NewDatafileProjectConfig(jsonDatafile []byte) (*DatafileProjectConfig, erro
8484
}
8585

8686
attributeMap, attributeKeyToIDMap := mappers.MapAttributes(datafile.Attributes)
87-
experiments, experimentKeyMap := mappers.MapExperiments(datafile.Experiments)
87+
experimentMap, experimentKeyMap := mappers.MapExperiments(datafile.Experiments)
8888
rolloutMap := mappers.MapRollouts(datafile.Rollouts)
8989
mergedAudiences := append(datafile.TypedAudiences, datafile.Audiences...)
9090
config := &DatafileProjectConfig{
9191
audienceMap: mappers.MapAudiences(mergedAudiences),
9292
attributeMap: attributeMap,
9393
attributeKeyToIDMap: attributeKeyToIDMap,
94-
experimentMap: experiments,
94+
experimentMap: experimentMap,
9595
experimentKeyToIDMap: experimentKeyMap,
9696
rolloutMap: rolloutMap,
97-
featureMap: mappers.MapFeatureFlags(datafile.FeatureFlags, rolloutMap),
97+
featureMap: mappers.MapFeatureFlags(datafile.FeatureFlags, rolloutMap, experimentMap),
9898
}
9999

100100
logger.Info("Datafile is valid.")

optimizely/config/datafileprojectconfig/mappers/feature.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,29 @@ import (
2222
)
2323

2424
// MapFeatureFlags maps the raw datafile feature flag entities to SDK Feature entities
25-
func MapFeatureFlags(featureFlags []datafileEntities.FeatureFlag, rolloutMap map[string]entities.Rollout) map[string]entities.Feature {
25+
func MapFeatureFlags(
26+
featureFlags []datafileEntities.FeatureFlag,
27+
rolloutMap map[string]entities.Rollout,
28+
experimentMap map[string]entities.Experiment,
29+
) map[string]entities.Feature {
2630

2731
featureMap := make(map[string]entities.Feature)
2832
for _, featureFlag := range featureFlags {
29-
// @TODO(mng): include experiments in the Feature
3033
feature := entities.Feature{
3134
Key: featureFlag.Key,
3235
ID: featureFlag.ID,
3336
}
3437
if rollout, ok := rolloutMap[featureFlag.RolloutID]; ok {
3538
feature.Rollout = rollout
3639
}
40+
featureExperiments := []entities.Experiment{}
41+
for _, experimentID := range featureFlag.ExperimentIDs {
42+
if experiment, ok := experimentMap[experimentID]; ok {
43+
featureExperiments = append(featureExperiments, experiment)
44+
}
45+
}
46+
47+
feature.FeatureExperiments = featureExperiments
3748
featureMap[featureFlag.Key] = feature
3849
}
3950
return featureMap

optimizely/config/datafileprojectconfig/mappers/feature_test.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ func TestMapFeatures(t *testing.T) {
2929
const testFeatureFlagString = `{
3030
"id": "21111",
3131
"key": "test_feature_21111",
32-
"rolloutId": "41111"
32+
"rolloutId": "41111",
33+
"experimentIds": ["31111", "31112"]
3334
}`
3435

3536
var rawFeatureFlag datafileEntities.FeatureFlag
@@ -40,12 +41,19 @@ func TestMapFeatures(t *testing.T) {
4041
rolloutMap := map[string]entities.Rollout{
4142
"41111": rollout,
4243
}
43-
featureMap := MapFeatureFlags(rawFeatureFlags, rolloutMap)
44+
experiment31111 := entities.Experiment{ID: "31111"}
45+
experiment31112 := entities.Experiment{ID: "31112"}
46+
experimentMap := map[string]entities.Experiment{
47+
"31111": experiment31111,
48+
"31112": experiment31112,
49+
}
50+
featureMap := MapFeatureFlags(rawFeatureFlags, rolloutMap, experimentMap)
4451
expectedFeatureMap := map[string]entities.Feature{
4552
"test_feature_21111": entities.Feature{
46-
ID: "21111",
47-
Key: "test_feature_21111",
48-
Rollout: rollout,
53+
ID: "21111",
54+
Key: "test_feature_21111",
55+
Rollout: rollout,
56+
FeatureExperiments: []entities.Experiment{experiment31111, experiment31112},
4957
},
5058
}
5159

optimizely/decision/composite_feature_service.go

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,14 @@
1717
package decision
1818

1919
import (
20+
"fmt"
21+
2022
"github.com/optimizely/go-sdk/optimizely/entities"
23+
"github.com/optimizely/go-sdk/optimizely/logging"
2124
)
2225

26+
var cfLogger = logging.GetLogger("CompositeFeatureService")
27+
2328
// CompositeFeatureService is the default out-of-the-box feature decision service
2429
type CompositeFeatureService struct {
2530
experimentDecisionService ExperimentDecisionService
@@ -29,7 +34,8 @@ type CompositeFeatureService struct {
2934
// NewCompositeFeatureService returns a new instance of the CompositeFeatureService
3035
func NewCompositeFeatureService() *CompositeFeatureService {
3136
return &CompositeFeatureService{
32-
rolloutDecisionService: NewRolloutService(),
37+
experimentDecisionService: NewCompositeExperimentService(),
38+
rolloutDecisionService: NewRolloutService(),
3339
}
3440
}
3541

@@ -47,12 +53,24 @@ func (f CompositeFeatureService) GetDecision(decisionContext FeatureDecisionCont
4753
}
4854

4955
experimentDecision, err := f.experimentDecisionService.GetDecision(experimentDecisionContext, userContext)
50-
featureDecision := FeatureDecision{
51-
Experiment: experiment,
52-
Decision: experimentDecision.Decision,
53-
Variation: experimentDecision.Variation,
56+
// If we get an empty string Variation ID it means that the user is assigned no variation, hence we
57+
// move onto Rollout evaluation
58+
if experimentDecision.Variation.ID != "" {
59+
featureDecision := FeatureDecision{
60+
Experiment: experiment,
61+
Decision: experimentDecision.Decision,
62+
Variation: experimentDecision.Variation,
63+
Source: FeatureTest,
64+
}
65+
66+
cfLogger.Debug(fmt.Sprintf(
67+
`Decision made for feature test with key "%s" for user "%s" with the following reason: "%s".`,
68+
feature.Key,
69+
userContext.ID,
70+
featureDecision.Reason,
71+
))
72+
return featureDecision, err
5473
}
55-
return featureDecision, err
5674
}
5775

5876
featureDecisionContext := FeatureDecisionContext{
@@ -61,5 +79,12 @@ func (f CompositeFeatureService) GetDecision(decisionContext FeatureDecisionCont
6179
}
6280
featureDecision, err := f.rolloutDecisionService.GetDecision(featureDecisionContext, userContext)
6381
featureDecision.Source = Rollout
82+
cfLogger.Debug(fmt.Sprintf(
83+
`Decision made for feature rollout with key "%s" for user "%s" with the following reason: "%s".`,
84+
feature.Key,
85+
userContext.ID,
86+
featureDecision.Reason,
87+
))
88+
6489
return featureDecision, err
6590
}

optimizely/decision/entities.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ type Source string
4040
const (
4141
// Rollout - the decision came from a rollout
4242
Rollout Source = "rollout"
43+
// FeatureTest - the decision came from a feature test
44+
FeatureTest Source = "feature-test"
4345
)
4446

4547
// Decision contains base information about a decision

optimizely/event/dispatcher.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import (
44
"bytes"
55
"encoding/json"
66
"fmt"
7-
"github.com/optimizely/go-sdk/optimizely/logging"
87
"net/http"
8+
9+
"github.com/optimizely/go-sdk/optimizely/logging"
910
)
1011

12+
const jsonContentType = "application/json"
13+
1114
// Dispatcher dispatches events
1215
type Dispatcher interface {
1316
DispatchEvent(event LogEvent, callback func(success bool))
@@ -23,7 +26,7 @@ var dispatcherLogger = logging.GetLogger("EventDispatcher")
2326
func (*HTTPEventDispatcher) DispatchEvent(event LogEvent, callback func(success bool)) {
2427

2528
jsonValue, _ := json.Marshal(event.event)
26-
resp, err := http.Post( event.endPoint, "application/json", bytes.NewBuffer(jsonValue))
29+
resp, err := http.Post(event.endPoint, jsonContentType, bytes.NewBuffer(jsonValue))
2730
// also check response codes
2831
// resp.StatusCode == 400 is an error
2932
success := true

optimizely/event/factory_test.go

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,11 @@ func TestCreateAndSendImpressionEvent(t *testing.T) {
9595

9696
processor.ProcessEvent(impressionUserEvent)
9797

98-
result, ok := processor.(*QueueingEventProcessor)
98+
assert.Equal(t, 1, processor.EventsCount())
9999

100-
if ok {
101-
assert.Equal(t, 1, result.EventsCount())
100+
time.Sleep(2000 * time.Millisecond)
102101

103-
time.Sleep(2000 * time.Millisecond)
104-
105-
assert.Equal(t, 0, result.EventsCount())
106-
}
102+
assert.Equal(t, 0, processor.EventsCount())
107103
}
108104

109105
func TestCreateAndSendConversionEvent(t *testing.T) {
@@ -114,15 +110,11 @@ func TestCreateAndSendConversionEvent(t *testing.T) {
114110

115111
processor.ProcessEvent(conversionUserEvent)
116112

117-
result, ok := processor.(*QueueingEventProcessor)
113+
assert.Equal(t, 1, processor.EventsCount())
118114

119-
if ok {
120-
assert.Equal(t, 1, result.EventsCount())
115+
time.Sleep(2000 * time.Millisecond)
121116

122-
time.Sleep(2000 * time.Millisecond)
123-
124-
assert.Equal(t, 0, result.EventsCount())
125-
}
117+
assert.Equal(t, 0, processor.EventsCount())
126118
}
127119

128120
func TestCreateConversionEventRevenue(t *testing.T) {

0 commit comments

Comments
 (0)