optimizely-sdk 3.4.0 → 3.8.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.rb +383 -49
- data/lib/optimizely/audience.rb +31 -43
- data/lib/optimizely/bucketer.rb +36 -33
- data/lib/optimizely/config/datafile_project_config.rb +19 -3
- data/lib/optimizely/config/proxy_config.rb +34 -0
- data/lib/optimizely/config_manager/async_scheduler.rb +6 -2
- data/lib/optimizely/config_manager/http_project_config_manager.rb +40 -23
- data/lib/optimizely/custom_attribute_condition_evaluator.rb +133 -37
- data/lib/optimizely/decide/optimizely_decide_option.rb +28 -0
- data/lib/optimizely/decide/optimizely_decision.rb +60 -0
- data/lib/optimizely/decide/optimizely_decision_message.rb +26 -0
- data/lib/optimizely/decision_service.rb +163 -139
- data/lib/optimizely/event/entity/decision.rb +6 -4
- data/lib/optimizely/event/entity/impression_event.rb +4 -2
- data/lib/optimizely/event/event_factory.rb +4 -3
- data/lib/optimizely/event/user_event_factory.rb +4 -3
- data/lib/optimizely/event_dispatcher.rb +8 -14
- data/lib/optimizely/exceptions.rb +17 -9
- data/lib/optimizely/helpers/constants.rb +19 -5
- data/lib/optimizely/helpers/http_utils.rb +64 -0
- data/lib/optimizely/helpers/variable_type.rb +8 -1
- data/lib/optimizely/optimizely_config.rb +2 -1
- data/lib/optimizely/optimizely_factory.rb +54 -5
- data/lib/optimizely/optimizely_user_context.rb +107 -0
- data/lib/optimizely/project_config.rb +5 -1
- data/lib/optimizely/semantic_version.rb +166 -0
- data/lib/optimizely/version.rb +1 -1
- metadata +9 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 62e8da2296d75e8f0cad746e8eca25c2670bde6bac084a840c46266322dc378f
|
4
|
+
data.tar.gz: 67d25b1695a294616b6dc62c7fa1d822c5262f273626d8c3c5a701e78b8e118f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8891366e7765049a20b552eff7bd070c51eacf635664096235de97c9858f039410f23a5a3e6a902aee2269008cc50c1a45b22026c831843962c3ca04aa9f35af
|
7
|
+
data.tar.gz: 358139de2ecbbe9c6b2f4473bee6e56b784d22ef0e4067fc20a7f0e85a8d7cf293a6fe3cf7b9f4f1dbcf17c463392f8cedad62a7ad651f2fb70ecaee59fd5391
|
data/lib/optimizely.rb
CHANGED
@@ -19,6 +19,9 @@ require_relative 'optimizely/audience'
|
|
19
19
|
require_relative 'optimizely/config/datafile_project_config'
|
20
20
|
require_relative 'optimizely/config_manager/http_project_config_manager'
|
21
21
|
require_relative 'optimizely/config_manager/static_project_config_manager'
|
22
|
+
require_relative 'optimizely/decide/optimizely_decide_option'
|
23
|
+
require_relative 'optimizely/decide/optimizely_decision'
|
24
|
+
require_relative 'optimizely/decide/optimizely_decision_message'
|
22
25
|
require_relative 'optimizely/decision_service'
|
23
26
|
require_relative 'optimizely/error_handler'
|
24
27
|
require_relative 'optimizely/event_builder'
|
@@ -34,9 +37,12 @@ require_relative 'optimizely/helpers/variable_type'
|
|
34
37
|
require_relative 'optimizely/logger'
|
35
38
|
require_relative 'optimizely/notification_center'
|
36
39
|
require_relative 'optimizely/optimizely_config'
|
40
|
+
require_relative 'optimizely/optimizely_user_context'
|
37
41
|
|
38
42
|
module Optimizely
|
39
43
|
class Project
|
44
|
+
include Optimizely::Decide
|
45
|
+
|
40
46
|
attr_reader :notification_center
|
41
47
|
# @api no-doc
|
42
48
|
attr_reader :config_manager, :decision_service, :error_handler, :event_dispatcher,
|
@@ -67,12 +73,21 @@ module Optimizely
|
|
67
73
|
sdk_key = nil,
|
68
74
|
config_manager = nil,
|
69
75
|
notification_center = nil,
|
70
|
-
event_processor = nil
|
76
|
+
event_processor = nil,
|
77
|
+
default_decide_options = []
|
71
78
|
)
|
72
79
|
@logger = logger || NoOpLogger.new
|
73
80
|
@error_handler = error_handler || NoOpErrorHandler.new
|
74
81
|
@event_dispatcher = event_dispatcher || EventDispatcher.new(logger: @logger, error_handler: @error_handler)
|
75
82
|
@user_profile_service = user_profile_service
|
83
|
+
@default_decide_options = []
|
84
|
+
|
85
|
+
if default_decide_options.is_a? Array
|
86
|
+
@default_decide_options = default_decide_options.clone
|
87
|
+
else
|
88
|
+
@logger.log(Logger::DEBUG, 'Provided default decide options is not an array.')
|
89
|
+
@default_decide_options = []
|
90
|
+
end
|
76
91
|
|
77
92
|
begin
|
78
93
|
validate_instantiation_options
|
@@ -107,6 +122,175 @@ module Optimizely
|
|
107
122
|
end
|
108
123
|
end
|
109
124
|
|
125
|
+
# Create a context of the user for which decision APIs will be called.
|
126
|
+
#
|
127
|
+
# A user context will be created successfully even when the SDK is not fully configured yet.
|
128
|
+
#
|
129
|
+
# @param user_id - The user ID to be used for bucketing.
|
130
|
+
# @param attributes - A Hash representing user attribute names and values.
|
131
|
+
#
|
132
|
+
# @return [OptimizelyUserContext] An OptimizelyUserContext associated with this OptimizelyClient.
|
133
|
+
# @return [nil] If user attributes are not in valid format.
|
134
|
+
|
135
|
+
def create_user_context(user_id, attributes = nil)
|
136
|
+
# We do not check for is_valid here as a user context can be created successfully
|
137
|
+
# even when the SDK is not fully configured.
|
138
|
+
|
139
|
+
# validate user_id
|
140
|
+
return nil unless Optimizely::Helpers::Validator.inputs_valid?(
|
141
|
+
{
|
142
|
+
user_id: user_id
|
143
|
+
}, @logger, Logger::ERROR
|
144
|
+
)
|
145
|
+
|
146
|
+
# validate attributes
|
147
|
+
return nil unless user_inputs_valid?(attributes)
|
148
|
+
|
149
|
+
user_context = OptimizelyUserContext.new(self, user_id, attributes)
|
150
|
+
user_context
|
151
|
+
end
|
152
|
+
|
153
|
+
def decide(user_context, key, decide_options = [])
|
154
|
+
# raising on user context as it is internal and not provided directly by the user.
|
155
|
+
raise if user_context.class != OptimizelyUserContext
|
156
|
+
|
157
|
+
reasons = []
|
158
|
+
|
159
|
+
# check if SDK is ready
|
160
|
+
unless is_valid
|
161
|
+
@logger.log(Logger::ERROR, InvalidProjectConfigError.new('decide').message)
|
162
|
+
reasons.push(OptimizelyDecisionMessage::SDK_NOT_READY)
|
163
|
+
return OptimizelyDecision.new(flag_key: key, user_context: user_context, reasons: reasons)
|
164
|
+
end
|
165
|
+
|
166
|
+
# validate that key is a string
|
167
|
+
unless key.is_a?(String)
|
168
|
+
@logger.log(Logger::ERROR, 'Provided key is invalid')
|
169
|
+
reasons.push(format(OptimizelyDecisionMessage::FLAG_KEY_INVALID, key))
|
170
|
+
return OptimizelyDecision.new(flag_key: key, user_context: user_context, reasons: reasons)
|
171
|
+
end
|
172
|
+
|
173
|
+
# validate that key maps to a feature flag
|
174
|
+
config = project_config
|
175
|
+
feature_flag = config.get_feature_flag_from_key(key)
|
176
|
+
unless feature_flag
|
177
|
+
@logger.log(Logger::ERROR, "No feature flag was found for key '#{key}'.")
|
178
|
+
reasons.push(format(OptimizelyDecisionMessage::FLAG_KEY_INVALID, key))
|
179
|
+
return OptimizelyDecision.new(flag_key: key, user_context: user_context, reasons: reasons)
|
180
|
+
end
|
181
|
+
|
182
|
+
# merge decide_options and default_decide_options
|
183
|
+
if decide_options.is_a? Array
|
184
|
+
decide_options += @default_decide_options
|
185
|
+
else
|
186
|
+
@logger.log(Logger::DEBUG, 'Provided decide options is not an array. Using default decide options.')
|
187
|
+
decide_options = @default_decide_options
|
188
|
+
end
|
189
|
+
|
190
|
+
# Create Optimizely Decision Result.
|
191
|
+
user_id = user_context.user_id
|
192
|
+
attributes = user_context.user_attributes
|
193
|
+
variation_key = nil
|
194
|
+
feature_enabled = false
|
195
|
+
rule_key = nil
|
196
|
+
flag_key = key
|
197
|
+
all_variables = {}
|
198
|
+
decision_event_dispatched = false
|
199
|
+
experiment = nil
|
200
|
+
decision_source = Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']
|
201
|
+
|
202
|
+
decision, reasons_received = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes, decide_options)
|
203
|
+
reasons.push(*reasons_received)
|
204
|
+
|
205
|
+
# Send impression event if Decision came from a feature test and decide options doesn't include disableDecisionEvent
|
206
|
+
if decision.is_a?(Optimizely::DecisionService::Decision)
|
207
|
+
experiment = decision.experiment
|
208
|
+
rule_key = experiment['key']
|
209
|
+
variation = decision['variation']
|
210
|
+
variation_key = variation['key']
|
211
|
+
feature_enabled = variation['featureEnabled']
|
212
|
+
decision_source = decision.source
|
213
|
+
end
|
214
|
+
|
215
|
+
unless decide_options.include? OptimizelyDecideOption::DISABLE_DECISION_EVENT
|
216
|
+
if decision_source == Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST'] || config.send_flag_decisions
|
217
|
+
send_impression(config, experiment, variation_key || '', flag_key, rule_key || '', feature_enabled, decision_source, user_id, attributes)
|
218
|
+
decision_event_dispatched = true
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Generate all variables map if decide options doesn't include excludeVariables
|
223
|
+
unless decide_options.include? OptimizelyDecideOption::EXCLUDE_VARIABLES
|
224
|
+
feature_flag['variables'].each do |variable|
|
225
|
+
variable_value = get_feature_variable_for_variation(key, feature_enabled, variation, variable, user_id)
|
226
|
+
all_variables[variable['key']] = Helpers::VariableType.cast_value_to_type(variable_value, variable['type'], @logger)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
should_include_reasons = decide_options.include? OptimizelyDecideOption::INCLUDE_REASONS
|
231
|
+
|
232
|
+
# Send notification
|
233
|
+
@notification_center.send_notifications(
|
234
|
+
NotificationCenter::NOTIFICATION_TYPES[:DECISION],
|
235
|
+
Helpers::Constants::DECISION_NOTIFICATION_TYPES['FLAG'],
|
236
|
+
user_id, (attributes || {}),
|
237
|
+
flag_key: flag_key,
|
238
|
+
enabled: feature_enabled,
|
239
|
+
variables: all_variables,
|
240
|
+
variation_key: variation_key,
|
241
|
+
rule_key: rule_key,
|
242
|
+
reasons: should_include_reasons ? reasons : [],
|
243
|
+
decision_event_dispatched: decision_event_dispatched
|
244
|
+
)
|
245
|
+
|
246
|
+
OptimizelyDecision.new(
|
247
|
+
variation_key: variation_key,
|
248
|
+
enabled: feature_enabled,
|
249
|
+
variables: all_variables,
|
250
|
+
rule_key: rule_key,
|
251
|
+
flag_key: flag_key,
|
252
|
+
user_context: user_context,
|
253
|
+
reasons: should_include_reasons ? reasons : []
|
254
|
+
)
|
255
|
+
end
|
256
|
+
|
257
|
+
def decide_all(user_context, decide_options = [])
|
258
|
+
# raising on user context as it is internal and not provided directly by the user.
|
259
|
+
raise if user_context.class != OptimizelyUserContext
|
260
|
+
|
261
|
+
# check if SDK is ready
|
262
|
+
unless is_valid
|
263
|
+
@logger.log(Logger::ERROR, InvalidProjectConfigError.new('decide_all').message)
|
264
|
+
return {}
|
265
|
+
end
|
266
|
+
|
267
|
+
keys = []
|
268
|
+
project_config.feature_flags.each do |feature_flag|
|
269
|
+
keys.push(feature_flag['key'])
|
270
|
+
end
|
271
|
+
decide_for_keys(user_context, keys, decide_options)
|
272
|
+
end
|
273
|
+
|
274
|
+
def decide_for_keys(user_context, keys, decide_options = [])
|
275
|
+
# raising on user context as it is internal and not provided directly by the user.
|
276
|
+
raise if user_context.class != OptimizelyUserContext
|
277
|
+
|
278
|
+
# check if SDK is ready
|
279
|
+
unless is_valid
|
280
|
+
@logger.log(Logger::ERROR, InvalidProjectConfigError.new('decide_for_keys').message)
|
281
|
+
return {}
|
282
|
+
end
|
283
|
+
|
284
|
+
enabled_flags_only = (!decide_options.nil? && (decide_options.include? OptimizelyDecideOption::ENABLED_FLAGS_ONLY)) || (@default_decide_options.include? OptimizelyDecideOption::ENABLED_FLAGS_ONLY)
|
285
|
+
|
286
|
+
decisions = {}
|
287
|
+
keys.each do |key|
|
288
|
+
decision = decide(user_context, key, decide_options)
|
289
|
+
decisions[key] = decision unless enabled_flags_only && !decision.enabled
|
290
|
+
end
|
291
|
+
decisions
|
292
|
+
end
|
293
|
+
|
110
294
|
# Buckets visitor and sends impression event to Optimizely.
|
111
295
|
#
|
112
296
|
# @param experiment_key - Experiment which needs to be activated.
|
@@ -140,7 +324,10 @@ module Optimizely
|
|
140
324
|
|
141
325
|
# Create and dispatch impression event
|
142
326
|
experiment = config.get_experiment_from_key(experiment_key)
|
143
|
-
send_impression(
|
327
|
+
send_impression(
|
328
|
+
config, experiment, variation_key, '', experiment_key, true,
|
329
|
+
Optimizely::DecisionService::DECISION_SOURCES['EXPERIMENT'], user_id, attributes
|
330
|
+
)
|
144
331
|
|
145
332
|
variation_key
|
146
333
|
end
|
@@ -219,7 +406,7 @@ module Optimizely
|
|
219
406
|
config = project_config
|
220
407
|
|
221
408
|
forced_variation_key = nil
|
222
|
-
forced_variation = @decision_service.get_forced_variation(config, experiment_key, user_id)
|
409
|
+
forced_variation, = @decision_service.get_forced_variation(config, experiment_key, user_id)
|
223
410
|
forced_variation_key = forced_variation['key'] if forced_variation
|
224
411
|
|
225
412
|
forced_variation_key
|
@@ -303,7 +490,7 @@ module Optimizely
|
|
303
490
|
return false
|
304
491
|
end
|
305
492
|
|
306
|
-
decision = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes)
|
493
|
+
decision, = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes)
|
307
494
|
|
308
495
|
feature_enabled = false
|
309
496
|
source_string = Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']
|
@@ -316,14 +503,23 @@ module Optimizely
|
|
316
503
|
experiment_key: decision.experiment['key'],
|
317
504
|
variation_key: variation['key']
|
318
505
|
}
|
319
|
-
# Send event if Decision came from
|
320
|
-
send_impression(
|
321
|
-
|
322
|
-
|
323
|
-
|
506
|
+
# Send event if Decision came from a feature test.
|
507
|
+
send_impression(
|
508
|
+
config, decision.experiment, variation['key'], feature_flag_key, decision.experiment['key'], feature_enabled, source_string, user_id, attributes
|
509
|
+
)
|
510
|
+
elsif decision.source == Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT'] && config.send_flag_decisions
|
511
|
+
send_impression(
|
512
|
+
config, decision.experiment, variation['key'], feature_flag_key, decision.experiment['key'], feature_enabled, source_string, user_id, attributes
|
513
|
+
)
|
324
514
|
end
|
325
515
|
end
|
326
516
|
|
517
|
+
if decision.nil? && config.send_flag_decisions
|
518
|
+
send_impression(
|
519
|
+
config, nil, '', feature_flag_key, '', feature_enabled, source_string, user_id, attributes
|
520
|
+
)
|
521
|
+
end
|
522
|
+
|
327
523
|
@notification_center.send_notifications(
|
328
524
|
NotificationCenter::NOTIFICATION_TYPES[:DECISION],
|
329
525
|
Helpers::Constants::DECISION_NOTIFICATION_TYPES['FEATURE'],
|
@@ -430,6 +626,32 @@ module Optimizely
|
|
430
626
|
variable_value
|
431
627
|
end
|
432
628
|
|
629
|
+
# Get the Json value of the specified variable in the feature flag in a Dict.
|
630
|
+
#
|
631
|
+
# @param feature_flag_key - String key of feature flag the variable belongs to
|
632
|
+
# @param variable_key - String key of variable for which we are getting the string value
|
633
|
+
# @param user_id - String user ID
|
634
|
+
# @param attributes - Hash representing visitor attributes and values which need to be recorded.
|
635
|
+
#
|
636
|
+
# @return [Dict] the Dict containing variable value.
|
637
|
+
# @return [nil] if the feature flag or variable are not found.
|
638
|
+
|
639
|
+
def get_feature_variable_json(feature_flag_key, variable_key, user_id, attributes = nil)
|
640
|
+
unless is_valid
|
641
|
+
@logger.log(Logger::ERROR, InvalidProjectConfigError.new('get_feature_variable_json').message)
|
642
|
+
return nil
|
643
|
+
end
|
644
|
+
variable_value = get_feature_variable_for_type(
|
645
|
+
feature_flag_key,
|
646
|
+
variable_key,
|
647
|
+
Optimizely::Helpers::Constants::VARIABLE_TYPES['JSON'],
|
648
|
+
user_id,
|
649
|
+
attributes
|
650
|
+
)
|
651
|
+
|
652
|
+
variable_value
|
653
|
+
end
|
654
|
+
|
433
655
|
# Get the Boolean value of the specified variable in the feature flag.
|
434
656
|
#
|
435
657
|
# @param feature_flag_key - String key of feature flag the variable belongs to
|
@@ -484,6 +706,71 @@ module Optimizely
|
|
484
706
|
variable_value
|
485
707
|
end
|
486
708
|
|
709
|
+
# Get values of all the variables in the feature flag and returns them in a Dict
|
710
|
+
#
|
711
|
+
# @param feature_flag_key - String key of feature flag
|
712
|
+
# @param user_id - String user ID
|
713
|
+
# @param attributes - Hash representing visitor attributes and values which need to be recorded.
|
714
|
+
#
|
715
|
+
# @return [Dict] the Dict containing all the varible values
|
716
|
+
# @return [nil] if the feature flag is not found.
|
717
|
+
|
718
|
+
def get_all_feature_variables(feature_flag_key, user_id, attributes = nil)
|
719
|
+
unless is_valid
|
720
|
+
@logger.log(Logger::ERROR, InvalidProjectConfigError.new('get_all_feature_variables').message)
|
721
|
+
return nil
|
722
|
+
end
|
723
|
+
|
724
|
+
return nil unless Optimizely::Helpers::Validator.inputs_valid?(
|
725
|
+
{
|
726
|
+
feature_flag_key: feature_flag_key,
|
727
|
+
user_id: user_id
|
728
|
+
},
|
729
|
+
@logger, Logger::ERROR
|
730
|
+
)
|
731
|
+
|
732
|
+
return nil unless user_inputs_valid?(attributes)
|
733
|
+
|
734
|
+
config = project_config
|
735
|
+
|
736
|
+
feature_flag = config.get_feature_flag_from_key(feature_flag_key)
|
737
|
+
unless feature_flag
|
738
|
+
@logger.log(Logger::INFO, "No feature flag was found for key '#{feature_flag_key}'.")
|
739
|
+
return nil
|
740
|
+
end
|
741
|
+
|
742
|
+
decision, = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes)
|
743
|
+
variation = decision ? decision['variation'] : nil
|
744
|
+
feature_enabled = variation ? variation['featureEnabled'] : false
|
745
|
+
all_variables = {}
|
746
|
+
|
747
|
+
feature_flag['variables'].each do |variable|
|
748
|
+
variable_value = get_feature_variable_for_variation(feature_flag_key, feature_enabled, variation, variable, user_id)
|
749
|
+
all_variables[variable['key']] = Helpers::VariableType.cast_value_to_type(variable_value, variable['type'], @logger)
|
750
|
+
end
|
751
|
+
|
752
|
+
source_string = Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']
|
753
|
+
if decision && decision['source'] == Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
|
754
|
+
source_info = {
|
755
|
+
experiment_key: decision.experiment['key'],
|
756
|
+
variation_key: variation['key']
|
757
|
+
}
|
758
|
+
source_string = Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
|
759
|
+
end
|
760
|
+
|
761
|
+
@notification_center.send_notifications(
|
762
|
+
NotificationCenter::NOTIFICATION_TYPES[:DECISION],
|
763
|
+
Helpers::Constants::DECISION_NOTIFICATION_TYPES['ALL_FEATURE_VARIABLES'], user_id, (attributes || {}),
|
764
|
+
feature_key: feature_flag_key,
|
765
|
+
feature_enabled: feature_enabled,
|
766
|
+
source: source_string,
|
767
|
+
variable_values: all_variables,
|
768
|
+
source_info: source_info || {}
|
769
|
+
)
|
770
|
+
|
771
|
+
all_variables
|
772
|
+
end
|
773
|
+
|
487
774
|
# Get the Integer value of the specified variable in the feature flag.
|
488
775
|
#
|
489
776
|
# @param feature_flag_key - String key of feature flag the variable belongs to
|
@@ -592,7 +879,7 @@ module Optimizely
|
|
592
879
|
|
593
880
|
return nil unless user_inputs_valid?(attributes)
|
594
881
|
|
595
|
-
variation_id = @decision_service.get_variation(config, experiment_key, user_id, attributes)
|
882
|
+
variation_id, = @decision_service.get_variation(config, experiment_key, user_id, attributes)
|
596
883
|
variation = config.get_variation_from_id(experiment_key, variation_id) unless variation_id.nil?
|
597
884
|
variation_key = variation['key'] if variation
|
598
885
|
decision_notification_type = if config.feature_experiment?(experiment['id'])
|
@@ -649,8 +936,6 @@ module Optimizely
|
|
649
936
|
# Error message logged in DatafileProjectConfig- get_feature_flag_from_key
|
650
937
|
return nil if variable.nil?
|
651
938
|
|
652
|
-
feature_enabled = false
|
653
|
-
|
654
939
|
# If variable_type is nil, set it equal to variable['type']
|
655
940
|
variable_type ||= variable['type']
|
656
941
|
# Returns nil if type differs
|
@@ -658,43 +943,24 @@ module Optimizely
|
|
658
943
|
@logger.log(Logger::WARN,
|
659
944
|
"Requested variable as type '#{variable_type}' but variable '#{variable_key}' is of type '#{variable['type']}'.")
|
660
945
|
return nil
|
661
|
-
else
|
662
|
-
source_string = Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']
|
663
|
-
decision = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes)
|
664
|
-
variable_value = variable['defaultValue']
|
665
|
-
if decision
|
666
|
-
variation = decision['variation']
|
667
|
-
if decision['source'] == Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
|
668
|
-
source_info = {
|
669
|
-
experiment_key: decision.experiment['key'],
|
670
|
-
variation_key: variation['key']
|
671
|
-
}
|
672
|
-
source_string = Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
|
673
|
-
end
|
674
|
-
feature_enabled = variation['featureEnabled']
|
675
|
-
if feature_enabled == true
|
676
|
-
variation_variable_usages = config.variation_id_to_variable_usage_map[variation['id']]
|
677
|
-
variable_id = variable['id']
|
678
|
-
if variation_variable_usages&.key?(variable_id)
|
679
|
-
variable_value = variation_variable_usages[variable_id]['value']
|
680
|
-
@logger.log(Logger::INFO,
|
681
|
-
"Got variable value '#{variable_value}' for variable '#{variable_key}' of feature flag '#{feature_flag_key}'.")
|
682
|
-
else
|
683
|
-
@logger.log(Logger::DEBUG,
|
684
|
-
"Variable '#{variable_key}' is not used in variation '#{variation['key']}'. Returning the default variable value '#{variable_value}'.")
|
685
|
-
end
|
686
|
-
else
|
687
|
-
@logger.log(Logger::DEBUG,
|
688
|
-
"Feature '#{feature_flag_key}' for variation '#{variation['key']}' is not enabled. Returning the default variable value '#{variable_value}'.")
|
689
|
-
end
|
690
|
-
else
|
691
|
-
@logger.log(Logger::INFO,
|
692
|
-
"User '#{user_id}' was not bucketed into any variation for feature flag '#{feature_flag_key}'. Returning the default variable value '#{variable_value}'.")
|
693
|
-
end
|
694
946
|
end
|
695
947
|
|
948
|
+
decision, = @decision_service.get_variation_for_feature(config, feature_flag, user_id, attributes)
|
949
|
+
variation = decision ? decision['variation'] : nil
|
950
|
+
feature_enabled = variation ? variation['featureEnabled'] : false
|
951
|
+
|
952
|
+
variable_value = get_feature_variable_for_variation(feature_flag_key, feature_enabled, variation, variable, user_id)
|
696
953
|
variable_value = Helpers::VariableType.cast_value_to_type(variable_value, variable_type, @logger)
|
697
954
|
|
955
|
+
source_string = Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']
|
956
|
+
if decision && decision['source'] == Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
|
957
|
+
source_info = {
|
958
|
+
experiment_key: decision.experiment['key'],
|
959
|
+
variation_key: variation['key']
|
960
|
+
}
|
961
|
+
source_string = Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
|
962
|
+
end
|
963
|
+
|
698
964
|
@notification_center.send_notifications(
|
699
965
|
NotificationCenter::NOTIFICATION_TYPES[:DECISION],
|
700
966
|
Helpers::Constants::DECISION_NOTIFICATION_TYPES['FEATURE_VARIABLE'], user_id, (attributes || {}),
|
@@ -710,6 +976,46 @@ module Optimizely
|
|
710
976
|
variable_value
|
711
977
|
end
|
712
978
|
|
979
|
+
def get_feature_variable_for_variation(feature_flag_key, feature_enabled, variation, variable, user_id)
|
980
|
+
# Helper method to get the non type-casted value for a variable attached to a
|
981
|
+
# feature flag. Returns appropriate variable value depending on whether there
|
982
|
+
# was a matching variation, feature was enabled or not or varible was part of the
|
983
|
+
# available variation or not. Also logs the appropriate message explaining how it
|
984
|
+
# evaluated the value of the variable.
|
985
|
+
#
|
986
|
+
# feature_flag_key - String key of feature flag the variable belongs to
|
987
|
+
# feature_enabled - Boolean indicating if feature is enabled or not
|
988
|
+
# variation - varition returned by decision service
|
989
|
+
# user_id - String user ID
|
990
|
+
#
|
991
|
+
# Returns string value of the variable.
|
992
|
+
|
993
|
+
config = project_config
|
994
|
+
variable_value = variable['defaultValue']
|
995
|
+
if variation
|
996
|
+
if feature_enabled == true
|
997
|
+
variation_variable_usages = config.variation_id_to_variable_usage_map[variation['id']]
|
998
|
+
variable_id = variable['id']
|
999
|
+
if variation_variable_usages&.key?(variable_id)
|
1000
|
+
variable_value = variation_variable_usages[variable_id]['value']
|
1001
|
+
@logger.log(Logger::INFO,
|
1002
|
+
"Got variable value '#{variable_value}' for variable '#{variable['key']}' of feature flag '#{feature_flag_key}'.")
|
1003
|
+
else
|
1004
|
+
@logger.log(Logger::DEBUG,
|
1005
|
+
"Variable value is not defined. Returning the default variable value '#{variable_value}' for variable '#{variable['key']}'.")
|
1006
|
+
|
1007
|
+
end
|
1008
|
+
else
|
1009
|
+
@logger.log(Logger::DEBUG,
|
1010
|
+
"Feature '#{feature_flag_key}' is not enabled for user '#{user_id}'. Returning the default variable value '#{variable_value}'.")
|
1011
|
+
end
|
1012
|
+
else
|
1013
|
+
@logger.log(Logger::INFO,
|
1014
|
+
"User '#{user_id}' was not bucketed into experiment or rollout for feature flag '#{feature_flag_key}'. Returning the default variable value '#{variable_value}'.")
|
1015
|
+
end
|
1016
|
+
variable_value
|
1017
|
+
end
|
1018
|
+
|
713
1019
|
def user_inputs_valid?(attributes = nil, event_tags = nil)
|
714
1020
|
# Helper method to validate user inputs.
|
715
1021
|
#
|
@@ -757,15 +1063,43 @@ module Optimizely
|
|
757
1063
|
raise InvalidInputError, 'event_dispatcher'
|
758
1064
|
end
|
759
1065
|
|
760
|
-
def send_impression(config, experiment, variation_key, user_id, attributes = nil)
|
1066
|
+
def send_impression(config, experiment, variation_key, flag_key, rule_key, enabled, rule_type, user_id, attributes = nil)
|
1067
|
+
if experiment.nil?
|
1068
|
+
experiment = {
|
1069
|
+
'id' => '',
|
1070
|
+
'key' => '',
|
1071
|
+
'layerId' => '',
|
1072
|
+
'status' => '',
|
1073
|
+
'variations' => [],
|
1074
|
+
'trafficAllocation' => [],
|
1075
|
+
'audienceIds' => [],
|
1076
|
+
'audienceConditions' => [],
|
1077
|
+
'forcedVariations' => {}
|
1078
|
+
}
|
1079
|
+
end
|
1080
|
+
|
761
1081
|
experiment_key = experiment['key']
|
762
|
-
|
763
|
-
|
1082
|
+
|
1083
|
+
variation_id = ''
|
1084
|
+
variation_id = config.get_variation_id_from_key(experiment_key, variation_key) if experiment_key != ''
|
1085
|
+
|
1086
|
+
metadata = {
|
1087
|
+
flag_key: flag_key,
|
1088
|
+
rule_key: rule_key,
|
1089
|
+
rule_type: rule_type,
|
1090
|
+
variation_key: variation_key,
|
1091
|
+
enabled: enabled
|
1092
|
+
}
|
1093
|
+
|
1094
|
+
user_event = UserEventFactory.create_impression_event(config, experiment, variation_id, metadata, user_id, attributes)
|
764
1095
|
@event_processor.process(user_event)
|
765
1096
|
return unless @notification_center.notification_count(NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE]).positive?
|
766
1097
|
|
767
1098
|
@logger.log(Logger::INFO, "Activating user '#{user_id}' in experiment '#{experiment_key}'.")
|
768
|
-
|
1099
|
+
|
1100
|
+
experiment = nil if experiment_key == ''
|
1101
|
+
variation = nil
|
1102
|
+
variation = config.get_variation_from_id(experiment_key, variation_id) unless experiment.nil?
|
769
1103
|
log_event = EventFactory.create_log_event(user_event, @logger)
|
770
1104
|
@notification_center.send_notifications(
|
771
1105
|
NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE],
|