optimizely-sdk 3.9.0 → 3.10.0

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
  SHA256:
3
- metadata.gz: 3502b43c8fae064fda3e1b237786a0285103d4ce9383a9d64cfc7b34f3f8c849
4
- data.tar.gz: 9b843fc3a7999e640f329812aeeb8e26474b5b85f29d27c0647ba7c3685cb73f
3
+ metadata.gz: 96375e01eed1e2a237d2c7c22a8c417bdb81fdff82414dd186900f521b13ea7e
4
+ data.tar.gz: 17d6f554563447c577cc5db13da3e4c5c6ac46f749e6f9756f8030336e6e516a
5
5
  SHA512:
6
- metadata.gz: a244307de60cf4a7e9b4e90bd0282e071e310701c09b4314a5f011f9f97a1f638205dc9e3de59611c247315314bfd58a0521690bbc3fa876575f3ef8fa917306
7
- data.tar.gz: b68df42eef86c3a4e1fd22f2b1b4cd3060df4ade0c9125862443164735778a16415af958a4002fba7adcdf078d1552a5ffef446bac22e7a269a5984445d1fd50
6
+ metadata.gz: c58553a209a14f5cfea1749da188666b0a178cf1e6ca9447e4de5a56b70cb0e651825d09fa1265503a94b7121171cc7d6abede6b347c2e2791476f48eeebbce8
7
+ data.tar.gz: 41140f1ba5e87a11410f05899d183d9a1793e0741e02c438481902b4c240dde90df6ec97d04228aab8655d75f2c2d97f1de168a7962b7107dfcbeb55afa8edb1
@@ -60,6 +60,7 @@ module Optimizely
60
60
  attr_reader :variation_key_map
61
61
  attr_reader :variation_id_map_by_experiment_id
62
62
  attr_reader :variation_key_map_by_experiment_id
63
+ attr_reader :flag_variation_map
63
64
 
64
65
  def initialize(datafile, logger, error_handler)
65
66
  # ProjectConfig init method to fetch and set project config data
@@ -123,6 +124,8 @@ module Optimizely
123
124
  @variation_key_map_by_experiment_id = {}
124
125
  @variation_id_to_variable_usage_map = {}
125
126
  @variation_id_to_experiment_map = {}
127
+ @flag_variation_map = {}
128
+
126
129
  @experiment_id_map.each_value do |exp|
127
130
  # Excludes experiments from rollouts
128
131
  variations = exp.fetch('variations')
@@ -138,6 +141,8 @@ module Optimizely
138
141
  exps = rollout.fetch('experiments')
139
142
  @rollout_experiment_id_map = @rollout_experiment_id_map.merge(generate_key_map(exps, 'id'))
140
143
  end
144
+
145
+ @flag_variation_map = generate_feature_variation_map(@feature_flags)
141
146
  @all_experiments = @experiment_id_map.merge(@rollout_experiment_id_map)
142
147
  @all_experiments.each do |id, exp|
143
148
  variations = exp.fetch('variations')
@@ -165,6 +170,24 @@ module Optimizely
165
170
  end
166
171
  end
167
172
 
173
+ def get_rules_for_flag(feature_flag)
174
+ # Retrieves rules for a given feature flag
175
+ #
176
+ # feature_flag - String key representing the feature_flag
177
+ #
178
+ # Returns rules in feature flag
179
+ rules = feature_flag['experimentIds'].map { |exp_id| @experiment_id_map[exp_id] }
180
+ rollout = feature_flag['rolloutId'].empty? ? nil : @rollout_id_map[feature_flag['rolloutId']]
181
+
182
+ if rollout
183
+ rollout_experiments = rollout.fetch('experiments')
184
+ rollout_experiments.each do |exp|
185
+ rules.push(exp)
186
+ end
187
+ end
188
+ rules
189
+ end
190
+
168
191
  def self.create(datafile, logger, error_handler, skip_json_validation)
169
192
  # Looks up and sets datafile and config based on response body.
170
193
  #
@@ -279,6 +302,13 @@ module Optimizely
279
302
  nil
280
303
  end
281
304
 
