optimizely-sdk 1.5.0 → 2.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3fae777d6ce539eb2e0809dbf38d4784130c8ade
4
- data.tar.gz: f65bee6796830490f4ee8f1a86da20ed7b4f01fb
3
+ metadata.gz: b6da10a1ad97624a7523adcdc6841b2999ab35d3
4
+ data.tar.gz: 3865376c1962511db8d8350041fbc830b29b16c6
5
5
  SHA512:
6
- metadata.gz: b8e6f2732d692b1844d3f0c990010aba9858c93b82244803962810d33c78b945f925936874097272705a1a7e724f9d3abad1c4c66ba358573ba32c8bd10d8515
7
- data.tar.gz: a8804feff56093f399f87425869a733e9b91b74f502b075a1d790364f47da5e524c36ecde23a626d2b8c977c344e5bd2fb5c93e742ceb5a2cfcf4a50710b9444
6
+ metadata.gz: bf35c547babd0be1d576bf8926e23fa9e9292b47bf6a74df9a8cda19b00bd2a3da974c7a840dba7d6ac3aa3132b192caa692b727bc5bdfdf3a3a85c933ef58da
7
+ data.tar.gz: b587d153eb46d6b779ebc298dfde188824f7c5f3fe1cc9c2b4e890878a9c190e9d9051baccbc8d18329670d81c5e5437e53f2d69d4c790a785b8fcb8fa2ac2e3
data/lib/optimizely.rb CHANGED
@@ -24,7 +24,6 @@ require_relative 'optimizely/helpers/group'
24
24
  require_relative 'optimizely/helpers/validator'
25
25
  require_relative 'optimizely/helpers/variable_type'
26
26
  require_relative 'optimizely/logger'
27
- require_relative 'optimizely/notification_center'
28
27
  require_relative 'optimizely/project_config'
29
28
 
30
29
  module Optimizely
@@ -39,7 +38,6 @@ module Optimizely
39
38
  attr_reader :event_builder
40
39
  attr_reader :event_dispatcher
41
40
  attr_reader :logger
42
- attr_reader :notification_center
43
41
 
44
42
  def initialize(datafile, event_dispatcher = nil, logger = nil, error_handler = nil, skip_json_validation = false, user_profile_service = nil)
45
43
  # Constructor for Projects.
@@ -85,7 +83,6 @@ module Optimizely
85
83
 
86
84
  @decision_service = DecisionService.new(@config, @user_profile_service)
87
85
  @event_builder = EventBuilder.new(@config)
88
- @notification_center = NotificationCenter.new(@logger, @error_handler)
89
86
  end
90
87
 
91
88
  def activate(experiment_key, user_id, attributes = nil)
@@ -134,11 +131,6 @@ module Optimizely
134
131
  return nil
135
132
  end
136
133
 
137
- unless user_id.is_a? String
138
- @logger.log(Logger::ERROR, "User id: #{user_id} is not a string")
139
- return nil
140
- end
141
-
142
134
  unless user_inputs_valid?(attributes)
143
135
  @logger.log(Logger::INFO, "Not activating user '#{user_id}.")
144
136
  return nil
@@ -155,36 +147,6 @@ module Optimizely
155
147
  nil
156
148
  end
157
149
 
158
- def set_forced_variation(experiment_key, user_id, variation_key)
159
- # Force a user into a variation for a given experiment.
160
- #
161
- # experiment_key - String - key identifying the experiment.
162
- # user_id - String - The user ID to be used for bucketing.
163
- # variation_key - The variation key specifies the variation which the user will
164
- # be forced into. If nil, then clear the existing experiment-to-variation mapping.
165
- #
166
- # Returns - Boolean - indicates if the set completed successfully.
167
-
168
- @config.set_forced_variation(experiment_key, user_id, variation_key);
169
- end
170
-
171
- def get_forced_variation(experiment_key, user_id)
172
- # Gets the forced variation for a given user and experiment.
173
- #
174
- # experiment_key - String - Key identifying the experiment.
175
- # user_id - String - The user ID to be used for bucketing.
176
- #
177
- # Returns String|nil The forced variation key.
178
-
179
- forced_variation_key = nil
180
- forced_variation = @config.get_forced_variation(experiment_key, user_id);
181
- if forced_variation
182
- forced_variation_key = forced_variation['key']
183
- end
184
-
185
- forced_variation_key
186
- end
187
-
188
150
  def track(event_key, user_id, attributes = nil, event_tags = nil)
