optimizely-sdk 3.6.0 → 3.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2020, 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
+
18
+ require 'json'
19
+
20
+ module Optimizely
21
+ module Decide
22
+ class OptimizelyDecision
23
+ attr_reader :variation_key, :enabled, :variables, :rule_key, :flag_key, :user_context, :reasons
24
+
25
+ def initialize(
26
+ variation_key: nil,
27
+ enabled: nil,
28
+ variables: nil,
29
+ rule_key: nil,
30
+ flag_key: nil,
31
+ user_context: nil,
32
+ reasons: nil
33
+ )
34
+ @variation_key = variation_key
35
+ @enabled = enabled || false
36
+ @variables = variables || {}
37
+ @rule_key = rule_key
38
+ @flag_key = flag_key
39
+ @user_context = user_context
40
+ @reasons = reasons || []
41
+ end
42
+
43
+ def as_json
44
+ {
45
+ variation_key: @variation_key,
46
+ enabled: @enabled,
47
+ variables: @variables,
48
+ rule_key: @rule_key,
49
+ flag_key: @flag_key,
50
+ user_context: @user_context.as_json,
51
+ reasons: @reasons
52
+ }
53
+ end
54
+
55
+ def to_json(*args)
56
+ as_json.to_json(*args)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2020, 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
+
18
+ module Optimizely
19
+ module Decide
20
+ module OptimizelyDecisionMessage
21
+ SDK_NOT_READY = 'Optimizely SDK not configured properly yet.'
22
+ FLAG_KEY_INVALID = 'No flag was found for key "%s".'
23
+ VARIABLE_VALUE_INVALID = 'Variable value for key "%s" is invalid or wrong type.'
24
+ end
25
+ end
26
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #
4
- # Copyright 2017-2020, Optimizely and contributors
4
+ # Copyright 2017-2021, 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.
@@ -40,6 +40,7 @@ module Optimizely
40
40
  Decision = Struct.new(:experiment, :variation, :source)
41
41
 
42
42
  DECISION_SOURCES = {
43
+ 'EXPERIMENT' => 'experiment',
43
44
  'FEATURE_TEST' => 'feature-test',
44
45
  'ROLLOUT' => 'rollout'
45
46
  }.freeze
@@ -51,77 +52,89 @@ module Optimizely
51
52
  @forced_variation_map = {}
52
53
  end
53
54
 
54
- def get_variation(project_config, experiment_key, user_id, attributes = nil)
55
+ def get_variation(project_config, experiment_id, user_id, attributes = nil, decide_options = [])
55
56
  # Determines variation into which user will be bucketed.
56
57
  #
57
58
  # project_config - project_config - Instance of ProjectConfig
58
- # experiment_key - Experiment for which visitor variation needs to be determined
59
+ # experiment_id - Experiment for which visitor variation needs to be determined
59
60
  # user_id - String ID for user
60
61
  # attributes - Hash representing user attributes
61
62
  #
62
63
  # Returns variation ID where visitor will be bucketed
63
64
  # (nil if experiment is inactive or user does not meet audience conditions)
64
65
 
66
+ decide_reasons = []
65
67
  # By default, the bucketing ID should be the user ID
66
- bucketing_id = get_bucketing_id(user_id, attributes)
68
+ bucketing_id, bucketing_id_reasons = get_bucketing_id(user_id, attributes)
69
+ decide_reasons.push(*bucketing_id_reasons)
67
70
  # Check to make sure experiment is active
68
- experiment = project_config.get_experiment_from_key(experiment_key)
69
- return nil if experiment.nil?
71
+ experiment = project_config.get_experiment_from_id(experiment_id)
72
+ return nil, decide_reasons if experiment.nil?
70
73
 
71
- experiment_id = experiment['id']
74
+ experiment_key = experiment['key']
72
75
  unless project_config.experiment_running?(experiment)
73
- @logger.log(Logger::INFO, "Experiment '#{experiment_key}' is not running.")
74
- return nil
76
+ message = "Experiment '#{experiment_key}' is not running."
77
+ @logger.log(Logger::INFO, message)
78
+ decide_reasons.push(message)
79
+ return nil, decide_reasons
75
80
  end
