optimizely-sdk 3.6.0 → 3.9.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.
@@ -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