optimizely-sdk 5.0.1 → 5.1.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/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
|