optimizely-sdk 5.0.1 → 5.1.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/decision_service.rb +48 -22
- data/lib/optimizely/helpers/validator.rb +2 -2
- data/lib/optimizely/optimizely_factory.rb +0 -1
- data/lib/optimizely/user_profile_tracker.rb +64 -0
- data/lib/optimizely/version.rb +1 -1
- data/lib/optimizely.rb +119 -55
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 79f6fce62ba26147eef50a92fefb2cb443df50b2b3a0f4c15f75352f4052aaa8
|
4
|
+
data.tar.gz: d09bfd23ad927fa5ae62c03467647c65bc21ac68d19d88dc81ef6dea441a25bf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 74e1e587ffdcadfcbbea0677d0de60376e7f677a8254d1e121d37994301c5ebd1959e21ff7bfea1493a441835a20e135dc4d4a4f1577939772b6e167d35f2789
|
7
|
+
data.tar.gz: 57c17f7508daeaed88755b9dc4463f019fb5b5a523c32cf5639375c06a07dd563786001282e59496d5e8c149c5d884ea5c16522d52edef03c40a0b9ff1890879
|
@@ -52,17 +52,20 @@ module Optimizely
|
|
52
52
|
@forced_variation_map = {}
|
53
53
|
end
|
54
54
|
|
55
|
-
def get_variation(project_config, experiment_id, user_context, decide_options = [])
|
55
|
+
def get_variation(project_config, experiment_id, user_context, user_profile_tracker = nil, decide_options = [], reasons = [])
|
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
60
|
# user_context - Optimizely user context instance
|
61
|
+
# user_profile_tracker: Tracker for reading and updating user profile of the user.
|
62
|
+
# reasons: Decision reasons.
|
61
63
|
#
|
62
64
|
# Returns variation ID where visitor will be bucketed
|
63
65
|
# (nil if experiment is inactive or user does not meet audience conditions)
|
64
|
-
|
66
|
+
user_profile_tracker = UserProfileTracker.new(user_context.user_id, @user_profile_service, @logger) unless user_profile_tracker.is_a?(Optimizely::UserProfileTracker)
|
65
67
|
decide_reasons = []
|
68
|
+
decide_reasons.push(*reasons)
|
66
69
|
user_id = user_context.user_id
|
67
70
|
attributes = user_context.user_attributes
|
68
71
|
# By default, the bucketing ID should be the user ID
|
@@ -92,10 +95,8 @@ module Optimizely
|
|
92
95
|
|
93
96
|
should_ignore_user_profile_service = decide_options.include? Optimizely::Decide::OptimizelyDecideOption::IGNORE_USER_PROFILE_SERVICE
|
94
97
|
# Check for saved bucketing decisions if decide_options do not include ignoreUserProfileService
|
95
|
-
unless should_ignore_user_profile_service
|
96
|
-
|
97
|
-
decide_reasons.push(*reasons_received)
|
98
|
-
saved_variation_id, reasons_received = get_saved_variation_id(project_config, experiment_id, user_profile)
|
98
|
+
unless should_ignore_user_profile_service && user_profile_tracker
|
99
|
+
saved_variation_id, reasons_received = get_saved_variation_id(project_config, experiment_id, user_profile_tracker.user_profile)
|
99
100
|
decide_reasons.push(*reasons_received)
|
100
101
|
if saved_variation_id
|
101
102
|
message = "Returning previously activated variation ID #{saved_variation_id} of experiment '#{experiment_key}' for user '#{user_id}' from user profile."
|
@@ -131,7 +132,7 @@ module Optimizely
|
|
131
132
|
decide_reasons.push(message)
|
132
133
|
|
133
134
|
# Persist bucketing decision
|
134
|
-
|
135
|
+
user_profile_tracker.update_user_profile(experiment_id, variation_id) unless should_ignore_user_profile_service && user_profile_tracker
|
135
136
|
[variation_id, decide_reasons]
|
136
137
|
end
|
137
138
|
|
@@ -143,21 +144,46 @@ module Optimizely
|
|
143
144
|
# user_context - Optimizely user context instance
|
144
145
|
#
|
145
146
|
# Returns Decision struct (nil if the user is not bucketed into any of the experiments on the feature)
|
147
|
+
get_variations_for_feature_list(project_config, [feature_flag], user_context, decide_options).first
|
148
|
+
end
|
146
149
|
|
147
|
-
|
148
|
-
|
149
|
-
#
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
150
|
+
def get_variations_for_feature_list(project_config, feature_flags, user_context, decide_options = [])
|
151
|
+
# Returns the list of experiment/variation the user is bucketed in for the given list of features.
|
152
|
+
#
|
153
|
+
# Args:
|
154
|
+
# project_config: Instance of ProjectConfig.
|
155
|
+
# feature_flags: Array of features for which we are determining if it is enabled or not for the given user.
|
156
|
+
# user_context: User context for user.
|
157
|
+
# decide_options: Decide options.
|
158
|
+
#
|
159
|
+
# Returns:
|
160
|
+
# Array of Decision struct.
|
161
|
+
ignore_ups = decide_options.include? Optimizely::Decide::OptimizelyDecideOption::IGNORE_USER_PROFILE_SERVICE
|
162
|
+
user_profile_tracker = nil
|
163
|
+
unless ignore_ups && @user_profile_service
|
164
|
+
user_profile_tracker = UserProfileTracker.new(user_context.user_id, @user_profile_service, @logger)
|
165
|
+
user_profile_tracker.load_user_profile
|
166
|
+
end
|
167
|
+
decisions = []
|
168
|
+
feature_flags.each do |feature_flag|
|
169
|
+
decide_reasons = []
|
170
|
+
# check if the feature is being experiment on and whether the user is bucketed into the experiment
|
171
|
+
decision, reasons_received = get_variation_for_feature_experiment(project_config, feature_flag, user_context, user_profile_tracker, decide_options)
|
172
|
+
decide_reasons.push(*reasons_received)
|
173
|
+
if decision
|
174
|
+
decisions << [decision, decide_reasons]
|
175
|
+
else
|
176
|
+
# Proceed to rollout if the decision is nil
|
177
|
+
rollout_decision, reasons_received = get_variation_for_feature_rollout(project_config, feature_flag, user_context)
|
178
|
+
decide_reasons.push(*reasons_received)
|
179
|
+
decisions << [rollout_decision, decide_reasons]
|
180
|
+
end
|
181
|
+
end
|
182
|
+
user_profile_tracker&.save_user_profile
|
183
|
+
decisions
|
158
184
|
end
|
159
185
|
|
160
|
-
def get_variation_for_feature_experiment(project_config, feature_flag, user_context, decide_options = [])
|
186
|
+
def get_variation_for_feature_experiment(project_config, feature_flag, user_context, user_profile_tracker, decide_options = [])
|
161
187
|
# Gets the variation the user is bucketed into for the feature flag's experiment.
|
162
188
|
#
|
163
189
|
# project_config - project_config - Instance of ProjectConfig
|
@@ -187,7 +213,7 @@ module Optimizely
|
|
187
213
|
end
|
188
214
|
|
189
215
|
experiment_id = experiment['id']
|
190
|
-
variation_id, reasons_received = get_variation_from_experiment_rule(project_config, feature_flag_key, experiment, user_context, decide_options)
|
216
|
+
variation_id, reasons_received = get_variation_from_experiment_rule(project_config, feature_flag_key, experiment, user_context, user_profile_tracker, decide_options)
|
191
217
|
decide_reasons.push(*reasons_received)
|
192
218
|
|
193
219
|
next unless variation_id
|
@@ -252,7 +278,7 @@ module Optimizely
|
|
252
278
|
[nil, decide_reasons]
|
253
279
|
end
|
254
280
|
|
255
|
-
def get_variation_from_experiment_rule(project_config, flag_key, rule, user, options = [])
|
281
|
+
def get_variation_from_experiment_rule(project_config, flag_key, rule, user, user_profile_tracker, options = [])
|
256
282
|
# Determine which variation the user is in for a given rollout.
|
257
283
|
# Returns the variation from experiment rules.
|
258
284
|
#
|
@@ -270,7 +296,7 @@ module Optimizely
|
|
270
296
|
|
271
297
|
return [variation['id'], reasons] if variation
|
272
298
|
|
273
|
-
variation_id, response_reasons = get_variation(project_config, rule['id'], user, options)
|
299
|
+
variation_id, response_reasons = get_variation(project_config, rule['id'], user, user_profile_tracker, options)
|
274
300
|
reasons.push(*response_reasons)
|
275
301
|
|
276
302
|
[variation_id, reasons]
|
@@ -122,11 +122,11 @@ module Optimizely
|
|
122
122
|
|
123
123
|
return false unless variables.respond_to?(:each) && !variables.empty?
|
124
124
|
|
125
|
-
is_valid = true
|
125
|
+
is_valid = true # rubocop:disable Lint/UselessAssignment
|
126
126
|
if variables.include? :user_id
|
127
127
|
# Empty str is a valid user ID.
|
128
128
|
unless variables[:user_id].is_a?(String)
|
129
|
-
is_valid = false
|
129
|
+
is_valid = false # rubocop:disable Lint/UselessAssignment
|
130
130
|
logger.log(level, "#{Constants::INPUT_VARIABLES['USER_ID']} is invalid")
|
131
131
|
end
|
132
132
|
variables.delete :user_id
|
@@ -142,7 +142,6 @@ module Optimizely
|
|
142
142
|
notification_center = nil,
|
143
143
|
settings = nil
|
144
144
|
)
|
145
|
-
|
146
145
|
error_handler ||= NoOpErrorHandler.new
|
147
146
|
logger ||= NoOpLogger.new
|
148
147
|
notification_center = notification_center.is_a?(Optimizely::NotificationCenter) ? notification_center : NotificationCenter.new(logger, error_handler)
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'logger'
|
4
|
+
|
5
|
+
module Optimizely
|
6
|
+
class UserProfileTracker
|
7
|
+
attr_reader :user_profile
|
8
|
+
|
9
|
+
def initialize(user_id, user_profile_service = nil, logger = nil)
|
10
|
+
@user_id = user_id
|
11
|
+
@user_profile_service = user_profile_service
|
12
|
+
@logger = logger || NoOpLogger.new
|
13
|
+
@profile_updated = false
|
14
|
+
@user_profile = {
|
15
|
+
user_id: user_id,
|
16
|
+
experiment_bucket_map: {}
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def load_user_profile(reasons = [], error_handler = nil)
|
21
|
+
return if reasons.nil?
|
22
|
+
|
23
|
+
begin
|
24
|
+
@user_profile = @user_profile_service.lookup(@user_id) if @user_profile_service
|
25
|
+
if @user_profile.nil?
|
26
|
+
@user_profile = {
|
27
|
+
user_id: @user_id,
|
28
|
+
experiment_bucket_map: {}
|
29
|
+
}
|
30
|
+
end
|
31
|
+
rescue => e
|
32
|
+
message = "Error while looking up user profile for user ID '#{@user_id}': #{e}."
|
33
|
+
reasons << message
|
34
|
+
@logger.log(Logger::ERROR, message)
|
35
|
+
error_handler&.handle_error(e)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def update_user_profile(experiment_id, variation_id)
|
40
|
+
user_id = @user_profile[:user_id]
|
41
|
+
begin
|
42
|
+
@user_profile[:experiment_bucket_map][experiment_id] = {
|
43
|
+
variation_id: variation_id
|
44
|
+
}
|
45
|
+
@profile_updated = true
|
46
|
+
@logger.log(Logger::INFO, "Updated variation ID #{variation_id} of experiment ID #{experiment_id} for user '#{user_id}'.")
|
47
|
+
rescue => e
|
48
|
+
@logger.log(Logger::ERROR, "Error while updating user profile for user ID '#{user_id}': #{e}.")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def save_user_profile(error_handler = nil)
|
53
|
+
return unless @profile_updated && @user_profile_service
|
54
|
+
|
55
|
+
begin
|
56
|
+
@user_profile_service.save(@user_profile)
|
57
|
+
@logger.log(Logger::INFO, "Saved user profile for user '#{@user_profile[:user_id]}'.")
|
58
|
+
rescue => e
|
59
|
+
@logger.log(Logger::ERROR, "Failed to save user profile for user '#{@user_profile[:user_id]}': #{e}.")
|
60
|
+
error_handler&.handle_error(e)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/optimizely/version.rb
CHANGED
data/lib/optimizely.rb
CHANGED
@@ -42,6 +42,7 @@ require_relative 'optimizely/optimizely_user_context'
|
|
42
42
|
require_relative 'optimizely/odp/lru_cache'
|
43
43
|
require_relative 'optimizely/odp/odp_manager'
|
44
44
|
require_relative 'optimizely/helpers/sdk_settings'
|
45
|
+
require_relative 'optimizely/user_profile_tracker'
|
45
46
|
|
46
47
|
module Optimizely
|
47
48
|
class Project
|
@@ -172,65 +173,18 @@ module Optimizely
|
|
172
173
|
OptimizelyUserContext.new(self, user_id, attributes)
|
173
174
|
end
|
174
175
|
|
175
|
-
def
|
176
|
-
# raising on user context as it is internal and not provided directly by the user.
|
177
|
-
raise if user_context.class != OptimizelyUserContext
|
178
|
-
|
179
|
-
reasons = []
|
180
|
-
|
181
|
-
# check if SDK is ready
|
182
|
-
unless is_valid
|
183
|
-
@logger.log(Logger::ERROR, InvalidProjectConfigError.new('decide').message)
|
184
|
-
reasons.push(OptimizelyDecisionMessage::SDK_NOT_READY)
|
185
|
-
return OptimizelyDecision.new(flag_key: key, user_context: user_context, reasons: reasons)
|
186
|
-
end
|
187
|
-
|
188
|
-
# validate that key is a string
|
189
|
-
unless key.is_a?(String)
|
190
|
-
@logger.log(Logger::ERROR, 'Provided key is invalid')
|
191
|
-
reasons.push(format(OptimizelyDecisionMessage::FLAG_KEY_INVALID, key))
|
192
|
-
return OptimizelyDecision.new(flag_key: key, user_context: user_context, reasons: reasons)
|
193
|
-
end
|
194
|
-
|
195
|
-
# validate that key maps to a feature flag
|
196
|
-
config = project_config
|
197
|
-
feature_flag = config.get_feature_flag_from_key(key)
|
198
|
-
unless feature_flag
|
199
|
-
@logger.log(Logger::ERROR, "No feature flag was found for key '#{key}'.")
|
200
|
-
reasons.push(format(OptimizelyDecisionMessage::FLAG_KEY_INVALID, key))
|
201
|
-
return OptimizelyDecision.new(flag_key: key, user_context: user_context, reasons: reasons)
|
202
|
-
end
|
203
|
-
|
204
|
-
# merge decide_options and default_decide_options
|
205
|
-
if decide_options.is_a? Array
|
206
|
-
decide_options += @default_decide_options
|
207
|
-
else
|
208
|
-
@logger.log(Logger::DEBUG, 'Provided decide options is not an array. Using default decide options.')
|
209
|
-
decide_options = @default_decide_options
|
210
|
-
end
|
211
|
-
|
176
|
+
def create_optimizely_decision(user_context, flag_key, decision, reasons, decide_options, config)
|
212
177
|
# Create Optimizely Decision Result.
|
213
178
|
user_id = user_context.user_id
|
214
179
|
attributes = user_context.user_attributes
|
215
180
|
variation_key = nil
|
216
181
|
feature_enabled = false
|
217
182
|
rule_key = nil
|
218
|
-
flag_key = key
|
219
183
|
all_variables = {}
|
220
184
|
decision_event_dispatched = false
|
185
|
+
feature_flag = config.get_feature_flag_from_key(flag_key)
|
221
186
|
experiment = nil
|
222
187
|
decision_source = Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']
|
223
|
-
context = Optimizely::OptimizelyUserContext::OptimizelyDecisionContext.new(key, nil)
|
224
|
-
variation, reasons_received = @decision_service.validated_forced_decision(config, context, user_context)
|
225
|
-
reasons.push(*reasons_received)
|
226
|
-
|
227
|
-
if variation
|
228
|
-
decision = Optimizely::DecisionService::Decision.new(nil, variation, Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST'])
|
229
|
-
else
|
230
|
-
decision, reasons_received = @decision_service.get_variation_for_feature(config, feature_flag, user_context, decide_options)
|
231
|
-
reasons.push(*reasons_received)
|
232
|
-
end
|
233
|
-
|
234
188
|
# Send impression event if Decision came from a feature test and decide options doesn't include disableDecisionEvent
|
235
189
|
if decision.is_a?(Optimizely::DecisionService::Decision)
|
236
190
|
experiment = decision.experiment
|
@@ -249,7 +203,7 @@ module Optimizely
|
|
249
203
|
# Generate all variables map if decide options doesn't include excludeVariables
|
250
204
|
unless decide_options.include? OptimizelyDecideOption::EXCLUDE_VARIABLES
|
251
205
|
feature_flag['variables'].each do |variable|
|
252
|
-
variable_value = get_feature_variable_for_variation(
|
206
|
+
variable_value = get_feature_variable_for_variation(flag_key, feature_enabled, variation, variable, user_id)
|
253
207
|
all_variables[variable['key']] = Helpers::VariableType.cast_value_to_type(variable_value, variable['type'], @logger)
|
254
208
|
end
|
255
209
|
end
|
@@ -281,6 +235,47 @@ module Optimizely
|
|
281
235
|
)
|
282
236
|
end
|
283
237
|
|
238
|
+
def decide(user_context, key, decide_options = [])
|
239
|
+
# raising on user context as it is internal and not provided directly by the user.
|
240
|
+
raise if user_context.class != OptimizelyUserContext
|
241
|
+
|
242
|
+
reasons = []
|
243
|
+
|
244
|
+
# check if SDK is ready
|
245
|
+
unless is_valid
|
246
|
+
@logger.log(Logger::ERROR, InvalidProjectConfigError.new('decide').message)
|
247
|
+
reasons.push(OptimizelyDecisionMessage::SDK_NOT_READY)
|
248
|
+
return OptimizelyDecision.new(flag_key: key, user_context: user_context, reasons: reasons)
|
249
|
+
end
|
250
|
+
|
251
|
+
# validate that key is a string
|
252
|
+
unless key.is_a?(String)
|
253
|
+
@logger.log(Logger::ERROR, 'Provided key is invalid')
|
254
|
+
reasons.push(format(OptimizelyDecisionMessage::FLAG_KEY_INVALID, key))
|
255
|
+
return OptimizelyDecision.new(flag_key: key, user_context: user_context, reasons: reasons)
|
256
|
+
end
|
257
|
+
|
258
|
+
# validate that key maps to a feature flag
|
259
|
+
config = project_config
|
260
|
+
feature_flag = config.get_feature_flag_from_key(key)
|
261
|
+
unless feature_flag
|
262
|
+
@logger.log(Logger::ERROR, "No feature flag was found for key '#{key}'.")
|
263
|
+
reasons.push(format(OptimizelyDecisionMessage::FLAG_KEY_INVALID, key))
|
264
|
+
return OptimizelyDecision.new(flag_key: key, user_context: user_context, reasons: reasons)
|
265
|
+
end
|
266
|
+
|
267
|
+
# merge decide_options and default_decide_options
|
268
|
+
if decide_options.is_a? Array
|
269
|
+
decide_options += @default_decide_options
|
270
|
+
else
|
271
|
+
@logger.log(Logger::DEBUG, 'Provided decide options is not an array. Using default decide options.')
|
272
|
+
decide_options = @default_decide_options
|
273
|
+
end
|
274
|
+
|
275
|
+
decide_options.delete(OptimizelyDecideOption::ENABLED_FLAGS_ONLY) if decide_options.include?(OptimizelyDecideOption::ENABLED_FLAGS_ONLY)
|
276
|
+
decide_for_keys(user_context, [key], decide_options, true)[key]
|
277
|
+
end
|
278
|
+
|
284
279
|
def decide_all(user_context, decide_options = [])
|
285
280
|
# raising on user context as it is internal and not provided directly by the user.
|
286
281
|
raise if user_context.class != OptimizelyUserContext
|
@@ -298,7 +293,7 @@ module Optimizely
|
|
298
293
|
decide_for_keys(user_context, keys, decide_options)
|
299
294
|
end
|
300
295
|
|
301
|
-
def decide_for_keys(user_context, keys, decide_options = [])
|
296
|
+
def decide_for_keys(user_context, keys, decide_options = [], ignore_default_options = false) # rubocop:disable Style/OptionalBooleanParameter
|
302
297
|
# raising on user context as it is internal and not provided directly by the user.
|
303
298
|
raise if user_context.class != OptimizelyUserContext
|
304
299
|
|
@@ -308,13 +303,79 @@ module Optimizely
|
|
308
303
|
return {}
|
309
304
|
end
|
310
305
|
|
311
|
-
|
306
|
+
# merge decide_options and default_decide_options
|
307
|
+
unless ignore_default_options
|
308
|
+
if decide_options.is_a?(Array)
|
309
|
+
decide_options += @default_decide_options
|
310
|
+
else
|
311
|
+
@logger.log(Logger::DEBUG, 'Provided decide options is not an array. Using default decide options.')
|
312
|
+
decide_options = @default_decide_options
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
# enabled_flags_only = (!decide_options.nil? && (decide_options.include? OptimizelyDecideOption::ENABLED_FLAGS_ONLY)) || (@default_decide_options.include? OptimizelyDecideOption::ENABLED_FLAGS_ONLY)
|
312
317
|
|
313
318
|
decisions = {}
|
319
|
+
valid_keys = []
|
320
|
+
decision_reasons_dict = {}
|
321
|
+
config = project_config
|
322
|
+
return decisions unless config
|
323
|
+
|
324
|
+
flags_without_forced_decision = []
|
325
|
+
flag_decisions = {}
|
326
|
+
|
314
327
|
keys.each do |key|
|
315
|
-
|
316
|
-
|
328
|
+
# Retrieve the feature flag from the project's feature flag key map
|
329
|
+
feature_flag = config.feature_flag_key_map[key]
|
330
|
+
|
331
|
+
# If the feature flag is nil, create a default OptimizelyDecision and move to the next key
|
332
|
+
if feature_flag.nil?
|
333
|
+
decisions[key] = OptimizelyDecision.new(nil, false, nil, nil, key, user_context, [])
|
334
|
+
next
|
335
|
+
end
|
336
|
+
valid_keys.push(key)
|
337
|
+
decision_reasons = []
|
338
|
+
decision_reasons_dict[key] = decision_reasons
|
339
|
+
|
340
|
+
config = project_config
|
341
|
+
context = Optimizely::OptimizelyUserContext::OptimizelyDecisionContext.new(key, nil)
|
342
|
+
variation, reasons_received = @decision_service.validated_forced_decision(config, context, user_context)
|
343
|
+
decision_reasons_dict[key].push(*reasons_received)
|
344
|
+
if variation
|
345
|
+
decision = Optimizely::DecisionService::Decision.new(nil, variation, Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST'])
|
346
|
+
flag_decisions[key] = decision
|
347
|
+
else
|
348
|
+
flags_without_forced_decision.push(feature_flag)
|
349
|
+
end
|
317
350
|
end
|
351
|
+
decision_list = @decision_service.get_variations_for_feature_list(config, flags_without_forced_decision, user_context, decide_options)
|
352
|
+
|
353
|
+
flags_without_forced_decision.each_with_index do |flag, i|
|
354
|
+
decision = decision_list[i][0]
|
355
|
+
reasons = decision_list[i][1]
|
356
|
+
flag_key = flag['key']
|
357
|
+
flag_decisions[flag_key] = decision
|
358
|
+
decision_reasons_dict[flag_key] ||= []
|
359
|
+
decision_reasons_dict[flag_key].push(*reasons)
|
360
|
+
end
|
361
|
+
valid_keys.each do |key|
|
362
|
+
flag_decision = flag_decisions[key]
|
363
|
+
decision_reasons = decision_reasons_dict[key]
|
364
|
+
optimizely_decision = create_optimizely_decision(
|
365
|
+
user_context,
|
366
|
+
key,
|
367
|
+
flag_decision,
|
368
|
+
decision_reasons,
|
369
|
+
decide_options,
|
370
|
+
config
|
371
|
+
)
|
372
|
+
|
373
|
+
enabled_flags_only_missing = !decide_options.include?(OptimizelyDecideOption::ENABLED_FLAGS_ONLY)
|
374
|
+
is_enabled = optimizely_decision.enabled
|
375
|
+
|
376
|
+
decisions[key] = optimizely_decision if enabled_flags_only_missing || is_enabled
|
377
|
+
end
|
378
|
+
|
318
379
|
decisions
|
319
380
|
end
|
320
381
|
|
@@ -959,7 +1020,10 @@ module Optimizely
|
|
959
1020
|
return nil unless user_inputs_valid?(attributes)
|
960
1021
|
|
961
1022
|
user_context = OptimizelyUserContext.new(self, user_id, attributes, identify: false)
|
962
|
-
|
1023
|
+
user_profile_tracker = UserProfileTracker.new(user_id, @user_profile_service, @logger)
|
1024
|
+
user_profile_tracker.load_user_profile
|
1025
|
+
variation_id, = @decision_service.get_variation(config, experiment_id, user_context, user_profile_tracker)
|
1026
|
+
user_profile_tracker.save_user_profile
|
963
1027
|
variation = config.get_variation_from_id(experiment_key, variation_id) unless variation_id.nil?
|
964
1028
|
variation_key = variation['key'] if variation
|
965
1029
|
decision_notification_type = if config.feature_experiment?(experiment_id)
|
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: 5.0
|
4
|
+
version: 5.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Optimizely
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-01-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -191,6 +191,7 @@ files:
|
|
191
191
|
- lib/optimizely/semantic_version.rb
|
192
192
|
- lib/optimizely/user_condition_evaluator.rb
|
193
193
|
- lib/optimizely/user_profile_service.rb
|
194
|
+
- lib/optimizely/user_profile_tracker.rb
|
194
195
|
- lib/optimizely/version.rb
|
195
196
|
homepage: https://github.com/optimizely/ruby-sdk
|
196
197
|
licenses:
|
@@ -213,7 +214,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
213
214
|
- !ruby/object:Gem::Version
|
214
215
|
version: '0'
|
215
216
|
requirements: []
|
216
|
-
rubygems_version: 3.4.
|
217
|
+
rubygems_version: 3.4.19
|
217
218
|
signing_key:
|
218
219
|
specification_version: 4
|
219
220
|
summary: Ruby SDK for Optimizely's testing framework
|