Skip to content

Commit 3f8dadb

Browse files
passing both user attributes and audience map to decision tree.
1 parent 13921ec commit 3f8dadb

File tree

7 files changed

+113
-41
lines changed

7 files changed

+113
-41
lines changed

optimizely/decision/evaluator/audience.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222

2323
// AudienceEvaluator evaluates an audience against the given user's attributes
2424
type AudienceEvaluator interface {
25-
Evaluate(audience entities.Audience, user entities.UserContext) bool
25+
Evaluate(audience entities.Audience, condTreeParams *ConditionTreeParameters) bool
2626
}
2727

2828
// TypedAudienceEvaluator evaluates typed audiences
@@ -39,6 +39,6 @@ func NewTypedAudienceEvaluator() *TypedAudienceEvaluator {
3939
}
4040

4141
// Evaluate evaluates the typed audience against the given user's attributes
42-
func (a TypedAudienceEvaluator) Evaluate(audience entities.Audience, user entities.UserContext) bool {
43-
return a.conditionTreeEvaluator.Evaluate(audience.ConditionTree, user)
42+
func (a TypedAudienceEvaluator) Evaluate(audience entities.Audience, condTreeParams *ConditionTreeParameters) bool {
43+
return a.conditionTreeEvaluator.Evaluate(audience.ConditionTree, condTreeParams)
4444
}

optimizely/decision/evaluator/condition.go

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,14 @@ const (
3232

3333
// ConditionEvaluator evaluates a condition against the given user's attributes
3434
type ConditionEvaluator interface {
35-
Evaluate(entities.Condition, interface{}) (bool, error)
35+
Evaluate(entities.Condition, *ConditionTreeParameters) (bool, error)
3636
}
3737

3838
// CustomAttributeConditionEvaluator evaluates conditions with custom attributes
3939
type CustomAttributeConditionEvaluator struct{}
4040

4141
// Evaluate returns true if the given user's attributes match the condition
42-
func (c CustomAttributeConditionEvaluator) Evaluate(condition entities.Condition, evalObject interface{}) (bool, error) {
42+
func (c CustomAttributeConditionEvaluator) Evaluate(condition entities.Condition, condTreeParams *ConditionTreeParameters) (bool, error) {
4343
// We should only be evaluating custom attributes
4444
if condition.Type != customAttributeType {
4545
return false, fmt.Errorf(`Unable to evaluator condition of type "%s"`, condition.Type)
@@ -66,7 +66,43 @@ func (c CustomAttributeConditionEvaluator) Evaluate(condition entities.Condition
6666
}
6767
}
6868

69-
user := evalObject.(entities.UserContext)
69+
user := *condTreeParams.User
70+
result, err := matcher.Match(user)
71+
return result, err
72+
}
73+
74+
// AudienceConditionEvaluator evaluates conditions with audience condition
75+
type AudienceConditionEvaluator struct{}
76+
77+
// Evaluate returns true if the given user's attributes match the condition
78+
func (c AudienceConditionEvaluator) Evaluate(condition entities.Condition, condTreeParams *ConditionTreeParameters) (bool, error) {
79+
// We should only be evaluating custom attributes
80+
if condition.Type != customAttributeType {
81+
return false, fmt.Errorf(`Unable to evaluator condition of type "%s"`, condition.Type)
82+
}
83+
84+
var matcher matchers.Matcher
85+
matchType := condition.Match
86+
switch matchType {
87+
case exactMatchType:
88+
matcher = matchers.ExactMatcher{
89+
Condition: condition,
90+
}
91+
case existsMatchType:
92+
matcher = matchers.ExistsMatcher{
93+
Condition: condition,
94+
}
95+
case ltMatchType:
96+
matcher = matchers.LtMatcher{
97+
Condition: condition,
98+
}
99+
case gtMatchType:
100+
matcher = matchers.GtMatcher{
101+
Condition: condition,
102+
}
103+
}
104+
105+
user := *condTreeParams.User
70106
result, err := matcher.Match(user)
71107
return result, err
72108
}

optimizely/decision/evaluator/condition_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ func TestCustomAttributeConditionEvaluator(t *testing.T) {
4040
},
4141
},
4242
}
43-
result, _ := conditionEvaluator.Evaluate(condition, user)
43+
44+
condTreeParams := NewCConditionTreeParameters(&user, map[string]entities.Audience{})
45+
result, _ := conditionEvaluator.Evaluate(condition, condTreeParams)
4446
assert.Equal(t, result, true)
4547

4648
// Test condition fails
@@ -51,6 +53,6 @@ func TestCustomAttributeConditionEvaluator(t *testing.T) {
5153
},
5254
},
5355
}
54-
result, _ = conditionEvaluator.Evaluate(condition, user)
56+
result, _ = conditionEvaluator.Evaluate(condition, condTreeParams)
5557
assert.Equal(t, result, false)
5658
}