189
151
  # Send conversion event to Optimizely.
190
152
  #
@@ -234,10 +196,6 @@ module Optimizely
234
196
  rescue => e
235
197
  @logger.log(Logger::ERROR, "Unable to dispatch conversion event. Error: #{e}")
236
198
  end
237
- @notification_center.send_notifications(
238
- NotificationCenter::NOTIFICATION_TYPES[:TRACK],
239
- event_key, user_id, attributes, event_tags, conversion_event
240
- )
241
199
  end
242
200
 
243
201
  def is_feature_enabled(feature_flag_key, user_id, attributes = nil)
@@ -519,11 +477,6 @@ module Optimizely
519
477
  rescue => e
520
478
  @logger.log(Logger::ERROR, "Unable to dispatch impression event. Error: #{e}")
521
479
  end
522
- variation = @config.get_variation_from_id(experiment_key, variation_id)
523
- @notification_center.send_notifications(
524
- NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE],
525
- experiment,user_id, attributes, variation, impression_event
526
- )
527
480
  end
528
481
  end
529
482
  end
@@ -20,7 +20,7 @@ module Optimizely
20
20
  class Bucketer
21
21
  # Optimizely bucketing algorithm that evenly distributes visitors.
22
22
 
23
- BUCKETING_ID_TEMPLATE = '%{bucketing_id}%{entity_id}'
23
+ BUCKETING_ID_TEMPLATE = '%{user_id}%{entity_id}'
24
24
  HASH_SEED = 1
25
25
  MAX_HASH_VALUE = 2**32
26
26
  MAX_TRAFFIC_VALUE = 10_000
@@ -35,15 +35,13 @@ module Optimizely
35
35
  @config = config
36
36
  end
37
37
 
38
- def bucket(experiment, bucketing_id, user_id)
38
+ def bucket(experiment, user_id)
39
39
  # Determines ID of variation to be shown for a given experiment key and user ID.
40
40
  #
41
41
  # experiment - Experiment for which visitor is to be bucketed.
42
- # bucketing_id - String A customer-assigned value used to generate the bucketing key
43
42
  # user_id - String ID for user.
44
43
  #
45
44
  # Returns variation in which visitor with ID user_id has been placed. Nil if no variation.
46
- return nil if experiment.nil?
47
45
 
48
46
  # check if experiment is in a group; if so, check if user is bucketed into specified experiment
49
47
  experiment_id = experiment['id']
@@ -53,7 +51,7 @@ module Optimizely
53
51
  group = @config.group_key_map.fetch(group_id)
54
52
  if Helpers::Group.random_policy?(group)
55
53
  traffic_allocations = group.fetch('trafficAllocation')
56
- bucketed_experiment_id = find_bucket(bucketing_id, user_id, group_id, traffic_allocations)
54
+ bucketed_experiment_id = find_bucket(user_id, group_id, traffic_allocations)
57
55
  # return if the user is not bucketed into any experiment
58
56
  unless bucketed_experiment_id
59
57
  @config.logger.log(Logger::INFO, "User '#{user_id}' is in no experiment.")
@@ -78,7 +76,7 @@ module Optimizely
78
76
  end
79
77
 
80
78
  traffic_allocations = experiment['trafficAllocation']
81
- variation_id = find_bucket(bucketing_id, user_id, experiment_id, traffic_allocations)
79
+ variation_id = find_bucket(user_id, experiment_id, traffic_allocations)
82
80
  if variation_id && variation_id != ''
83
81
  variation = @config.get_variation_from_id(experiment_key, variation_id)