305
+ def get_variation_from_flag(flag_key, target_value, attribute)
306
+ variations = @flag_variation_map[flag_key]
307
+ return variations.select { |variation| variation[attribute] == target_value }.first if variations
308
+
309
+ nil
310
+ end
311
+
282
312
  def get_variation_from_id(experiment_key, variation_id)
283
313
  # Get variation given experiment key and variation ID
284
314
  #
@@ -494,6 +524,20 @@ module Optimizely
494
524
 
495
525
  private
496
526
 
527
+ def generate_feature_variation_map(feature_flags)
528
+ flag_variation_map = {}
529
+ feature_flags.each do |flag|
530
+ variations = []
531
+ get_rules_for_flag(flag).each do |rule|
532
+ rule['variations'].each do |rule_variation|
533
+ variations.push(rule_variation) if variations.select { |variation| variation['id'] == rule_variation['id'] }.empty?
534
+ end
535
+ end
536
+ flag_variation_map[flag['key']] = variations
537
+ end
538
+ flag_variation_map
539
+ end
540
+
497
541
  def generate_key_map(array, key)
498
542
  # Helper method to generate map from key to hash in array of hashes
499
543
  #
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #
4
- # Copyright 2017-2021, Optimizely and contributors
4
+ # Copyright 2017-2022, 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.
@@ -52,18 +52,19 @@ module Optimizely
52
52
  @forced_variation_map = {}
53
53
  end
54
54
 
55
- def get_variation(project_config, experiment_id, user_id, attributes = nil, decide_options = [])
55
+ def get_variation(project_config, experiment_id, user_context, decide_options = [])
56
56
  # Determines variation into which user will be bucketed.
57
57
  #
58
58
  # project_config - project_config - Instance of ProjectConfig
59
59
  # experiment_id - Experiment for which visitor variation needs to be determined
60
- # user_id - String ID for user
61
- # attributes - Hash representing user attributes
60
+ # user_context - Optimizely user context instance
62
61
  #
63
62
  # Returns variation ID where visitor will be bucketed
64
63
  # (nil if experiment is inactive or user does not meet audience conditions)
65
64
 
66
65
  decide_reasons = []
66
+ user_id = user_context.user_id
67
+ attributes = user_context.user_attributes
67
68
  # By default, the bucketing ID should be the user ID
68
69
  bucketing_id, bucketing_id_reasons = get_bucketing_id(user_id, attributes)
69
70
  decide_reasons.push(*bucketing_id_reasons)
@@ -134,40 +135,39 @@ module Optimizely
134
135
  [variation_id, decide_reasons]
135
136
  end
136
137
 
137
- def get_variation_for_feature(project_config, feature_flag, user_id, attributes = nil, decide_options = [])
138
+ def get_variation_for_feature(project_config, feature_flag, user_context, decide_options = [])
138
139
  # Get the variation the user is bucketed into for the given FeatureFlag.
139
140
  #
140
141
  # project_config - project_config - Instance of ProjectConfig
141
142
  # feature_flag - The feature flag the user wants to access
142
- # user_id - String ID for the user
143
- # attributes - Hash representing user attributes
143
+ # user_context - Optimizely user context instance
144
144
  #
145
145
  # Returns Decision struct (nil if the user is not bucketed into any of the experiments on the feature)
146
146
 
147
147
  decide_reasons = []
148
148
 
149
149
  # check if the feature is being experiment on and whether the user is bucketed into the experiment
150
- decision, reasons_received = get_variation_for_feature_experiment(project_config, feature_flag, user_id, attributes, decide_options)
150
+ decision, reasons_received = get_variation_for_feature_experiment(project_config, feature_flag, user_context, decide_options)
151
151
  decide_reasons.push(*reasons_received)
152
152
  return decision, decide_reasons unless decision.nil?
153
153
 
154
- decision, reasons_received = 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_context)
155
155
  decide_reasons.push(*reasons_received)
156
156
 
157
157
  [decision, decide_reasons]
158
158
  end
159
159
 
160
- def get_variation_for_feature_experiment(project_config, feature_flag, user_id, attributes = nil, decide_options = [])
160
+ def get_variation_for_feature_experiment(project_config, feature_flag, user_context, decide_options = [])
161
161
  # Gets the variation the user is bucketed into for the feature flag's experiment.