76
81
 
77
82
  # Check if a forced variation is set for the user
78
- forced_variation = get_forced_variation(project_config, experiment_key, user_id)
79
- return forced_variation['id'] if forced_variation
83
+ forced_variation, reasons_received = get_forced_variation(project_config, experiment['key'], user_id)
84
+ decide_reasons.push(*reasons_received)
85
+ return forced_variation['id'], decide_reasons if forced_variation
80
86
 
81
87
  # Check if user is in a white-listed variation
82
- whitelisted_variation_id = get_whitelisted_variation_id(project_config, experiment_key, user_id)
83
- return whitelisted_variation_id if whitelisted_variation_id
84
-
85
- # Check for saved bucketing decisions
86
- user_profile = get_user_profile(user_id)
87
- saved_variation_id = get_saved_variation_id(project_config, experiment_id, user_profile)
88
- if saved_variation_id
89
- @logger.log(
90
- Logger::INFO,
91
- "Returning previously activated variation ID #{saved_variation_id} of experiment '#{experiment_key}' for user '#{user_id}' from user profile."
92
- )
93
- return saved_variation_id
88
+ whitelisted_variation_id, reasons_received = get_whitelisted_variation_id(project_config, experiment_id, user_id)
89
+ decide_reasons.push(*reasons_received)
90
+ return whitelisted_variation_id, decide_reasons if whitelisted_variation_id
91
+
92
+ should_ignore_user_profile_service = decide_options.include? Optimizely::Decide::OptimizelyDecideOption::IGNORE_USER_PROFILE_SERVICE
93
+ # Check for saved bucketing decisions if decide_options do not include ignoreUserProfileService
94
+ unless should_ignore_user_profile_service
95
+ user_profile, reasons_received = get_user_profile(user_id)
96
+ decide_reasons.push(*reasons_received)
97
+ saved_variation_id, reasons_received = get_saved_variation_id(project_config, experiment_id, user_profile)
98
+ decide_reasons.push(*reasons_received)
99
+ if saved_variation_id
100
+ message = "Returning previously activated variation ID #{saved_variation_id} of experiment '#{experiment_key}' for user '#{user_id}' from user profile."
101
+ @logger.log(Logger::INFO, message)
102
+ decide_reasons.push(message)
103
+ return saved_variation_id, decide_reasons
104
+ end
94
105
  end
95
106
 
96
107
  # Check audience conditions
97
- unless Audience.user_meets_audience_conditions?(project_config, experiment, attributes, @logger)
98
- @logger.log(
99
- Logger::INFO,
100
- "User '#{user_id}' does not meet the conditions to be in experiment '#{experiment_key}'."
101
- )
102
- return nil
108
+ user_meets_audience_conditions, reasons_received = Audience.user_meets_audience_conditions?(project_config, experiment, attributes, @logger)
109
+ decide_reasons.push(*reasons_received)
110
+ unless user_meets_audience_conditions
111
+ message = "User '#{user_id}' does not meet the conditions to be in experiment '#{experiment_key}'."
112
+ @logger.log(Logger::INFO, message)
113
+ decide_reasons.push(message)
114
+ return nil, decide_reasons
103
115
  end
104
116
 
105
117
  # Bucket normally
106
- variation = @bucketer.bucket(project_config, experiment, bucketing_id, user_id)
118
+ variation, bucket_reasons = @bucketer.bucket(project_config, experiment, bucketing_id, user_id)
119
+ decide_reasons.push(*bucket_reasons)
107
120
  variation_id = variation ? variation['id'] : nil
108
121
 
122
+ message = ''
109
123
  if variation_id
110
124
  variation_key = variation['key']
111
- @logger.log(
112
- Logger::INFO,
113
- "User '#{user_id}' is in variation '#{variation_key}' of experiment '#{experiment_key}'."
114
- )
125
+ message = "User '#{user_id}' is in variation '#{variation_key}' of experiment '#{experiment_id}'."
115
126
  else
116
- @logger.log(Logger::INFO, "User '#{user_id}' is in no variation.")
127
+ message = "User '#{user_id}' is in no variation."
117
128
  end
