Skip to content

Commit 9d97c16

Browse files
authored
feat(OptimizelyConfig): Add new fields to OptimizelyConfig. (#322)
## Summary The following new public properties are added to OptimizelyConfig: - sdkKey - environmentKey - attributes - audiences - events - experimentRules and deliveryRules to OptimizelyFeature - audiences to OptimizelyExperiment
1 parent 43dd94a commit 9d97c16

28 files changed

+1934
-694
lines changed

pkg/config/datafileprojectconfig/config.go

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ type DatafileProjectConfig struct {
4444
experimentMap map[string]entities.Experiment
4545
featureMap map[string]entities.Feature
4646
groupMap map[string]entities.Group
47+
rollouts []entities.Rollout
4748
rolloutMap map[string]entities.Rollout
4849
anonymizeIP bool
4950
botFiltering bool
@@ -77,6 +78,14 @@ func (c DatafileProjectConfig) GetAnonymizeIP() bool {
7778
return c.anonymizeIP
7879
}
7980

81+
// GetAttributes returns attributes
82+
func (c DatafileProjectConfig) GetAttributes() (attributeList []entities.Attribute) {
83+
for _, attribute := range c.attributeMap {
84+
attributeList = append(attributeList, attribute)
85+
}
86+
return attributeList
87+
}
88+
8089
// GetAttributeID returns attributeID
8190
func (c DatafileProjectConfig) GetAttributeID(key string) string {
8291
return c.attributeKeyToIDMap[key]
@@ -97,6 +106,14 @@ func (c DatafileProjectConfig) GetEnvironmentKey() string {
97106
return c.environmentKey
98107
}
99108

109+
// GetEvents returns all events
110+
func (c DatafileProjectConfig) GetEvents() (eventList []entities.Event) {
111+
for _, event := range c.eventMap {
112+
eventList = append(eventList, event)
113+
}
114+
return eventList
115+
}
116+
100117
// GetEventByKey returns the event with the given key
101118
func (c DatafileProjectConfig) GetEventByKey(eventKey string) (entities.Event, error) {
102119
if event, ok := c.eventMap[eventKey]; ok {
@@ -157,6 +174,19 @@ func (c DatafileProjectConfig) GetExperimentList() (experimentList []entities.Ex
157174
return experimentList
158175
}
159176

177+
// GetRolloutList returns an array of all the rollouts
178+
func (c DatafileProjectConfig) GetRolloutList() (rolloutList []entities.Rollout) {
179+
return c.rollouts
180+
}
181+
182+
// GetAudienceList returns an array of all the audiences
183+
func (c DatafileProjectConfig) GetAudienceList() (audienceList []entities.Audience) {
184+
for _, audience := range c.audienceMap {
185+
audienceList = append(audienceList, audience)
186+
}
187+
return audienceList
188+
}
189+
160190
// GetAudienceByID returns the audience with the given ID
161191
func (c DatafileProjectConfig) GetAudienceByID(audienceID string) (entities.Audience, error) {
162192
if audience, ok := c.audienceMap[audienceID]; ok {
@@ -213,29 +243,32 @@ func NewDatafileProjectConfig(jsonDatafile []byte, logger logging.OptimizelyLogP
213243
attributeMap, attributeKeyToIDMap := mappers.MapAttributes(datafile.Attributes)
214244
allExperiments := mappers.MergeExperiments(datafile.Experiments, datafile.Groups)
215245
groupMap, experimentGroupMap := mappers.MapGroups(datafile.Groups)
216-
experimentMap, experimentKeyMap := mappers.MapExperiments(allExperiments, experimentGroupMap)
246+
experimentIDMap, experimentKeyMap := mappers.MapExperiments(allExperiments, experimentGroupMap)
217247

218-
rolloutMap := mappers.MapRollouts(datafile.Rollouts)
248+
rollouts, rolloutMap := mappers.MapRollouts(datafile.Rollouts)
219249
eventMap := mappers.MapEvents(datafile.Events)
220250
mergedAudiences := append(datafile.TypedAudiences, datafile.Audiences...)
221-
featureMap := mappers.MapFeatures(datafile.FeatureFlags, rolloutMap, experimentMap)
251+
featureMap := mappers.MapFeatures(datafile.FeatureFlags, rolloutMap, experimentIDMap)
252+
audienceMap := mappers.MapAudiences(mergedAudiences)
253+
222254
config := &DatafileProjectConfig{
223255
datafile: string(jsonDatafile),
224256
accountID: datafile.AccountID,
225257
anonymizeIP: datafile.AnonymizeIP,
226258
attributeKeyToIDMap: attributeKeyToIDMap,
227-
audienceMap: mappers.MapAudiences(mergedAudiences),
259+
audienceMap: audienceMap,
228260
attributeMap: attributeMap,
229261
botFiltering: datafile.BotFiltering,
230262
sdkKey: datafile.SDKKey,
231263
environmentKey: datafile.EnvironmentKey,
232264
experimentKeyToIDMap: experimentKeyMap,
233-
experimentMap: experimentMap,
265+
experimentMap: experimentIDMap,
234266
groupMap: groupMap,
235267
eventMap: eventMap,
236268
featureMap: featureMap,
237269
projectID: datafile.ProjectID,
238270
revision: datafile.Revision,
271+
rollouts: rollouts,
239272
rolloutMap: rolloutMap,
240273
sendFlagDecisions: datafile.SendFlagDecisions,
241274
}

pkg/config/datafileprojectconfig/config_test.go

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ func TestNewDatafileProjectConfigNil(t *testing.T) {
3939
}
4040

4141
func TestNewDatafileProjectConfigNotNil(t *testing.T) {
42-
dpc := DatafileProjectConfig{accountID: "123", revision: "1", projectID: "12345", sdkKey: "a", environmentKey: "production"}
43-
jsonDatafileStr := `{"accountID": "123", "revision": "1", "projectId": "12345", "version": "4", "sdkKey": "a", "environmentKey": "production"}`
42+
dpc := DatafileProjectConfig{accountID: "123", revision: "1", projectID: "12345", sdkKey: "a", environmentKey: "production", eventMap: map[string]entities.Event{"event_single_targeted_exp": {Key: "event_single_targeted_exp"}}, attributeMap: map[string]entities.Attribute{"10401066170": {ID: "10401066170"}}}
43+
jsonDatafileStr := `{"accountID":"123","revision":"1","projectId":"12345","version":"4","sdkKey":"a","environmentKey":"production","events":[{"key":"event_single_targeted_exp"}],"attributes":[{"id":"10401066170"}]}`
4444
jsonDatafile := []byte(jsonDatafileStr)
4545
projectConfig, err := NewDatafileProjectConfig(jsonDatafile, logging.GetLogger("", "DatafileProjectConfig"))
4646
assert.Nil(t, err)
@@ -52,6 +52,16 @@ func TestNewDatafileProjectConfigNotNil(t *testing.T) {
5252
assert.Equal(t, dpc.sdkKey, projectConfig.sdkKey)
5353
}
5454

55+
func TestGetDatafile(t *testing.T) {
56+
jsonDatafileStr := `{"accountID": "123", "revision": "1", "projectId": "12345", "version": "4", "sdkKey": "a", "environmentKey": "production"}`
57+
jsonDatafile := []byte(jsonDatafileStr)
58+
config := &DatafileProjectConfig{
59+
datafile: string(jsonDatafile),
60+
}
61+
62+
assert.Equal(t, string(jsonDatafile), config.GetDatafile())
63+
}
64+
5565
func TestGetProjectID(t *testing.T) {
5666
projectID := "projectID"
5767
config := &DatafileProjectConfig{
@@ -88,6 +98,14 @@ func TestGetAnonymizeIP(t *testing.T) {
8898
assert.Equal(t, anonymizeIP, config.GetAnonymizeIP())
8999
}
90100

101+
func TestGetAttributes(t *testing.T) {
102+
config := &DatafileProjectConfig{
103+
attributeMap: map[string]entities.Attribute{"id1": {ID: "id1", Key: "key"}, "id2": {ID: "id1", Key: "key"}},
104+
}
105+
106+
assert.Equal(t, []entities.Attribute{config.attributeMap["id1"], config.attributeMap["id2"]}, config.GetAttributes())
107+
}
108+
91109
func TestGetAttributeID(t *testing.T) {
92110
id := "id"
93111
key := "key"
@@ -115,6 +133,13 @@ func TestGetEnvironmentKey(t *testing.T) {
115133
assert.Equal(t, "production", config.GetEnvironmentKey())
116134
}
117135

136+
func TestGetEvents(t *testing.T) {
137+
config := &DatafileProjectConfig{
138+
eventMap: map[string]entities.Event{"key": {ID: "5", Key: "key"}},
139+
}
140+
assert.Equal(t, []entities.Event{config.eventMap["key"]}, config.GetEvents())
141+
}
142+
118143
func TestGetBotFiltering(t *testing.T) {
119144
botFiltering := true
120145
config := &DatafileProjectConfig{
@@ -298,20 +323,34 @@ func TestGetExperimentList(t *testing.T) {
298323
experiment := entities.Experiment{
299324
Key: key,
300325
}
326+
301327
experimentMap := make(map[string]entities.Experiment)
302328
experimentMap[id] = experiment
303329

304330
config := &DatafileProjectConfig{
305331
experimentKeyToIDMap: experimentKeyToIDMap,
306332
experimentMap: experimentMap,
307333
}
308-
309334
experiments := config.GetExperimentList()
310335

311336
assert.Equal(t, 1, len(experiments))
312337
assert.Equal(t, experiment, experiments[0])
313338
}
314339

340+
func TestGetRolloutList(t *testing.T) {
341+
config := &DatafileProjectConfig{
342+
rollouts: []entities.Rollout{{ID: "5"}},
343+
}
344+
assert.Equal(t, config.rollouts, config.GetRolloutList())
345+
}
346+
347+
func TestGetAudienceList(t *testing.T) {
348+
config := &DatafileProjectConfig{
349+
audienceMap: map[string]entities.Audience{"5": {ID: "5", Name: "one"}, "6": {ID: "6", Name: "two"}},
350+
}
351+
assert.ElementsMatch(t, []entities.Audience{config.audienceMap["5"], config.audienceMap["6"]}, config.GetAudienceList())
352+
}
353+
315354
func TestGetAudienceByID(t *testing.T) {
316355
id := "id"
317356
audience := entities.Audience{
@@ -409,6 +448,13 @@ func TestGetGroupByID(t *testing.T) {
409448
assert.Equal(t, group, actual)
410449
}
411450

451+
func TestSendFlagDecisions(t *testing.T) {
452+
config := &DatafileProjectConfig{
453+
sendFlagDecisions: true,
454+
}
455+
assert.Equal(t, config.sendFlagDecisions, config.SendFlagDecisions())
456+
}
457+
412458
func TestGetGroupByIDMissingIDError(t *testing.T) {
413459
config := &DatafileProjectConfig{}
414460
_, err := config.GetGroupByID("id")

pkg/config/datafileprojectconfig/mappers/attribute_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ func TestMapAttributes(t *testing.T) {
4242

4343
attributeMap, attributeKeyToIDMap := MapAttributes(attrList)
4444

45-
expectedAttributeMap := map[string]entities.Attribute{"1": {"1", "one"},
46-
"2": {"2", "two"}, "3": {"3", "three"}, "5": {"5", "one"}}
45+
expectedAttributeMap := map[string]entities.Attribute{"1": {ID: "1", Key: "one"},
46+
"2": {ID: "2", Key: "two"}, "3": {ID: "3", Key: "three"}, "5": {ID: "5", Key: "one"}}
4747
expectedAttributeKeyToIDMap := map[string]string{"one": "5", "three": "3", "two": "2"}
4848

4949
assert.Equal(t, attributeMap, expectedAttributeMap)

pkg/config/datafileprojectconfig/mappers/audience.go

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/****************************************************************************
2-
* Copyright 2019, Optimizely, Inc. and contributors *
2+
* Copyright 2019,2021, Optimizely, Inc. and contributors *
33
* *
44
* Licensed under the Apache License, Version 2.0 (the "License"); *
55
* you may not use this file except in compliance with the License. *
@@ -23,22 +23,25 @@ import (
2323
)
2424

2525
// MapAudiences maps the raw datafile audience entities to SDK Audience entities
26-
func MapAudiences(audiences []datafileEntities.Audience) map[string]entities.Audience {
26+
func MapAudiences(audiences []datafileEntities.Audience) (audienceMap map[string]entities.Audience) {
2727

28-
audienceMap := make(map[string]entities.Audience)
28+
audienceMap = make(map[string]entities.Audience)
29+
// Since typed audiences were added prior to audiences,
30+
// they will be given priority in the audienceMap and list
2931
for _, audience := range audiences {
3032
_, ok := audienceMap[audience.ID]
3133
if !ok {
32-
conditionTree, err := buildConditionTree(audience.Conditions)
33-
if err != nil {
34-
// @TODO: handle error
35-
func() {}() // cheat the linters
34+
audience := entities.Audience{
35+
ID: audience.ID,
36+
Name: audience.Name,
37+
Conditions: audience.Conditions,
3638
}
37-
audienceMap[audience.ID] = entities.Audience{
38-
ID: audience.ID,
39-
Name: audience.Name,
40-
ConditionTree: conditionTree,
39+
conditionTree, err := buildConditionTree(audience.Conditions)
40+
if err == nil {
41+
audience.ConditionTree = conditionTree
4142
}
43+
44+
audienceMap[audience.ID] = audience
4245
}
4346
}
4447
return audienceMap

pkg/config/datafileprojectconfig/mappers/audience_test.go

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/****************************************************************************
2-
* Copyright 2019, Optimizely, Inc. and contributors *
2+
* Copyright 2019,2021 Optimizely, Inc. and contributors *
33
* *
44
* Licensed under the Apache License, Version 2.0 (the "License"); *
55
* you may not use this file except in compliance with the License. *
@@ -29,21 +29,43 @@ import (
2929
func TestMapAudiencesEmptyList(t *testing.T) {
3030

3131
audienceMap := MapAudiences(nil)
32-
3332
expectedAudienceMap := map[string]entities.Audience{}
3433

35-
assert.Equal(t, audienceMap, expectedAudienceMap)
36-
34+
assert.Equal(t, expectedAudienceMap, audienceMap)
3735
}
38-
func TestMapAudiences(t *testing.T) {
3936

40-
audienceList := []datafileEntities.Audience{{ID: "1", Name: "one"}, {ID: "2", Name: "two"},
41-
{ID: "3", Name: "three"}, {ID: "2", Name: "four"}, {ID: "5", Name: "one"}}
37+
func TestMapAudiences(t *testing.T) {
4238

39+
expectedConditions := "[\"and\", [\"or\", [\"or\", {\"name\": \"s_foo\", \"type\": \"custom_attribute\", \"value\": \"foo\"}]]]"
40+
audienceList := []datafileEntities.Audience{{ID: "1", Name: "one", Conditions: expectedConditions}, {ID: "2", Name: "two"},
41+
{ID: "3", Name: "three"}, {ID: "2", Name: "four"}, {ID: "1", Name: "one"}}
4342
audienceMap := MapAudiences(audienceList)
4443

45-
expectedAudienceMap := map[string]entities.Audience{"1": {ID: "1", Name: "one"}, "2": {ID: "2", Name: "two"},
46-
"3": {ID: "3", Name: "three"}, "5": {ID: "5", Name: "one"}}
44+
expectedConditionTree := &entities.TreeNode{
45+
Operator: "and",
46+
Nodes: []*entities.TreeNode{
47+
{
48+
Operator: "or",
49+
Nodes: []*entities.TreeNode{
50+
{
51+
Operator: "or",
52+
Nodes: []*entities.TreeNode{
53+
{
54+
Item: entities.Condition{
55+
Name: "s_foo",
56+
Type: "custom_attribute",
57+
Value: "foo",
58+
StringRepresentation: `{"name":"s_foo","type":"custom_attribute","value":"foo"}`,
59+
},
60+
},
61+
},
62+
},
63+
},
64+
},
65+
},
66+
}
67+
expectedAudienceMap := map[string]entities.Audience{"1": {ID: "1", Name: "one", ConditionTree: expectedConditionTree, Conditions: expectedConditions}, "2": {ID: "2", Name: "two"},
68+
"3": {ID: "3", Name: "three"}}
4769

48-
assert.Equal(t, audienceMap, expectedAudienceMap)
70+
assert.Equal(t, expectedAudienceMap, audienceMap)
4971
}

pkg/config/datafileprojectconfig/mappers/condition_trees.go

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/****************************************************************************
2-
* Copyright 2019-2020, Optimizely, Inc. and contributors *
2+
* Copyright 2019-2021, Optimizely, Inc. and contributors *
33
* *
44
* Licensed under the Apache License, Version 2.0 (the "License"); *
55
* you may not use this file except in compliance with the License. *
@@ -28,6 +28,16 @@ import (
2828
var errEmptyTree = errors.New("empty tree")
2929
var json = jsoniter.ConfigCompatibleWithStandardLibrary
3030

31+
// OperatorType defines logical operator for conditions
32+
type OperatorType string
33+
34+
// Default conditional operators
35+
const (
36+
And OperatorType = "and"
37+
Or OperatorType = "or"
38+
Not OperatorType = "not"
39+
)
40+
3141
// Takes the conditions array from the audience in the datafile and turns it into a condition tree
3242
func buildConditionTree(conditions interface{}) (conditionTree *entities.TreeNode, retErr error) {
3343

@@ -135,7 +145,6 @@ func createLeafCondition(typedV map[string]interface{}, node *entities.TreeNode)
135145
// Takes the conditions array from the audience in the datafile and turns it into a condition tree
136146
func buildAudienceConditionTree(conditions interface{}) (conditionTree *entities.TreeNode, err error) {
137147

138-
var operators = []string{"or", "and", "not"} // any other operators?
139148
value := reflect.ValueOf(conditions)
140149
visited := make(map[interface{}]bool)
141150

@@ -161,7 +170,7 @@ func buildAudienceConditionTree(conditions interface{}) (conditionTree *entities
161170
n := &entities.TreeNode{}
162171
typedV := v.Index(i).Interface()
163172
if value, ok := typedV.(string); ok {
164-
if stringInSlice(value, operators) {
173+
if isValidOperator(value) {
165174
n.Operator = typedV.(string)
166175
root.Operator = n.Operator
167176
continue
@@ -188,11 +197,11 @@ func buildAudienceConditionTree(conditions interface{}) (conditionTree *entities
188197
return conditionTree, err
189198
}
190199

191-
func stringInSlice(str string, list []string) bool {
192-
for _, v := range list {
193-
if v == str {
194-
return true
195-
}
200+
func isValidOperator(op string) bool {
201+
operator := OperatorType(op)
202+
switch operator {
203+
case And, Or, Not:
204+
return true
196205
}
197206
return false
198207
}

pkg/config/datafileprojectconfig/mappers/events.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ import (
2323
)
2424

2525
// MapEvents maps the raw datafile event entities to SDK Event entities
26-
func MapEvents(events []datafileEntities.Event) map[string]entities.Event {
27-
eventMap := make(map[string]entities.Event)
26+
func MapEvents(events []datafileEntities.Event) (eventMap map[string]entities.Event) {
27+
eventMap = make(map[string]entities.Event)
2828
for _, event := range events {
29-
entityEvent := entities.Event{ID: event.ID, Key: event.Key, ExperimentIds: event.ExperimentIds}
29+
entityEvent := entities.Event(event)
3030
eventMap[entityEvent.Key] = entityEvent
3131
}
3232

pkg/config/datafileprojectconfig/mappers/events_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func TestMapEvents(t *testing.T) {
3737
rawEvents := []datafileEntities.Event{rawEvent}
3838
eventsMap := MapEvents(rawEvents)
3939
expectedEventMap := map[string]entities.Event{
40-
"event1": entities.Event{
40+
"event1": {
4141
ID: "some_id",
4242
Key: "event1",
4343
ExperimentIds: []string{"11111", "11112"},

0 commit comments

Comments
 (0)