84
82
  variation_key = variation ? variation['key'] : nil
@@ -98,18 +96,18 @@ module Optimizely
98
96
  nil
99
97
  end
100
98
 
101
- def find_bucket(bucketing_id, user_id, parent_id, traffic_allocations)
99
+ def find_bucket(user_id, parent_id, traffic_allocations)
102
100
  # Helper function to find the matching entity ID for a given bucketing value in a list of traffic allocations.
103
101
  #
104
- # bucketing_id - String A customer-assigned value user to generate bucketing key
105
102
  # user_id - String ID for user
106
103
  # parent_id - String entity ID to use for bucketing ID
107
104
  # traffic_allocations - Array of traffic allocations
108
105
  #
109
106
  # Returns entity ID corresponding to the provided bucket value or nil if no match is found.
110
- bucketing_key = sprintf(BUCKETING_ID_TEMPLATE, bucketing_id: bucketing_id, entity_id: parent_id)
111
- bucket_value = generate_bucket_value(bucketing_key)
112
- @config.logger.log(Logger::DEBUG, "Assigned bucket #{bucket_value} to user '#{user_id}' with bucketing ID: '#{bucketing_id}'.")
107
+
108
+ bucketing_id = sprintf(BUCKETING_ID_TEMPLATE, user_id: user_id, entity_id: parent_id)
109
+ bucket_value = generate_bucket_value(bucketing_id)
110
+ @config.logger.log(Logger::DEBUG, "Assigned bucket #{bucket_value} to user '#{user_id}'.")
113
111
 
114
112
  traffic_allocations.each do |traffic_allocation|
115
113
  current_end_of_range = traffic_allocation['endOfRange']
@@ -124,25 +122,25 @@ module Optimizely
124
122
 
125
123
  private
126
124
 
127
- def generate_bucket_value(bucketing_key)
125
+ def generate_bucket_value(bucketing_id)
128
126
  # Helper function to generate bucket value in half-closed interval [0, MAX_TRAFFIC_VALUE).
129
127
  #
130
- # bucketing_key - String - Value used to generate bucket value
128
+ # bucketing_id - String ID for bucketing.
131
129
  #
132
- # Returns bucket value corresponding to the provided bucketing key.
130
+ # Returns bucket value corresponding to the provided bucketing ID.
133
131
 
134
- ratio = (generate_unsigned_hash_code_32_bit(bucketing_key)).to_f / MAX_HASH_VALUE
132
+ ratio = (generate_unsigned_hash_code_32_bit(bucketing_id)).to_f / MAX_HASH_VALUE
135
133
  (ratio * MAX_TRAFFIC_VALUE).to_i
136
134
  end
137
135
 
138
- def generate_unsigned_hash_code_32_bit(bucketing_key)
136
+ def generate_unsigned_hash_code_32_bit(bucketing_id)
139
137
  # Helper function to retreive hash code
140
138
  #
141
- # bucketing_key - String - Value used for the key of the murmur hash
139
+ # bucketing_id - String ID for bucketing.
142
140
  #
143
141
  # Returns hash code which is a 32 bit unsigned integer.
144
142
 
145
- MurmurHash3::V32.str_hash(bucketing_key, @bucket_seed) & UNSIGNED_MAX_32_BIT_VALUE
143
+ MurmurHash3::V32.str_hash(bucketing_id, @bucket_seed) & UNSIGNED_MAX_32_BIT_VALUE
146
144
  end
147
145
  end
148
146
  end
@@ -16,31 +16,27 @@
16
16
  require_relative './bucketer'
17
17
 
18
18
  module Optimizely
19
-
20
- RESERVED_ATTRIBUTE_KEY_BUCKETING_ID = "\$opt_bucketing_id".freeze
21
-
22
19
  class DecisionService
23
20
  # Optimizely's decision service that determines into which variation of an experiment a user will be allocated.
24
21
  #
25
22
  # The decision service contains all logic relating to how a user bucketing decisions is made.
26
23
  # This includes all of the following (in order):
27
24
  #