162
162
  #
163
163
  # project_config - project_config - Instance of ProjectConfig
164
164
  # feature_flag - The feature flag the user wants to access
165
- # user_id - String ID for the user
166
- # attributes - Hash representing user attributes
165
+ # user_context - Optimizely user context instance
167
166
  #
168
167
  # Returns Decision struct (nil if the user is not bucketed into any of the experiments on the feature)
169
168
  # or nil if the user is not bucketed into any of the experiments on the feature
170
169
  decide_reasons = []
170
+ user_id = user_context.user_id
171
171
  feature_flag_key = feature_flag['key']
172
172
  if feature_flag['experimentIds'].empty?
173
173
  message = "The feature flag '#{feature_flag_key}' is not used in any experiments."
@@ -187,12 +187,13 @@ module Optimizely
187
187
  end
188
188
 
189
189
  experiment_id = experiment['id']
190
- variation_id, reasons_received = get_variation(project_config, experiment_id, user_id, attributes, decide_options)
190
+ variation_id, reasons_received = get_variation_from_experiment_rule(project_config, feature_flag_key, experiment, user_context, decide_options)
191
191
  decide_reasons.push(*reasons_received)
192
192
 
193
193
  next unless variation_id
194
194
 
195
195
  variation = project_config.get_variation_from_id_by_experiment_id(experiment_id, variation_id)
196
+ variation = project_config.get_variation_from_flag(feature_flag['key'], variation_id, 'id') if variation.nil?
196
197
 
197
198
  return Decision.new(experiment, variation, DECISION_SOURCES['FEATURE_TEST']), decide_reasons
198
199
  end
@@ -204,22 +205,20 @@ module Optimizely
204
205
  [nil, decide_reasons]
205
206
  end
206
207
 
207
- def get_variation_for_feature_rollout(project_config, feature_flag, user_id, attributes = nil)
208
+ def get_variation_for_feature_rollout(project_config, feature_flag, user_context)
208
209
  # Determine which variation the user is in for a given rollout.
209
210
  # Returns the variation of the first experiment the user qualifies for.
210
211
  #
211
212
  # project_config - project_config - Instance of ProjectConfig
212
213
  # feature_flag - The feature flag the user wants to access
213
- # user_id - String ID for the user
214
- # attributes - Hash representing user attributes
214
+ # user_context - Optimizely user context instance
215
215
  #
216
216
  # Returns the Decision struct or nil if not bucketed into any of the targeting rules
217
217
  decide_reasons = []
218
- bucketing_id, bucketing_id_reasons = get_bucketing_id(user_id, attributes)
219
- decide_reasons.push(*bucketing_id_reasons)
218
+
220
219
  rollout_id = feature_flag['rolloutId']
220
+ feature_flag_key = feature_flag['key']
221
221
  if rollout_id.nil? || rollout_id.empty?
222
- feature_flag_key = feature_flag['key']
223
222
  message = "Feature flag '#{feature_flag_key}' is not used in a rollout."
224
223
  @logger.log(Logger::DEBUG, message)
225
224
  decide_reasons.push(message)
@@ -236,60 +235,102 @@ module Optimizely
236
235
 
237
236
  return nil, decide_reasons if rollout['experiments'].empty?
238
237
 
238
+ index = 0
239
239
  rollout_rules = rollout['experiments']
240
- number_of_rules = rollout_rules.length - 1
241
-
242
- # Go through each experiment in order and try to get the variation for the user
243
- number_of_rules.times do |index|
244
- rollout_rule = rollout_rules[index]
245
- logging_key = index + 1
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)
240
+ while index < rollout_rules.length
241
+ variation, skip_to_everyone_else, reasons_received = get_variation_from_delivery_rule(project_config, feature_flag_key, rollout_rules, index, user_context)
248
242
  decide_reasons.push(*reasons_received)
249
- # Check that user meets audience conditions for targeting rule
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)
254
- # move onto the next targeting rule
255
- next
243
+ if variation
244
+ rule = rollout_rules[index]
245
+ feature_decision = Decision.new(rule, variation, DECISION_SOURCES['ROLLOUT'])
246
+ return [feature_decision, decide_reasons]
256
247
  end
