Skip to content

Commit 896f726

Browse files
Merge pull request #38 from optimizely/pawel/OASIS-4943
restructuring code to fit audience condition into existing code.
2 parents 42d6d54 + 3812cdb commit 896f726

20 files changed

+641
-258
lines changed

optimizely/config/datafileProjectConfig/config.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ func (c DatafileProjectConfig) GetAudienceByID(audienceID string) (entities.Audi
116116
return entities.Audience{}, errors.New(errMessage)
117117
}
118118

119+
// GetAudienceMap returns the audience map
120+
func (c DatafileProjectConfig) GetAudienceMap() map[string]entities.Audience {
121+
return c.audienceMap
122+
}
123+
119124
// GetExperimentByKey returns the experiment with the given key
120125
func (c DatafileProjectConfig) GetExperimentByKey(experimentKey string) (entities.Experiment, error) {
121126
if experimentID, ok := c.experimentKeyToIDMap[experimentKey]; ok {

optimizely/config/datafileProjectConfig/entities/entities.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ type Audience struct {
2525

2626
// Experiment represents an Experiment object from the Optimizely datafile
2727
type Experiment struct {
28-
// @TODO(mng): include audienceConditions
29-
ID string `json:"id"`
30-
Key string `json:"key"`
31-
LayerID string `json:"layerId"`
32-
Status string `json:"status"`
33-
Variations []Variation `json:"variations"`
34-
TrafficAllocation []trafficAllocation `json:"trafficAllocation"`
35-
AudienceIds []string `json:"audienceIds"`
36-
ForcedVariations map[string]string `json:"forcedVariations"`
28+
ID string `json:"id"`
29+
Key string `json:"key"`
30+
LayerID string `json:"layerId"`
31+
Status string `json:"status"`
32+
Variations []Variation `json:"variations"`
33+
TrafficAllocation []trafficAllocation `json:"trafficAllocation"`
34+
AudienceIds []string `json:"audienceIds"`
35+
ForcedVariations map[string]string `json:"forcedVariations"`
36+
AudienceConditions interface{} `json:"audienceConditions"`
3737
}
3838

3939
// FeatureFlag represents a FeatureFlag object from the Optimizely datafile
@@ -60,8 +60,8 @@ type Variation struct {
6060
}
6161

6262
type Event struct {
63-
ID string `json:"id"`
64-
Key string `json:"key"`
63+
ID string `json:"id"`
64+
Key string `json:"key"`
6565
ExperimentIds []string `json:"experimentIds"`
6666
}
6767

optimizely/config/datafileProjectConfig/mappers/audience.go

Lines changed: 0 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@
1717
package mappers
1818

1919
import (
20-
"encoding/json"
21-
"reflect"
22-
2320
datafileEntities "github.com/optimizely/go-sdk/optimizely/config/datafileProjectConfig/entities"
2421
"github.com/optimizely/go-sdk/optimizely/entities"
2522
)
@@ -41,62 +38,3 @@ func MapAudiences(audiences []datafileEntities.Audience) map[string]entities.Aud
4138
}
4239
return audienceMap
4340
}
44-
45-
// Takes the conditions array from the audience in the datafile and turns it into a condition tree
46-
func buildConditionTree(conditions interface{}) (*entities.ConditionTreeNode, error) {
47-
48-
value := reflect.ValueOf(conditions)
49-
visited := make(map[interface{}]bool)
50-
var retErr error
51-
52-
conditionTree := &entities.ConditionTreeNode{}
53-
var populateConditions func(v reflect.Value, root *entities.ConditionTreeNode)
54-
populateConditions = func(v reflect.Value, root *entities.ConditionTreeNode) {
55-
56-
for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
57-
if v.Kind() == reflect.Ptr {
58-
// Check for recursive data
59-
if visited[v.Interface()] {
60-
return
61-
}
62-
visited[v.Interface()] = true
63-
}
64-
v = v.Elem()
65-
}
66-
67-
switch v.Kind() {
68-
69-
case reflect.Slice, reflect.Array:
70-
for i := 0; i < v.Len(); i++ {
71-
n := &entities.ConditionTreeNode{}
72-
typedV := v.Index(i).Interface()
73-
switch typedV.(type) {
74-
case string:
75-
n.Operator = typedV.(string)
76-
root.Operator = n.Operator
77-
continue
78-
79-
case map[string]interface{}:
80-
jsonBody, err := json.Marshal(typedV)
81-
if err != nil {
82-
retErr = err
83-
return
84-
}
85-
condition := entities.Condition{}
86-
if err := json.Unmarshal(jsonBody, &condition); err != nil {
87-
retErr = err
88-
return
89-
}
90-
n.Condition = condition
91-
}
92-
93-
root.Nodes = append(root.Nodes, n)
94-
95-
populateConditions(v.Index(i), n)
96-
}
97-
}
98-
}
99-
100-
populateConditions(value, conditionTree)
101-
return conditionTree, retErr
102-
}

optimizely/config/datafileProjectConfig/mappers/audience_test.go

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -15,46 +15,3 @@
1515
***************************************************************************/
1616