28
- # 1. Check experiment status
29
- # 2. Check forced bucketing
30
- # 3. Check whitelisting
31
- # 4. Check user profile service for past bucketing decisions (sticky bucketing)
32
- # 5. Check audience targeting
33
- # 6. Use Murmurhash3 to bucket the user
34
-
25
+ # 1. Checking experiment status
26
+ # 2. Checking whitelisting
27
+ # 3. Checking user profile service for past bucketing decisions (sticky bucketing)
28
+ # 3. Checking audience targeting
29
+ # 4. Using Murmurhash3 to bucket the user
30
+
35
31
  attr_reader :bucketer
36
32
  attr_reader :config
37
-
33
+
38
34
  def initialize(config, user_profile_service = nil)
39
35
  @config = config
40
36
  @user_profile_service = user_profile_service
41
37
  @bucketer = Bucketer.new(@config)
42
38
  end
43
-
39
+
44
40
  def get_variation(experiment_key, user_id, attributes = nil)
45
41
  # Determines variation into which user will be bucketed.
46
42
  #
@@ -49,65 +45,52 @@ module Optimizely
49
45
  # attributes - Hash representing user attributes
50
46
  #
51
47
  # Returns variation ID where visitor will be bucketed (nil if experiment is inactive or user does not meet audience conditions)
52
-
53
- # By default, the bucketing ID should be the user ID
54
- bucketing_id = user_id;
55
-
56
- # If the bucketing ID key is defined in attributes, then use that in place of the userID
57
- if attributes and attributes[RESERVED_ATTRIBUTE_KEY_BUCKETING_ID].is_a? String
58
- unless attributes[RESERVED_ATTRIBUTE_KEY_BUCKETING_ID].empty?
59
- bucketing_id = attributes[RESERVED_ATTRIBUTE_KEY_BUCKETING_ID]
60
- @config.logger.log(Logger::DEBUG, "Setting the bucketing ID '#{bucketing_id}'")
61
- end
62
- end
63
-
48
+
64
49
  # Check to make sure experiment is active
65
50
  experiment = @config.get_experiment_from_key(experiment_key)
66
- return nil if experiment.nil?
67
-
51
+ if experiment.nil?
52
+ return nil
53
+ end
54
+
68
55
  experiment_id = experiment['id']
69
56
  unless @config.experiment_running?(experiment)
70
57
  @config.logger.log(Logger::INFO, "Experiment '#{experiment_key}' is not running.")
71
58
  return nil
72
59
  end
73
-
74
- # Check if a forced variation is set for the user
75
- forced_variation = @config.get_forced_variation(experiment_key, user_id)
76
- return forced_variation['id'] if forced_variation
77
-
78
- # Check if user is in a white-listed variation
79
- whitelisted_variation_id = get_whitelisted_variation_id(experiment_key, user_id)
80
- return whitelisted_variation_id if whitelisted_variation_id
81
-
60
+
61
+ # Check if user is in a forced variation
62
+ forced_variation_id = get_forced_variation_id(experiment_key, user_id)
63
+ return forced_variation_id if forced_variation_id
64
+
82
65
  # Check for saved bucketing decisions
83
66
  user_profile = get_user_profile(user_id)
84
67
  saved_variation_id = get_saved_variation_id(experiment_id, user_profile)
85
68
  if saved_variation_id
86
69
  @config.logger.log(
87
- Logger::INFO,
88
- "Returning previously activated variation ID #{saved_variation_id} of experiment '#{experiment_key}' for user '#{user_id}' from user profile."
70
+ Logger::INFO,
71
+ "Returning previously activated variation ID #{saved_variation_id} of experiment '#{experiment_key}' for user '#{user_id}' from user profile."
89
72
  )
90
73
  return saved_variation_id
91
74
  end
92
-
75
+
93
76
  # Check audience conditions
94
77
  unless Audience.user_in_experiment?(@config, experiment, attributes)