257
248
 
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)
249
+ index = skip_to_everyone_else ? (rollout_rules.length - 1) : (index + 1)
250
+ end
261
251
 
262
- # Evaluate if user satisfies the traffic allocation for this rollout rule
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?
252
+ [nil, decide_reasons]
253
+ end
266
254
 
267
- break
268
- end
255
+ def get_variation_from_experiment_rule(project_config, flag_key, rule, user, options = [])
256
+ # Determine which variation the user is in for a given rollout.
257
+ # Returns the variation from experiment rules.
258
+ #
259
+ # project_config - project_config - Instance of ProjectConfig
260
+ # flag_key - The feature flag the user wants to access
261
+ # rule - An experiment rule key
262
+ # user - Optimizely user context instance
263
+ #
264
+ # Returns variation_id and reasons
265
+ reasons = []
269
266
 
270
- # get last rule which is the everyone else rule
271
- everyone_else_experiment = rollout_rules[number_of_rules]
272
- logging_key = 'Everyone Else'
267
+ context = Optimizely::OptimizelyUserContext::OptimizelyDecisionContext.new(flag_key, rule['key'])
268
+ variation, forced_reasons = validated_forced_decision(project_config, context, user)
269
+ reasons.push(*forced_reasons)
273
270
 
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)
276
- # Check that user meets audience conditions for last rule
271
+ return [variation['id'], reasons] if variation
272
+
273
+ variation_id, response_reasons = get_variation(project_config, rule['id'], user, options)
274
+ reasons.push(*response_reasons)
275
+
276
+ [variation_id, reasons]
277
+ end
278
+
279
+ def get_variation_from_delivery_rule(project_config, flag_key, rules, rule_index, user)
280
+ # Determine which variation the user is in for a given rollout.
281
+ # Returns the variation from delivery rules.
282
+ #
283
+ # project_config - project_config - Instance of ProjectConfig
284
+ # flag_key - The feature flag the user wants to access
285
+ # rule - An experiment rule key
286
+ # user - Optimizely user context instance
287
+ #
288
+ # Returns variation, boolean to skip for eveyone else rule and reasons
289
+ reasons = []
290
+ skip_to_everyone_else = false
291
+ rule = rules[rule_index]
292
+ context = Optimizely::OptimizelyUserContext::OptimizelyDecisionContext.new(flag_key, rule['key'])
293
+ variation, forced_reasons = validated_forced_decision(project_config, context, user)
294
+ reasons.push(*forced_reasons)
295
+
296
+ return [variation, skip_to_everyone_else, reasons] if variation
297
+
298
+ user_id = user.user_id
299
+ attributes = user.user_attributes
300
+ bucketing_id, bucketing_id_reasons = get_bucketing_id(user_id, attributes)
301
+ reasons.push(*bucketing_id_reasons)
302
+
303
+ everyone_else = (rule_index == rules.length - 1)
304
+
305
+ logging_key = everyone_else ? 'Everyone Else' : (rule_index + 1).to_s
306
+
307
+ user_meets_audience_conditions, reasons_received = Audience.user_meets_audience_conditions?(project_config, rule, attributes, @logger, 'ROLLOUT_AUDIENCE_EVALUATION_LOGS', logging_key)
308
+ reasons.push(*reasons_received)
277
309
  unless user_meets_audience_conditions
278
- message = "User '#{user_id}' does not meet the audience conditions for targeting rule '#{logging_key}'."
310
+ message = "User '#{user_id}' does not meet the conditions for targeting rule '#{logging_key}'."
279
311
  @logger.log(Logger::DEBUG, message)
280
- decide_reasons.push(message)
281
- return nil, decide_reasons
312
+ reasons.push(message)
313
+ return [nil, skip_to_everyone_else, reasons]
282
314
  end
283
315
 
284
316
  message = "User '#{user_id}' meets the audience conditions for targeting rule '#{logging_key}'."
285
317
  @logger.log(Logger::DEBUG, message)
