optimizely-sdk 5.1.0 → 5.2.1
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 +166 -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 +250 -59
- 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 +60 -21
- metadata +5 -3
|
@@ -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,45 @@ 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
|
+
# Ensure holdout has layerId field (holdouts don't have campaigns)
|
|
127
|
+
holdout['layerId'] ||= ''
|
|
128
|
+
|
|
129
|
+
@holdout_id_map[holdout['id']] = holdout
|
|
130
|
+
|
|
131
|
+
included_flags = holdout['includedFlags'] || []
|
|
132
|
+
excluded_flags = holdout['excludedFlags'] || []
|
|
133
|
+
|
|
134
|
+
case [included_flags.empty?, excluded_flags.empty?]
|
|
135
|
+
when [true, true]
|
|
136
|
+
# No included or excluded flags - this is a global holdout
|
|
137
|
+
@global_holdouts << holdout
|
|
138
|
+
|
|
139
|
+
when [false, true], [false, false]
|
|
140
|
+
# Has included flags - add to included_holdouts map
|
|
141
|
+
included_flags.each do |flag_id|
|
|
142
|
+
@included_holdouts[flag_id] ||= []
|
|
143
|
+
@included_holdouts[flag_id] << holdout
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
when [true, false]
|
|
147
|
+
# No included flags but has excluded flags - global with exclusions
|
|
148
|
+
@global_holdouts << holdout
|
|
149
|
+
|
|
150
|
+
excluded_flags.each do |flag_id|
|
|
151
|
+
@excluded_holdouts[flag_id] ||= []
|
|
152
|
+
@excluded_holdouts[flag_id] << holdout
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
105
156
|
|
|
106
157
|
@experiment_id_map.each_value do |exp|
|
|
107
158
|
# Excludes experiments from rollouts
|
|
@@ -155,6 +206,31 @@ module Optimizely
|
|
|
155
206
|
@experiment_feature_map[experiment_id] = [feature_flag['id']]
|
|
156
207
|
end
|
|
157
208
|
end
|
|
209
|
+
|
|
210
|
+
# Adding Holdout variations in variation id and key maps
|
|
211
|
+
return unless @holdouts && !@holdouts.empty?
|
|
212
|
+
|
|
213
|
+
@holdouts.each do |holdout|
|
|
214
|
+
next unless holdout['status'] == 'Running'
|
|
215
|
+
|
|
216
|
+
holdout_key = holdout['key']
|
|
217
|
+
holdout_id = holdout['id']
|
|
218
|
+
|
|
219
|
+
@variation_key_map[holdout_key] = {}
|
|
220
|
+
@variation_id_map[holdout_key] = {}
|
|
221
|
+
@variation_id_map_by_experiment_id[holdout_id] = {}
|
|
222
|
+
@variation_key_map_by_experiment_id[holdout_id] = {}
|
|
223
|
+
|
|
224
|
+
variations = holdout['variations']
|
|
225
|
+
next unless variations && !variations.empty?
|
|
226
|
+
|
|
227
|
+
variations.each do |variation|
|
|
228
|
+
@variation_key_map[holdout_key][variation['key']] = variation
|
|
229
|
+
@variation_id_map[holdout_key][variation['id']] = variation
|
|
230
|
+
@variation_key_map_by_experiment_id[holdout_id][variation['key']] = variation
|
|
231
|
+
@variation_id_map_by_experiment_id[holdout_id][variation['id']] = variation
|
|
232
|
+
end
|
|
233
|
+
end
|
|
158
234
|
end
|
|
159
235
|
|
|
160
236
|
def get_rules_for_flag(feature_flag)
|
|
@@ -440,6 +516,40 @@ module Optimizely
|
|
|
440
516
|
nil
|
|
441
517
|
end
|
|
442
518
|
|
|
519
|
+
def get_attribute_by_key(attribute_key)
|
|
520
|
+
# Get attribute for the provided attribute key.
|
|
521
|
+
#
|
|
522
|
+
# Args:
|
|
523
|
+
# Attribute key for which attribute is to be fetched.
|
|
524
|
+
#
|
|
525
|
+
# Returns:
|
|
526
|
+
# Attribute corresponding to the provided attribute key.
|
|
527
|
+
attribute = @attribute_key_map[attribute_key]
|
|
528
|
+
return attribute if attribute
|
|
529
|
+
|
|
530
|
+
invalid_attribute_error = InvalidAttributeError.new(attribute_key)
|
|
531
|
+
@logger.log Logger::ERROR, invalid_attribute_error.message
|
|
532
|
+
@error_handler.handle_error invalid_attribute_error
|
|
533
|
+
nil
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
def get_attribute_key_by_id(attribute_id)
|
|
537
|
+
# Get attribute key for the provided attribute ID.
|
|
538
|
+
#
|
|
539
|
+
# Args:
|
|
540
|
+
# Attribute ID for which attribute is to be fetched.
|
|
541
|
+
#
|
|
542
|
+
# Returns:
|
|
543
|
+
# Attribute key corresponding to the provided attribute ID.
|
|
544
|
+
attribute = @attribute_id_to_key_map[attribute_id]
|
|
545
|
+
return attribute if attribute
|
|
546
|
+
|
|
547
|
+
invalid_attribute_error = InvalidAttributeError.new(attribute_id)
|
|
548
|
+
@logger.log Logger::ERROR, invalid_attribute_error.message
|
|
549
|
+
@error_handler.handle_error invalid_attribute_error
|
|
550
|
+
nil
|
|
551
|
+
end
|
|
552
|
+
|
|
443
553
|
def variation_id_exists?(experiment_id, variation_id)
|
|
444
554
|
# Determines if a given experiment ID / variation ID pair exists in the datafile
|
|
445
555
|
#
|
|
@@ -524,6 +634,60 @@ module Optimizely
|
|
|
524
634
|
@rollout_experiment_id_map.key?(experiment_id)
|
|
525
635
|
end
|
|
526
636
|
|
|
637
|
+
def get_holdouts_for_flag(flag_id)
|
|
638
|
+
# Helper method to get holdouts from an applied feature flag
|
|
639
|
+
#
|
|
640
|
+
# flag_id - (REQUIRED) ID of the feature flag
|
|
641
|
+
# This parameter is required and should not be null/nil
|
|
642
|
+
#
|
|
643
|
+
# Returns the holdouts that apply for a specific flag
|
|
644
|
+
|
|
645
|
+
return [] if @holdouts.nil? || @holdouts.empty?
|
|
646
|
+
|
|
647
|
+
# Check cache first (before validation, so we cache the validation result too)
|
|
648
|
+
return @flag_holdouts_map[flag_id] if @flag_holdouts_map.key?(flag_id)
|
|
649
|
+
|
|
650
|
+
# Validate that the flag exists in the datafile
|
|
651
|
+
flag_exists = @feature_flags.any? { |flag| flag['id'] == flag_id }
|
|
652
|
+
unless flag_exists
|
|
653
|
+
# Cache the empty result for non-existent flags
|
|
654
|
+
@flag_holdouts_map[flag_id] = []
|
|
655
|
+
return []
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
# Prioritize global holdouts first
|
|
659
|
+
excluded = @excluded_holdouts[flag_id] || []
|
|
660
|
+
|
|
661
|
+
active_holdouts = if excluded.any?
|
|
662
|
+
@global_holdouts.reject { |holdout| excluded.include?(holdout) }
|
|
663
|
+
else
|
|
664
|
+
@global_holdouts.dup
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
# Append included holdouts
|
|
668
|
+
included = @included_holdouts[flag_id] || []
|
|
669
|
+
active_holdouts.concat(included)
|
|
670
|
+
|
|
671
|
+
# Cache the result
|
|
672
|
+
@flag_holdouts_map[flag_id] = active_holdouts
|
|
673
|
+
|
|
674
|
+
@flag_holdouts_map[flag_id] || []
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
def get_holdout(holdout_id)
|
|
678
|
+
# Helper method to get holdout from holdout ID
|
|
679
|
+
#
|
|
680
|
+
# holdout_id - ID of the holdout
|
|
681
|
+
#
|
|
682
|
+
# Returns the holdout
|
|
683
|
+
|
|
684
|
+
holdout = @holdout_id_map[holdout_id]
|
|
685
|
+
return holdout if holdout
|
|
686
|
+
|
|
687
|
+
@logger.log Logger::ERROR, "Holdout with ID '#{holdout_id}' not found."
|
|
688
|
+
nil
|
|
689
|
+
end
|
|
690
|
+
|
|
527
691
|
private
|
|
528
692
|
|
|
529
693
|
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
|