95
78
  @config.logger.log(
96
- Logger::INFO,
97
- "User '#{user_id}' does not meet the conditions to be in experiment '#{experiment_key}'."
79
+ Logger::INFO,
80
+ "User '#{user_id}' does not meet the conditions to be in experiment '#{experiment_key}'."
98
81
  )
99
82
  return nil
100
83
  end
101
-
84
+
102
85
  # Bucket normally
103
- variation = @bucketer.bucket(experiment, bucketing_id, user_id)
86
+ variation = @bucketer.bucket(experiment, user_id)
104
87
  variation_id = variation ? variation['id'] : nil
105
-
88
+
106
89
  # Persist bucketing decision
107
90
  save_user_profile(user_profile, experiment_id, variation_id)
108
91
  variation_id
109
92
  end
110
-
93
+
111
94
  def get_variation_for_feature(feature_flag, user_id, attributes = nil)
112
95
  # Get the variation the user is bucketed into for the given FeatureFlag.
113
96
  #
@@ -116,33 +99,35 @@ module Optimizely
116
99
  # attributes - Hash representing user attributes
117
100
  #
118
101
  # Returns hash with the experiment and variation where visitor will be bucketed (nil if the user is not bucketed into any of the experiments on the feature)
119
-
102
+
120
103
  # check if the feature is being experiment on and whether the user is bucketed into the experiment
121
104
  decision = get_variation_for_feature_experiment(feature_flag, user_id, attributes)
122
- return decision unless decision.nil?
123
-
105
+ unless decision.nil?
106
+ return decision
107
+ end
108
+
124
109
  feature_flag_key = feature_flag['key']
125
110
  variation = get_variation_for_feature_rollout(feature_flag, user_id, attributes)
126
111
  if variation
127
112
  @config.logger.log(
128
- Logger::INFO,
129
- "User '#{user_id}' is in the rollout for feature flag '#{feature_flag_key}'."
113
+ Logger::INFO,
114
+ "User '#{user_id}' is in the rollout for feature flag '#{feature_flag_key}'."
130
115
  )
131
116
  # return decision with nil experiment so we don't track impressions for it
132
117
  return {
133
- 'experiment' => nil,
134
- 'variation' => variation
118
+ 'experiment' => nil,
119
+ 'variation' => variation
135
120
  }
136
121
  else
137
122
  @config.logger.log(
138
- Logger::INFO,
139
- "User '#{user_id}' is not in the rollout for feature flag '#{feature_flag_key}'."
123
+ Logger::INFO,
124
+ "User '#{user_id}' is not in the rollout for feature flag '#{feature_flag_key}'."
140
125
  )
141
126
  end
142
-
127
+
143
128
  return nil
144
129
  end
145
-
130
+
146
131
  def get_variation_for_feature_experiment(feature_flag, user_id, attributes = nil)
147
132
  # Gets the variation the user is bucketed into for the feature flag's experiment.
148
133
  #
@@ -152,7 +137,7 @@ module Optimizely
152
137
  #
153
138
  # Returns a hash with the experiment and variation where visitor will be bucketed
154
139
  # or nil if the user is not bucketed into any of the experiments on the feature
155
-
140
+
156
141
  feature_flag_key = feature_flag['key']
157
142
  unless feature_flag['experimentIds'].empty?
158
143
  # check if experiment is part of mutex group
@@ -160,12 +145,12 @@ module Optimizely
160
145
  experiment = @config.experiment_id_map[experiment_id]
161
146
  unless experiment
162
147
  @config.logger.log(
163
- Logger::DEBUG,
164
- "Feature flag experiment with ID '#{experiment_id}' is not in the datafile."
148
+ Logger::DEBUG,
149
+ "Feature flag experiment with ID '#{experiment_id}' is not in the datafile."
165
150
  )
166
151
  return nil
167
152
  end
168
-
153
+
169
154
  group_id = experiment['groupId']
170
155
  # if experiment is part of mutex group we first determine which experiment (if any) in the group the user is part of
171
156
  if group_id and @config.group_key_map.has_key?(group_id)
