optimizely-sdk 2.1.1 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/optimizely.rb +19 -47
- data/lib/optimizely/audience.rb +67 -13
- data/lib/optimizely/bucketer.rb +4 -1
- data/lib/optimizely/condition_tree_evaluator.rb +121 -0
- data/lib/optimizely/custom_attribute_condition_evaluator.rb +273 -0
- data/lib/optimizely/decision_service.rb +14 -9
- data/lib/optimizely/event_builder.rb +17 -26
- data/lib/optimizely/helpers/constants.rb +26 -1
- data/lib/optimizely/helpers/validator.rb +57 -4
- data/lib/optimizely/notification_center.rb +1 -0
- data/lib/optimizely/project_config.rb +30 -20
- data/lib/optimizely/version.rb +2 -2
- metadata +47 -18
- data/lib/optimizely/condition.rb +0 -135
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a6269e81defa38267703852ceb2e5d29de7ddad0
|
4
|
+
data.tar.gz: 2bd5d9f48a01e25dea810ca7b42bfc1c63c2bf40
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a81f892eef13d1f9c28a17daf44dc320a6c075e43b24f89e70b7f5d58ef91a2db35013e86b5de2b9be90360dcf53fd8757e7c54652f30b5ded0c71e2fe2bb95d
|
7
|
+
data.tar.gz: 77570a8ef7bb7dcfcbed470742686603cb361dc8fb593d737525e6b93e330084833d4c3b277c535a3c93131d1e10cf18296db70e9bb1246e915ead1456b78af9
|
data/lib/optimizely.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2016-
|
4
|
+
# Copyright 2016-2019, Optimizely and contributors
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -201,24 +201,14 @@ module Optimizely
|
|
201
201
|
|
202
202
|
return nil unless user_inputs_valid?(attributes, event_tags)
|
203
203
|
|
204
|
-
|
205
|
-
|
206
|
-
@config.logger.log(Logger::INFO, "Not tracking user '#{user_id}'.")
|
204
|
+
event = @config.get_event_from_key(event_key)
|
205
|
+
unless event
|
206
|
+
@config.logger.log(Logger::INFO, "Not tracking user '#{user_id}' for event '#{event_key}'.")
|
207
207
|
return nil
|
208
208
|
end
|
209
209
|
|
210
|
-
|
211
|
-
|
212
|
-
experiment_variation_map = get_valid_experiments_for_event(event_key, user_id, attributes)
|
213
|
-
|
214
|
-
# Don't track events without valid experiments attached
|
215
|
-
if experiment_variation_map.empty?
|
216
|
-
@logger.log(Logger::INFO, "There are no valid experiments for event '#{event_key}' to track.")
|
217
|
-
return nil
|
218
|
-
end
|
219
|
-
|
220
|
-
conversion_event = @event_builder.create_conversion_event(event_key, user_id, attributes,
|
221
|
-
event_tags, experiment_variation_map)
|
210
|
+
conversion_event = @event_builder.create_conversion_event(event, user_id, attributes, event_tags)
|
211
|
+
@config.logger.log(Logger::INFO, "Tracking event '#{event_key}' for user '#{user_id}'.")
|
222
212
|
@logger.log(Logger::INFO,
|
223
213
|
"Dispatching conversion event to URL #{conversion_event.url} with params #{conversion_event.params}.")
|
224
214
|
begin
|
@@ -258,6 +248,8 @@ module Optimizely
|
|
258
248
|
}, @logger, Logger::ERROR
|
259
249
|
)
|
260
250
|
|
251
|
+
return false unless user_inputs_valid?(attributes)
|
252
|
+
|
261
253
|
feature_flag = @config.get_feature_flag_from_key(feature_flag_key)
|
262
254
|
unless feature_flag
|
263
255
|
@logger.log(Logger::ERROR, "No feature flag was found for key '#{feature_flag_key}'.")
|
@@ -305,7 +297,13 @@ module Optimizely
|
|
305
297
|
return enabled_features
|
306
298
|
end
|
307
299
|
|
308
|
-
return enabled_features unless Optimizely::Helpers::Validator.inputs_valid?(
|
300
|
+
return enabled_features unless Optimizely::Helpers::Validator.inputs_valid?(
|
301
|
+
{
|
302
|
+
user_id: user_id
|
303
|
+
}, @logger, Logger::ERROR
|
304
|
+
)
|
305
|
+
|
306
|
+
return enabled_features unless user_inputs_valid?(attributes)
|
309
307
|
|
310
308
|
@config.feature_flags.each do |feature|
|
311
309
|
enabled_features.push(feature['key']) if is_feature_enabled(
|
@@ -444,12 +442,14 @@ module Optimizely
|
|
444
442
|
{
|
445
443
|
feature_flag_key: feature_flag_key,
|
446
444
|
variable_key: variable_key,
|
447
|
-
|
448
|
-
|
445
|
+
user_id: user_id,
|
446
|
+
variable_type: variable_type
|
449
447
|
},
|
450
448
|
@logger, Logger::ERROR
|
451
449
|
)
|
452
450
|
|
451
|
+
return nil unless user_inputs_valid?(attributes)
|
452
|
+
|
453
453
|
feature_flag = @config.get_feature_flag_from_key(feature_flag_key)
|
454
454
|
unless feature_flag
|
455
455
|
@logger.log(Logger::INFO, "No feature flag was found for key '#{feature_flag_key}'.")
|
@@ -492,34 +492,6 @@ module Optimizely
|
|
492
492
|
variable_value
|
493
493
|
end
|
494
494
|
|
495
|
-
def get_valid_experiments_for_event(event_key, user_id, attributes)
|
496
|
-
# Get the experiments that we should be tracking for the given event.
|
497
|
-
#
|
498
|
-
# event_key - Event key representing the event which needs to be recorded.
|
499
|
-
# user_id - String ID for user.
|
500
|
-
# attributes - Map of attributes of the user.
|
501
|
-
#
|
502
|
-
# Returns Map where each object contains the ID of the experiment to track and the ID of the variation the user
|
503
|
-
# is bucketed into.
|
504
|
-
|
505
|
-
valid_experiments = {}
|
506
|
-
experiment_ids = @config.get_experiment_ids_for_event(event_key)
|
507
|
-
experiment_ids.each do |experiment_id|
|
508
|
-
experiment_key = @config.get_experiment_key(experiment_id)
|
509
|
-
variation_key = get_variation(experiment_key, user_id, attributes)
|
510
|
-
|
511
|
-
if variation_key.nil?
|
512
|
-
@logger.log(Logger::INFO, "Not tracking user '#{user_id}' for experiment '#{experiment_key}'.")
|
513
|
-
next
|
514
|
-
end
|
515
|
-
|
516
|
-
variation_id = @config.get_variation_id_from_key(experiment_key, variation_key)
|
517
|
-
valid_experiments[experiment_id] = variation_id
|
518
|
-
end
|
519
|
-
|
520
|
-
valid_experiments
|
521
|
-
end
|
522
|
-
|
523
495
|
def user_inputs_valid?(attributes = nil, event_tags = nil)
|
524
496
|
# Helper method to validate user inputs.
|
525
497
|
#
|
data/lib/optimizely/audience.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2016-2017, Optimizely and contributors
|
4
|
+
# Copyright 2016-2017, 2019, Optimizely and contributors
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -16,7 +16,9 @@
|
|
16
16
|
# limitations under the License.
|
17
17
|
#
|
18
18
|
require 'json'
|
19
|
-
require_relative './
|
19
|
+
require_relative './custom_attribute_condition_evaluator'
|
20
|
+
require_relative 'condition_tree_evaluator'
|
21
|
+
require_relative 'helpers/constants'
|
20
22
|
|
21
23
|
module Optimizely
|
22
24
|
module Audience
|
@@ -30,26 +32,78 @@ module Optimizely
|
|
30
32
|
# attributes - Hash representing user attributes which will be used in determining if
|
31
33
|
# the audience conditions are met.
|
32
34
|
#
|
33
|
-
# Returns boolean representing if user satisfies audience conditions for
|
35
|
+
# Returns boolean representing if user satisfies audience conditions for the audiences or not.
|
34
36
|
|
35
|
-
|
37
|
+
audience_conditions = experiment['audienceConditions'] || experiment['audienceIds']
|
38
|
+
|
39
|
+
config.logger.log(
|
40
|
+
Logger::DEBUG,
|
41
|
+
format(
|
42
|
+
Helpers::Constants::AUDIENCE_EVALUATION_LOGS['EVALUATING_AUDIENCES_COMBINED'],
|
43
|
+
experiment['key'],
|
44
|
+
audience_conditions
|
45
|
+
)
|
46
|
+
)
|
36
47
|
|
37
48
|
# Return true if there are no audiences
|
38
|
-
|
49
|
+
if audience_conditions.empty?
|
50
|
+
config.logger.log(
|
51
|
+
Logger::INFO,
|
52
|
+
format(
|
53
|
+
Helpers::Constants::AUDIENCE_EVALUATION_LOGS['AUDIENCE_EVALUATION_RESULT_COMBINED'],
|
54
|
+
experiment['key'],
|
55
|
+
'TRUE'
|
56
|
+
)
|
57
|
+
)
|
58
|
+
return true
|
59
|
+
end
|
60
|
+
|
61
|
+
attributes ||= {}
|
39
62
|
|
40
|
-
|
41
|
-
|
63
|
+
custom_attr_condition_evaluator = CustomAttributeConditionEvaluator.new(attributes, config.logger)
|
64
|
+
|
65
|
+
evaluate_custom_attr = lambda do |condition|
|
66
|
+
return custom_attr_condition_evaluator.evaluate(condition)
|
67
|
+
end
|
42
68
|
|
43
|
-
|
44
|
-
@condition_evaluator = ConditionEvaluator.new(attributes)
|
45
|
-
audience_ids.each do |audience_id|
|
69
|
+
evaluate_audience = lambda do |audience_id|
|
46
70
|
audience = config.get_audience_from_id(audience_id)
|
71
|
+
return nil unless audience
|
72
|
+
|
47
73
|
audience_conditions = audience['conditions']
|
48
|
-
|
49
|
-
|
74
|
+
config.logger.log(
|
75
|
+
Logger::DEBUG,
|
76
|
+
format(
|
77
|
+
Helpers::Constants::AUDIENCE_EVALUATION_LOGS['EVALUATING_AUDIENCE'],
|
78
|
+
audience_id,
|
79
|
+
audience_conditions
|
80
|
+
)
|
81
|
+
)
|
82
|
+
|
83
|
+
audience_conditions = JSON.parse(audience_conditions) if audience_conditions.is_a?(String)
|
84
|
+
result = ConditionTreeEvaluator.evaluate(audience_conditions, evaluate_custom_attr)
|
85
|
+
result_str = result.nil? ? 'UNKNOWN' : result.to_s.upcase
|
86
|
+
config.logger.log(
|
87
|
+
Logger::INFO,
|
88
|
+
format(Helpers::Constants::AUDIENCE_EVALUATION_LOGS['AUDIENCE_EVALUATION_RESULT'], audience_id, result_str)
|
89
|
+
)
|
90
|
+
result
|
50
91
|
end
|
51
92
|
|
52
|
-
|
93
|
+
eval_result = ConditionTreeEvaluator.evaluate(audience_conditions, evaluate_audience)
|
94
|
+
|
95
|
+
eval_result ||= false
|
96
|
+
|
97
|
+
config.logger.log(
|
98
|
+
Logger::INFO,
|
99
|
+
format(
|
100
|
+
Helpers::Constants::AUDIENCE_EVALUATION_LOGS['AUDIENCE_EVALUATION_RESULT_COMBINED'],
|
101
|
+
experiment['key'],
|
102
|
+
eval_result.to_s.upcase
|
103
|
+
)
|
104
|
+
)
|
105
|
+
|
106
|
+
eval_result
|
53
107
|
end
|
54
108
|
end
|
55
109
|
end
|
data/lib/optimizely/bucketer.rb
CHANGED
@@ -93,7 +93,10 @@ module Optimizely
|
|
93
93
|
|
94
94
|
# Handle the case when the traffic range is empty due to sticky bucketing
|
95
95
|
if variation_id == ''
|
96
|
-
@config.logger.log(
|
96
|
+
@config.logger.log(
|
97
|
+
Logger::DEBUG,
|
98
|
+
'Bucketed into an empty traffic range. Returning nil.'
|
99
|
+
)
|
97
100
|
end
|
98
101
|
|
99
102
|
@config.logger.log(Logger::INFO, "User '#{user_id}' is in no variation.")
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright 2019, Optimizely and contributors
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
module Optimizely
|
19
|
+
module ConditionTreeEvaluator
|
20
|
+
# Operator types
|
21
|
+
AND_CONDITION = 'and'
|
22
|
+
OR_CONDITION = 'or'
|
23
|
+
NOT_CONDITION = 'not'
|
24
|
+
|
25
|
+
EVALUATORS_BY_OPERATOR_TYPE = {
|
26
|
+
AND_CONDITION => :and_evaluator,
|
27
|
+
OR_CONDITION => :or_evaluator,
|
28
|
+
NOT_CONDITION => :not_evaluator
|
29
|
+
}.freeze
|
30
|
+
|
31
|
+
module_function
|
32
|
+
|
33
|
+
def evaluate(conditions, leaf_evaluator)
|
34
|
+
# Top level method to evaluate audience conditions.
|
35
|
+
#
|
36
|
+
# conditions - Nested array of and/or conditions.
|
37
|
+
# Example: ['and', operand_1, ['or', operand_2, operand_3]]
|
38
|
+
#
|
39
|
+
# leaf_evaluator - Function which will be called to evaluate leaf condition values.
|
40
|
+
#
|
41
|
+
# Returns boolean if the given user attributes match/don't match the given conditions,
|
42
|
+
# nil if the given conditions are invalid or can't be evaluated.
|
43
|
+
|
44
|
+
if conditions.is_a? Array
|
45
|
+
first_operator = conditions[0]
|
46
|
+
rest_of_conditions = conditions[1..-1]
|
47
|
+
|
48
|
+
# Operator to apply is not explicit - assume 'or'
|
49
|
+
unless EVALUATORS_BY_OPERATOR_TYPE.include?(conditions[0])
|
50
|
+
first_operator = OR_CONDITION
|
51
|
+
rest_of_conditions = conditions
|
52
|
+
end
|
53
|
+
|
54
|
+
return send(EVALUATORS_BY_OPERATOR_TYPE[first_operator], rest_of_conditions, leaf_evaluator)
|
55
|
+
end
|
56
|
+
|
57
|
+
leaf_evaluator.call(conditions)
|
58
|
+
end
|
59
|
+
|
60
|
+
def and_evaluator(conditions, leaf_evaluator)
|
61
|
+
# Evaluates an array of conditions as if the evaluator had been applied
|
62
|
+
# to each entry and the results AND-ed together.
|
63
|
+
#
|
64
|
+
# conditions - Array of conditions ex: [operand_1, operand_2]
|
65
|
+
#
|
66
|
+
# leaf_evaluator - Function which will be called to evaluate leaf condition values.
|
67
|
+
#
|
68
|
+
# Returns boolean if the user attributes match/don't match the given conditions,
|
69
|
+
# nil if the user attributes and conditions can't be evaluated.
|
70
|
+
|
71
|
+
found_nil = false
|
72
|
+
conditions.each do |condition|
|
73
|
+
result = evaluate(condition, leaf_evaluator)
|
74
|
+
return result if result == false
|
75
|
+
|
76
|
+
found_nil = true if result.nil?
|
77
|
+
end
|
78
|
+
|
79
|
+
found_nil ? nil : true
|
80
|
+
end
|
81
|
+
|
82
|
+
def not_evaluator(single_condition, leaf_evaluator)
|
83
|
+
# Evaluates an array of conditions as if the evaluator had been applied
|
84
|
+
# to a single entry and NOT was applied to the result.
|
85
|
+
#
|
86
|
+
# single_condition - Array of a single condition ex: [operand_1]
|
87
|
+
#
|
88
|
+
# leaf_evaluator - Function which will be called to evaluate leaf condition values.
|
89
|
+
#
|
90
|
+
# Returns boolean if the user attributes match/don't match the given conditions,
|
91
|
+
# nil if the user attributes and conditions can't be evaluated.
|
92
|
+
|
93
|
+
return nil if single_condition.empty?
|
94
|
+
|
95
|
+
result = evaluate(single_condition[0], leaf_evaluator)
|
96
|
+
result.nil? ? nil : !result
|
97
|
+
end
|
98
|
+
|
99
|
+
def or_evaluator(conditions, leaf_evaluator)
|
100
|
+
# Evaluates an array of conditions as if the evaluator had been applied
|
101
|
+
# to each entry and the results OR-ed together.
|
102
|
+
#
|
103
|
+
# conditions - Array of conditions ex: [operand_1, operand_2]
|
104
|
+
#
|
105
|
+
# leaf_evaluator - Function which will be called to evaluate leaf condition values.
|
106
|
+
#
|
107
|
+
# Returns boolean if the user attributes match/don't match the given conditions,
|
108
|
+
# nil if the user attributes and conditions can't be evaluated.
|
109
|
+
|
110
|
+
found_nil = false
|
111
|
+
conditions.each do |condition|
|
112
|
+
result = evaluate(condition, leaf_evaluator)
|
113
|
+
return result if result == true
|
114
|
+
|
115
|
+
found_nil = true if result.nil?
|
116
|
+
end
|
117
|
+
|
118
|
+
found_nil ? nil : false
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,273 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright 2019, Optimizely and contributors
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
require_relative 'helpers/constants'
|
19
|
+
require_relative 'helpers/validator'
|
20
|
+
|
21
|
+
module Optimizely
|
22
|
+
class CustomAttributeConditionEvaluator
|
23
|
+
CUSTOM_ATTRIBUTE_CONDITION_TYPE = 'custom_attribute'
|
24
|
+
|
25
|
+
# Conditional match types
|
26
|
+
EXACT_MATCH_TYPE = 'exact'
|
27
|
+
EXISTS_MATCH_TYPE = 'exists'
|
28
|
+
GREATER_THAN_MATCH_TYPE = 'gt'
|
29
|
+
LESS_THAN_MATCH_TYPE = 'lt'
|
30
|
+
SUBSTRING_MATCH_TYPE = 'substring'
|
31
|
+
|
32
|
+
EVALUATORS_BY_MATCH_TYPE = {
|
33
|
+
EXACT_MATCH_TYPE => :exact_evaluator,
|
34
|
+
EXISTS_MATCH_TYPE => :exists_evaluator,
|
35
|
+
GREATER_THAN_MATCH_TYPE => :greater_than_evaluator,
|
36
|
+
LESS_THAN_MATCH_TYPE => :less_than_evaluator,
|
37
|
+
SUBSTRING_MATCH_TYPE => :substring_evaluator
|
38
|
+
}.freeze
|
39
|
+
|
40
|
+
attr_reader :user_attributes
|
41
|
+
|
42
|
+
def initialize(user_attributes, logger)
|
43
|
+
@user_attributes = user_attributes
|
44
|
+
@logger = logger
|
45
|
+
end
|
46
|
+
|
47
|
+
def evaluate(leaf_condition)
|
48
|
+
# Top level method to evaluate audience conditions.
|
49
|
+
#
|
50
|
+
# conditions - Nested array of and/or conditions.
|
51
|
+
# Example: ['and', operand_1, ['or', operand_2, operand_3]]
|
52
|
+
#
|
53
|
+
# Returns boolean if the given user attributes match/don't match the given conditions,
|
54
|
+
# nil if the given conditions can't be evaluated.
|
55
|
+
|
56
|
+
unless leaf_condition['type'] == CUSTOM_ATTRIBUTE_CONDITION_TYPE
|
57
|
+
@logger.log(
|
58
|
+
Logger::WARN,
|
59
|
+
format(Helpers::Constants::AUDIENCE_EVALUATION_LOGS['UNKNOWN_CONDITION_TYPE'], leaf_condition)
|
60
|
+
)
|
61
|
+
return nil
|
62
|
+
end
|
63
|
+
|
64
|
+
condition_match = leaf_condition['match'] || EXACT_MATCH_TYPE
|
65
|
+
|
66
|
+
if !@user_attributes.key?(leaf_condition['name']) && condition_match != EXISTS_MATCH_TYPE
|
67
|
+
@logger.log(
|
68
|
+
Logger::DEBUG,
|
69
|
+
format(
|
70
|
+
Helpers::Constants::AUDIENCE_EVALUATION_LOGS['MISSING_ATTRIBUTE_VALUE'],
|
71
|
+
leaf_condition,
|
72
|
+
leaf_condition['name']
|
73
|
+
)
|
74
|
+
)
|
75
|
+
return nil
|
76
|
+
end
|
77
|
+
|
78
|
+
if @user_attributes[leaf_condition['name']].nil? && condition_match != EXISTS_MATCH_TYPE
|
79
|
+
@logger.log(
|
80
|
+
Logger::DEBUG,
|
81
|
+
format(
|
82
|
+
Helpers::Constants::AUDIENCE_EVALUATION_LOGS['NULL_ATTRIBUTE_VALUE'],
|
83
|
+
leaf_condition,
|
84
|
+
leaf_condition['name']
|
85
|
+
)
|
86
|
+
)
|
87
|
+
return nil
|
88
|
+
end
|
89
|
+
|
90
|
+
unless EVALUATORS_BY_MATCH_TYPE.include?(condition_match)
|
91
|
+
@logger.log(
|
92
|
+
Logger::WARN,
|
93
|
+
format(Helpers::Constants::AUDIENCE_EVALUATION_LOGS['UNKNOWN_MATCH_TYPE'], leaf_condition)
|
94
|
+
)
|
95
|
+
return nil
|
96
|
+
end
|
97
|
+
|
98
|
+
send(EVALUATORS_BY_MATCH_TYPE[condition_match], leaf_condition)
|
99
|
+
end
|
100
|
+
|
101
|
+
def exact_evaluator(condition)
|
102
|
+
# Evaluate the given exact match condition for the given user attributes.
|
103
|
+
#
|
104
|
+
# Returns boolean true if numbers values matched, i.e 2 is equal to 2.0
|
105
|
+
# true if the user attribute value is equal (===) to the condition value,
|
106
|
+
# false if the user attribute value is not equal (!==) to the condition value,
|
107
|
+
# nil if the condition value or user attribute value has an invalid type,
|
108
|
+
# or if there is a mismatch between the user attribute type and the condition value type.
|
109
|
+
|
110
|
+
condition_value = condition['value']
|
111
|
+
|
112
|
+
user_provided_value = @user_attributes[condition['name']]
|
113
|
+
|
114
|
+
if !value_type_valid_for_exact_conditions?(condition_value) ||
|
115
|
+
(condition_value.is_a?(Numeric) && !Helpers::Validator.finite_number?(condition_value))
|
116
|
+
@logger.log(
|
117
|
+
Logger::WARN,
|
118
|
+
format(Helpers::Constants::AUDIENCE_EVALUATION_LOGS['UNKNOWN_CONDITION_VALUE'], condition)
|
119
|
+
)
|
120
|
+
return nil
|
121
|
+
end
|
122
|
+
|
123
|
+
if !value_type_valid_for_exact_conditions?(user_provided_value) ||
|
124
|
+
!Helpers::Validator.same_types?(condition_value, user_provided_value)
|
125
|
+
@logger.log(
|
126
|
+
Logger::WARN,
|
127
|
+
format(
|
128
|
+
Helpers::Constants::AUDIENCE_EVALUATION_LOGS['UNEXPECTED_TYPE'],
|
129
|
+
condition,
|
130
|
+
user_provided_value.class,
|
131
|
+
condition['name']
|
132
|
+
)
|
133
|
+
)
|
134
|
+
return nil
|
135
|
+
end
|
136
|
+
|
137
|
+
if user_provided_value.is_a?(Numeric) && !Helpers::Validator.finite_number?(user_provided_value)
|
138
|
+
@logger.log(
|
139
|
+
Logger::WARN,
|
140
|
+
format(
|
141
|
+
Helpers::Constants::AUDIENCE_EVALUATION_LOGS['INFINITE_ATTRIBUTE_VALUE'],
|
142
|
+
condition,
|
143
|
+
condition['name']
|
144
|
+
)
|
145
|
+
)
|
146
|
+
return nil
|
147
|
+
end
|
148
|
+
|
149
|
+
condition_value == user_provided_value
|
150
|
+
end
|
151
|
+
|
152
|
+
def exists_evaluator(condition)
|
153
|
+
# Evaluate the given exists match condition for the given user attributes.
|
154
|
+
# Returns boolean true if both:
|
155
|
+
# 1) the user attributes have a value for the given condition, and
|
156
|
+
# 2) the user attribute value is neither nil nor undefined
|
157
|
+
# Returns false otherwise
|
158
|
+
|
159
|
+
!@user_attributes[condition['name']].nil?
|
160
|
+
end
|
161
|
+
|
162
|
+
def greater_than_evaluator(condition)
|
163
|
+
# Evaluate the given greater than match condition for the given user attributes.
|
164
|
+
# Returns boolean true if the user attribute value is greater than the condition value,
|
165
|
+
# false if the user attribute value is less than or equal to the condition value,
|
166
|
+
# nil if the condition value isn't a number or the user attribute value isn't a number.
|
167
|
+
|
168
|
+
condition_value = condition['value']
|
169
|
+
user_provided_value = @user_attributes[condition['name']]
|
170
|
+
|
171
|
+
return nil unless valid_numeric_values?(user_provided_value, condition_value, condition)
|
172
|
+
|
173
|
+
user_provided_value > condition_value
|
174
|
+
end
|
175
|
+
|
176
|
+
def less_than_evaluator(condition)
|
177
|
+
# Evaluate the given less than match condition for the given user attributes.
|
178
|
+
# Returns boolean true if the user attribute value is less than the condition value,
|
179
|
+
# false if the user attribute value is greater than or equal to the condition value,
|
180
|
+
# nil if the condition value isn't a number or the user attribute value isn't a number.
|
181
|
+
|
182
|
+
condition_value = condition['value']
|
183
|
+
user_provided_value = @user_attributes[condition['name']]
|
184
|
+
|
185
|
+
return nil unless valid_numeric_values?(user_provided_value, condition_value, condition)
|
186
|
+
|
187
|
+
user_provided_value < condition_value
|
188
|
+
end
|
189
|
+
|
190
|
+
def substring_evaluator(condition)
|
191
|
+
# Evaluate the given substring match condition for the given user attributes.
|
192
|
+
# Returns boolean true if the condition value is a substring of the user attribute value,
|
193
|
+
# false if the condition value is not a substring of the user attribute value,
|
194
|
+
# nil if the condition value isn't a string or the user attribute value isn't a string.
|
195
|
+
|
196
|
+
condition_value = condition['value']
|
197
|
+
user_provided_value = @user_attributes[condition['name']]
|
198
|
+
|
199
|
+
unless condition_value.is_a?(String)
|
200
|
+
@logger.log(
|
201
|
+
Logger::WARN,
|
202
|
+
format(Helpers::Constants::AUDIENCE_EVALUATION_LOGS['UNKNOWN_CONDITION_VALUE'], condition)
|
203
|
+
)
|
204
|
+
return nil
|
205
|
+
end
|
206
|
+
|
207
|
+
unless user_provided_value.is_a?(String)
|
208
|
+
@logger.log(
|
209
|
+
Logger::WARN,
|
210
|
+
format(
|
211
|
+
Helpers::Constants::AUDIENCE_EVALUATION_LOGS['UNEXPECTED_TYPE'],
|
212
|
+
condition,
|
213
|
+
user_provided_value.class,
|
214
|
+
condition['name']
|
215
|
+
)
|
216
|
+
)
|
217
|
+
return nil
|
218
|
+
end
|
219
|
+
|
220
|
+
user_provided_value.include? condition_value
|
221
|
+
end
|
222
|
+
|
223
|
+
private
|
224
|
+
|
225
|
+
def valid_numeric_values?(user_value, condition_value, condition)
|
226
|
+
# Returns true if user and condition values are valid numeric.
|
227
|
+
# false otherwise.
|
228
|
+
|
229
|
+
unless Helpers::Validator.finite_number?(condition_value)
|
230
|
+
@logger.log(
|
231
|
+
Logger::WARN,
|
232
|
+
format(Helpers::Constants::AUDIENCE_EVALUATION_LOGS['UNKNOWN_CONDITION_VALUE'], condition)
|
233
|
+
)
|
234
|
+
return false
|
235
|
+
end
|
236
|
+
|
237
|
+
unless user_value.is_a?(Numeric)
|
238
|
+
@logger.log(
|
239
|
+
Logger::WARN,
|
240
|
+
format(
|
241
|
+
Helpers::Constants::AUDIENCE_EVALUATION_LOGS['UNEXPECTED_TYPE'],
|
242
|
+
condition,
|
243
|
+
user_value.class,
|
244
|
+
condition['name']
|
245
|
+
)
|
246
|
+
)
|
247
|
+
return false
|
248
|
+
end
|
249
|
+
|
250
|
+
unless Helpers::Validator.finite_number?(user_value)
|
251
|
+
@logger.log(
|
252
|
+
Logger::WARN,
|
253
|
+
format(
|
254
|
+
Helpers::Constants::AUDIENCE_EVALUATION_LOGS['INFINITE_ATTRIBUTE_VALUE'],
|
255
|
+
condition,
|
256
|
+
condition['name']
|
257
|
+
)
|
258
|
+
)
|
259
|
+
return false
|
260
|
+
end
|
261
|
+
|
262
|
+
true
|
263
|
+
end
|
264
|
+
|
265
|
+
def value_type_valid_for_exact_conditions?(value)
|
266
|
+
# Returns true if the value is valid for exact conditions. Valid values include
|
267
|
+
# strings or booleans or is a number.
|
268
|
+
# false otherwise.
|
269
|
+
|
270
|
+
(Helpers::Validator.boolean? value) || (value.is_a? String) || value.is_a?(Numeric)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|