Skip to content

Commit 13921ec

Browse files
restructuring code to fit audience condition into existing code.
1 parent 99f8802 commit 13921ec

File tree

10 files changed

+341
-150
lines changed

10 files changed

+341
-150
lines changed

optimizely/config/datafileProjectConfig/entities/entities.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,15 @@ type Audience struct {
2626
// Experiment represents an Experiment object from the Optimizely datafile
2727
type Experiment struct {
2828
// @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"`
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"`
37+
AudienceConditions interface{} `json:"audienceConditions"`
3738
}
3839

3940
// FeatureFlag represents a FeatureFlag object from the Optimizely datafile
@@ -60,8 +61,8 @@ type Variation struct {
6061
}
6162

6263
type Event struct {
63-
ID string `json:"id"`
64-
Key string `json:"key"`
64+
ID string `json:"id"`
65+
Key string `json:"key"`
6566
ExperimentIds []string `json:"experimentIds"`
6667
}
6768

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: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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.ConditionTreeNode, retErr error) {
30+
31+
value := reflect.ValueOf(conditions)
32+
visited := make(map[interface{}]bool)
33+
34+
conditionTree = &entities.ConditionTreeNode{}
35+
var populateConditions func(v reflect.Value, root *entities.ConditionTreeNode)
36+
populateConditions = func(v reflect.Value, root *entities.ConditionTreeNode) {
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.ConditionTreeNode{}
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.Condition = 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.ConditionTreeNode, 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.ConditionTreeNode{}
98+
var populateConditions func(v reflect.Value, root *entities.ConditionTreeNode)
99+
populateConditions = func(v reflect.Value, root *entities.ConditionTreeNode) {
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.ConditionTreeNode{}
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.Condition.Value = value
127+
n.Condition.Type = "audience_condition"
128+
n.Condition.Name = "optimizely_populated"
129+
}
130+
}
131+
132+
root.Nodes = append(root.Nodes, n)
133+
134+
populateConditions(v.Index(i), n)
135+
}
136+
}
137+
}
138+
139+
populateConditions(value, conditionTree)
140+
141+
if conditionTree.Nodes == nil && conditionTree.Operator == "" {
142+
err = ErrEmptyTree
143+
conditionTree = nil
144+
}
145+
146+
return conditionTree, err
147+
}
148+
149+
func stringInSlice(str string, list []string) bool {
150+
for _, v := range list {
151+
if v == str {
152+
return true
153+
}
154+
}
155+
return false
156+
}

0 commit comments

Comments
 (0)