@@ -173,15 +158,15 @@ module Optimizely
173
158
  bucketed_experiment_id = @bucketer.find_bucket(user_id, group_id, group['trafficAllocation'])
174
159
  if bucketed_experiment_id.nil?
175
160
  @config.logger.log(
176
- Logger::INFO,
177
- "The user '#{user_id}' is not bucketed into any of the experiments on the feature '#{feature_flag_key}'."
161
+ Logger::INFO,
162
+ "The user '#{user_id}' is not bucketed into any of the experiments on the feature '#{feature_flag_key}'."
178
163
  )
179
164
  return nil
180
165
  end
181
166
  else
182
167
  bucketed_experiment_id = experiment_id
183
168
  end
184
-
169
+
185
170
  if feature_flag['experimentIds'].include?(bucketed_experiment_id)
186
171
  experiment = @config.experiment_id_map[bucketed_experiment_id]
187
172
  experiment_key = experiment['key']
@@ -189,30 +174,30 @@ module Optimizely
189
174
  unless variation_id.nil?
190
175
  variation = @config.variation_id_map[experiment_key][variation_id]
191
176
  @config.logger.log(
192
- Logger::INFO,
193
- "The user '#{user_id}' is bucketed into experiment '#{experiment_key}' of feature '#{feature_flag_key}'."
177
+ Logger::INFO,
178
+ "The user '#{user_id}' is bucketed into experiment '#{experiment_key}' of feature '#{feature_flag_key}'."
194
179
  )
195
180
  return {
196
- 'variation' => variation,
197
- 'experiment' => experiment
181
+ 'variation' => variation,
182
+ 'experiment' => experiment
198
183
  }
199
184
  else
200
185
  @config.logger.log(
201
- Logger::INFO,
202
- "The user '#{user_id}' is not bucketed into any of the experiments on the feature '#{feature_flag_key}'."
186
+ Logger::INFO,
187
+ "The user '#{user_id}' is not bucketed into any of the experiments on the feature '#{feature_flag_key}'."
203
188
  )
204
189
  end
205
190
  end
206
191
  else
207
192
  @config.logger.log(
208
- Logger::DEBUG,
209
- "The feature flag '#{feature_flag_key}' is not used in any experiments."
193
+ Logger::DEBUG,
194
+ "The feature flag '#{feature_flag_key}' is not used in any experiments."
210
195
  )
211
196
  end
212
-
197
+
213
198
  return nil
214
199
  end
215
-
200
+
216
201
  def get_variation_for_feature_rollout(feature_flag, user_id, attributes = nil)
217
202
  # Determine which variation the user is in for a given rollout.
218
203
  # Returns the variation of the first experiment the user qualifies for.
@@ -222,110 +207,110 @@ module Optimizely
222
207
  # attributes - Hash representing user attributes
223
208
  #
224
209
  # Returns the variation the user is bucketed into or nil if not bucketed into any of the targeting rules
225
-
210
+
226
211
  rollout_id = feature_flag['rolloutId']
212
+ feature_flag_key = feature_flag['key']
227
213
  if rollout_id.nil? or rollout_id.empty?
228
- feature_flag_key = feature_flag['key']
229
214
  @config.logger.log(
230
- Logger::DEBUG,
231
- "Feature flag '#{feature_flag_key}' is not part of a rollout."
215
+ Logger::DEBUG,
216
+ "Feature flag '#{feature_flag_key}' is not part of a rollout."
232
217
  )
233
218
  return nil
234
219
  end
235
-
220
+
236
221
  rollout = @config.get_rollout_from_id(rollout_id)
237
222
  unless rollout.nil? or rollout['experiments'].empty?
238
223
  rollout_experiments = rollout['experiments']
239
224
  number_of_rules = rollout_experiments.length - 1
240
-
225
+
241
226
  # Go through each experiment in order and try to get the variation for the user
242
227
  for index in (0...number_of_rules)
243
228
  experiment = rollout_experiments[index]
244
229
  experiment_key = experiment['key']
245
-
230
+
246
231
  # Check that user meets audience conditions for targeting rule
