@@ -51,16 +51,7 @@ def get_variation(experiment_key, user_id, attributes = nil)
51
51
# Returns variation ID where visitor will be bucketed (nil if experiment is inactive or user does not meet audience conditions)
52
52
53
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
-
54
+ bucketing_id = get_bucketing_id ( user_id , attributes )
64
55
# Check to make sure experiment is active
65
56
experiment = @config . get_experiment_from_key ( experiment_key )
66
57
if experiment . nil?
@@ -121,16 +112,14 @@ def get_variation_for_feature(feature_flag, user_id, attributes = nil)
121
112
122
113
# check if the feature is being experiment on and whether the user is bucketed into the experiment
123
114
decision = get_variation_for_feature_experiment ( feature_flag , user_id , attributes )
124
- unless decision . nil?
125
- return decision
126
- end
115
+ return decision unless decision . nil?
127
116
128
117
feature_flag_key = feature_flag [ 'key' ]
129
118
variation = get_variation_for_feature_rollout ( feature_flag , user_id , attributes )
130
119
if variation
131
120
@config . logger . log (
132
121
Logger ::INFO ,
133
- "User '#{ user_id } ' is in the rollout for feature flag '#{ feature_flag_key } '."
122
+ "User '#{ user_id } ' is bucketed into a rollout for feature flag '#{ feature_flag_key } '."
134
123
)
135
124
# return decision with nil experiment so we don't track impressions for it
136
125
return {
@@ -140,11 +129,11 @@ def get_variation_for_feature(feature_flag, user_id, attributes = nil)
140
129
else
141
130
@config . logger . log (
142
131
Logger ::INFO ,
143
- "User '#{ user_id } ' is not in the rollout for feature flag '#{ feature_flag_key } '."
132
+ "User '#{ user_id } ' is not bucketed into a rollout for feature flag '#{ feature_flag_key } '."
144
133
)
145
134
end
146
135
147
- return nil
136
+ nil
148
137
end
149
138
150
139
def get_variation_for_feature_experiment ( feature_flag , user_id , attributes = nil )
@@ -156,65 +145,43 @@ def get_variation_for_feature_experiment(feature_flag, user_id, attributes = nil
156
145
#
157
146
# Returns a hash with the experiment and variation where visitor will be bucketed
158
147
# or nil if the user is not bucketed into any of the experiments on the feature
159
-
160
148
feature_flag_key = feature_flag [ 'key' ]
161
- unless feature_flag [ 'experimentIds' ] . empty?
162
- # check if experiment is part of mutex group
163
- experiment_id = feature_flag [ 'experimentIds' ] [ 0 ]
164
- experiment = @config . experiment_id_map [ experiment_id ]
165
- unless experiment
166
- @config . logger . log (
167
- Logger ::DEBUG ,
168
- "Feature flag experiment with ID '#{ experiment_id } ' is not in the datafile."
169
- )
170
- return nil
171
- end
172
-
173
- group_id = experiment [ 'groupId' ]
174
- # if experiment is part of mutex group we first determine which experiment (if any) in the group the user is part of
175
- if group_id and @config . group_key_map . has_key? ( group_id )
176
- group = @config . group_key_map [ group_id ]
177
- bucketed_experiment_id = @bucketer . find_bucket ( user_id , group_id , group [ 'trafficAllocation' ] )
178
- if bucketed_experiment_id . nil?
149
+ if feature_flag [ 'experimentIds' ] . empty?
150
+ @config . logger . log (
151
+ Logger ::DEBUG ,
152
+ "The feature flag '#{ feature_flag_key } ' is not used in any experiments."
153
+ )
154
+ return nil
155
+ else
156
+ # Evaluate each experiment and return the first bucketed experiment variation
157
+ feature_flag [ 'experimentIds' ] . each do |experiment_id |
158
+ experiment = @config . experiment_id_map [ experiment_id ]
159
+ unless experiment
179
160
@config . logger . log (
180
- Logger ::INFO ,
181
- "The user '#{ user_id } ' is not bucketed into any of the experiments on the feature ' #{ feature_flag_key } ' ."
161
+ Logger ::DEBUG ,
162
+ "Feature flag experiment with ID '#{ experiment_id } ' is not in the datafile ."
182
163
)
183
164
return nil
184
165
end
185
- else
186
- bucketed_experiment_id = experiment_id
187
- end
188
-
189
- if feature_flag [ 'experimentIds' ] . include? ( bucketed_experiment_id )
190
- experiment = @config . experiment_id_map [ bucketed_experiment_id ]
191
166
experiment_key = experiment [ 'key' ]
192
167
variation_id = get_variation ( experiment_key , user_id , attributes )
193
- unless variation_id . nil?
194
- variation = @config . variation_id_map [ experiment_key ] [ variation_id ]
195
- @config . logger . log (
196
- Logger ::INFO ,
197
- "The user '#{ user_id } ' is bucketed into experiment '#{ experiment_key } ' of feature '#{ feature_flag_key } '."
198
- )
199
- return {
200
- 'variation' => variation ,
201
- 'experiment' => experiment
202
- }
203
- else
204
- @config . logger . log (
205
- Logger ::INFO ,
206
- "The user '#{ user_id } ' is not bucketed into any of the experiments on the feature '#{ feature_flag_key } '."
207
- )
208
- end
168
+ next unless variation_id
169
+ variation = @config . variation_id_map [ experiment_key ] [ variation_id ]
170
+ @config . logger . log ( Logger ::INFO ,
171
+ "The user '#{ user_id } ' is bucketed into experiment '#{ experiment_key } ' of feature " \
172
+ "'#{ feature_flag_key } '." )
173
+ return {
174
+ 'experiment' => experiment ,
175
+ 'variation' => variation
176
+ }
209
177
end
210
- else
211
178
@config . logger . log (
212
- Logger ::DEBUG ,
213
- "The feature flag '#{ feature_flag_key } ' is not used in any experiments."
179
+ Logger ::INFO ,
180
+ "The user '#{ user_id } ' is not bucketed into any of the experiments on the feature ' #{ feature_flag_key } ' ."
214
181
)
215
182
end
216
183
217
- return nil
184
+ nil
218
185
end
219
186
220
187
def get_variation_for_feature_rollout ( feature_flag , user_id , attributes = nil )
@@ -227,72 +194,75 @@ def get_variation_for_feature_rollout(feature_flag, user_id, attributes = nil)
227
194
#
228
195
# Returns the variation the user is bucketed into or nil if not bucketed into any of the targeting rules
229
196
197
+ bucketing_id = get_bucketing_id ( user_id , attributes )
198
+
230
199
rollout_id = feature_flag [ 'rolloutId' ]
231
- if rollout_id . nil? or rollout_id . empty?
200
+ if rollout_id . nil? || rollout_id . empty?
232
201
feature_flag_key = feature_flag [ 'key' ]
233
202
@config . logger . log (
234
203
Logger ::DEBUG ,
235
- "Feature flag '#{ feature_flag_key } ' is not part of a rollout."
204
+ "Feature flag '#{ feature_flag_key } ' is not used in a rollout."
236
205
)
237
206
return nil
238
207
end
239
208
240
209
rollout = @config . get_rollout_from_id ( rollout_id )
241
- unless rollout . nil? or rollout [ 'experiments' ] . empty?
242
- rollout_experiments = rollout [ 'experiments' ]
243
- number_of_rules = rollout_experiments . length - 1
210
+ if rollout . nil?
211
+ @config . logger . log (
212
+ Logger ::DEBUG ,
213
+ "Rollout with ID '#{ rollout_id } ' is not in the datafile '#{ feature_flag [ 'key' ] } '"
214
+ )
215
+ return nil
216
+ end
244
217
245
- # Go through each experiment in order and try to get the variation for the user
246
- for index in ( 0 ...number_of_rules )
247
- experiment = rollout_experiments [ index ]
248
- experiment_key = experiment [ 'key' ]
218
+ return nil if rollout [ 'experiments' ] . empty?
249
219
250
- # Check that user meets audience conditions for targeting rule
251
- unless Audience . user_in_experiment? ( @config , experiment , attributes )
252
- @config . logger . log (
253
- Logger ::DEBUG ,
254
- "User '#{ user_id } ' does not meet the conditions to be in experiment '#{ experiment_key } '."
255
- )
256
- # move onto the next targeting rule
257
- next
258
- end
220
+ rollout_rules = rollout [ 'experiments' ]
221
+ number_of_rules = rollout_rules . length - 1
259
222
260
- @config . logger . log (
261
- Logger ::DEBUG ,
262
- "User '#{ user_id } ' meets conditions for targeting rule '#{ index + 1 } '."
263
- )
264
- variation = @bucketer . bucket ( experiment , user_id )
265
- unless variation . nil?
266
- variation_key = variation [ 'key' ]
267
- return variation
268
- end
223
+ # Go through each experiment in order and try to get the variation for the user
224
+ number_of_rules . times do |index |
225
+ rollout_rule = rollout_rules [ index ]
226
+ audience_id = rollout_rule [ 'audienceIds' ] [ 0 ]
227
+ audience = @config . get_audience_from_id ( audience_id )
228
+ audience_name = audience [ 'name' ]
269
229
270
- # User failed traffic allocation, jump to Everyone Else rule
230
+ # Check that user meets audience conditions for targeting rule
231
+ unless Audience . user_in_experiment? ( @config , rollout_rule , attributes )
271
232
@config . logger . log (
272
233
Logger ::DEBUG ,
273
- "User '#{ user_id } ' is not in the traffic group for the targeting rule. Checking 'Eveyrone Else' rule now ."
234
+ "User '#{ user_id } ' does not meet the conditions to be in rollout rule for audience ' #{ audience_name } ' ."
274
235
)
275
- break
236
+ # move onto the next targeting rule
237
+ next
276
238
end
277
239
278
- # Evalute the "Everyone Else" rule, which is the last rule.
279
- everyone_else_experiment = rollout_experiments [ number_of_rules ]
280
- variation = @bucketer . bucket ( everyone_else_experiment , user_id )
281
- unless variation . nil?
282
- @config . logger . log (
283
- Logger ::DEBUG ,
284
- "User '#{ user_id } ' meets conditions for targeting rule 'Everyone Else'."
285
- )
286
- return variation
287
- end
240
+ @config . logger . log (
241
+ Logger ::DEBUG ,
242
+ "Attempting to bucket user '#{ user_id } ' into rollout rule for audience '#{ audience_name } '."
243
+ )
244
+ # Evaluate if user satisfies the traffic allocation for this rollout rule
245
+ variation = @bucketer . bucket ( rollout_rule , bucketing_id , user_id )
246
+ return variation unless variation . nil?
288
247
248
+ # User failed traffic allocation, jump to Everyone Else rule
289
249
@config . logger . log (
290
250
Logger ::DEBUG ,
291
- "User '#{ user_id } ' does not meet conditions for targeting rule 'Everyone Else'."
251
+ "User '#{ user_id } ' was excluded due to traffic allocation. Checking 'Everyone Else' rule now ."
292
252
)
253
+ break
293
254
end
294
255
295
- return nil
256
+ # get last rule which is the everyone else rule
257
+ everyone_else_experiment = rollout_rules [ number_of_rules ]
258
+ variation = @bucketer . bucket ( everyone_else_experiment , bucketing_id , user_id )
259
+ return variation unless variation . nil?
260
+
261
+ @config . logger . log (
262
+ Logger ::DEBUG ,
263
+ "User '#{ user_id } ' was excluded from the 'Everyone Else' rule for feature flag"
264
+ )
265
+ nil
296
266
end
297
267
298
268
private
@@ -374,7 +344,6 @@ def get_user_profile(user_id)
374
344
user_profile
375
345
end
376
346
377
-
378
347
def save_user_profile ( user_profile , experiment_id , variation_id )
379
348
# Save a given bucketing decision to a given user profile
380
349
#
@@ -387,13 +356,32 @@ def save_user_profile(user_profile, experiment_id, variation_id)
387
356
user_id = user_profile [ :user_id ]
388
357
begin
389
358
user_profile [ :experiment_bucket_map ] [ experiment_id ] = {
390
- : variation_id => variation_id
359
+ variation_id : variation_id
391
360
}
392
361
@user_profile_service . save ( user_profile )
393
362
@config . logger . log ( Logger ::INFO , "Saved variation ID #{ variation_id } of experiment ID #{ experiment_id } for user '#{ user_id } '." )
394
363
rescue => e
395
364
@config . logger . log ( Logger ::ERROR , "Error while saving user profile for user ID '#{ user_id } ': #{ e } ." )
396
365
end
397
366
end
367
+
368
+ def get_bucketing_id ( user_id , attributes )
369
+ # Gets the Bucketing Id for Bucketing
370
+ #
371
+ # user_id - String user ID
372
+ # attributes - Hash user attributes
373
+
374
+ # By default, the bucketing ID should be the user ID
375
+ bucketing_id = user_id
376
+
377
+ # If the bucketing ID key is defined in attributes, then use that in place of the userID
378
+ if attributes && attributes [ RESERVED_ATTRIBUTE_KEY_BUCKETING_ID ] . is_a? ( String )
379
+ unless attributes [ RESERVED_ATTRIBUTE_KEY_BUCKETING_ID ] . empty?
380
+ bucketing_id = attributes [ RESERVED_ATTRIBUTE_KEY_BUCKETING_ID ]
381
+ @config . logger . log ( Logger ::DEBUG , "Setting the bucketing ID '#{ bucketing_id } '" )
382
+ end
383
+ end
384
+ bucketing_id
385
+ end
398
386
end
399
387
end
0 commit comments