1717
package mappers
18-
19-
import (
20-
"encoding/json"
21-
"testing"
22-
23-
"github.com/optimizely/go-sdk/optimizely/entities"
24-
"github.com/stretchr/testify/assert"
25-
)
26-
27-
func TestBuildConditionTreeSimpleAudienceCondition(t *testing.T) {
28-
conditionString := "[ \"and\", [ \"or\", [ \"or\", { \"type\": \"custom_attribute\", \"name\": \"s_foo\", \"match\": \"exact\", \"value\": \"foo\" } ] ] ]"
29-
var conditions interface{}
30-
json.Unmarshal([]byte(conditionString), &conditions)
31-
conditionTree, err := buildConditionTree(conditions)
32-
if err != nil {
33-
assert.Fail(t, err.Error())
34-
}
35-
36-
expectedConditionTree := &entities.ConditionTreeNode{
37-
Operator: "and",
38-
Nodes: []*entities.ConditionTreeNode{
39-
&entities.ConditionTreeNode{
40-
Operator: "or",
41-
Nodes: []*entities.ConditionTreeNode{
42-
&entities.ConditionTreeNode{
43-
Operator: "or",
44-
Nodes: []*entities.ConditionTreeNode{
45-
&entities.ConditionTreeNode{
46-
Condition: entities.Condition{
47-
Name: "s_foo",
48-
Match: "exact",
49-
Type: "custom_attribute",
50-
Value: "foo",
51-
},
52-
},
53-
},
54-
},
55-
},
56-
},
57-
},
58-
}
59-
assert.Equal(t, expectedConditionTree, conditionTree)
60-
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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 mappers
18+
19+
import (
20+
"encoding/json"
21+
"errors"
22+
"github.com/optimizely/go-sdk/optimizely/entities"
23+
"reflect"
24+
)
25+
26+
var ErrEmptyTree = errors.New("Empty Tree")
27+
28+
// Takes the conditions array from the audience in the datafile and turns it into a condition tree
29+
func buildConditionTree(conditions interface{}) (conditionTree *entities.TreeNode, retErr error) {
30+
31+
value := reflect.ValueOf(conditions)
32+
visited := make(map[interface{}]bool)
33+
34+
conditionTree = &entities.TreeNode{}
35+
var populateConditions func(v reflect.Value, root *entities.TreeNode)
36+
populateConditions = func(v reflect.Value, root *entities.TreeNode) {
37+
38+
for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
39+
if v.Kind() == reflect.Ptr {
40+
// Check for recursive data
41+
if visited[v.Interface()] {
42+
return
43+
}
44+
visited[v.Interface()] = true
45+
}
46+
v = v.Elem()
47+
}
48+
49+
switch v.Kind() {
50+
51+
case reflect.Slice, reflect.Array:
52+
for i := 0; i < v.Len(); i++ {
53+
n := &entities.TreeNode{}
54+
typedV := v.Index(i).Interface()
55+
switch typedV.(type) {
56+
case string:
57+
n.Operator = typedV.(string)
58+
root.Operator = n.Operator
59+
continue
60+
61+
case map[string]interface{}:
62+
jsonBody, err := json.Marshal(typedV)
63+
if err != nil {
64+
retErr = err
65+
return
66+
}
67+
condition := entities.Condition{}
68+
if err := json.Unmarshal(jsonBody, &condition); err != nil {
69+
retErr = err
70+
return
71+
}
72+
n.Item = condition
73+
}
74+
75+
root.Nodes = append(root.Nodes, n)
76+
77+
populateConditions(v.Index(i), n)
78+
}
79+
}
80+
}
81+
82+
populateConditions(value, conditionTree)
83+
if conditionTree.Nodes == nil && conditionTree.Operator == "" {
84+
retErr = ErrEmptyTree
85+
conditionTree = nil
86+
}
87+
return conditionTree, retErr
88+
}
89+
90+
// Takes the conditions array from the audience in the datafile and turns it into a condition tree
91+
func buildAudienceConditionTree(conditions interface{}) (conditionTree *entities.TreeNode, err error) {
92+
93+
var operators = []string{"or", "and", "not"} // any other operators?
94+
value := reflect.ValueOf(conditions)
95+
visited := make(map[interface{}]bool)
96+
97+
conditionTree = &entities.TreeNode{}
98+
var populateConditions func(v reflect.Value, root *entities.TreeNode)
99+
populateConditions = func(v reflect.Value, root *entities.TreeNode) {
100+
101+
for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
102+
if v.Kind() == reflect.Ptr {
103+
// Check for recursive data
104+
if visited[v.Interface()] {
105+
return
106+
}
107+
visited[v.Interface()] = true
108+
}
109+
v = v.Elem()
110+
}
111+
112+
switch v.Kind() {
113+
114+
case reflect.Slice, reflect.Array:
115+
for i := 0; i < v.Len(); i++ {
116+
n := &entities.TreeNode{}
117+
typedV := v.Index(i).Interface()
118+
switch typedV.(type) {
119+
case string:
120+
value := typedV.(string)
121+
if stringInSlice(value, operators) {
122+
n.Operator = typedV.(string)
123+
root.Operator = n.Operator
124+
continue
125+
} else {
126+
n.Item = value
127+
128+
}
129+
}
130+
131+
root.Nodes = append(root.Nodes, n)
132+
133+
populateConditions(v.Index(i), n)
134+
}
135+
}
136+
}
137+
138+
populateConditions(value, conditionTree)
139+
140+
if conditionTree.Nodes == nil && conditionTree.Operator == "" {
141+
err = ErrEmptyTree
142+
conditionTree = nil
143+
}
144+
145+
return conditionTree, err
146+
}
147+
148+
func stringInSlice(str string, list []string) bool {
149+
for _, v := range list {
150+
if v == str {
151+
return true
152+
}
153+
}
154+
return false
155+
}

0 commit comments

Comments
 (0)