129
+ @logger.log(Logger::INFO, message)
130
+ decide_reasons.push(message)
118
131
 
119
132
  # Persist bucketing decision
120
- save_user_profile(user_profile, experiment_id, variation_id)
121
- variation_id
133
+ save_user_profile(user_profile, experiment_id, variation_id) unless should_ignore_user_profile_service
134
+ [variation_id, decide_reasons]
122
135
  end
123
136
 
124
- def get_variation_for_feature(project_config, feature_flag, user_id, attributes = nil)
137
+ def get_variation_for_feature(project_config, feature_flag, user_id, attributes = nil, decide_options = [])
125
138
  # Get the variation the user is bucketed into for the given FeatureFlag.
126
139
  #
127
140
  # project_config - project_config - Instance of ProjectConfig
@@ -131,16 +144,20 @@ module Optimizely
131
144
  #
132
145
  # Returns Decision struct (nil if the user is not bucketed into any of the experiments on the feature)
133
146
 
147
+ decide_reasons = []
148
+
134
149
  # check if the feature is being experiment on and whether the user is bucketed into the experiment
135
- decision = get_variation_for_feature_experiment(project_config, feature_flag, user_id, attributes)
136
- return decision unless decision.nil?
150
+ decision, reasons_received = get_variation_for_feature_experiment(project_config, feature_flag, user_id, attributes, decide_options)
151
+ decide_reasons.push(*reasons_received)
152
+ return decision, decide_reasons unless decision.nil?
137
153
 
138
- decision = get_variation_for_feature_rollout(project_config, feature_flag, user_id, attributes)
154
+ decision, reasons_received = get_variation_for_feature_rollout(project_config, feature_flag, user_id, attributes)
155
+ decide_reasons.push(*reasons_received)
139
156
 
140
- decision
157
+ [decision, decide_reasons]
141
158
  end
142
159
 
143
- def get_variation_for_feature_experiment(project_config, feature_flag, user_id, attributes = nil)
160
+ def get_variation_for_feature_experiment(project_config, feature_flag, user_id, attributes = nil, decide_options = [])
144
161
  # Gets the variation the user is bucketed into for the feature flag's experiment.
145
162
  #
146
163
  # project_config - project_config - Instance of ProjectConfig
@@ -150,42 +167,41 @@ module Optimizely
150
167
  #
151
168
  # Returns Decision struct (nil if the user is not bucketed into any of the experiments on the feature)
152
169
  # or nil if the user is not bucketed into any of the experiments on the feature
170
+ decide_reasons = []
153
171
  feature_flag_key = feature_flag['key']
154
172
  if feature_flag['experimentIds'].empty?
155
- @logger.log(
156
- Logger::DEBUG,
157
- "The feature flag '#{feature_flag_key}' is not used in any experiments."
158
- )
159
- return nil
173
+ message = "The feature flag '#{feature_flag_key}' is not used in any experiments."
174
+ @logger.log(Logger::DEBUG, message)
175
+ decide_reasons.push(message)
176
+ return nil, decide_reasons
160
177
  end
161
178
 
162
179
  # Evaluate each experiment and return the first bucketed experiment variation
163
180
  feature_flag['experimentIds'].each do |experiment_id|
164
181
  experiment = project_config.experiment_id_map[experiment_id]
165
182
  unless experiment
166
- @logger.log(
167
- Logger::DEBUG,
168
- "Feature flag experiment with ID '#{experiment_id}' is not in the datafile."
169
- )
170
- return nil
183
+ message = "Feature flag experiment with ID '#{experiment_id}' is not in the datafile."
184
+ @logger.log(Logger::DEBUG, message)
185
+ decide_reasons.push(message)
186
+ return nil, decide_reasons
171
187
  end
172
188
 
173
- experiment_key = experiment['key']
174
- variation_id = get_variation(project_config, experiment_key, user_id, attributes)
189
+ experiment_id = experiment['id']
190
+ variation_id, reasons_received = get_variation(project_config, experiment_id, user_id, attributes, decide_options)
191
+ decide_reasons.push(*reasons_received)
175
192
 
176
193
  next unless variation_id
177
194
 
