Skip to content

Commit 688a80e

Browse files
feat: Implement OptimizelyConfig API (#216)
* feat: Implement OptimizelyConfig API
1 parent aedd149 commit 688a80e

17 files changed

+1361
-21
lines changed

pkg/client/client.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ func (o *OptimizelyClient) GetEnabledFeatures(userContext entities.UserContext)
145145
}
146146
}()
147147

148-
projectConfig, err := o.GetProjectConfig()
148+
projectConfig, err := o.getProjectConfig()
149149
if err != nil {
150150
logger.Error("Error retrieving ProjectConfig", err)
151151
return enabledFeatures, err
@@ -318,7 +318,7 @@ func (o *OptimizelyClient) Track(eventKey string, userContext entities.UserConte
318318
}
319319
}()
320320

321-
projectConfig, e := o.GetProjectConfig()
321+
projectConfig, e := o.getProjectConfig()
322322
if e != nil {
323323
logger.Error("Optimizely SDK tracking error", e)
324324
return e
@@ -364,7 +364,7 @@ func (o *OptimizelyClient) getFeatureDecision(featureKey, variableKey string, us
364364
userID := userContext.ID
365365
logger.Debug(fmt.Sprintf(`Evaluating feature "%s" for user "%s".`, featureKey, userID))
366366

367-
projectConfig, e := o.GetProjectConfig()
367+
projectConfig, e := o.getProjectConfig()
368368
if e != nil {
369369
logger.Error("Error calling getFeatureDecision", e)
370370
return decisionContext, featureDecision, e
@@ -405,7 +405,7 @@ func (o *OptimizelyClient) getExperimentDecision(experimentKey string, userConte
405405
userID := userContext.ID
406406
logger.Debug(fmt.Sprintf(`Evaluating experiment "%s" for user "%s".`, experimentKey, userID))
407407

408-
projectConfig, e := o.GetProjectConfig()
408+
projectConfig, e := o.getProjectConfig()
409409
if e != nil {
410410
return decisionContext, experimentDecision, e
411411
}
@@ -475,8 +475,7 @@ func (o *OptimizelyClient) RemoveOnTrack(id int) error {
475475
return nil
476476
}
477477

478-
// GetProjectConfig returns the current ProjectConfig or nil if the instance is not valid.
479-
func (o *OptimizelyClient) GetProjectConfig() (projectConfig config.ProjectConfig, err error) {
478+
func (o *OptimizelyClient) getProjectConfig() (projectConfig config.ProjectConfig, err error) {
480479

481480
projectConfig, err = o.ConfigManager.GetConfig()
482481
if err != nil {
@@ -486,6 +485,13 @@ func (o *OptimizelyClient) GetProjectConfig() (projectConfig config.ProjectConfi
486485
return projectConfig, nil
487486
}
488487

488+
// GetOptimizelyConfig returns OptimizelyConfig object
489+
func (o *OptimizelyClient) GetOptimizelyConfig() (optimizelyConfig *config.OptimizelyConfig) {
490+
491+
return o.ConfigManager.GetOptimizelyConfig()
492+
493+
}
494+
489495
// Close closes the Optimizely instance and stops any ongoing tasks from its children components.
490496
func (o *OptimizelyClient) Close() {
491497
o.execGroup.TerminateAndWait()

pkg/client/client_test.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,12 +1186,24 @@ func TestGetProjectConfigIsValid(t *testing.T) {
11861186
ConfigManager: mockConfigManager,
11871187
}
11881188

1189-
actual, err := client.GetProjectConfig()
1189+
actual, err := client.getProjectConfig()
11901190

11911191
assert.Nil(t, err)
11921192
assert.Equal(t, mockConfigManager.projectConfig, actual)
11931193
}
11941194

1195+
func TestGetOptimizelyConfig(t *testing.T) {
1196+
mockConfigManager := ValidProjectConfigManager()
1197+
1198+
client := OptimizelyClient{
1199+
ConfigManager: mockConfigManager,
1200+
}
1201+
1202+
optimizelyConfig := client.GetOptimizelyConfig()
1203+
1204+
assert.Equal(t, &config.OptimizelyConfig{Revision: "232"}, optimizelyConfig)
1205+
}
1206+
11951207
func TestGetFeatureDecisionValid(t *testing.T) {
11961208
testFeatureKey := "test_feature_key"
11971209
testVariableKey := "test_feature_flag_key"
@@ -2095,7 +2107,7 @@ func (s *ClientTestSuiteTrackEvent) TestTrackNotificationNotCalledWhenSendThrows
20952107
}
20962108

20972109
mockNotificationCenter := new(MockNotificationCenter)
2098-
config, err := s.client.GetProjectConfig()
2110+
config, err := s.client.getProjectConfig()
20992111
s.NoError(err)
21002112
configEvent, err := config.GetEventByKey("sample_conversion")
21012113
s.NoError(err)

pkg/client/fixtures_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ func (p *MockProjectConfigManager) RemoveOnProjectConfigUpdate(id int) error {
100100
return nil
101101
}
102102

103+
func (p *MockProjectConfigManager) GetOptimizelyConfig() *config.OptimizelyConfig {
104+
105+
optimizelyConfig := &config.OptimizelyConfig{}
106+
optimizelyConfig.Revision = "232"
107+
return optimizelyConfig
108+
}
109+
103110
type MockDecisionService struct {
104111
decision.Service
105112
mock.Mock

pkg/config/datafileprojectconfig/config.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,14 @@ func (c DatafileProjectConfig) GetFeatureList() (featureList []entities.Feature)
127127
return featureList
128128
}
129129

130+
// GetExperimentList returns an array of all the experiments
131+
func (c DatafileProjectConfig) GetExperimentList() (experimentList []entities.Experiment) {
132+
for _, experiment := range c.experimentMap {
133+
experimentList = append(experimentList, experiment)
134+
}
135+
return experimentList
136+
}
137+
130138
// GetAudienceByID returns the audience with the given ID
131139
func (c DatafileProjectConfig) GetAudienceByID(audienceID string) (entities.Audience, error) {
132140
if audience, ok := c.audienceMap[audienceID]; ok {

pkg/config/datafileprojectconfig/config_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,29 @@ func TestGetFeatureList(t *testing.T) {
266266
assert.Equal(t, feature, features[0])
267267
}
268268

269+
func TestGetExperimentList(t *testing.T) {
270+
id := "id"
271+
key := "key"
272+
experimentKeyToIDMap := make(map[string]string)
273+
experimentKeyToIDMap[key] = id
274+
275+
experiment := entities.Experiment{
276+
Key: key,
277+
}
278+
experimentMap := make(map[string]entities.Experiment)
279+
experimentMap[id] = experiment
280+
281+
config := &DatafileProjectConfig{
282+
experimentKeyToIDMap: experimentKeyToIDMap,
283+
experimentMap: experimentMap,
284+
}
285+
286+
experiments := config.GetExperimentList()
287+
288+
assert.Equal(t, 1, len(experiments))
289+
assert.Equal(t, experiment, experiments[0])
290+
}
291+
269292
func TestGetAudienceByID(t *testing.T) {
270293
id := "id"
271294
audience := entities.Audience{

pkg/config/interface.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type ProjectConfig interface {
3535
GetExperimentByKey(string) (entities.Experiment, error)
3636
GetFeatureByKey(string) (entities.Feature, error)
3737
GetVariableByKey(featureKey string, variableKey string) (entities.Variable, error)
38+
GetExperimentList() []entities.Experiment
3839
GetFeatureList() []entities.Feature
3940
GetGroupByID(string) (entities.Group, error)
4041
GetProjectID() string
@@ -44,6 +45,7 @@ type ProjectConfig interface {
4445
// ProjectConfigManager maintains an instance of the ProjectConfig
4546
type ProjectConfigManager interface {
4647
GetConfig() (ProjectConfig, error)
48+
GetOptimizelyConfig() *OptimizelyConfig
4749
RemoveOnProjectConfigUpdate(id int) error
4850
OnProjectConfigUpdate(callback func(notification.ProjectConfigUpdateNotification)) (int, error)
4951
}

pkg/config/optimizely_config.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/****************************************************************************
2+
* Copyright 2019, Optimizely, Inc. and contributors *
3+
* *
4+
* Licensed under the Apache License, Version 2.0 (the "License"); *
5+
* you may not use this file except in compliance with the License. *
6+
* You may obtain a copy of the License at *
7+
* *
8+
* http://www.apache.org/licenses/LICENSE-2.0 *
9+
* *
10+
* Unless required by applicable law or agreed to in writing, software *
11+
* distributed under the License is distributed on an "AS IS" BASIS, *
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13+
* See the License for the specific language governing permissions and *
14+
* limitations under the License. *
15+
***************************************************************************/
16+
17+
// Package config //
18+
package config
19+
20+
import "github.com/optimizely/go-sdk/pkg/entities"
21+
22+
// OptimizelyConfig is a snapshot of the experiments and features in the project config
23+
type OptimizelyConfig struct {
24+
Revision string
25+
ExperimentsMap map[string]OptimizelyExperiment
26+
FeaturesMap map[string]OptimizelyFeature
27+
}
28+
29+
// OptimizelyExperiment has experiment info
30+
type OptimizelyExperiment struct {
31+
ID string
32+
Key string
33+
VariationsMap map[string]OptimizelyVariation
34+
}
35+
36+
// OptimizelyFeature has feature info
37+
type OptimizelyFeature struct {
38+
ID string
39+
Key string
40+
ExperimentsMap map[string]OptimizelyExperiment
41+
VariablesMap map[string]OptimizelyVariable
42+
}
43+
44+
// OptimizelyVariation has variation info
45+
type OptimizelyVariation struct {
46+
ID string
47+
Key string
48+
FeatureEnabled bool
49+
VariablesMap map[string]OptimizelyVariable
50+
}
51+
52+
// OptimizelyVariable has variable info
53+
type OptimizelyVariable struct {
54+
ID string
55+
Key string
56+
Type string
57+
Value string
58+
}
59+
60+
func getVariableByIDMap(features []entities.Feature) (variableByIDMap map[string]entities.Variable) {
61+
variableByIDMap = map[string]entities.Variable{}
62+
for _, feature := range features {
63+
for _, variable := range feature.VariableMap {
64+
variableByIDMap[variable.ID] = variable
65+
}
66+
}
67+
return variableByIDMap
68+
}
69+
70+
func getExperimentVariablesMap(features []entities.Feature) (experimentVariableMap map[string]map[string]OptimizelyVariable) {
71+
experimentVariableMap = map[string]map[string]OptimizelyVariable{}
72+
for _, feature := range features {
73+
74+
var optimizelyVariableMap = map[string]OptimizelyVariable{}
75+
for _, variable := range feature.VariableMap {
76+
optimizelyVariableMap[variable.Key] = OptimizelyVariable{Key: variable.Key, ID: variable.ID, Value: variable.DefaultValue, Type: string(variable.Type)}
77+
78+
}
79+
for _, experiment := range feature.FeatureExperiments {
80+
experimentVariableMap[experiment.Key] = optimizelyVariableMap
81+
}
82+
}
83+
return experimentVariableMap
84+
}
85+
86+
func getExperimentMap(features []entities.Feature, experiments []entities.Experiment, variableByIDMap map[string]entities.Variable) (optlyExperimentMap map[string]OptimizelyExperiment) {
87+
88+
optlyExperimentMap = map[string]OptimizelyExperiment{}
89+
experimentVariablesMap := getExperimentVariablesMap(features)
90+
91+
for _, experiment := range experiments {
92+
var optlyVariationsMap = map[string]OptimizelyVariation{}
93+
for _, variation := range experiment.Variations {
94+
var optlyVariablesMap = map[string]OptimizelyVariable{}
95+
96+
if variableMap, ok := experimentVariablesMap[experiment.Key]; ok {
97+
for index, element := range variableMap { // copy by value
98+
optlyVariablesMap[index] = element
99+
}
100+
}
101+
102+
for _, variable := range variation.Variables {
103+
if experiment.IsFeatureExperiment && variation.FeatureEnabled {
104+
if convertedVariable, ok := variableByIDMap[variable.ID]; ok {
105+
optlyVariable := OptimizelyVariable{Key: convertedVariable.Key, ID: convertedVariable.ID,
106+
Type: string(convertedVariable.Type), Value: variable.Value}
107+
optlyVariablesMap[convertedVariable.Key] = optlyVariable
108+
}
109+
}
110+
}
111+
optVariation := OptimizelyVariation{ID: variation.ID, Key: variation.Key, VariablesMap: optlyVariablesMap, FeatureEnabled: variation.FeatureEnabled}
112+
optlyVariationsMap[variation.Key] = optVariation
113+
}
114+
optlyExperiment := OptimizelyExperiment{ID: experiment.ID, Key: experiment.Key, VariationsMap: optlyVariationsMap}
115+
optlyExperimentMap[experiment.Key] = optlyExperiment
116+
}
117+
return optlyExperimentMap
118+
}
119+
120+
func getFeatureMap(features []entities.Feature, experimentsMap map[string]OptimizelyExperiment) (optlyFeatureMap map[string]OptimizelyFeature) {
121+
122+
optlyFeatureMap = map[string]OptimizelyFeature{}
123+
124+
for _, feature := range features {
125+
126+
var optlyFeatureVariablesMap = map[string]OptimizelyVariable{}
127+
for _, featureVarible := range feature.VariableMap {
128+
optlyVariable := OptimizelyVariable{Key: featureVarible.Key, ID: featureVarible.ID,
129+
Type: string(featureVarible.Type), Value: featureVarible.DefaultValue}
130+
optlyFeatureVariablesMap[featureVarible.Key] = optlyVariable
131+
}
132+
133+
var optlyExperimentMap = map[string]OptimizelyExperiment{}
134+
for _, experiment := range feature.FeatureExperiments {
135+
optlyExperimentMap[experiment.Key] = experimentsMap[experiment.Key]
136+
}
137+
138+
optlyFeature := OptimizelyFeature{ID: feature.ID, Key: feature.Key, ExperimentsMap: optlyExperimentMap, VariablesMap: optlyFeatureVariablesMap}
139+
optlyFeatureMap[feature.Key] = optlyFeature
140+
141+
}
142+
return optlyFeatureMap
143+
}
144+
145+
// NewOptimizelyConfig constructs OptimizelyConfig object
146+
func NewOptimizelyConfig(projConfig ProjectConfig) *OptimizelyConfig {
147+
148+
if projConfig == nil {
149+
return nil
150+
}
151+
featuresList := projConfig.GetFeatureList()
152+
experimentsList := projConfig.GetExperimentList()
153+
revision := projConfig.GetRevision()
154+
155+
optimizelyConfig := &OptimizelyConfig{}
156+
157+
variableByIDMap := getVariableByIDMap(featuresList)
158+
159+
optimizelyConfig.ExperimentsMap = getExperimentMap(featuresList, experimentsList, variableByIDMap)
160+
optimizelyConfig.FeaturesMap = getFeatureMap(featuresList, optimizelyConfig.ExperimentsMap)
161+
optimizelyConfig.Revision = revision
162+
163+
return optimizelyConfig
164+
}

0 commit comments

Comments
 (0)