286
- decide_reasons.push(message)
318
+ reasons.push(message)
319
+ bucket_variation, bucket_reasons = @bucketer.bucket(project_config, rule, bucketing_id, user_id)
287
320
 
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?
321
+ reasons.push(*bucket_reasons)
291
322
 
292
- [nil, decide_reasons]
323
+ if bucket_variation
324
+ message = "User '#{user_id}' is in the traffic group of targeting rule '#{logging_key}'."
325
+ @logger.log(Logger::DEBUG, message)
326
+ reasons.push(message)
327
+ elsif !everyone_else
328
+ message = "User '#{user_id}' is not in the traffic group for targeting rule '#{logging_key}'."
329
+ @logger.log(Logger::DEBUG, message)
330
+ reasons.push(message)
331
+ skip_to_everyone_else = true
332
+ end
333
+ [bucket_variation, skip_to_everyone_else, reasons]
293
334
  end
294
335
 
295
336
  def set_forced_variation(project_config, experiment_key, user_id, variation_key)
@@ -376,6 +417,28 @@ module Optimizely
376
417
  [variation, decide_reasons]
377
418
  end
378
419
 
420
+ def validated_forced_decision(project_config, context, user_context)
421
+ decision = user_context.get_forced_decision(context)
422
+ flag_key = context[:flag_key]
423
+ rule_key = context[:rule_key]
424
+ variation_key = decision ? decision[:variation_key] : decision
425
+ reasons = []
426
+ target = rule_key ? "flag (#{flag_key}), rule (#{rule_key})" : "flag (#{flag_key})"
427
+ if variation_key
428
+ variation = project_config.get_variation_from_flag(flag_key, variation_key, 'key')
429
+ if variation
430
+ reason = "Variation (#{variation_key}) is mapped to #{target} and user (#{user_context.user_id}) in the forced decision map."
431
+ reasons.push(reason)
432
+ return variation, reasons
433
+ else
434
+ reason = "Invalid variation is mapped to #{target} and user (#{user_context.user_id}) in the forced decision map."
435
+ reasons.push(reason)
436
+ end
437
+ end
438
+
439
+ [nil, reasons]
440
+ end
441
+
379
442
  private
380
443
 
381
444
  def get_whitelisted_variation_id(project_config, experiment_id, user_id)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #
4
- # Copyright 2020, Optimizely and contributors
4
+ # Copyright 2020-2022, 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.
@@ -23,16 +23,26 @@ module Optimizely
23
23
  # Representation of an Optimizely User Context using which APIs are to be called.
24
24
 
25
25
  attr_reader :user_id
26
+ attr_reader :forced_decisions
27
+ attr_reader :OptimizelyDecisionContext
28
+ attr_reader :OptimizelyForcedDecision
29
+ attr_reader :optimizely_client
26
30
 
31
+ OptimizelyDecisionContext = Struct.new(:flag_key, :rule_key)
32
+ OptimizelyForcedDecision = Struct.new(:variation_key)
27
33
  def initialize(optimizely_client, user_id, user_attributes)
28
34
  @attr_mutex = Mutex.new
35
+ @forced_decision_mutex = Mutex.new
29
36
  @optimizely_client = optimizely_client
30
37
  @user_id = user_id
31
38
  @user_attributes = user_attributes.nil? ? {} : user_attributes.clone
39
+ @forced_decisions = {}
32
40
  end
33
41
 
34
42
  def clone
35
- OptimizelyUserContext.new(@optimizely_client, @user_id, user_attributes)
43
+ user_context = OptimizelyUserContext.new(@optimizely_client, @user_id, user_attributes)
44
+ @forced_decision_mutex.synchronize { user_context.instance_variable_set('@forced_decisions', @forced_decisions.dup) unless @forced_decisions.empty? }
45
+ user_context
36
46
  end
37
47
 
38
48
  def user_attributes
@@ -85,6 +95,68 @@ module Optimizely
85
95
  @optimizely_client&.decide_all(clone, options)
86
96
  end
87
97
 
