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 +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: []
|