optimizely/decision/evaluator/condition_tree.go

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
)
2222

2323
const customAttributeType = "custom_attribute"
24+
const audienceCondition = "audience_condition"
2425

2526
const (
2627
// "and" operator returns true if all conditions evaluate to true
@@ -31,6 +32,15 @@ const (
3132
orOperator = "or"
3233
)
3334

35+
type ConditionTreeParameters struct {
36+
User *entities.UserContext
37+
AudienceMap map[string]entities.Audience
38+
}
39+
40+
func NewCConditionTreeParameters(user *entities.UserContext, audience map[string]entities.Audience) *ConditionTreeParameters {
41+
return &ConditionTreeParameters{User: user, AudienceMap: audience}
42+
}
43+
3444
// ConditionTreeEvaluator evaluates a condition tree
3545
type ConditionTreeEvaluator struct {
3646
conditionEvaluatorMap map[string]ConditionEvaluator
@@ -41,33 +51,34 @@ func NewConditionTreeEvaluator() *ConditionTreeEvaluator {
4151
// For now, only one evaluator per attribute type
4252
conditionEvaluatorMap := make(map[string]ConditionEvaluator)
4353
conditionEvaluatorMap[customAttributeType] = CustomAttributeConditionEvaluator{}
54+
conditionEvaluatorMap[audienceCondition] = AudienceConditionEvaluator{}
4455
return &ConditionTreeEvaluator{
4556
conditionEvaluatorMap: conditionEvaluatorMap,
4657
}
4758
}
4859

4960
// entities.UserContext
5061
// Evaluate returns true if the userAttributes satisfy the given condition tree
51-
func (c ConditionTreeEvaluator) Evaluate(node *entities.ConditionTreeNode, evalObject interface{}) bool {
62+
func (c ConditionTreeEvaluator) Evaluate(node *entities.ConditionTreeNode, condTreeParams *ConditionTreeParameters) bool {
5263
// This wrapper method converts the conditionEvalResult to a boolean
53-
result, _ := c.evaluate(node, evalObject)
64+
result, _ := c.evaluate(node, condTreeParams)
5465
return result == true
5566
}
5667

5768
// Helper method to recursively evaluate a condition tree
5869
// Returns the result of the evaluation and whether the evaluation of the condition is valid or not (to handle null bubbling)
59-
func (c ConditionTreeEvaluator) evaluate(node *entities.ConditionTreeNode, evalObject interface{}) (evalResult bool, isValid bool) {
70+
func (c ConditionTreeEvaluator) evaluate(node *entities.ConditionTreeNode, condTreeParams *ConditionTreeParameters) (evalResult bool, isValid bool) {
6071
operator := node.Operator
6172
if operator != "" {
6273
switch operator {
6374
case andOperator:
64-
return c.evaluateAnd(node.Nodes, evalObject)
75+
return c.evaluateAnd(node.Nodes, condTreeParams)
6576
case notOperator:
66-
return c.evaluateNot(node.Nodes, evalObject)
77+
return c.evaluateNot(node.Nodes, condTreeParams)
6778
case orOperator:
6879
fallthrough
6980
default:
70-
return c.evaluateOr(node.Nodes, evalObject)
81+
return c.evaluateOr(node.Nodes, condTreeParams)
7182
}
7283
}
7384

@@ -77,18 +88,18 @@ func (c ConditionTreeEvaluator) evaluate(node *entities.ConditionTreeNode, evalO
7788
// Result is invalid
7889
return false, false
7990
}
80-
result, err := conditionEvaluator.Evaluate(node.Condition, evalObject)
91+
result, err := conditionEvaluator.Evaluate(node.Condition, condTreeParams)
8192
if err != nil {
8293
// Result is invalid
8394
return false, false
8495
}
8596
return result, true
8697
}
8798

88-
func (c ConditionTreeEvaluator) evaluateAnd(nodes []*entities.ConditionTreeNode, evalObject interface{}) (evalResult bool, isValid bool) {
99+
func (c ConditionTreeEvaluator) evaluateAnd(nodes []*entities.ConditionTreeNode, condTreeParams *ConditionTreeParameters) (evalResult bool, isValid bool) {
89100
sawInvalid := false
90101
for _, node := range nodes {
91-
result, isValid := c.evaluate(node, evalObject)
102+
result, isValid := c.evaluate(node, condTreeParams)
92103
if !isValid {
93104
return false, isValid
94105
} else if result == false {
@@ -104,9 +115,9 @@ func (c ConditionTreeEvaluator) evaluateAnd(nodes []*entities.ConditionTreeNode,
104115
return true, true
105116
}
106117

107-
func (c ConditionTreeEvaluator) evaluateNot(nodes []*entities.ConditionTreeNode, evalObject interface{}) (evalResult bool, isValid bool) {
118+
func (c ConditionTreeEvaluator) evaluateNot(nodes []*entities.ConditionTreeNode, condTreeParams *ConditionTreeParameters) (evalResult bool, isValid bool) {
108119
if len(nodes) > 0 {
109-
result, isValid := c.evaluate(nodes[0], evalObject)
120+
result, isValid := c.evaluate(nodes[0], condTreeParams)
110121
if !isValid {
111122
return false, false
112123
}
@@ -115,10 +126,10 @@ func (c ConditionTreeEvaluator) evaluateNot(nodes []*entities.ConditionTreeNode,
115126
return false, false
116127
}
117128

118-
func (c ConditionTreeEvaluator) evaluateOr(nodes []*entities.ConditionTreeNode, evalObject interface{}) (evalResult bool, isValid bool) {
129+
func (c ConditionTreeEvaluator) evaluateOr(nodes []*entities.ConditionTreeNode, condTreeParams *ConditionTreeParameters) (evalResult bool, isValid bool) {
119130
sawInvalid := false
120131
for _, node := range nodes {
121-
result, isValid := c.evaluate(node, evalObject)
132+
result, isValid := c.evaluate(node, condTreeParams)
122133
if !isValid {
123134
sawInvalid = true
124135
} else if result == true {

optimizely/decision/evaluator/condition_tree_test.go

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ func TestConditionTreeEvaluateSimpleCondition(t *testing.T) {
4848
},
4949
},
5050
}
51-
result := conditionTreeEvaluator.Evaluate(conditionTree, user)
51+
condTreeParams := NewCConditionTreeParameters(&user, map[string]e.Audience{})
52+
result := conditionTreeEvaluator.Evaluate(conditionTree, condTreeParams)
5253
assert.True(t, result)
5354

5455
// Test no match
@@ -59,7 +60,7 @@ func TestConditionTreeEvaluateSimpleCondition(t *testing.T) {
5960
},
6061
},
6162
}
62-
result = conditionTreeEvaluator.Evaluate(conditionTree, user)
63+
result = conditionTreeEvaluator.Evaluate(conditionTree, condTreeParams)
6364
assert.False(t, result)
6465
}
6566

@@ -85,7 +86,9 @@ func TestConditionTreeEvaluateMultipleOrConditions(t *testing.T) {
8586
},
8687
},
8788
}
88-
result := conditionTreeEvaluator.Evaluate(conditionTree, user)
89+
90+
condTreeParams := NewCConditionTreeParameters(&user, map[string]e.Audience{})
91+
result := conditionTreeEvaluator.Evaluate(conditionTree, condTreeParams)
8992
assert.True(t, result)
9093

9194
// Test match bool
@@ -96,7 +99,7 @@ func TestConditionTreeEvaluateMultipleOrConditions(t *testing.T) {
9699
},
97100
},
98101
}
99-
result = conditionTreeEvaluator.Evaluate(conditionTree, user)
102+
result = conditionTreeEvaluator.Evaluate(conditionTree, condTreeParams)
100103
assert.True(t, result)
101104

102105
// Test match both
@@ -108,7 +111,7 @@ func TestConditionTreeEvaluateMultipleOrConditions(t *testing.T) {
108111
},
109112
},
110113
}
111-
result = conditionTreeEvaluator.Evaluate(conditionTree, user)
114+
result = conditionTreeEvaluator.Evaluate(conditionTree, condTreeParams)
112115
assert.True(t, result)
113116

114117
// Test no match
@@ -120,7 +123,7 @@ func TestConditionTreeEvaluateMultipleOrConditions(t *testing.T) {
120123
},
121124
},
122125
}
123-
result = conditionTreeEvaluator.Evaluate(conditionTree, user)
126+
result = conditionTreeEvaluator.Evaluate(conditionTree, condTreeParams)
124127
assert.False(t, result)
125128
}
126129

@@ -146,7 +149,9 @@ func TestConditionTreeEvaluateMultipleAndConditions(t *testing.T) {
146149
},
147150
},
148151
}
149-
result := conditionTreeEvaluator.Evaluate(conditionTree, user)
152+
153+
condTreeParams := NewCConditionTreeParameters(&user, map[string]e.Audience{})
154+
result := conditionTreeEvaluator.Evaluate(conditionTree, condTreeParams)
150155
assert.False(t, result)
151156