178
- variation = project_config.variation_id_map[experiment_key][variation_id]
195
+ variation = project_config.get_variation_from_id_by_experiment_id(experiment_id, variation_id)
179
196
 
180
- return Decision.new(experiment, variation, DECISION_SOURCES['FEATURE_TEST'])
197
+ return Decision.new(experiment, variation, DECISION_SOURCES['FEATURE_TEST']), decide_reasons
181
198
  end
182
199
 
183
- @logger.log(
184
- Logger::INFO,
185
- "The user '#{user_id}' is not bucketed into any of the experiments on the feature '#{feature_flag_key}'."
186
- )
200
+ message = "The user '#{user_id}' is not bucketed into any of the experiments on the feature '#{feature_flag_key}'."
201
+ @logger.log(Logger::INFO, message)
202
+ decide_reasons.push(message)
187
203
 
188
- nil
204
+ [nil, decide_reasons]
189
205
  end
190
206
 
191
207
  def get_variation_for_feature_rollout(project_config, feature_flag, user_id, attributes = nil)
@@ -198,27 +214,27 @@ module Optimizely
198
214
  # attributes - Hash representing user attributes
199
215
  #
200
216
  # Returns the Decision struct or nil if not bucketed into any of the targeting rules
201
- bucketing_id = get_bucketing_id(user_id, attributes)
217
+ decide_reasons = []
218
+ bucketing_id, bucketing_id_reasons = get_bucketing_id(user_id, attributes)
219
+ decide_reasons.push(*bucketing_id_reasons)
202
220
  rollout_id = feature_flag['rolloutId']
203
221
  if rollout_id.nil? || rollout_id.empty?
204
222
  feature_flag_key = feature_flag['key']
205
- @logger.log(
206
- Logger::DEBUG,
207
- "Feature flag '#{feature_flag_key}' is not used in a rollout."
208
- )
209
- return nil
223
+ message = "Feature flag '#{feature_flag_key}' is not used in a rollout."
224
+ @logger.log(Logger::DEBUG, message)
225
+ decide_reasons.push(message)
226
+ return nil, decide_reasons
210
227
  end
211
228
 
212
229
  rollout = project_config.get_rollout_from_id(rollout_id)
213
230
  if rollout.nil?
214
- @logger.log(
215
- Logger::DEBUG,
216
- "Rollout with ID '#{rollout_id}' is not in the datafile '#{feature_flag['key']}'"
217
- )
218
- return nil
231
+ message = "Rollout with ID '#{rollout_id}' is not in the datafile '#{feature_flag['key']}'"
232
+ @logger.log(Logger::DEBUG, message)
233
+ decide_reasons.push(message)
234
+ return nil, decide_reasons
219
235
  end
220
236
 
221
- return nil if rollout['experiments'].empty?
237
+ return nil, decide_reasons if rollout['experiments'].empty?
222
238
 
223
239
  rollout_rules = rollout['experiments']
224
240
  number_of_rules = rollout_rules.length - 1
@@ -228,24 +244,25 @@ module Optimizely
228
244
  rollout_rule = rollout_rules[index]
229
245
  logging_key = index + 1
230
246
 
247
+ user_meets_audience_conditions, reasons_received = Audience.user_meets_audience_conditions?(project_config, rollout_rule, attributes, @logger, 'ROLLOUT_AUDIENCE_EVALUATION_LOGS', logging_key)
248
+ decide_reasons.push(*reasons_received)
231
249
  # Check that user meets audience conditions for targeting rule
232
- unless Audience.user_meets_audience_conditions?(project_config, rollout_rule, attributes, @logger, 'ROLLOUT_AUDIENCE_EVALUATION_LOGS', logging_key)
233
- @logger.log(
234
- Logger::DEBUG,
235
- "User '#{user_id}' does not meet the audience conditions for targeting rule '#{logging_key}'."
236
- )
250
+ unless user_meets_audience_conditions
251
+ message = "User '#{user_id}' does not meet the audience conditions for targeting rule '#{logging_key}'."
252
+ @logger.log(Logger::DEBUG, message)
253
+ decide_reasons.push(message)
237
254
  # move onto the next targeting rule
238
255
  next
239
256
  end
240
257
 