98
+ # Sets the forced decision (variation key) for a given flag and an optional rule.
99
+ #
100
+ # @param context - An OptimizelyDecisionContext object containg flag key and rule key.
101
+ # @param decision - An OptimizelyForcedDecision object containing variation key
102
+ #
103
+ # @return - true if the forced decision has been set successfully.
104
+
105
+ def set_forced_decision(context, decision)
106
+ flag_key = context[:flag_key]
107
+ return false if flag_key.nil?
108
+
109
+ @forced_decision_mutex.synchronize { @forced_decisions[context] = decision }
110
+
111
+ true
112
+ end
113
+
114
+ def find_forced_decision(context)
115
+ return nil if @forced_decisions.empty?
116
+
117
+ decision = nil
118
+ @forced_decision_mutex.synchronize { decision = @forced_decisions[context] }
119
+ decision
120
+ end
121
+
122
+ # Returns the forced decision for a given flag and an optional rule.
123
+ #
124
+ # @param context - An OptimizelyDecisionContext object containg flag key and rule key.
125
+ #
126
+ # @return - A variation key or nil if forced decisions are not set for the parameters.
127
+
128
+ def get_forced_decision(context)
129
+ find_forced_decision(context)
130
+ end
131
+
132
+ # Removes the forced decision for a given flag and an optional rule.
133
+ #
134
+ # @param context - An OptimizelyDecisionContext object containg flag key and rule key.
135
+ #
136
+ # @return - true if the forced decision has been removed successfully.
137
+
138
+ def remove_forced_decision(context)
139
+ deleted = false
140
+ @forced_decision_mutex.synchronize do
141
+ if @forced_decisions.key?(context)
142
+ @forced_decisions.delete(context)
143
+ deleted = true
144
+ end
145
+ end
146
+ deleted
147
+ end
148
+
149
+ # Removes all forced decisions bound to this user context.
150
+ #
151
+ # @return - true if forced decisions have been removed successfully.
152
+
153
+ def remove_all_forced_decisions
154
+ return false if @optimizely_client&.get_optimizely_config.nil?
155
+
156
+ @forced_decision_mutex.synchronize { @forced_decisions.clear }
157
+ true
158
+ end
159
+
88
160
  # Track an event
89
161
  #
90
162
  # @param event_key - Event key representing the event which needs to be recorded.
@@ -17,5 +17,5 @@
17
17
  #
18
18
  module Optimizely
19
19
  CLIENT_ENGINE = 'ruby-sdk'
20
- VERSION = '3.9.0'
20
+ VERSION = '3.10.0'
21
21
  end
data/lib/optimizely.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #
4
- # Copyright 2016-2021, Optimizely and contributors
4
+ # Copyright 2016-2022, 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.
@@ -198,17 +198,24 @@ module Optimizely
198
198
  decision_event_dispatched = false
199
199
  experiment = nil
200
200
  decision_source = Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']
201
-
202
- decision, reasons_received = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes, decide_options)
201
+ context = Optimizely::OptimizelyUserContext::OptimizelyDecisionContext.new(key, nil)
202
+ variation, reasons_received = @decision_service.validated_forced_decision(config, context, user_context)
203
203
  reasons.push(*reasons_received)
204
204
 
205
+ if variation
206
+ decision = Optimizely::DecisionService::Decision.new(nil, variation, Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST'])
207
+ else
208
+ decision, reasons_received = @decision_service.get_variation_for_feature(config, feature_flag, user_context, decide_options)
209
+ reasons.push(*reasons_received)
210
+ end
211
+
205
212
  # Send impression event if Decision came from a feature test and decide options doesn't include disableDecisionEvent
206
213
  if decision.is_a?(Optimizely::DecisionService::Decision)
207
214
  experiment = decision.experiment
208
- rule_key = experiment['key']
215
+ rule_key = experiment ? experiment['key'] : nil
209
216
  variation = decision['variation']
210
- variation_key = variation['key']
211
- feature_enabled = variation['featureEnabled']
217
+ variation_key = variation ? variation['key'] : nil
218
+ feature_enabled = variation ? variation['featureEnabled'] : false
212
219
  decision_source = decision.source
213
220
  end
214
221
 
@@ -291,6 +298,19 @@ module Optimizely
291
298
  decisions
292
299
  end
293
300
 
301
+ # Gets variation using variation key or id and flag key.
302
+ #
303
+ # @param flag_key - flag key from which the variation is required.
304
+ # @param target_value - variation value either id or key that will be matched.
305
+ # @param attribute - string representing variation attribute.
306
+ #
307
+ # @return [variation]
308
+ # @return [nil] if no variation found in flag_variation_map.
309
+
310
+ def get_flag_variation(flag_key, target_value, attribute)
311
+ project_config.get_variation_from_flag(flag_key, target_value, attribute)
312
+ end
313
+
294
314
  # Buckets visitor and sends impression event to Optimizely.
295
315
  #
296
316
  # @param experiment_key - Experiment which needs to be activated.
@@ -490,7 +510,8 @@ module Optimizely
490
510
  return false
491
511
  end
492
512
 
493
- decision, = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes)
513
+ user_context = create_user_context(user_id, attributes)
514
+ decision, = @decision_service.get_variation_for_feature(config, feature_flag, user_context)
494
515
 
