optimizely-sdk 3.9.0 → 3.10.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.
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: []