241
- @logger.log(
242
- Logger::DEBUG,
243
- "User '#{user_id}' meets the audience conditions for targeting rule '#{logging_key}'."
244
- )
258
+ message = "User '#{user_id}' meets the audience conditions for targeting rule '#{logging_key}'."
259
+ @logger.log(Logger::DEBUG, message)
260
+ decide_reasons.push(message)
245
261
 
246
262
  # Evaluate if user satisfies the traffic allocation for this rollout rule
247
- variation = @bucketer.bucket(project_config, rollout_rule, bucketing_id, user_id)
248
- return Decision.new(rollout_rule, variation, DECISION_SOURCES['ROLLOUT']) unless variation.nil?
263
+ variation, bucket_reasons = @bucketer.bucket(project_config, rollout_rule, bucketing_id, user_id)
264
+ decide_reasons.push(*bucket_reasons)
265
+ return Decision.new(rollout_rule, variation, DECISION_SOURCES['ROLLOUT']), decide_reasons unless variation.nil?
249
266
 
250
267
  break
251
268
  end
@@ -253,23 +270,26 @@ module Optimizely
253
270
  # get last rule which is the everyone else rule
254
271
  everyone_else_experiment = rollout_rules[number_of_rules]
255
272
  logging_key = 'Everyone Else'
273
+
274
+ user_meets_audience_conditions, reasons_received = Audience.user_meets_audience_conditions?(project_config, everyone_else_experiment, attributes, @logger, 'ROLLOUT_AUDIENCE_EVALUATION_LOGS', logging_key)
275
+ decide_reasons.push(*reasons_received)
256
276
  # Check that user meets audience conditions for last rule
257
- unless Audience.user_meets_audience_conditions?(project_config, everyone_else_experiment, attributes, @logger, 'ROLLOUT_AUDIENCE_EVALUATION_LOGS', logging_key)
258
- @logger.log(
259
- Logger::DEBUG,
260
- "User '#{user_id}' does not meet the audience conditions for targeting rule '#{logging_key}'."
261
- )
262
- return nil
277
+ unless user_meets_audience_conditions
278
+ message = "User '#{user_id}' does not meet the audience conditions for targeting rule '#{logging_key}'."
279
+ @logger.log(Logger::DEBUG, message)
280
+ decide_reasons.push(message)
281
+ return nil, decide_reasons
263
282
  end
264
283
 
265
- @logger.log(
266
- Logger::DEBUG,
267
- "User '#{user_id}' meets the audience conditions for targeting rule '#{logging_key}'."
268
- )
269
- variation = @bucketer.bucket(project_config, everyone_else_experiment, bucketing_id, user_id)
270
- return Decision.new(everyone_else_experiment, variation, DECISION_SOURCES['ROLLOUT']) unless variation.nil?
284
+ message = "User '#{user_id}' meets the audience conditions for targeting rule '#{logging_key}'."
285
+ @logger.log(Logger::DEBUG, message)
286
+ decide_reasons.push(message)
271
287
 
272
- nil
288
+ variation, bucket_reasons = @bucketer.bucket(project_config, everyone_else_experiment, bucketing_id, user_id)
289
+ decide_reasons.push(*bucket_reasons)
290
+ return Decision.new(everyone_else_experiment, variation, DECISION_SOURCES['ROLLOUT']), decide_reasons unless variation.nil?
291
+
292
+ [nil, decide_reasons]
273
293
  end
274
294
 
275
295
  def set_forced_variation(project_config, experiment_key, user_id, variation_key)
@@ -295,7 +315,7 @@ module Optimizely
295
315
  return true
296
316
  end
297
317
 
298
- variation_id = project_config.get_variation_id_from_key(experiment_key, variation_key)
318
+ variation_id = project_config.get_variation_id_from_key_by_experiment_id(experiment_id, variation_key)
299
319
 
300
320
  # check if the variation exists in the datafile
301
321
  unless variation_id
@@ -314,14 +334,16 @@ module Optimizely
314
334
  # Gets the forced variation for the given user and experiment.
315
335
  #
316
336
  # project_config - Instance of ProjectConfig
317
- # experiment_key - String Key for experiment
337
+ # experiment_key - String key for experiment
318
338
  # user_id - String ID for user
