optimizely-sdk 3.1.1 → 3.2.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 +169 -94
- data/lib/optimizely/audience.rb +7 -7
- data/lib/optimizely/bucketer.rb +16 -17
- data/lib/optimizely/config/datafile_project_config.rb +411 -0
- data/lib/optimizely/config_manager/async_scheduler.rb +91 -0
- data/lib/optimizely/config_manager/http_project_config_manager.rb +282 -0
- data/lib/optimizely/config_manager/project_config_manager.rb +24 -0
- data/lib/optimizely/config_manager/static_project_config_manager.rb +43 -0
- data/lib/optimizely/decision_service.rb +142 -52
- data/lib/optimizely/event_builder.rb +21 -28
- data/lib/optimizely/exceptions.rb +17 -1
- data/lib/optimizely/helpers/constants.rb +19 -0
- data/lib/optimizely/notification_center.rb +1 -0
- data/lib/optimizely/optimizely_factory.rb +73 -0
- data/lib/optimizely/project_config.rb +29 -434
- data/lib/optimizely/version.rb +1 -1
- metadata +12 -6
data/lib/optimizely/audience.rb
CHANGED
@@ -24,7 +24,7 @@ module Optimizely
|
|
24
24
|
module Audience
|
25
25
|
module_function
|
26
26
|
|
27
|
-
def user_in_experiment?(config, experiment, attributes)
|
27
|
+
def user_in_experiment?(config, experiment, attributes, logger)
|
28
28
|
# Determine for given experiment if user satisfies the audiences for the experiment.
|
29
29
|
#
|
30
30
|
# config - Representation of the Optimizely project config.
|
@@ -36,7 +36,7 @@ module Optimizely
|
|
36
36
|
|
37
37
|
audience_conditions = experiment['audienceConditions'] || experiment['audienceIds']
|
38
38
|
|
39
|
-
|
39
|
+
logger.log(
|
40
40
|
Logger::DEBUG,
|
41
41
|
format(
|
42
42
|
Helpers::Constants::AUDIENCE_EVALUATION_LOGS['EVALUATING_AUDIENCES_COMBINED'],
|
@@ -47,7 +47,7 @@ module Optimizely
|
|
47
47
|
|
48
48
|
# Return true if there are no audiences
|
49
49
|
if audience_conditions.empty?
|
50
|
-
|
50
|
+
logger.log(
|
51
51
|
Logger::INFO,
|
52
52
|
format(
|
53
53
|
Helpers::Constants::AUDIENCE_EVALUATION_LOGS['AUDIENCE_EVALUATION_RESULT_COMBINED'],
|
@@ -60,7 +60,7 @@ module Optimizely
|
|
60
60
|
|
61
61
|
attributes ||= {}
|
62
62
|
|
63
|
-
custom_attr_condition_evaluator = CustomAttributeConditionEvaluator.new(attributes,
|
63
|
+
custom_attr_condition_evaluator = CustomAttributeConditionEvaluator.new(attributes, logger)
|
64
64
|
|
65
65
|
evaluate_custom_attr = lambda do |condition|
|
66
66
|
return custom_attr_condition_evaluator.evaluate(condition)
|
@@ -71,7 +71,7 @@ module Optimizely
|
|
71
71
|
return nil unless audience
|
72
72
|
|
73
73
|
audience_conditions = audience['conditions']
|
74
|
-
|
74
|
+
logger.log(
|
75
75
|
Logger::DEBUG,
|
76
76
|
format(
|
77
77
|
Helpers::Constants::AUDIENCE_EVALUATION_LOGS['EVALUATING_AUDIENCE'],
|
@@ -83,7 +83,7 @@ module Optimizely
|
|
83
83
|
audience_conditions = JSON.parse(audience_conditions) if audience_conditions.is_a?(String)
|
84
84
|
result = ConditionTreeEvaluator.evaluate(audience_conditions, evaluate_custom_attr)
|
85
85
|
result_str = result.nil? ? 'UNKNOWN' : result.to_s.upcase
|
86
|
-
|
86
|
+
logger.log(
|
87
87
|
Logger::INFO,
|
88
88
|
format(Helpers::Constants::AUDIENCE_EVALUATION_LOGS['AUDIENCE_EVALUATION_RESULT'], audience_id, result_str)
|
89
89
|
)
|
@@ -94,7 +94,7 @@ module Optimizely
|
|
94
94
|
|
95
95
|
eval_result ||= false
|
96
96
|
|
97
|
-
|
97
|
+
logger.log(
|
98
98
|
Logger::INFO,
|
99
99
|
format(
|
100
100
|
Helpers::Constants::AUDIENCE_EVALUATION_LOGS['AUDIENCE_EVALUATION_RESULT_COMBINED'],
|
data/lib/optimizely/bucketer.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.
|
@@ -28,18 +28,17 @@ module Optimizely
|
|
28
28
|
MAX_TRAFFIC_VALUE = 10_000
|
29
29
|
UNSIGNED_MAX_32_BIT_VALUE = 0xFFFFFFFF
|
30
30
|
|
31
|
-
def initialize(
|
32
|
-
# Bucketer init method to set bucketing seed and
|
33
|
-
#
|
34
|
-
|
35
|
-
|
31
|
+
def initialize(logger)
|
32
|
+
# Bucketer init method to set bucketing seed and logger.
|
33
|
+
# logger - Optional component which provides a log method to log messages.
|
34
|
+
@logger = logger
|
36
35
|
@bucket_seed = HASH_SEED
|
37
|
-
@config = config
|
38
36
|
end
|
39
37
|
|
40
|
-
def bucket(experiment, bucketing_id, user_id)
|
38
|
+
def bucket(project_config, experiment, bucketing_id, user_id)
|
41
39
|
# Determines ID of variation to be shown for a given experiment key and user ID.
|
42
40
|
#
|
41
|
+
# project_config - Instance of ProjectConfig
|
43
42
|
# experiment - Experiment for which visitor is to be bucketed.
|
44
43
|
# bucketing_id - String A customer-assigned value used to generate the bucketing key
|
45
44
|
# user_id - String ID for user.
|
@@ -52,19 +51,19 @@ module Optimizely
|
|
52
51
|
experiment_key = experiment['key']
|
53
52
|
group_id = experiment['groupId']
|
54
53
|
if group_id
|
55
|
-
group =
|
54
|
+
group = project_config.group_id_map.fetch(group_id)
|
56
55
|
if Helpers::Group.random_policy?(group)
|
57
56
|
traffic_allocations = group.fetch('trafficAllocation')
|
58
57
|
bucketed_experiment_id = find_bucket(bucketing_id, user_id, group_id, traffic_allocations)
|
59
58
|
# return if the user is not bucketed into any experiment
|
60
59
|
unless bucketed_experiment_id
|
61
|
-
@
|
60
|
+
@logger.log(Logger::INFO, "User '#{user_id}' is in no experiment.")
|
62
61
|
return nil
|
63
62
|
end
|
64
63
|
|
65
64
|
# return if the user is bucketed into a different experiment than the one specified
|
66
65
|
if bucketed_experiment_id != experiment_id
|
67
|
-
@
|
66
|
+
@logger.log(
|
68
67
|
Logger::INFO,
|
69
68
|
"User '#{user_id}' is not in experiment '#{experiment_key}' of group #{group_id}."
|
70
69
|
)
|
@@ -72,7 +71,7 @@ module Optimizely
|
|
72
71
|
end
|
73
72
|
|
74
73
|
# continue bucketing if the user is bucketed into the experiment specified
|
75
|
-
@
|
74
|
+
@logger.log(
|
76
75
|
Logger::INFO,
|
77
76
|
"User '#{user_id}' is in experiment '#{experiment_key}' of group #{group_id}."
|
78
77
|
)
|
@@ -82,9 +81,9 @@ module Optimizely
|
|
82
81
|
traffic_allocations = experiment['trafficAllocation']
|
83
82
|
variation_id = find_bucket(bucketing_id, user_id, experiment_id, traffic_allocations)
|
84
83
|
if variation_id && variation_id != ''
|
85
|
-
variation =
|
84
|
+
variation = project_config.get_variation_from_id(experiment_key, variation_id)
|
86
85
|
variation_key = variation ? variation['key'] : nil
|
87
|
-
@
|
86
|
+
@logger.log(
|
88
87
|
Logger::INFO,
|
89
88
|
"User '#{user_id}' is in variation '#{variation_key}' of experiment '#{experiment_key}'."
|
90
89
|
)
|
@@ -93,13 +92,13 @@ module Optimizely
|
|
93
92
|
|
94
93
|
# Handle the case when the traffic range is empty due to sticky bucketing
|
95
94
|
if variation_id == ''
|
96
|
-
@
|
95
|
+
@logger.log(
|
97
96
|
Logger::DEBUG,
|
98
97
|
'Bucketed into an empty traffic range. Returning nil.'
|
99
98
|
)
|
100
99
|
end
|
101
100
|
|
102
|
-
@
|
101
|
+
@logger.log(Logger::INFO, "User '#{user_id}' is in no variation.")
|
103
102
|
nil
|
104
103
|
end
|
105
104
|
|
@@ -114,7 +113,7 @@ module Optimizely
|
|
114
113
|
# Returns entity ID corresponding to the provided bucket value or nil if no match is found.
|
115
114
|
bucketing_key = format(BUCKETING_ID_TEMPLATE, bucketing_id: bucketing_id, entity_id: parent_id)
|
116
115
|
bucket_value = generate_bucket_value(bucketing_key)
|
117
|
-
@
|
116
|
+
@logger.log(Logger::DEBUG, "Assigned bucket #{bucket_value} to user '#{user_id}' "\
|
118
117
|
"with bucketing ID: '#{bucketing_id}'.")
|
119
118
|
|
120
119
|
traffic_allocations.each do |traffic_allocation|
|
@@ -0,0 +1,411 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2019, Optimizely and contributors
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
require 'json'
|
18
|
+
require 'optimizely/project_config'
|
19
|
+
require 'optimizely/helpers/constants'
|
20
|
+
require 'optimizely/helpers/validator'
|
21
|
+
module Optimizely
|
22
|
+
class DatafileProjectConfig < ProjectConfig
|
23
|
+
# Representation of the Optimizely project config.
|
24
|
+
RUNNING_EXPERIMENT_STATUS = ['Running'].freeze
|
25
|
+
RESERVED_ATTRIBUTE_PREFIX = '$opt_'
|
26
|
+
|
27
|
+
attr_reader :account_id
|
28
|
+
attr_reader :attributes
|
29
|
+
attr_reader :audiences
|
30
|
+
attr_reader :typed_audiences
|
31
|
+
attr_reader :events
|
32
|
+
attr_reader :experiments
|
33
|
+
attr_reader :feature_flags
|
34
|
+
attr_reader :groups
|
35
|
+
attr_reader :project_id
|
36
|
+
# Boolean - denotes if Optimizely should remove the last block of visitors' IP address before storing event data
|
37
|
+
attr_reader :anonymize_ip
|
38
|
+
attr_reader :bot_filtering
|
39
|
+
attr_reader :revision
|
40
|
+
attr_reader :rollouts
|
41
|
+
attr_reader :version
|
42
|
+
|
43
|
+
attr_reader :attribute_key_map
|
44
|
+
attr_reader :audience_id_map
|
45
|
+
attr_reader :event_key_map
|
46
|
+
attr_reader :experiment_feature_map
|
47
|
+
attr_reader :experiment_id_map
|
48
|
+
attr_reader :experiment_key_map
|
49
|
+
attr_reader :feature_flag_key_map
|
50
|
+
attr_reader :feature_variable_key_map
|
51
|
+
attr_reader :group_id_map
|
52
|
+
attr_reader :rollout_id_map
|
53
|
+
attr_reader :rollout_experiment_key_map
|
54
|
+
attr_reader :variation_id_map
|
55
|
+
attr_reader :variation_id_to_variable_usage_map
|
56
|
+
attr_reader :variation_key_map
|
57
|
+
|
58
|
+
def initialize(datafile, logger, error_handler)
|
59
|
+
# ProjectConfig init method to fetch and set project config data
|
60
|
+
#
|
61
|
+
# datafile - JSON string representing the project
|
62
|
+
|
63
|
+
config = JSON.parse(datafile)
|
64
|
+
|
65
|
+
@error_handler = error_handler
|
66
|
+
@logger = logger
|
67
|
+
@version = config['version']
|
68
|
+
|
69
|
+
raise InvalidDatafileVersionError, @version unless Helpers::Constants::SUPPORTED_VERSIONS.value?(@version)
|
70
|
+
|
71
|
+
@account_id = config['accountId']
|
72
|
+
@attributes = config.fetch('attributes', [])
|
73
|
+
@audiences = config.fetch('audiences', [])
|
74
|
+
@typed_audiences = config.fetch('typedAudiences', [])
|
75
|
+
@events = config.fetch('events', [])
|
76
|
+
@experiments = config['experiments']
|
77
|
+
@feature_flags = config.fetch('featureFlags', [])
|
78
|
+
@groups = config.fetch('groups', [])
|
79
|
+
@project_id = config['projectId']
|
80
|
+
@anonymize_ip = config.key?('anonymizeIP') ? config['anonymizeIP'] : false
|
81
|
+
@bot_filtering = config['botFiltering']
|
82
|
+
@revision = config['revision']
|
83
|
+
@rollouts = config.fetch('rollouts', [])
|
84
|
+
|
85
|
+
# Utility maps for quick lookup
|
86
|
+
@attribute_key_map = generate_key_map(@attributes, 'key')
|
87
|
+
@event_key_map = generate_key_map(@events, 'key')
|
88
|
+
@group_id_map = generate_key_map(@groups, 'id')
|
89
|
+
@group_id_map.each do |key, group|
|
90
|
+
exps = group.fetch('experiments')
|
91
|
+
exps.each do |exp|
|
92
|
+
@experiments.push(exp.merge('groupId' => key))
|
93
|
+
end
|
94
|
+
end
|
95
|
+
@experiment_key_map = generate_key_map(@experiments, 'key')
|
96
|
+
@experiment_id_map = generate_key_map(@experiments, 'id')
|
97
|
+
@audience_id_map = generate_key_map(@audiences, 'id')
|
98
|
+
@audience_id_map = @audience_id_map.merge(generate_key_map(@typed_audiences, 'id')) unless @typed_audiences.empty?
|
99
|
+
@variation_id_map = {}
|
100
|
+
@variation_key_map = {}
|
101
|
+
@variation_id_to_variable_usage_map = {}
|
102
|
+
@variation_id_to_experiment_map = {}
|
103
|
+
@experiment_key_map.each_value do |exp|
|
104
|
+
# Excludes experiments from rollouts
|
105
|
+
variations = exp.fetch('variations')
|
106
|
+
variations.each do |variation|
|
107
|
+
variation_id = variation['id']
|
108
|
+
@variation_id_to_experiment_map[variation_id] = exp
|
109
|
+
end
|
110
|
+
end
|
111
|
+
@rollout_id_map = generate_key_map(@rollouts, 'id')
|
112
|
+
# split out the experiment key map for rollouts
|
113
|
+
@rollout_experiment_key_map = {}
|
114
|
+
@rollout_id_map.each_value do |rollout|
|
115
|
+
exps = rollout.fetch('experiments')
|
116
|
+
@rollout_experiment_key_map = @rollout_experiment_key_map.merge(generate_key_map(exps, 'key'))
|
117
|
+
end
|
118
|
+
@all_experiments = @experiment_key_map.merge(@rollout_experiment_key_map)
|
119
|
+
@all_experiments.each do |key, exp|
|
120
|
+
variations = exp.fetch('variations')
|
121
|
+
variations.each do |variation|
|
122
|
+
variation_id = variation['id']
|
123
|
+
variation['featureEnabled'] = variation['featureEnabled'] == true
|
124
|
+
variation_variables = variation['variables']
|
125
|
+
next if variation_variables.nil?
|
126
|
+
|
127
|
+
@variation_id_to_variable_usage_map[variation_id] = generate_key_map(variation_variables, 'id')
|
128
|
+
end
|
129
|
+
@variation_id_map[key] = generate_key_map(variations, 'id')
|
130
|
+
@variation_key_map[key] = generate_key_map(variations, 'key')
|
131
|
+
end
|
132
|
+
@feature_flag_key_map = generate_key_map(@feature_flags, 'key')
|
133
|
+
@experiment_feature_map = {}
|
134
|
+
@feature_variable_key_map = {}
|
135
|
+
@feature_flag_key_map.each do |key, feature_flag|
|
136
|
+
@feature_variable_key_map[key] = generate_key_map(feature_flag['variables'], 'key')
|
137
|
+
feature_flag['experimentIds'].each do |experiment_id|
|
138
|
+
@experiment_feature_map[experiment_id] = [feature_flag['id']]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.create(datafile, logger, error_handler, skip_json_validation)
|
144
|
+
# Looks up and sets datafile and config based on response body.
|
145
|
+
#
|
146
|
+
# datafile - JSON string representing the Optimizely project.
|
147
|
+
# logger - Provides a logger instance.
|
148
|
+
# error_handler - Provides a handle_error method to handle exceptions.
|
149
|
+
# skip_json_validation - Optional boolean param which allows skipping JSON schema
|
150
|
+
# validation upon object invocation. By default JSON schema validation will be performed.
|
151
|
+
# Returns instance of DatafileProjectConfig, nil otherwise.
|
152
|
+
if !skip_json_validation && !Helpers::Validator.datafile_valid?(datafile)
|
153
|
+
default_logger = SimpleLogger.new
|
154
|
+
default_logger.log(Logger::ERROR, InvalidInputError.new('datafile').message)
|
155
|
+
return nil
|
156
|
+
end
|
157
|
+
|
158
|
+
begin
|
159
|
+
config = new(datafile, logger, error_handler)
|
160
|
+
rescue StandardError => e
|
161
|
+
default_logger = SimpleLogger.new
|
162
|
+
error_msg = e.class == InvalidDatafileVersionError ? e.message : InvalidInputError.new('datafile').message
|
163
|
+
error_to_handle = e.class == InvalidDatafileVersionError ? InvalidDatafileVersionError : InvalidInputError
|
164
|
+
default_logger.log(Logger::ERROR, error_msg)
|
165
|
+
error_handler.handle_error error_to_handle
|
166
|
+
return nil
|
167
|
+
end
|
168
|
+
|
169
|
+
config
|
170
|
+
end
|
171
|
+
|
172
|
+
def experiment_running?(experiment)
|
173
|
+
# Determine if experiment corresponding to given key is running
|
174
|
+
#
|
175
|
+
# experiment - Experiment
|
176
|
+
#
|
177
|
+
# Returns true if experiment is running
|
178
|
+
RUNNING_EXPERIMENT_STATUS.include?(experiment['status'])
|
179
|
+
end
|
180
|
+
|
181
|
+
def get_experiment_from_key(experiment_key)
|
182
|
+
# Retrieves experiment ID for a given key
|
183
|
+
#
|
184
|
+
# experiment_key - String key representing the experiment
|
185
|
+
#
|
186
|
+
# Returns Experiment or nil if not found
|
187
|
+
|
188
|
+
experiment = @experiment_key_map[experiment_key]
|
189
|
+
return experiment if experiment
|
190
|
+
|
191
|
+
@logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
|
192
|
+
@error_handler.handle_error InvalidExperimentError
|
193
|
+
nil
|
194
|
+
end
|
195
|
+
|
196
|
+
def get_experiment_key(experiment_id)
|
197
|
+
# Retrieves experiment key for a given ID.
|
198
|
+
#
|
199
|
+
# experiment_id - String ID representing the experiment.
|
200
|
+
#
|
201
|
+
# Returns String key.
|
202
|
+
|
203
|
+
experiment = @experiment_id_map[experiment_id]
|
204
|
+
return experiment['key'] unless experiment.nil?
|
205
|
+
|
206
|
+
@logger.log Logger::ERROR, "Experiment id '#{experiment_id}' is not in datafile."
|
207
|
+
@error_handler.handle_error InvalidExperimentError
|
208
|
+
nil
|
209
|
+
end
|
210
|
+
|
211
|
+
def get_event_from_key(event_key)
|
212
|
+
# Get event for the provided event key.
|
213
|
+
#
|
214
|
+
# event_key - Event key for which event is to be determined.
|
215
|
+
#
|
216
|
+
# Returns Event corresponding to the provided event key.
|
217
|
+
|
218
|
+
event = @event_key_map[event_key]
|
219
|
+
return event if event
|
220
|
+
|
221
|
+
@logger.log Logger::ERROR, "Event '#{event_key}' is not in datafile."
|
222
|
+
@error_handler.handle_error InvalidEventError
|
223
|
+
nil
|
224
|
+
end
|
225
|
+
|
226
|
+
def get_audience_from_id(audience_id)
|
227
|
+
# Get audience for the provided audience ID
|
228
|
+
#
|
229
|
+
# audience_id - ID of the audience
|
230
|
+
#
|
231
|
+
# Returns the audience
|
232
|
+
|
233
|
+
audience = @audience_id_map[audience_id]
|
234
|
+
return audience if audience
|
235
|
+
|
236
|
+
@logger.log Logger::ERROR, "Audience '#{audience_id}' is not in datafile."
|
237
|
+
@error_handler.handle_error InvalidAudienceError
|
238
|
+
nil
|
239
|
+
end
|
240
|
+
|
241
|
+
def get_variation_from_id(experiment_key, variation_id)
|
242
|
+
# Get variation given experiment key and variation ID
|
243
|
+
#
|
244
|
+
# experiment_key - Key representing parent experiment of variation
|
245
|
+
# variation_id - ID of the variation
|
246
|
+
#
|
247
|
+
# Returns the variation or nil if not found
|
248
|
+
|
249
|
+
variation_id_map = @variation_id_map[experiment_key]
|
250
|
+
if variation_id_map
|
251
|
+
variation = variation_id_map[variation_id]
|
252
|
+
return variation if variation
|
253
|
+
|
254
|
+
@logger.log Logger::ERROR, "Variation id '#{variation_id}' is not in datafile."
|
255
|
+
@error_handler.handle_error InvalidVariationError
|
256
|
+
return nil
|
257
|
+
end
|
258
|
+
|
259
|
+
@logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
|
260
|
+
@error_handler.handle_error InvalidExperimentError
|
261
|
+
nil
|
262
|
+
end
|
263
|
+
|
264
|
+
def get_variation_id_from_key(experiment_key, variation_key)
|
265
|
+
# Get variation ID given experiment key and variation key
|
266
|
+
#
|
267
|
+
# experiment_key - Key representing parent experiment of variation
|
268
|
+
# variation_key - Key of the variation
|
269
|
+
#
|
270
|
+
# Returns ID of the variation
|
271
|
+
|
272
|
+
variation_key_map = @variation_key_map[experiment_key]
|
273
|
+
if variation_key_map
|
274
|
+
variation = variation_key_map[variation_key]
|
275
|
+
return variation['id'] if variation
|
276
|
+
|
277
|
+
@logger.log Logger::ERROR, "Variation key '#{variation_key}' is not in datafile."
|
278
|
+
@error_handler.handle_error InvalidVariationError
|
279
|
+
return nil
|
280
|
+
end
|
281
|
+
|
282
|
+
@logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
|
283
|
+
@error_handler.handle_error InvalidExperimentError
|
284
|
+
nil
|
285
|
+
end
|
286
|
+
|
287
|
+
def get_whitelisted_variations(experiment_key)
|
288
|
+
# Retrieves whitelisted variations for a given experiment Key
|
289
|
+
#
|
290
|
+
# experiment_key - String Key representing the experiment
|
291
|
+
#
|
292
|
+
# Returns whitelisted variations for the experiment or nil
|
293
|
+
|
294
|
+
experiment = @experiment_key_map[experiment_key]
|
295
|
+
return experiment['forcedVariations'] if experiment
|
296
|
+
|
297
|
+
@logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
|
298
|
+
@error_handler.handle_error InvalidExperimentError
|
299
|
+
end
|
300
|
+
|
301
|
+
def get_attribute_id(attribute_key)
|
302
|
+
# Get attribute ID for the provided attribute key.
|
303
|
+
#
|
304
|
+
# Args:
|
305
|
+
# Attribute key for which attribute is to be fetched.
|
306
|
+
#
|
307
|
+
# Returns:
|
308
|
+
# Attribute ID corresponding to the provided attribute key.
|
309
|
+
attribute = @attribute_key_map[attribute_key]
|
310
|
+
has_reserved_prefix = attribute_key.to_s.start_with?(RESERVED_ATTRIBUTE_PREFIX)
|
311
|
+
unless attribute.nil?
|
312
|
+
if has_reserved_prefix
|
313
|
+
@logger.log(Logger::WARN, "Attribute '#{attribute_key}' unexpectedly has reserved prefix '#{RESERVED_ATTRIBUTE_PREFIX}'; "\
|
314
|
+
'using attribute ID instead of reserved attribute name.')
|
315
|
+
end
|
316
|
+
return attribute['id']
|
317
|
+
end
|
318
|
+
return attribute_key if has_reserved_prefix
|
319
|
+
|
320
|
+
@logger.log Logger::ERROR, "Attribute key '#{attribute_key}' is not in datafile."
|
321
|
+
@error_handler.handle_error InvalidAttributeError
|
322
|
+
nil
|
323
|
+
end
|
324
|
+
|
325
|
+
def variation_id_exists?(experiment_id, variation_id)
|
326
|
+
# Determines if a given experiment ID / variation ID pair exists in the datafile
|
327
|
+
#
|
328
|
+
# experiment_id - String experiment ID
|
329
|
+
# variation_id - String variation ID
|
330
|
+
#
|
331
|
+
# Returns true if variation is in datafile
|
332
|
+
|
333
|
+
experiment_key = get_experiment_key(experiment_id)
|
334
|
+
variation_id_map = @variation_id_map[experiment_key]
|
335
|
+
if variation_id_map
|
336
|
+
variation = variation_id_map[variation_id]
|
337
|
+
return true if variation
|
338
|
+
|
339
|
+
@logger.log Logger::ERROR, "Variation ID '#{variation_id}' is not in datafile."
|
340
|
+
@error_handler.handle_error InvalidVariationError
|
341
|
+
end
|
342
|
+
|
343
|
+
false
|
344
|
+
end
|
345
|
+
|
346
|
+
def get_feature_flag_from_key(feature_flag_key)
|
347
|
+
# Retrieves the feature flag with the given key
|
348
|
+
#
|
349
|
+
# feature_flag_key - String feature key
|
350
|
+
#
|
351
|
+
# Returns feature flag if found, otherwise nil
|
352
|
+
feature_flag = @feature_flag_key_map[feature_flag_key]
|
353
|
+
return feature_flag if feature_flag
|
354
|
+
|
355
|
+
@logger.log Logger::ERROR, "Feature flag key '#{feature_flag_key}' is not in datafile."
|
356
|
+
nil
|
357
|
+
end
|
358
|
+
|
359
|
+
def get_feature_variable(feature_flag, variable_key)
|
360
|
+
# Retrieves the variable with the given key for the given feature
|
361
|
+
#
|
362
|
+
# feature_flag - The feature flag for which we are retrieving the variable
|
363
|
+
# variable_key - String variable key
|
364
|
+
#
|
365
|
+
# Returns variable if found, otherwise nil
|
366
|
+
feature_flag_key = feature_flag['key']
|
367
|
+
variable = @feature_variable_key_map[feature_flag_key][variable_key]
|
368
|
+
return variable if variable
|
369
|
+
|
370
|
+
@logger.log Logger::ERROR, "No feature variable was found for key '#{variable_key}' in feature flag "\
|
371
|
+
"'#{feature_flag_key}'."
|
372
|
+
nil
|
373
|
+
end
|
374
|
+
|
375
|
+
def get_rollout_from_id(rollout_id)
|
376
|
+
# Retrieves the rollout with the given ID
|
377
|
+
#
|
378
|
+
# rollout_id - String rollout ID
|
379
|
+
#
|
380
|
+
# Returns the rollout if found, otherwise nil
|
381
|
+
rollout = @rollout_id_map[rollout_id]
|
382
|
+
return rollout if rollout
|
383
|
+
|
384
|
+
@logger.log Logger::ERROR, "Rollout with ID '#{rollout_id}' is not in the datafile."
|
385
|
+
nil
|
386
|
+
end
|
387
|
+
|
388
|
+
def feature_experiment?(experiment_id)
|
389
|
+
# Determines if given experiment is a feature test.
|
390
|
+
#
|
391
|
+
# experiment_id - String experiment ID
|
392
|
+
#
|
393
|
+
# Returns true if experiment belongs to any feature,
|
394
|
+
# false otherwise.
|
395
|
+
@experiment_feature_map.key?(experiment_id)
|
396
|
+
end
|
397
|
+
|
398
|
+
private
|
399
|
+
|
400
|
+
def generate_key_map(array, key)
|
401
|
+
# Helper method to generate map from key to hash in array of hashes
|
402
|
+
#
|
403
|
+
# array - Array consisting of hash
|
404
|
+
# key - Key in each hash which will be key in the map
|
405
|
+
#
|
406
|
+
# Returns map mapping key to hash
|
407
|
+
|
408
|
+
Hash[array.map { |obj| [obj[key], obj] }]
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|