optimizely-sdk 5.1.0 → 5.2.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/audience.rb +15 -1
- data/lib/optimizely/bucketer.rb +34 -13
- data/lib/optimizely/cmab/cmab_client.rb +230 -0
- data/lib/optimizely/cmab/cmab_service.rb +218 -0
- data/lib/optimizely/config/datafile_project_config.rb +140 -2
- data/lib/optimizely/config_manager/http_project_config_manager.rb +1 -1
- data/lib/optimizely/decide/optimizely_decide_option.rb +3 -0
- data/lib/optimizely/decide/optimizely_decision.rb +19 -0
- data/lib/optimizely/decision_service.rb +252 -57
- data/lib/optimizely/event/entity/event_context.rb +5 -2
- data/lib/optimizely/event/event_factory.rb +8 -2
- data/lib/optimizely/event/user_event_factory.rb +2 -0
- data/lib/optimizely/event_builder.rb +15 -5
- data/lib/optimizely/exceptions.rb +24 -0
- data/lib/optimizely/helpers/constants.rb +46 -0
- data/lib/optimizely/helpers/sdk_settings.rb +5 -2
- data/lib/optimizely/helpers/validator.rb +2 -2
- data/lib/optimizely/odp/lru_cache.rb +14 -1
- data/lib/optimizely/optimizely_factory.rb +56 -1
- data/lib/optimizely/project_config.rb +6 -0
- data/lib/optimizely/version.rb +1 -1
- data/lib/optimizely.rb +59 -20
- metadata +4 -2
|
@@ -27,12 +27,14 @@ module Optimizely
|
|
|
27
27
|
attr_reader :datafile, :account_id, :attributes, :audiences, :typed_audiences, :events,
|
|
28
28
|
:experiments, :feature_flags, :groups, :project_id, :bot_filtering, :revision,
|
|
29
29
|
:sdk_key, :environment_key, :rollouts, :version, :send_flag_decisions,
|
|
30
|
-
:attribute_key_map, :
|
|
30
|
+
:attribute_key_map, :attribute_id_to_key_map, :attribute_id_map,
|
|
31
|
+
:audience_id_map, :event_key_map, :experiment_feature_map,
|
|
31
32
|
:experiment_id_map, :experiment_key_map, :feature_flag_key_map, :feature_variable_key_map,
|
|
32
33
|
:group_id_map, :rollout_id_map, :rollout_experiment_id_map, :variation_id_map,
|
|
33
34
|
:variation_id_to_variable_usage_map, :variation_key_map, :variation_id_map_by_experiment_id,
|
|
34
35
|
:variation_key_map_by_experiment_id, :flag_variation_map, :integration_key_map, :integrations,
|
|
35
|
-
:public_key_for_odp, :host_for_odp, :all_segments
|
|
36
|
+
:public_key_for_odp, :host_for_odp, :all_segments, :region, :holdouts, :holdout_id_map,
|
|
37
|
+
:global_holdouts, :included_holdouts, :excluded_holdouts, :flag_holdouts_map
|
|
36
38
|
# Boolean - denotes if Optimizely should remove the last block of visitors' IP address before storing event data
|
|
37
39
|
attr_reader :anonymize_ip
|
|
38
40
|
|
|
@@ -68,6 +70,11 @@ module Optimizely
|
|
|
68
70
|
@rollouts = config.fetch('rollouts', [])
|
|
69
71
|
@send_flag_decisions = config.fetch('sendFlagDecisions', false)
|
|
70
72
|
@integrations = config.fetch('integrations', [])
|
|
73
|
+
@region = config.fetch('region', 'US')
|
|
74
|
+
@holdouts = config.fetch('holdouts', [])
|
|
75
|
+
|
|
76
|
+
# Default to US region if not specified
|
|
77
|
+
@region = 'US' if @region.nil? || @region.empty?
|
|
71
78
|
|
|
72
79
|
# Json type is represented in datafile as a subtype of string for the sake of backwards compatibility.
|
|
73
80
|
# Converting it to a first-class json type while creating Project Config
|
|
@@ -82,6 +89,11 @@ module Optimizely
|
|
|
82
89
|
|
|
83
90
|
# Utility maps for quick lookup
|
|
84
91
|
@attribute_key_map = generate_key_map(@attributes, 'key')
|
|
92
|
+
@attribute_id_map = generate_key_map(@attributes, 'id')
|
|
93
|
+
@attribute_id_to_key_map = {}
|
|
94
|
+
@attributes.each do |attribute|
|
|
95
|
+
@attribute_id_to_key_map[attribute['id']] = attribute['key']
|
|
96
|
+
end
|
|
85
97
|
@event_key_map = generate_key_map(@events, 'key')
|
|
86
98
|
@group_id_map = generate_key_map(@groups, 'id')
|
|
87
99
|
@group_id_map.each do |key, group|
|
|
@@ -102,6 +114,34 @@ module Optimizely
|
|
|
102
114
|
@variation_id_to_variable_usage_map = {}
|
|
103
115
|
@variation_id_to_experiment_map = {}
|
|
104
116
|
@flag_variation_map = {}
|
|
117
|
+
@holdout_id_map = {}
|
|
118
|
+
@global_holdouts = {}
|
|
119
|
+
@included_holdouts = {}
|
|
120
|
+
@excluded_holdouts = {}
|
|
121
|
+
@flag_holdouts_map = {}
|
|
122
|
+
|
|
123
|
+
@holdouts.each do |holdout|
|
|
124
|
+
next unless holdout['status'] == 'Running'
|
|
125
|
+
|
|
126
|
+
@holdout_id_map[holdout['id']] = holdout
|
|
127
|
+
|
|
128
|
+
if holdout['includedFlags'].nil? || holdout['includedFlags'].empty?
|
|
129
|
+
@global_holdouts[holdout['id']] = holdout
|
|
130
|
+
|
|
131
|
+
excluded_flags = holdout['excludedFlags']
|
|
132
|
+
if excluded_flags && !excluded_flags.empty?
|
|
133
|
+
excluded_flags.each do |flag_id|
|
|
134
|
+
@excluded_holdouts[flag_id] ||= []
|
|
135
|
+
@excluded_holdouts[flag_id] << holdout
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
else
|
|
139
|
+
holdout['includedFlags'].each do |flag_id|
|
|
140
|
+
@included_holdouts[flag_id] ||= []
|
|
141
|
+
@included_holdouts[flag_id] << holdout
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
105
145
|
|
|
106
146
|
@experiment_id_map.each_value do |exp|
|
|
107
147
|
# Excludes experiments from rollouts
|
|
@@ -154,6 +194,43 @@ module Optimizely
|
|
|
154
194
|
feature_flag['experimentIds'].each do |experiment_id|
|
|
155
195
|
@experiment_feature_map[experiment_id] = [feature_flag['id']]
|
|
156
196
|
end
|
|
197
|
+
|
|
198
|
+
flag_id = feature_flag['id']
|
|
199
|
+
applicable_holdouts = []
|
|
200
|
+
|
|
201
|
+
applicable_holdouts.concat(@included_holdouts[flag_id]) if @included_holdouts[flag_id]
|
|
202
|
+
|
|
203
|
+
@global_holdouts.each_value do |holdout|
|
|
204
|
+
excluded_flag_ids = holdout['excludedFlags'] || []
|
|
205
|
+
applicable_holdouts << holdout unless excluded_flag_ids.include?(flag_id)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
@flag_holdouts_map[key] = applicable_holdouts unless applicable_holdouts.empty?
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Adding Holdout variations in variation id and key maps
|
|
212
|
+
return unless @holdouts && !@holdouts.empty?
|
|
213
|
+
|
|
214
|
+
@holdouts.each do |holdout|
|
|
215
|
+
next unless holdout['status'] == 'Running'
|
|
216
|
+
|
|
217
|
+
holdout_key = holdout['key']
|
|
218
|
+
holdout_id = holdout['id']
|
|
219
|
+
|
|
220
|
+
@variation_key_map[holdout_key] = {}
|
|
221
|
+
@variation_id_map[holdout_key] = {}
|
|
222
|
+
@variation_id_map_by_experiment_id[holdout_id] = {}
|
|
223
|
+
@variation_key_map_by_experiment_id[holdout_id] = {}
|
|
224
|
+
|
|
225
|
+
variations = holdout['variations']
|
|
226
|
+
next unless variations && !variations.empty?
|
|
227
|
+
|
|
228
|
+
variations.each do |variation|
|
|
229
|
+
@variation_key_map[holdout_key][variation['key']] = variation
|
|
230
|
+
@variation_id_map[holdout_key][variation['id']] = variation
|
|
231
|
+
@variation_key_map_by_experiment_id[holdout_id][variation['key']] = variation
|
|
232
|
+
@variation_id_map_by_experiment_id[holdout_id][variation['id']] = variation
|
|
233
|
+
end
|
|
157
234
|
end
|
|
158
235
|
end
|
|
159
236
|
|
|
@@ -440,6 +517,40 @@ module Optimizely
|
|
|
440
517
|
nil
|
|
441
518
|
end
|
|
442
519
|
|
|
520
|
+
def get_attribute_by_key(attribute_key)
|
|
521
|
+
# Get attribute for the provided attribute key.
|
|
522
|
+
#
|
|
523
|
+
# Args:
|
|
524
|
+
# Attribute key for which attribute is to be fetched.
|
|
525
|
+
#
|
|
526
|
+
# Returns:
|
|
527
|
+
# Attribute corresponding to the provided attribute key.
|
|
528
|
+
attribute = @attribute_key_map[attribute_key]
|
|
529
|
+
return attribute if attribute
|
|
530
|
+
|
|
531
|
+
invalid_attribute_error = InvalidAttributeError.new(attribute_key)
|
|
532
|
+
@logger.log Logger::ERROR, invalid_attribute_error.message
|
|
533
|
+
@error_handler.handle_error invalid_attribute_error
|
|
534
|
+
nil
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
def get_attribute_key_by_id(attribute_id)
|
|
538
|
+
# Get attribute key for the provided attribute ID.
|
|
539
|
+
#
|
|
540
|
+
# Args:
|
|
541
|
+
# Attribute ID for which attribute is to be fetched.
|
|
542
|
+
#
|
|
543
|
+
# Returns:
|
|
544
|
+
# Attribute key corresponding to the provided attribute ID.
|
|
545
|
+
attribute = @attribute_id_to_key_map[attribute_id]
|
|
546
|
+
return attribute if attribute
|
|
547
|
+
|
|
548
|
+
invalid_attribute_error = InvalidAttributeError.new(attribute_id)
|
|
549
|
+
@logger.log Logger::ERROR, invalid_attribute_error.message
|
|
550
|
+
@error_handler.handle_error invalid_attribute_error
|
|
551
|
+
nil
|
|
552
|
+
end
|
|
553
|
+
|
|
443
554
|
def variation_id_exists?(experiment_id, variation_id)
|
|
444
555
|
# Determines if a given experiment ID / variation ID pair exists in the datafile
|
|
445
556
|
#
|
|
@@ -524,6 +635,33 @@ module Optimizely
|
|
|
524
635
|
@rollout_experiment_id_map.key?(experiment_id)
|
|
525
636
|
end
|
|
526
637
|
|
|
638
|
+
def get_holdouts_for_flag(flag_id)
|
|
639
|
+
# Helper method to get holdouts from an applied feature flag
|
|
640
|
+
#
|
|
641
|
+
# flag_id - (REQUIRED) ID of the feature flag
|
|
642
|
+
# This parameter is required and should not be null/nil
|
|
643
|
+
#
|
|
644
|
+
# Returns the holdouts that apply for a specific flag
|
|
645
|
+
|
|
646
|
+
return [] if @holdouts.nil? || @holdouts.empty?
|
|
647
|
+
|
|
648
|
+
@flag_holdouts_map[flag_id] || []
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
def get_holdout(holdout_id)
|
|
652
|
+
# Helper method to get holdout from holdout ID
|
|
653
|
+
#
|
|
654
|
+
# holdout_id - ID of the holdout
|
|
655
|
+
#
|
|
656
|
+
# Returns the holdout
|
|
657
|
+
|
|
658
|
+
holdout = @holdout_id_map[holdout_id]
|
|
659
|
+
return holdout if holdout
|
|
660
|
+
|
|
661
|
+
@logger.log Logger::ERROR, "Holdout with ID '#{holdout_id}' not found."
|
|
662
|
+
nil
|
|
663
|
+
end
|
|
664
|
+
|
|
527
665
|
private
|
|
528
666
|
|
|
529
667
|
def generate_feature_variation_map(feature_flags)
|
|
@@ -326,7 +326,7 @@ module Optimizely
|
|
|
326
326
|
unless url
|
|
327
327
|
url_template ||= @access_token.nil? ? Helpers::Constants::CONFIG_MANAGER['DATAFILE_URL_TEMPLATE'] : Helpers::Constants::CONFIG_MANAGER['AUTHENTICATED_DATAFILE_URL_TEMPLATE']
|
|
328
328
|
begin
|
|
329
|
-
return
|
|
329
|
+
return url_template % sdk_key
|
|
330
330
|
rescue
|
|
331
331
|
error_msg = "Invalid url_template #{url_template} provided."
|
|
332
332
|
@logger.log(Logger::ERROR, error_msg)
|
|
@@ -23,6 +23,9 @@ module Optimizely
|
|
|
23
23
|
IGNORE_USER_PROFILE_SERVICE = 'IGNORE_USER_PROFILE_SERVICE'
|
|
24
24
|
INCLUDE_REASONS = 'INCLUDE_REASONS'
|
|
25
25
|
EXCLUDE_VARIABLES = 'EXCLUDE_VARIABLES'
|
|
26
|
+
IGNORE_CMAB_CACHE = 'IGNORE_CMAB_CACHE'
|
|
27
|
+
RESET_CMAB_CACHE = 'RESET_CMAB_CACHE'
|
|
28
|
+
INVALIDATE_USER_CMAB_CACHE = 'INVALIDATE_USER_CMAB_CACHE'
|
|
26
29
|
end
|
|
27
30
|
end
|
|
28
31
|
end
|
|
@@ -55,6 +55,25 @@ module Optimizely
|
|
|
55
55
|
def to_json(*args)
|
|
56
56
|
as_json.to_json(*args)
|
|
57
57
|
end
|
|
58
|
+
|
|
59
|
+
# Create a new OptimizelyDecision representing an error state.
|
|
60
|
+
#
|
|
61
|
+
# @param key [String] The flag key
|
|
62
|
+
# @param user [OptimizelyUserContext] The user context
|
|
63
|
+
# @param reasons [Array<String>] List of reasons explaining the error
|
|
64
|
+
#
|
|
65
|
+
# @return [OptimizelyDecision] OptimizelyDecision with error state values
|
|
66
|
+
def self.new_error_decision(key, user, reasons = [])
|
|
67
|
+
new(
|
|
68
|
+
variation_key: nil,
|
|
69
|
+
enabled: false,
|
|
70
|
+
variables: {},
|
|
71
|
+
rule_key: nil,
|
|
72
|
+
flag_key: key,
|
|
73
|
+
user_context: user,
|
|
74
|
+
reasons: reasons
|
|
75
|
+
)
|
|
76
|
+
end
|
|
58
77
|
end
|
|
59
78
|
end
|
|
60
79
|
end
|