247
232
  unless Audience.user_in_experiment?(@config, experiment, attributes)
248
233
  @config.logger.log(
249
- Logger::DEBUG,
250
- "User '#{user_id}' does not meet the conditions to be in experiment '#{experiment_key}'."
234
+ Logger::DEBUG,
235
+ "User '#{user_id}' does not meet the conditions to be in experiment '#{experiment_key}' of rollout with feature flag '#{feature_flag_key}'."
251
236
  )
252
237
  # move onto the next targeting rule
253
238
  next
254
239
  end
255
-
240
+
256
241
  @config.logger.log(
257
- Logger::DEBUG,
258
- "User '#{user_id}' meets conditions for targeting rule '#{index + 1}'."
242
+ Logger::DEBUG,
243
+ "User '#{user_id}' meets conditions for targeting rule '#{index + 1}'."
259
244
  )
260
245
  variation = @bucketer.bucket(experiment, user_id)
261
246
  unless variation.nil?
262
247
  variation_key = variation['key']
263
248
  return variation
264
249
  end
265
-
250
+
266
251
  # User failed traffic allocation, jump to Everyone Else rule
267
252
  @config.logger.log(
268
- Logger::DEBUG,
269
- "User '#{user_id}' is not in the traffic group for the targeting rule. Checking 'Eveyrone Else' rule now."
253
+ Logger::DEBUG,
254
+ "User '#{user_id}' is not in the traffic group for the targeting rule. Checking 'Eveyrone Else' rule now."
270
255
  )
271
256
  break
272
257
  end
273
-
258
+
274
259
  # Evalute the "Everyone Else" rule, which is the last rule.
275
260
  everyone_else_experiment = rollout_experiments[number_of_rules]
276
261
  variation = @bucketer.bucket(everyone_else_experiment, user_id)
277
262
  unless variation.nil?
278
263
  @config.logger.log(
279
- Logger::DEBUG,
280
- "User '#{user_id}' meets conditions for targeting rule 'Everyone Else'."
264
+ Logger::DEBUG,
265
+ "User '#{user_id}' meets conditions for targeting rule 'Everyone Else' of rollout with feature flag '#{feature_flag_key}'."
281
266
  )
282
267
  return variation
283
268
  end
284
-
269
+
285
270
  @config.logger.log(
286
- Logger::DEBUG,
287
- "User '#{user_id}' does not meet conditions for targeting rule 'Everyone Else'."
271
+ Logger::DEBUG,
272
+ "User '#{user_id}' does not meet conditions for targeting rule 'Everyone Else' of rollout with feature flag '#{feature_flag_key}'."
288
273
  )
289
274
  end
290
-
275
+
291
276
  return nil
292
277
  end
293
-
278
+
294
279
  private
295
-
296
- def get_whitelisted_variation_id(experiment_key, user_id)
297
- # Determine if a user is whitelisted into a variation for the given experiment and return the ID of that variation
280
+
281
+ def get_forced_variation_id(experiment_key, user_id)
282
+ # Determine if a user is forced into a variation for the given experiment and return the ID of that variation
298
283
  #
299
284
  # experiment_key - Key representing the experiment for which user is to be bucketed
300
285
  # user_id - ID for the user
301
286
  #
302
- # Returns variation ID into which user_id is whitelisted (nil if no variation)
303
-
304
- whitelisted_variations = @config.get_whitelisted_variations(experiment_key)
305
-
306
- return nil unless whitelisted_variations
307
-
308
- whitelisted_variation_key = whitelisted_variations[user_id]
309
-
310
- return nil unless whitelisted_variation_key
311
-
312
- whitelisted_variation_id = @config.get_variation_id_from_key(experiment_key, whitelisted_variation_key)
313
-
314
- unless whitelisted_variation_id
287
+ # Returns variation ID into which user_id is forced (nil if no variation)
288
+
289
+ forced_variations = @config.get_forced_variations(experiment_key)
290
+
291
+ return nil unless forced_variations
292
+
293
+ forced_variation_key = forced_variations[user_id]
294
+
295
+ return nil unless forced_variation_key
296
+
297
+ forced_variation_id = @config.get_variation_id_from_key(experiment_key, forced_variation_key)
298
+
299
+ unless forced_variation_id
315
300
  @config.logger.log(
316
- Logger::INFO,
317
- "User '#{user_id}' is whitelisted into variation '#{whitelisted_variation_key}', which is not in the datafile."
301
+ Logger::INFO,
302
+ "User '#{user_id}' is whitelisted into variation '#{forced_variation_key}', which is not in the datafile."
318
303
  )
