42
42
require_relative 'optimizely/odp/lru_cache'
43
43
require_relative 'optimizely/odp/odp_manager'
44
44
require_relative 'optimizely/helpers/sdk_settings'
45
+ require_relative 'optimizely/user_profile_tracker'
45
46
46
47
module Optimizely
47
48
class Project
@@ -172,65 +173,18 @@ def create_user_context(user_id, attributes = nil)
172
173
OptimizelyUserContext . new ( self , user_id , attributes )
173
174
end
174
175
175
- def decide ( user_context , key , decide_options = [ ] )
176
- # raising on user context as it is internal and not provided directly by the user.
177
- raise if user_context . class != OptimizelyUserContext
178
-
179
- reasons = [ ]
180
-
181
- # check if SDK is ready
182
- unless is_valid
183
- @logger . log ( Logger ::ERROR , InvalidProjectConfigError . new ( 'decide' ) . message )
184
- reasons . push ( OptimizelyDecisionMessage ::SDK_NOT_READY )
185
- return OptimizelyDecision . new ( flag_key : key , user_context : user_context , reasons : reasons )
186
- end
187
-
188
- # validate that key is a string
189
- unless key . is_a? ( String )
190
- @logger . log ( Logger ::ERROR , 'Provided key is invalid' )
191
- reasons . push ( format ( OptimizelyDecisionMessage ::FLAG_KEY_INVALID , key ) )
192
- return OptimizelyDecision . new ( flag_key : key , user_context : user_context , reasons : reasons )
193
- end
194
-
195
- # validate that key maps to a feature flag
196
- config = project_config
197
- feature_flag = config . get_feature_flag_from_key ( key )
198
- unless feature_flag
199
- @logger . log ( Logger ::ERROR , "No feature flag was found for key '#{ key } '." )
200
- reasons . push ( format ( OptimizelyDecisionMessage ::FLAG_KEY_INVALID , key ) )
201
- return OptimizelyDecision . new ( flag_key : key , user_context : user_context , reasons : reasons )
202
- end
203
-
204
- # merge decide_options and default_decide_options
205
- if decide_options . is_a? Array
206
- decide_options += @default_decide_options
207
- else
208
- @logger . log ( Logger ::DEBUG , 'Provided decide options is not an array. Using default decide options.' )
209
- decide_options = @default_decide_options
210
- end
211
-
176
+ def create_optimizely_decision ( user_context , flag_key , decision , reasons , decide_options , config )
212
177
# Create Optimizely Decision Result.
213
178
user_id = user_context . user_id
214
179
attributes = user_context . user_attributes
215
180
variation_key = nil
216
181
feature_enabled = false
217
182
rule_key = nil
218
- flag_key = key
219
183
all_variables = { }
220
184
decision_event_dispatched = false
185
+ feature_flag = config . get_feature_flag_from_key ( flag_key )
221
186
experiment = nil
222
187
decision_source = Optimizely ::DecisionService ::DECISION_SOURCES [ 'ROLLOUT' ]
223
- context = Optimizely ::OptimizelyUserContext ::OptimizelyDecisionContext . new ( key , nil )
224
- variation , reasons_received = @decision_service . validated_forced_decision ( config , context , user_context )
225
- reasons . push ( *reasons_received )
226
-
227
- if variation
228
- decision = Optimizely ::DecisionService ::Decision . new ( nil , variation , Optimizely ::DecisionService ::DECISION_SOURCES [ 'FEATURE_TEST' ] )
229
- else
230
- decision , reasons_received = @decision_service . get_variation_for_feature ( config , feature_flag , user_context , decide_options )
231
- reasons . push ( *reasons_received )
232
- end
233
-
234
188
# Send impression event if Decision came from a feature test and decide options doesn't include disableDecisionEvent
235
189
if decision . is_a? ( Optimizely ::DecisionService ::Decision )
236
190
experiment = decision . experiment
@@ -249,7 +203,7 @@ def decide(user_context, key, decide_options = [])
249
203
# Generate all variables map if decide options doesn't include excludeVariables
250
204
unless decide_options . include? OptimizelyDecideOption ::EXCLUDE_VARIABLES
251
205
feature_flag [ 'variables' ] . each do |variable |
252
- variable_value = get_feature_variable_for_variation ( key , feature_enabled , variation , variable , user_id )
206
+ variable_value = get_feature_variable_for_variation ( flag_key , feature_enabled , variation , variable , user_id )
253
207
all_variables [ variable [ 'key' ] ] = Helpers ::VariableType . cast_value_to_type ( variable_value , variable [ 'type' ] , @logger )
254
208
end
255
209
end
@@ -281,6 +235,47 @@ def decide(user_context, key, decide_options = [])
281
235
)
282
236
end
283
237
238
+ def decide ( user_context , key , decide_options = [ ] )
239
+ # raising on user context as it is internal and not provided directly by the user.
240
+ raise if user_context . class != OptimizelyUserContext
241
+
242
+ reasons = [ ]
243
+
244
+ # check if SDK is ready
245
+ unless is_valid
246
+ @logger . log ( Logger ::ERROR , InvalidProjectConfigError . new ( 'decide' ) . message )
247
+ reasons . push ( OptimizelyDecisionMessage ::SDK_NOT_READY )
248
+ return OptimizelyDecision . new ( flag_key : key , user_context : user_context , reasons : reasons )
249
+ end
250
+
251
+ # validate that key is a string
252
+ unless key . is_a? ( String )
253
+ @logger . log ( Logger ::ERROR , 'Provided key is invalid' )
254
+ reasons . push ( format ( OptimizelyDecisionMessage ::FLAG_KEY_INVALID , key ) )
255
+ return OptimizelyDecision . new ( flag_key : key , user_context : user_context , reasons : reasons )
256
+ end
257
+
258
+ # validate that key maps to a feature flag
259
+ config = project_config
260
+ feature_flag = config . get_feature_flag_from_key ( key )
261
+ unless feature_flag
262
+ @logger . log ( Logger ::ERROR , "No feature flag was found for key '#{ key } '." )
263
+ reasons . push ( format ( OptimizelyDecisionMessage ::FLAG_KEY_INVALID , key ) )
264
+ return OptimizelyDecision . new ( flag_key : key , user_context : user_context , reasons : reasons )
265
+ end
266
+
267
+ # merge decide_options and default_decide_options
268
+ if decide_options . is_a? Array
269
+ decide_options += @default_decide_options
270
+ else
271
+ @logger . log ( Logger ::DEBUG , 'Provided decide options is not an array. Using default decide options.' )
272
+ decide_options = @default_decide_options
273
+ end
274
+
275
+ decide_options . delete ( OptimizelyDecideOption ::ENABLED_FLAGS_ONLY ) if decide_options . include? ( OptimizelyDecideOption ::ENABLED_FLAGS_ONLY )
276
+ decide_for_keys ( user_context , [ key ] , decide_options , true ) [ key ]
277
+ end
278
+
284
279
def decide_all ( user_context , decide_options = [ ] )
285
280
# raising on user context as it is internal and not provided directly by the user.
286
281
raise if user_context . class != OptimizelyUserContext
@@ -298,7 +293,7 @@ def decide_all(user_context, decide_options = [])
298
293
decide_for_keys ( user_context , keys , decide_options )
299
294
end
300
295
301
- def decide_for_keys ( user_context , keys , decide_options = [ ] )
296
+ def decide_for_keys ( user_context , keys , decide_options = [ ] , ignore_default_options = false ) # rubocop:disable Style/OptionalBooleanParameter
302
297
# raising on user context as it is internal and not provided directly by the user.
303
298
raise if user_context . class != OptimizelyUserContext
304
299
@@ -308,13 +303,79 @@ def decide_for_keys(user_context, keys, decide_options = [])
308
303
return { }
309
304
end
310
305
311
- enabled_flags_only = ( !decide_options . nil? && ( decide_options . include? OptimizelyDecideOption ::ENABLED_FLAGS_ONLY ) ) || ( @default_decide_options . include? OptimizelyDecideOption ::ENABLED_FLAGS_ONLY )
306
+ # merge decide_options and default_decide_options
307
+ unless ignore_default_options
308
+ if decide_options . is_a? ( Array )
309
+ decide_options += @default_decide_options
310
+ else
311
+ @logger . log ( Logger ::DEBUG , 'Provided decide options is not an array. Using default decide options.' )
312
+ decide_options = @default_decide_options
313
+ end
314
+ end
315
+
316
+ # enabled_flags_only = (!decide_options.nil? && (decide_options.include? OptimizelyDecideOption::ENABLED_FLAGS_ONLY)) || (@default_decide_options.include? OptimizelyDecideOption::ENABLED_FLAGS_ONLY)
312
317
313
318
decisions = { }
319
+ valid_keys = [ ]
320
+ decision_reasons_dict = { }
321
+ config = project_config
322
+ return decisions unless config
323
+
324
+ flags_without_forced_decision = [ ]
325
+ flag_decisions = { }
326
+
314
327
keys . each do |key |
315
- decision = decide ( user_context , key , decide_options )
316
- decisions [ key ] = decision unless enabled_flags_only && !decision . enabled
328
+ # Retrieve the feature flag from the project's feature flag key map
329
+ feature_flag = config . feature_flag_key_map [ key ]
330
+
331
+ # If the feature flag is nil, create a default OptimizelyDecision and move to the next key
332
+ if feature_flag . nil?
333
+ decisions [ key ] = OptimizelyDecision . new ( nil , false , nil , nil , key , user_context , [ ] )
334
+ next
335
+ end
336
+ valid_keys . push ( key )
337
+ decision_reasons = [ ]
338
+ decision_reasons_dict [ key ] = decision_reasons
339
+
340
+ config = project_config
341
+ context = Optimizely ::OptimizelyUserContext ::OptimizelyDecisionContext . new ( key , nil )
342
+ variation , reasons_received = @decision_service . validated_forced_decision ( config , context , user_context )
343
+ decision_reasons_dict [ key ] . push ( *reasons_received )
344
+ if variation
345
+ decision = Optimizely ::DecisionService ::Decision . new ( nil , variation , Optimizely ::DecisionService ::DECISION_SOURCES [ 'FEATURE_TEST' ] )
346
+ flag_decisions [ key ] = decision
347
+ else
348
+ flags_without_forced_decision . push ( feature_flag )
349
+ end
317
350
end
351
+ decision_list = @decision_service . get_variations_for_feature_list ( config , flags_without_forced_decision , user_context , decide_options )
352
+
353
+ flags_without_forced_decision . each_with_index do |flag , i |
354
+ decision = decision_list [ i ] [ 0 ]
355
+ reasons = decision_list [ i ] [ 1 ]
356
+ flag_key = flag [ 'key' ]
357
+ flag_decisions [ flag_key ] = decision
358
+ decision_reasons_dict [ flag_key ] ||= [ ]
359
+ decision_reasons_dict [ flag_key ] . push ( *reasons )
360
+ end
361
+ valid_keys . each do |key |
362
+ flag_decision = flag_decisions [ key ]
363
+ decision_reasons = decision_reasons_dict [ key ]
364
+ optimizely_decision = create_optimizely_decision (
365
+ user_context ,
366
+ key ,
367
+ flag_decision ,
368
+ decision_reasons ,
369
+ decide_options ,
370
+ config
371
+ )
372
+
373
+ enabled_flags_only_missing = !decide_options . include? ( OptimizelyDecideOption ::ENABLED_FLAGS_ONLY )
374
+ is_enabled = optimizely_decision . enabled
375
+
376
+ decisions [ key ] = optimizely_decision if enabled_flags_only_missing || is_enabled
377
+ end
378
+
318
379
decisions
319
380
end
320
381
@@ -959,7 +1020,10 @@ def get_variation_with_config(experiment_key, user_id, attributes, config)
959
1020
return nil unless user_inputs_valid? ( attributes )
960
1021
961
1022
user_context = OptimizelyUserContext . new ( self , user_id , attributes , identify : false )
962
- variation_id , = @decision_service . get_variation ( config , experiment_id , user_context )
1023
+ user_profile_tracker = UserProfileTracker . new ( user_id , @user_profile_service , @logger )
1024
+ user_profile_tracker . load_user_profile
1025
+ variation_id , = @decision_service . get_variation ( config , experiment_id , user_context , user_profile_tracker )
1026
+ user_profile_tracker . save_user_profile
963
1027
variation = config . get_variation_from_id ( experiment_key , variation_id ) unless variation_id . nil?
964
1028
variation_key = variation [ 'key' ] if variation
965
1029
decision_notification_type = if config . feature_experiment? ( experiment_id )
0 commit comments