optimizely-sdk 3.4.0 → 3.8.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.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],
|