495
516
  feature_enabled = false
496
517
  source_string = Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']
@@ -739,7 +760,8 @@ module Optimizely
739
760
  return nil
740
761
  end
741
762
 
742
- decision, = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes)
763
+ user_context = create_user_context(user_id, attributes)
764
+ decision, = @decision_service.get_variation_for_feature(config, feature_flag, user_context)
743
765
  variation = decision ? decision['variation'] : nil
744
766
  feature_enabled = variation ? variation['featureEnabled'] : false
745
767
  all_variables = {}
@@ -881,7 +903,8 @@ module Optimizely
881
903
 
882
904
  return nil unless user_inputs_valid?(attributes)
883
905
 
884
- variation_id, = @decision_service.get_variation(config, experiment_id, user_id, attributes)
906
+ user_context = create_user_context(user_id, attributes)
907
+ variation_id, = @decision_service.get_variation(config, experiment_id, user_context)
885
908
  variation = config.get_variation_from_id(experiment_key, variation_id) unless variation_id.nil?
886
909
  variation_key = variation['key'] if variation
887
910
  decision_notification_type = if config.feature_experiment?(experiment_id)
@@ -947,7 +970,8 @@ module Optimizely
947
970
  return nil
948
971
  end
949
972
 
950
- decision, = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes)
973
+ user_context = create_user_context(user_id, attributes)
974
+ decision, = @decision_service.get_variation_for_feature(config, feature_flag, user_context)
951
975
  variation = decision ? decision['variation'] : nil
952
976
  feature_enabled = variation ? variation['featureEnabled'] : false
953
977
 
@@ -1083,8 +1107,12 @@ module Optimizely
1083
1107
  experiment_id = experiment['id']
1084
1108
  experiment_key = experiment['key']
1085
1109
 
1086
- variation_id = ''
1087
- variation_id = config.get_variation_id_from_key_by_experiment_id(experiment_id, variation_key) if experiment_id != ''
1110
+ variation_id = config.get_variation_id_from_key_by_experiment_id(experiment_id, variation_key) unless experiment_id.empty?
1111
+
1112
+ unless variation_id
1113
+ variation = !flag_key.empty? ? get_flag_variation(flag_key, variation_key, 'key') : nil
1114
+ variation_id = variation ? variation['id'] : ''
1115
+ end
1088
1116
 
1089
1117
  metadata = {
1090
1118
  flag_key: flag_key,
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: optimizely-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.9.0
4
+ version: 3.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Optimizely
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-16 00:00:00.000000000 Z
11
+ date: 2022-01-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -185,7 +185,7 @@ homepage: https://www.optimizely.com/
185
185
  licenses:
186
186
  - Apache-2.0
187
187
  metadata: {}
188
- post_install_message:
188
+ post_install_message:
189
189
  rdoc_options: []
190
190
  require_paths:
191
191
  - lib
@@ -200,8 +200,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
200
200
  - !ruby/object:Gem::Version
201
201
  version: '0'
202
202
  requirements: []
203
- rubygems_version: 3.0.1
204
- signing_key:
203
+ rubygems_version: 3.2.32
204
+ signing_key:
205
205
  specification_version: 4
206
206
  summary: Ruby SDK for Optimizely's testing framework
207
207
  test_files: []