optimizely-sdk 2.1.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|