319
304
  return nil
320
305
  end
321
-
306
+
322
307
  @config.logger.log(
323
- Logger::INFO,
324
- "User '#{user_id}' is whitelisted into variation '#{whitelisted_variation_key}' of experiment '#{experiment_key}'."
308
+ Logger::INFO,
309
+ "User '#{user_id}' is whitelisted into variation '#{forced_variation_key}' of experiment '#{experiment_key}'."
325
310
  )
326
- whitelisted_variation_id
311
+ forced_variation_id
327
312
  end
328
-
313
+
329
314
  def get_saved_variation_id(experiment_id, user_profile)
330
315
  # Retrieve variation ID of stored bucketing decision for a given experiment from a given user profile
331
316
  #
@@ -334,56 +319,56 @@ module Optimizely
334
319
  #
335
320
  # Returns string variation ID (nil if no decision is found)
336
321
  return nil unless user_profile[:experiment_bucket_map]
337
-
322
+
338
323
  decision = user_profile[:experiment_bucket_map][experiment_id]
339
324
  return nil unless decision
340
325
  variation_id = decision[:variation_id]
341
326
  return variation_id if @config.variation_id_exists?(experiment_id, variation_id)
342
-
327
+
343
328
  @config.logger.log(
344
- Logger::INFO,
345
- "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."
329
+ Logger::INFO,
330
+ "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."
346
331
  )
347
332
  nil
348
333
  end
349
-
334
+
350
335
  def get_user_profile(user_id)
351
336
  # Determine if a user is forced into a variation for the given experiment and return the ID of that variation
352
337
  #
353
338
  # user_id - String ID for the user
354
339
  #
355
340
  # Returns Hash stored user profile (or a default one if lookup fails or user profile service not provided)
356
-
341
+
357
342
  user_profile = {
358
- :user_id => user_id,
359
- :experiment_bucket_map => {}
343
+ :user_id => user_id,
344
+ :experiment_bucket_map => {}
360
345
  }
361
-
346
+
362
347
  return user_profile unless @user_profile_service
363
-
348
+
364
349
  begin
365
350
  user_profile = @user_profile_service.lookup(user_id) || user_profile
366
351
  rescue => e
367
352
  @config.logger.log(Logger::ERROR, "Error while looking up user profile for user ID '#{user_id}': #{e}.")
368
353
  end
369
-
354
+
370
355
  user_profile
371
356
  end
372
-
373
-
357
+
358
+
374
359
  def save_user_profile(user_profile, experiment_id, variation_id)
375
360
  # Save a given bucketing decision to a given user profile
376
361
  #
377
362
  # user_profile - Hash user profile
378
363
  # experiment_id - String experiment ID
379
364
  # variation_id - String variation ID
380
-
365
+
381
366
  return unless @user_profile_service
382
-
367
+
383
368
  user_id = user_profile[:user_id]
384
369
  begin
385
370
  user_profile[:experiment_bucket_map][experiment_id] = {
386
- :variation_id => variation_id
371
+ :variation_id => variation_id
387
372
  }
388
373
  @user_profile_service.save(user_profile)
389
374
  @config.logger.log(Logger::INFO, "Saved variation ID #{variation_id} of experiment ID #{experiment_id} for user '#{user_id}'.")
@@ -392,4 +377,4 @@ module Optimizely
392
377
  end
393
378
  end
394
379
  end
395
- end
380
+ end