152157
// Test only bool match with NULL bubbling
@@ -157,7 +162,7 @@ func TestConditionTreeEvaluateMultipleAndConditions(t *testing.T) {
157162
},
158163
},
159164
}
160-
result = conditionTreeEvaluator.Evaluate(conditionTree, user)
165+
result = conditionTreeEvaluator.Evaluate(conditionTree, condTreeParams)
161166
assert.False(t, result)
162167

163168
// Test match both
@@ -169,7 +174,7 @@ func TestConditionTreeEvaluateMultipleAndConditions(t *testing.T) {
169174
},
170175
},
171176
}
172-
result = conditionTreeEvaluator.Evaluate(conditionTree, user)
177+
result = conditionTreeEvaluator.Evaluate(conditionTree, condTreeParams)
173178
assert.True(t, result)
174179

175180
// Test no match
@@ -181,7 +186,7 @@ func TestConditionTreeEvaluateMultipleAndConditions(t *testing.T) {
181186
},
182187
},
183188
}
184-
result = conditionTreeEvaluator.Evaluate(conditionTree, user)
189+
result = conditionTreeEvaluator.Evaluate(conditionTree, condTreeParams)
185190
assert.False(t, result)
186191
}
187192

@@ -218,7 +223,9 @@ func TestConditionTreeEvaluateNotCondition(t *testing.T) {
218223
},
219224
},
220225
}
221-
result := conditionTreeEvaluator.Evaluate(conditionTree, user)
226+
227+
condTreeParams := NewCConditionTreeParameters(&user, map[string]e.Audience{})
228+
result := conditionTreeEvaluator.Evaluate(conditionTree, condTreeParams)
222229
assert.True(t, result)
223230

