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 +4 -4
- data/lib/optimizely/config/datafile_project_config.rb +44 -0
- data/lib/optimizely/decision_service.rb +120 -57
- data/lib/optimizely/optimizely_user_context.rb +74 -2
- data/lib/optimizely/version.rb +1 -1
- data/lib/optimizely.rb +40 -12
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 96375e01eed1e2a237d2c7c22a8c417bdb81fdff82414dd186900f521b13ea7e
|
4
|
+
data.tar.gz: 17d6f554563447c577cc5db13da3e4c5c6ac46f749e6f9756f8030336e6e516a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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-
|
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,
|
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
|
-
#
|
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,
|
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
|
-
#
|
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,
|
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,
|
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,
|
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
|
-
#
|
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 =
|
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,
|
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
|
-
#
|
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
|
-
|
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
|
-
|
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
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
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
|
-
|
259
|
-
|
260
|
-
decide_reasons.push(message)
|
249
|
+
index = skip_to_everyone_else ? (rollout_rules.length - 1) : (index + 1)
|
250
|
+
end
|
261
251
|
|
262
|
-
|
263
|
-
|
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
|
-
|
268
|
-
|
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
|
-
|
271
|
-
|
272
|
-
|
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
|
-
|
275
|
-
|
276
|
-
|
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
|
310
|
+
message = "User '#{user_id}' does not meet the conditions for targeting rule '#{logging_key}'."
|
279
311
|
@logger.log(Logger::DEBUG, message)
|
280
|
-
|
281
|
-
return nil,
|
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
|
-
|
318
|
+
reasons.push(message)
|
319
|
+
bucket_variation, bucket_reasons = @bucketer.bucket(project_config, rule, bucketing_id, user_id)
|
287
320
|
|
288
|
-
|
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
|
-
|
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.
|
data/lib/optimizely/version.rb
CHANGED
data/lib/optimizely.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2016-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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:
|
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.
|
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: []
|