319
339
  #
320
340
  # Returns Variation The variation which the given user and experiment should be forced into
321
341
 
342
+ decide_reasons = []
322
343
  unless @forced_variation_map.key? user_id
323
- @logger.log(Logger::DEBUG, "User '#{user_id}' is not in the forced variation map.")
324
- return nil
344
+ message = "User '#{user_id}' is not in the forced variation map."
345
+ @logger.log(Logger::DEBUG, message)
346
+ return nil, decide_reasons
325
347
  end
326
348
 
327
349
  experiment_to_variation_map = @forced_variation_map[user_id]
@@ -329,32 +351,34 @@ module Optimizely
329
351
  experiment_id = experiment['id'] if experiment
330
352
  # check for nil and empty string experiment ID
331
353
  # this case is logged in get_experiment_from_key
332
- return nil if experiment_id.nil? || experiment_id.empty?
354
+ return nil, decide_reasons if experiment_id.nil? || experiment_id.empty?
333
355
 
334
356
  unless experiment_to_variation_map.key? experiment_id
335
- @logger.log(Logger::DEBUG, "No experiment '#{experiment_key}' mapped to user '#{user_id}' "\
336
- 'in the forced variation map.')
337
- return nil
357
+ message = "No experiment '#{experiment_id}' mapped to user '#{user_id}' in the forced variation map."
358
+ @logger.log(Logger::DEBUG, message)
359
+ decide_reasons.push(message)
360
+ return nil, decide_reasons
338
361
  end
339
362
 
340
363
  variation_id = experiment_to_variation_map[experiment_id]
341
364
  variation_key = ''
342
- variation = project_config.get_variation_from_id(experiment_key, variation_id)
365
+ variation = project_config.get_variation_from_id_by_experiment_id(experiment_id, variation_id)
343
366
  variation_key = variation['key'] if variation
344
367
 
345
368
  # check if the variation exists in the datafile
346
369
  # this case is logged in get_variation_from_id
347
- return nil if variation_key.empty?
370
+ return nil, decide_reasons if variation_key.empty?
348
371
 
349
- @logger.log(Logger::DEBUG, "Variation '#{variation_key}' is mapped to experiment '#{experiment_key}' "\
350
- "and user '#{user_id}' in the forced variation map")
372
+ message = "Variation '#{variation_key}' is mapped to experiment '#{experiment_id}' and user '#{user_id}' in the forced variation map"
373
+ @logger.log(Logger::DEBUG, message)
374
+ decide_reasons.push(message)
351
375
 
352
- variation
376
+ [variation, decide_reasons]
353
377
  end
354
378
 
355
379
  private
356
380
 
357
- def get_whitelisted_variation_id(project_config, experiment_key, user_id)
381
+ def get_whitelisted_variation_id(project_config, experiment_id, user_id)
358
382
  # Determine if a user is whitelisted into a variation for the given experiment and return the ID of that variation
359
383
  #
360
384
  # project_config - project_config - Instance of ProjectConfig
@@ -363,29 +387,26 @@ module Optimizely
363
387
  #
364
388
  # Returns variation ID into which user_id is whitelisted (nil if no variation)
365
389
 
366
- whitelisted_variations = project_config.get_whitelisted_variations(experiment_key)
390
+ whitelisted_variations = project_config.get_whitelisted_variations(experiment_id)
367
391
 
368
- return nil unless whitelisted_variations
392
+ return nil, nil unless whitelisted_variations
369
393
 
370
394
  whitelisted_variation_key = whitelisted_variations[user_id]
371
395
 
372
- return nil unless whitelisted_variation_key
396
+ return nil, nil unless whitelisted_variation_key
373
397
 
374
- whitelisted_variation_id = project_config.get_variation_id_from_key(experiment_key, whitelisted_variation_key)
398
+ whitelisted_variation_id = project_config.get_variation_id_from_key_by_experiment_id(experiment_id, whitelisted_variation_key)
375
399
 
376
400
  unless whitelisted_variation_id