224231
// Test match bool
@@ -229,7 +236,7 @@ func TestConditionTreeEvaluateNotCondition(t *testing.T) {
229236
},
230237
},
231238
}
232-
result = conditionTreeEvaluator.Evaluate(conditionTree, user)
239+
result = conditionTreeEvaluator.Evaluate(conditionTree, condTreeParams)
233240
assert.True(t, result)
234241

235242
// Test match both
@@ -241,7 +248,7 @@ func TestConditionTreeEvaluateNotCondition(t *testing.T) {
241248
},
242249
},
243250
}
244-
result = conditionTreeEvaluator.Evaluate(conditionTree, user)
251+
result = conditionTreeEvaluator.Evaluate(conditionTree, condTreeParams)
245252
assert.True(t, result)
246253

247254
// Test no match
@@ -253,7 +260,7 @@ func TestConditionTreeEvaluateNotCondition(t *testing.T) {
253260
},
254261
},
255262
}
256-
result = conditionTreeEvaluator.Evaluate(conditionTree, user)
263+
result = conditionTreeEvaluator.Evaluate(conditionTree, condTreeParams)
257264
assert.False(t, result)
258265
}
259266

@@ -303,7 +310,9 @@ func TestConditionTreeEvaluateMultipleMixedConditions(t *testing.T) {
303310
},
304311
},
305312
}
306-
result := conditionTreeEvaluator.Evaluate(conditionTree, user)
313+
314+
condTreeParams := NewCConditionTreeParameters(&user, map[string]e.Audience{})
315+
result := conditionTreeEvaluator.Evaluate(conditionTree, condTreeParams)
307316
assert.True(t, result)
308317

