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.
@@ -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, :audience_id_map, :event_key_map, :experiment_feature_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 (url_template % sdk_key)
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