377
- @logger.log(
378
- Logger::INFO,
379
- "User '#{user_id}' is whitelisted into variation '#{whitelisted_variation_key}', which is not in the datafile."
380
- )
381
- return nil
401
+ message = "User '#{user_id}' is whitelisted into variation '#{whitelisted_variation_key}', which is not in the datafile."
402
+ @logger.log(Logger::INFO, message)
403
+ return nil, message
382
404
  end
383
405
 
384
- @logger.log(
385
- Logger::INFO,
386
- "User '#{user_id}' is whitelisted into variation '#{whitelisted_variation_key}' of experiment '#{experiment_key}'."
387
- )
388
- whitelisted_variation_id
406
+ message = "User '#{user_id}' is whitelisted into variation '#{whitelisted_variation_key}' of experiment '#{experiment_id}'."
407
+ @logger.log(Logger::INFO, message)
408
+
409
+ [whitelisted_variation_id, message]
389
410
  end
390
411
 
391
412
  def get_saved_variation_id(project_config, experiment_id, user_profile)
@@ -396,19 +417,18 @@ module Optimizely
396
417
  # user_profile - Hash user profile
397
418
  #
398
419
  # Returns string variation ID (nil if no decision is found)
399
- return nil unless user_profile[:experiment_bucket_map]
420
+ return nil, nil unless user_profile[:experiment_bucket_map]
400
421
 
401
422
  decision = user_profile[:experiment_bucket_map][experiment_id]
402
- return nil unless decision
423
+ return nil, nil unless decision
403
424
 
404
425
  variation_id = decision[:variation_id]
405
- return variation_id if project_config.variation_id_exists?(experiment_id, variation_id)
426
+ return variation_id, nil if project_config.variation_id_exists?(experiment_id, variation_id)
427
+
428
+ message = "User '#{user_profile[:user_id]}' was previously bucketed into variation ID '#{variation_id}' for experiment '#{experiment_id}', but no matching variation was found. Re-bucketing user."
429
+ @logger.log(Logger::INFO, message)
406
430
 
407
- @logger.log(
408
- Logger::INFO,
409
- "User '#{user_profile['user_id']}' was previously bucketed into variation ID '#{variation_id}' for experiment '#{experiment_id}', but no matching variation was found. Re-bucketing user."
410
- )
411
- nil
431
+ [nil, message]
412
432
  end
413
433
 
414
434
  def get_user_profile(user_id)
@@ -423,15 +443,17 @@ module Optimizely
423
443
  experiment_bucket_map: {}
424
444
  }
425
445
 
426
- return user_profile unless @user_profile_service
446
+ return user_profile, nil unless @user_profile_service
427
447
 
448
+ message = nil
428
449
  begin
429
450
  user_profile = @user_profile_service.lookup(user_id) || user_profile
430
451
  rescue => e
431
- @logger.log(Logger::ERROR, "Error while looking up user profile for user ID '#{user_id}': #{e}.")
452
+ message = "Error while looking up user profile for user ID '#{user_id}': #{e}."
453
+ @logger.log(Logger::ERROR, message)
432
454
  end
433
455
 
434
- user_profile
456
+ [user_profile, message]
435
457
  end
436
458
 
437
459
  def save_user_profile(user_profile, experiment_id, variation_id)
@@ -462,16 +484,17 @@ module Optimizely
462
484
  # attributes - Hash user attributes
463
485
  # Returns String representing bucketing ID if it is a String type in attributes else return user ID
464
486
 
465
- return user_id unless attributes
487
+ return user_id, nil unless attributes
466
488
 
467
489
  bucketing_id = attributes[Optimizely::Helpers::Constants::CONTROL_ATTRIBUTES['BUCKETING_ID']]
468
490
 
469
491
  if bucketing_id
470
- return bucketing_id if bucketing_id.is_a?(String)
492
+ return bucketing_id, nil if bucketing_id.is_a?(String)
471
493
 
472
- @logger.log(Logger::WARN, 'Bucketing ID attribute is not a string. Defaulted to user ID.')
494
+ message = 'Bucketing ID attribute is not a string. Defaulted to user ID.'
495
+ @logger.log(Logger::WARN, message)
473
496
  end
474
- user_id
497
+ [user_id, message]
475
498
  end
476
499
  end
477
500
  end