309318
// Test only match the NOT condition
@@ -316,7 +325,7 @@ func TestConditionTreeEvaluateMultipleMixedConditions(t *testing.T) {
316325
},
317326
},
318327
}
319-
result = conditionTreeEvaluator.Evaluate(conditionTree, user)
328+
result = conditionTreeEvaluator.Evaluate(conditionTree, condTreeParams)
320329
assert.True(t, result)
321330

322331
// Test only match the int condition
@@ -329,7 +338,7 @@ func TestConditionTreeEvaluateMultipleMixedConditions(t *testing.T) {
329338
},
330339
},
331340
}
332-
result = conditionTreeEvaluator.Evaluate(conditionTree, user)
341+
result = conditionTreeEvaluator.Evaluate(conditionTree, condTreeParams)
333342
assert.True(t, result)
334343

335344
// Test no match
@@ -342,6 +351,6 @@ func TestConditionTreeEvaluateMultipleMixedConditions(t *testing.T) {
342351
},
343352
},
344353
}
345-
result = conditionTreeEvaluator.Evaluate(conditionTree, user)
354+
result = conditionTreeEvaluator.Evaluate(conditionTree, condTreeParams)
346355
assert.False(t, result)
347356
}

optimizely/decision/experiment_targeting_service.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ func (s ExperimentTargetingService) GetDecision(decisionContext ExperimentDecisi
3939
experiment := decisionContext.Experiment
4040
if len(experiment.AudienceIds) > 0 {
4141
experimentAudience := decisionContext.AudienceMap[experiment.AudienceIds[0]]
42-
evalResult := s.audienceEvaluator.Evaluate(experimentAudience, userContext)
42+
condTreeParams := evaluator.NewCConditionTreeParameters(&userContext, map[string]entities.Audience{})
43+
evalResult := s.audienceEvaluator.Evaluate(experimentAudience, condTreeParams)
4344
if evalResult == false {
4445
// user not targeted for experiment, return an empty variation
4546
experimentDecision.DecisionMade = true

0 commit comments

Comments
 (0)