optimizely-sdk 3.1.1 → 3.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.
@@ -48,26 +48,16 @@ module Optimizely
48
48
  class BaseEventBuilder
49
49
  CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom'
50
50
 
51
- attr_reader :config
52
- attr_reader :logger
53
-
54
- def initialize(config, logger)
55
- @config = config
51
+ def initialize(logger)
56
52
  @logger = logger
57
53
  end
58
54
 
59
55
  private
60
56
 
61
- def bot_filtering
62
- # Get bot filtering bool
63
- #
64
- # Returns 'botFiltering' value in the datafile.
65
- @config.bot_filtering
66
- end
67
-
68
- def get_common_params(user_id, attributes)
57
+ def get_common_params(project_config, user_id, attributes)
69
58
  # Get params which are used in both conversion and impression events.
70
59
  #
60
+ # project_config - +Object+ Instance of ProjectConfig
71
61
  # user_id - +String+ ID for user
72
62
  # attributes - +Hash+ representing user attributes and values which need to be recorded.
73
63
  #
@@ -79,7 +69,7 @@ module Optimizely
79
69
  # Omit attribute values that are not supported by the log endpoint.
80
70
  attribute_value = attributes[attribute_key]
81
71
  if Helpers::Validator.attribute_valid?(attribute_key, attribute_value)
82
- attribute_id = @config.get_attribute_id attribute_key
72
+ attribute_id = project_config.get_attribute_id attribute_key
83
73
  if attribute_id
84
74
  visitor_attributes.push(
85
75
  entity_id: attribute_id,
@@ -91,18 +81,18 @@ module Optimizely
91
81
  end
92
82
  end
93
83
  # Append Bot Filtering Attribute
94
- if bot_filtering == true || bot_filtering == false
84
+ if project_config.bot_filtering == true || project_config.bot_filtering == false
95
85
  visitor_attributes.push(
96
86
  entity_id: Optimizely::Helpers::Constants::CONTROL_ATTRIBUTES['BOT_FILTERING'],
97
87
  key: Optimizely::Helpers::Constants::CONTROL_ATTRIBUTES['BOT_FILTERING'],
98
88
  type: CUSTOM_ATTRIBUTE_FEATURE_TYPE,
99
- value: bot_filtering
89
+ value: project_config.bot_filtering
100
90
  )
101
91
  end
102
92
 
103
93
  common_params = {
104
- account_id: @config.account_id,
105
- project_id: @config.project_id,
94
+ account_id: project_config.account_id,
95
+ project_id: project_config.project_id,
106
96
  visitors: [
107
97
  {
108
98
  attributes: visitor_attributes,
@@ -110,8 +100,8 @@ module Optimizely
110
100
  visitor_id: user_id
111
101
  }
112
102
  ],
113
- anonymize_ip: @config.anonymize_ip,
114
- revision: @config.revision,
103
+ anonymize_ip: project_config.anonymize_ip,
104
+ revision: project_config.revision,
115
105
  client_name: CLIENT_ENGINE,
116
106
  enrich_decisions: true,
117
107
  client_version: VERSION
@@ -126,9 +116,10 @@ module Optimizely
126
116
  POST_HEADERS = {'Content-Type' => 'application/json'}.freeze
127
117
  ACTIVATE_EVENT_KEY = 'campaign_activated'
128
118
 
129
- def create_impression_event(experiment, variation_id, user_id, attributes)
119
+ def create_impression_event(project_config, experiment, variation_id, user_id, attributes)
130
120
  # Create impression Event to be sent to the logging endpoint.
131
121
  #
122
+ # project_config - +Object+ Instance of ProjectConfig
132
123
  # experiment - +Object+ Experiment for which impression needs to be recorded.
133
124
  # variation_id - +String+ ID for variation which would be presented to user.
134
125
  # user_id - +String+ ID for user.
@@ -136,16 +127,17 @@ module Optimizely
136
127
  #
137
128
  # Returns +Event+ encapsulating the impression event.
138
129
 
139
- event_params = get_common_params(user_id, attributes)
140
- impression_params = get_impression_params(experiment, variation_id)
130
+ event_params = get_common_params(project_config, user_id, attributes)
131
+ impression_params = get_impression_params(project_config, experiment, variation_id)
141
132
  event_params[:visitors][0][:snapshots].push(impression_params)
142
133
 
143
134
  Event.new(:post, ENDPOINT, event_params, POST_HEADERS)
144
135
  end
145
136
 
146
- def create_conversion_event(event, user_id, attributes, event_tags)
137
+ def create_conversion_event(project_config, event, user_id, attributes, event_tags)
147
138
  # Create conversion Event to be sent to the logging endpoint.
148
139
  #
140
+ # project_config - +Object+ Instance of ProjectConfig
149
141
  # event - +Object+ Event which needs to be recorded.
150
142
  # user_id - +String+ ID for user.
151
143
  # attributes - +Hash+ representing user attributes and values which need to be recorded.
@@ -153,7 +145,7 @@ module Optimizely
153
145
  #
154
146
  # Returns +Event+ encapsulating the conversion event.
155
147
 
156
- event_params = get_common_params(user_id, attributes)
148
+ event_params = get_common_params(project_config, user_id, attributes)
157
149
  conversion_params = get_conversion_params(event, event_tags)
158
150
  event_params[:visitors][0][:snapshots] = [conversion_params]
159
151
 
@@ -162,9 +154,10 @@ module Optimizely
162
154
 
163
155
  private
164
156
 
165
- def get_impression_params(experiment, variation_id)
157
+ def get_impression_params(project_config, experiment, variation_id)
166
158
  # Creates object of params specific to impression events
167
159
  #
160
+ # project_config - +Object+ Instance of ProjectConfig
168
161
  # experiment - +Hash+ experiment for which impression needs to be recorded
169
162
  # variation_id - +string+ ID for variation which would be presented to user
170
163
  #
@@ -175,12 +168,12 @@ module Optimizely
175
168
 
176
169
  impression_event_params = {
177
170
  decisions: [{
178
- campaign_id: @config.experiment_key_map[experiment_key]['layerId'],
171
+ campaign_id: project_config.experiment_key_map[experiment_key]['layerId'],
179
172
  experiment_id: experiment_id,
180
173
  variation_id: variation_id
181
174
  }],
182
175
  events: [{
183
- entity_id: @config.experiment_key_map[experiment_key]['layerId'],
176
+ entity_id: project_config.experiment_key_map[experiment_key]['layerId'],
184
177
  timestamp: create_timestamp,
185
178
  key: ACTIVATE_EVENT_KEY,
186
179
  uuid: create_uuid
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #
4
- # Copyright 2016-2018, Optimizely and contributors
4
+ # Copyright 2016-2019, Optimizely and contributors
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
7
7
  # you may not use this file except in compliance with the License.
@@ -105,4 +105,20 @@ module Optimizely
105
105
  super
106
106
  end
107
107
  end
108
+
109
+ class InvalidInputsError < Error
110
+ # Raised when an invalid inputs are provided during Project instantiation
111
+
112
+ def initialize(msg)
113
+ super msg
114
+ end
115
+ end
116
+
117
+ class InvalidProjectConfigError < Error
118
+ # Raised when a public method fails due to an invalid datafile
119
+
120
+ def initialize(aborted_method)
121
+ super("Optimizely instance is not valid. Failing '#{aborted_method}'.")
122
+ end
123
+ end
108
124
  end
@@ -359,6 +359,25 @@ module Optimizely
359
359
  'FEATURE_TEST' => 'feature-test',
360
360
  'FEATURE_VARIABLE' => 'feature-variable'
361
361
  }.freeze
362
+
363
+ CONFIG_MANAGER = {
364
+ 'DATAFILE_URL_TEMPLATE' => 'https://cdn.optimizely.com/datafiles/%s.json',
365
+ # Default time in seconds to block the get_config call until config has been initialized.
366
+ 'DEFAULT_BLOCKING_TIMEOUT' => 15,
367
+ # Default config update interval of 5 minutes
368
+ 'DEFAULT_UPDATE_INTERVAL' => 5 * 60,
369
+ # Maximum update interval or blocking timeout: 30 days
370
+ 'MAX_SECONDS_LIMIT' => 2_592_000,
371
+ # Minimum update interval or blocking timeout: 1 second
372
+ 'MIN_SECONDS_LIMIT' => 1,
373
+ # Time in seconds before which request for datafile times out
374
+ 'REQUEST_TIMEOUT' => 10
375
+ }.freeze
376
+
377
+ HTTP_HEADERS = {
378
+ 'IF_MODIFIED_SINCE' => 'If-Modified-Since',
379
+ 'LAST_MODIFIED' => 'Last-Modified'
380
+ }.freeze
362
381
  end
363
382
  end
364
383
  end
@@ -24,6 +24,7 @@ module Optimizely
24
24
  # DEPRECATED: ACTIVATE notification type is deprecated since relase 3.1.0.
25
25
  ACTIVATE: 'ACTIVATE: experiment, user_id, attributes, variation, event',
26
26
  DECISION: 'DECISION: type, user_id, attributes, decision_info',
27
+ OPTIMIZELY_CONFIG_UPDATE: 'optimizely_config_update',
27
28
  TRACK: 'TRACK: event_key, user_id, attributes, event_tags, event'
28
29
  }.freeze
29
30
 
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright 2019, Optimizely and contributors
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'optimizely'
20
+ module Optimizely
21
+ class OptimizelyFactory
22
+ # Returns a new optimizely instance.
23
+ #
24
+ # @params sdk_key - Required String uniquely identifying the fallback datafile corresponding to project.
25
+ # @param fallback datafile - Optional JSON string datafile.
26
+ def self.default_instance(sdk_key, datafile = nil)
27
+ Optimizely::Project.new(datafile, nil, nil, nil, nil, nil, sdk_key)
28
+ end
29
+
30
+ # Returns a new optimizely instance.
31
+ #
32
+ # @param config_manager - Required ConfigManagerInterface Responds to get_config.
33
+ def self.default_instance_with_config_manager(config_manager)
34
+ Optimizely::Project.new(nil, nil, nil, nil, nil, nil, nil, config_manager)
35
+ end
36
+
37
+ # Returns a new optimizely instance.
38
+ #
39
+ # @params sdk_key - Required String uniquely identifying the datafile corresponding to project.
40
+ # @param fallback datafile - Optional JSON string datafile.
41
+ # @param event_dispatcher - Optional EventDispatcherInterface Provides a dispatch_event method which if given a URL and params sends a request to it.
42
+ # @param logger - Optional LoggerInterface Provides a log method to log messages. By default nothing would be logged.
43
+ # @param error_handler - Optional ErrorHandlerInterface which provides a handle_error method to handle exceptions.
44
+ # By default all exceptions will be suppressed.
45
+ # @param skip_json_validation - Optional Boolean param to skip JSON schema validation of the provided datafile.
46
+ # @param user_profile_service - Optional UserProfileServiceInterface Provides methods to store and retreive user profiles.
47
+ # @param config_manager - Optional ConfigManagerInterface Responds to get_config.
48
+ # @param notification_center - Optional Instance of NotificationCenter.
49
+ def self.custom_instance(
50
+ sdk_key,
51
+ datafile = nil,
52
+ event_dispatcher = nil,
53
+ logger = nil,
54
+ error_handler = nil,
55
+ skip_json_validation = false,
56
+ user_profile_service = nil,
57
+ config_manager = nil,
58
+ notification_center = nil
59
+ )
60
+ Optimizely::Project.new(
61
+ datafile,
62
+ event_dispatcher,
63
+ logger,
64
+ error_handler,
65
+ skip_json_validation,
66
+ user_profile_service,
67
+ sdk_key,
68
+ config_manager,
69
+ notification_center
70
+ )
71
+ end
72
+ end
73
+ end
@@ -14,469 +14,64 @@
14
14
  # See the License for the specific language governing permissions and
15
15
  # limitations under the License.
16
16
  #
17
- require 'json'
18
- require_relative 'helpers/constants'
19
- require_relative 'helpers/validator'
20
17
 
21
18
  module Optimizely
22
19
  class ProjectConfig
23
- # Representation of the Optimizely project config.
24
- RUNNING_EXPERIMENT_STATUS = ['Running'].freeze
25
- RESERVED_ATTRIBUTE_PREFIX = '$opt_'
20
+ # ProjectConfig is an interface capturing the experiment, variation and feature definitions.
21
+ # The default implementation of ProjectConfig can be found in DatafileProjectConfig.
26
22
 
27
- # Gets project config attributes.
28
- attr_reader :error_handler
29
- attr_reader :logger
23
+ def account_id; end
30
24
 
31
- attr_reader :account_id
32
- attr_reader :attributes
33
- attr_reader :audiences
34
- attr_reader :typed_audiences
35
- attr_reader :events
36
- attr_reader :experiments
37
- attr_reader :feature_flags
38
- attr_reader :groups
39
- attr_reader :project_id
40
- # Boolean - denotes if Optimizely should remove the last block of visitors' IP address before storing event data
41
- attr_reader :anonymize_ip
42
- attr_reader :bot_filtering
43
- attr_reader :revision
44
- attr_reader :rollouts
45
- attr_reader :version
25
+ def attributes; end
46
26
 
47
- attr_reader :attribute_key_map
48
- attr_reader :audience_id_map
49
- attr_reader :event_key_map
50
- attr_reader :experiment_feature_map
51
- attr_reader :experiment_id_map
52
- attr_reader :experiment_key_map
53
- attr_reader :feature_flag_key_map
54
- attr_reader :feature_variable_key_map
55
- attr_reader :group_key_map
56
- attr_reader :rollout_id_map
57
- attr_reader :rollout_experiment_key_map
58
- attr_reader :variation_id_map
59
- attr_reader :variation_id_to_variable_usage_map
60
- attr_reader :variation_key_map
27
+ def audiences; end
61
28
 
62
- # Hash of user IDs to a Hash
63
- # of experiments to variations. This contains all the forced variations
64
- # set by the user by calling setForcedVariation (it is not the same as the
65
- # whitelisting forcedVariations data structure in the Experiments class).
66
- attr_reader :forced_variation_map
29
+ def typed_audiences; end
67
30
 
68
- def initialize(datafile, logger, error_handler)
69
- # ProjectConfig init method to fetch and set project config data
70
- #
71
- # datafile - JSON string representing the project
31
+ def events; end
72
32
 
73
- config = JSON.parse(datafile)
33
+ def experiments; end
74
34
 
75
- @error_handler = error_handler
76
- @logger = logger
77
- @version = config['version']
35
+ def feature_flags; end
78
36
 
79
- raise InvalidDatafileVersionError, @version unless Helpers::Constants::SUPPORTED_VERSIONS.value?(@version)
37
+ def groups; end
80
38
 
81
- @account_id = config['accountId']
82
- @attributes = config.fetch('attributes', [])
83
- @audiences = config.fetch('audiences', [])
84
- @typed_audiences = config.fetch('typedAudiences', [])
85
- @events = config.fetch('events', [])
86
- @experiments = config['experiments']
87
- @feature_flags = config.fetch('featureFlags', [])
88
- @groups = config.fetch('groups', [])
89
- @project_id = config['projectId']
90
- @anonymize_ip = config.key?('anonymizeIP') ? config['anonymizeIP'] : false
91
- @bot_filtering = config['botFiltering']
92
- @revision = config['revision']
93
- @rollouts = config.fetch('rollouts', [])
39
+ def project_id; end
94
40
 
95
- # Utility maps for quick lookup
96
- @attribute_key_map = generate_key_map(@attributes, 'key')
97
- @event_key_map = generate_key_map(@events, 'key')
98
- @group_key_map = generate_key_map(@groups, 'id')
99
- @group_key_map.each do |key, group|
100
- exps = group.fetch('experiments')
101
- exps.each do |exp|
102
- @experiments.push(exp.merge('groupId' => key))
103
- end
104
- end
105
- @experiment_key_map = generate_key_map(@experiments, 'key')
106
- @experiment_id_map = generate_key_map(@experiments, 'id')
107
- @audience_id_map = generate_key_map(@audiences, 'id')
108
- @audience_id_map = @audience_id_map.merge(generate_key_map(@typed_audiences, 'id')) unless @typed_audiences.empty?
109
- @variation_id_map = {}
110
- @variation_key_map = {}
111
- @forced_variation_map = {}
112
- @variation_id_to_variable_usage_map = {}
113
- @variation_id_to_experiment_map = {}
114
- @experiment_key_map.each_value do |exp|
115
- # Excludes experiments from rollouts
116
- variations = exp.fetch('variations')
117
- variations.each do |variation|
118
- variation_id = variation['id']
119
- @variation_id_to_experiment_map[variation_id] = exp
120
- end
121
- end
122
- @rollout_id_map = generate_key_map(@rollouts, 'id')
123
- # split out the experiment key map for rollouts
124
- @rollout_experiment_key_map = {}
125
- @rollout_id_map.each_value do |rollout|
126
- exps = rollout.fetch('experiments')
127
- @rollout_experiment_key_map = @rollout_experiment_key_map.merge(generate_key_map(exps, 'key'))
128
- end
129
- @all_experiments = @experiment_key_map.merge(@rollout_experiment_key_map)
130
- @all_experiments.each do |key, exp|
131
- variations = exp.fetch('variations')
132
- variations.each do |variation|
133
- variation_id = variation['id']
134
- variation['featureEnabled'] = variation['featureEnabled'] == true
135
- variation_variables = variation['variables']
136
- next if variation_variables.nil?
41
+ def anonymize_ip; end
137
42
 
138
- @variation_id_to_variable_usage_map[variation_id] = generate_key_map(variation_variables, 'id')
139
- end
140
- @variation_id_map[key] = generate_key_map(variations, 'id')
141
- @variation_key_map[key] = generate_key_map(variations, 'key')
142
- end
143
- @feature_flag_key_map = generate_key_map(@feature_flags, 'key')
144
- @experiment_feature_map = {}
145
- @feature_variable_key_map = {}
146
- @feature_flag_key_map.each do |key, feature_flag|
147
- @feature_variable_key_map[key] = generate_key_map(feature_flag['variables'], 'key')
148
- feature_flag['experimentIds'].each do |experiment_id|
149
- @experiment_feature_map[experiment_id] = [feature_flag['id']]
150
- end
151
- end
152
- end
43
+ def bot_filtering; end
153
44
 
154
- def experiment_running?(experiment)
155
- # Determine if experiment corresponding to given key is running
156
- #
157
- # experiment - Experiment
158
- #
159
- # Returns true if experiment is running
160
- RUNNING_EXPERIMENT_STATUS.include?(experiment['status'])
161
- end
45
+ def revision; end
162
46
 
163
- def get_experiment_from_key(experiment_key)
164
- # Retrieves experiment ID for a given key
165
- #
166
- # experiment_key - String key representing the experiment
167
- #
168
- # Returns Experiment or nil if not found
47
+ def rollouts; end
169
48
 
170
- experiment = @experiment_key_map[experiment_key]
171
- return experiment if experiment
49
+ def experiment_running?(experiment); end
172
50
 
173
- @logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
174
- @error_handler.handle_error InvalidExperimentError
175
- nil
176
- end
51
+ def get_experiment_from_key(experiment_key); end
177
52
 
178
- def get_experiment_key(experiment_id)
179
- # Retrieves experiment key for a given ID.
180
- #
181
- # experiment_id - String ID representing the experiment.
182
- #
183
- # Returns String key.
53
+ def get_experiment_key(experiment_id); end
184
54
 
185
- experiment = @experiment_id_map[experiment_id]
186
- return experiment['key'] unless experiment.nil?
55
+ def get_event_from_key(event_key); end
187
56
 
188
- @logger.log Logger::ERROR, "Experiment id '#{experiment_id}' is not in datafile."
189
- @error_handler.handle_error InvalidExperimentError
190
- nil
191
- end
57
+ def get_audience_from_id(audience_id); end
192
58
 
193
- def get_event_from_key(event_key)
194
- # Get event for the provided event key.
195
- #
196
- # event_key - Event key for which event is to be determined.
197
- #
198
- # Returns Event corresponding to the provided event key.
59
+ def get_variation_from_id(experiment_key, variation_id); end
199
60
 
200
- event = @event_key_map[event_key]
201
- return event if event
61
+ def get_variation_id_from_key(experiment_key, variation_key); end
202
62
 
203
- @logger.log Logger::ERROR, "Event '#{event_key}' is not in datafile."
204
- @error_handler.handle_error InvalidEventError
205
- nil
206
- end
63
+ def get_whitelisted_variations(experiment_key); end
207
64
 
208
- def get_audience_from_id(audience_id)
209
- # Get audience for the provided audience ID
210
- #
211
- # audience_id - ID of the audience
212
- #
213
- # Returns the audience
65
+ def get_attribute_id(attribute_key); end
214
66
 
215
- audience = @audience_id_map[audience_id]
216
- return audience if audience
67
+ def variation_id_exists?(experiment_id, variation_id); end
217
68
 
218
- @logger.log Logger::ERROR, "Audience '#{audience_id}' is not in datafile."
219
- @error_handler.handle_error InvalidAudienceError
220
- nil
221
- end
69
+ def get_feature_flag_from_key(feature_flag_key); end
222
70
 
223
- def get_variation_from_id(experiment_key, variation_id)
224
- # Get variation given experiment key and variation ID
225
- #
226
- # experiment_key - Key representing parent experiment of variation
227
- # variation_id - ID of the variation
228
- #
229
- # Returns the variation or nil if not found
71
+ def get_feature_variable(feature_flag, variable_key); end
230
72
 
231
- variation_id_map = @variation_id_map[experiment_key]
232
- if variation_id_map
233
- variation = variation_id_map[variation_id]
234
- return variation if variation
73
+ def get_rollout_from_id(rollout_id); end
235
74
 
236
- @logger.log Logger::ERROR, "Variation id '#{variation_id}' is not in datafile."
237
- @error_handler.handle_error InvalidVariationError
238
- return nil
239
- end
240
-
241
- @logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
242
- @error_handler.handle_error InvalidExperimentError
243
- nil
244
- end
245
-
246
- def get_variation_id_from_key(experiment_key, variation_key)
247
- # Get variation ID given experiment key and variation key
248
- #
249
- # experiment_key - Key representing parent experiment of variation
250
- # variation_key - Key of the variation
251
- #
252
- # Returns ID of the variation
253
-
254
- variation_key_map = @variation_key_map[experiment_key]
255
- if variation_key_map
256
- variation = variation_key_map[variation_key]
257
- return variation['id'] if variation
258
-
259
- @logger.log Logger::ERROR, "Variation key '#{variation_key}' is not in datafile."
260
- @error_handler.handle_error InvalidVariationError
261
- return nil
262
- end
263
-
264
- @logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
265
- @error_handler.handle_error InvalidExperimentError
266
- nil
267
- end
268
-
269
- def get_whitelisted_variations(experiment_key)
270
- # Retrieves whitelisted variations for a given experiment Key
271
- #
272
- # experiment_key - String Key representing the experiment
273
- #
274
- # Returns whitelisted variations for the experiment or nil
275
-
276
- experiment = @experiment_key_map[experiment_key]
277
- return experiment['forcedVariations'] if experiment
278
-
279
- @logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
280
- @error_handler.handle_error InvalidExperimentError
281
- end
282
-
283
- def get_forced_variation(experiment_key, user_id)
284
- # Gets the forced variation for the given user and experiment.
285
- #
286
- # experiment_key - String Key for experiment.
287
- # user_id - String ID for user
288
- #
289
- # Returns Variation The variation which the given user and experiment should be forced into.
290
-
291
- return nil unless Optimizely::Helpers::Validator.inputs_valid?(
292
- {
293
- experiment_key: experiment_key,
294
- user_id: user_id
295
- }, @logger, Logger::DEBUG
296
- )
297
-
298
- unless @forced_variation_map.key? user_id
299
- @logger.log(Logger::DEBUG, "User '#{user_id}' is not in the forced variation map.")
300
- return nil
301
- end
302
-
303
- experiment_to_variation_map = @forced_variation_map[user_id]
304
- experiment = get_experiment_from_key(experiment_key)
305
- experiment_id = experiment['id'] if experiment
306
- # check for nil and empty string experiment ID
307
- # this case is logged in get_experiment_from_key
308
- return nil if experiment_id.nil? || experiment_id.empty?
309
-
310
- unless experiment_to_variation_map.key? experiment_id
311
- @logger.log(Logger::DEBUG, "No experiment '#{experiment_key}' mapped to user '#{user_id}' "\
312
- 'in the forced variation map.')
313
- return nil
314
- end
315
-
316
- variation_id = experiment_to_variation_map[experiment_id]
317
- variation_key = ''
318
- variation = get_variation_from_id(experiment_key, variation_id)
319
- variation_key = variation['key'] if variation
320
-
321
- # check if the variation exists in the datafile
322
- # this case is logged in get_variation_from_id
323
- return nil if variation_key.empty?
324
-
325
- @logger.log(Logger::DEBUG, "Variation '#{variation_key}' is mapped to experiment '#{experiment_key}' "\
326
- "and user '#{user_id}' in the forced variation map")
327
-
328
- variation
329
- end
330
-
331
- def set_forced_variation(experiment_key, user_id, variation_key)
332
- # Sets a Hash of user IDs to a Hash of experiments to forced variations.
333
- #
334
- # experiment_key - String Key for experiment.
335
- # user_id - String ID for user.
336
- # variation_key - String Key for variation. If null, then clear the existing experiment-to-variation mapping.
337
- #
338
- # Returns a boolean value that indicates if the set completed successfully.
339
-
340
- input_values = {experiment_key: experiment_key, user_id: user_id}
341
- input_values[:variation_key] = variation_key unless variation_key.nil?
342
- return false unless Optimizely::Helpers::Validator.inputs_valid?(input_values, @logger, Logger::DEBUG)
343
-
344
- experiment = get_experiment_from_key(experiment_key)
345
- experiment_id = experiment['id'] if experiment
346
- # check if the experiment exists in the datafile
347
- return false if experiment_id.nil? || experiment_id.empty?
348
-
349
- # clear the forced variation if the variation key is null
350
- if variation_key.nil?
351
- @forced_variation_map[user_id].delete(experiment_id) if @forced_variation_map.key? user_id
352
- @logger.log(Logger::DEBUG, "Variation mapped to experiment '#{experiment_key}' has been removed for user "\
353
- "'#{user_id}'.")
354
- return true
355
- end
356
-
357
- variation_id = get_variation_id_from_key(experiment_key, variation_key)
358
-
359
- # check if the variation exists in the datafile
360
- unless variation_id
361
- # this case is logged in get_variation_id_from_key
362
- return false
363
- end
364
-
365
- @forced_variation_map[user_id] = {} unless @forced_variation_map.key? user_id
366
- @forced_variation_map[user_id][experiment_id] = variation_id
367
- @logger.log(Logger::DEBUG, "Set variation '#{variation_id}' for experiment '#{experiment_id}' and "\
368
- "user '#{user_id}' in the forced variation map.")
369
- true
370
- end
371
-
372
- def get_attribute_id(attribute_key)
373
- # Get attribute ID for the provided attribute key.
374
- #
375
- # Args:
376
- # Attribute key for which attribute is to be fetched.
377
- #
378
- # Returns:
379
- # Attribute ID corresponding to the provided attribute key.
380
- attribute = @attribute_key_map[attribute_key]
381
- has_reserved_prefix = attribute_key.to_s.start_with?(RESERVED_ATTRIBUTE_PREFIX)
382
- unless attribute.nil?
383
- if has_reserved_prefix
384
- @logger.log(Logger::WARN, "Attribute '#{attribute_key}' unexpectedly has reserved prefix '#{RESERVED_ATTRIBUTE_PREFIX}'; "\
385
- 'using attribute ID instead of reserved attribute name.')
386
- end
387
- return attribute['id']
388
- end
389
- return attribute_key if has_reserved_prefix
390
-
391
- @logger.log Logger::ERROR, "Attribute key '#{attribute_key}' is not in datafile."
392
- @error_handler.handle_error InvalidAttributeError
393
- nil
394
- end
395
-
396
- def variation_id_exists?(experiment_id, variation_id)
397
- # Determines if a given experiment ID / variation ID pair exists in the datafile
398
- #
399
- # experiment_id - String experiment ID
400
- # variation_id - String variation ID
401
- #
402
- # Returns true if variation is in datafile
403
-
404
- experiment_key = get_experiment_key(experiment_id)
405
- variation_id_map = @variation_id_map[experiment_key]
406
- if variation_id_map
407
- variation = variation_id_map[variation_id]
408
- return true if variation
409
-
410
- @logger.log Logger::ERROR, "Variation ID '#{variation_id}' is not in datafile."
411
- @error_handler.handle_error InvalidVariationError
412
- end
413
-
414
- false
415
- end
416
-
417
- def get_feature_flag_from_key(feature_flag_key)
418
- # Retrieves the feature flag with the given key
419
- #
420
- # feature_flag_key - String feature key
421
- #
422
- # Returns feature flag if found, otherwise nil
423
- feature_flag = @feature_flag_key_map[feature_flag_key]
424
- return feature_flag if feature_flag
425
-
426
- @logger.log Logger::ERROR, "Feature flag key '#{feature_flag_key}' is not in datafile."
427
- nil
428
- end
429
-
430
- def get_feature_variable(feature_flag, variable_key)
431
- # Retrieves the variable with the given key for the given feature
432
- #
433
- # feature_flag - The feature flag for which we are retrieving the variable
434
- # variable_key - String variable key
435
- #
436
- # Returns variable if found, otherwise nil
437
- feature_flag_key = feature_flag['key']
438
- variable = @feature_variable_key_map[feature_flag_key][variable_key]
439
- return variable if variable
440
-
441
- @logger.log Logger::ERROR, "No feature variable was found for key '#{variable_key}' in feature flag "\
442
- "'#{feature_flag_key}'."
443
- nil
444
- end
445
-
446
- def get_rollout_from_id(rollout_id)
447
- # Retrieves the rollout with the given ID
448
- #
449
- # rollout_id - String rollout ID
450
- #
451
- # Returns the rollout if found, otherwise nil
452
- rollout = @rollout_id_map[rollout_id]
453
- return rollout if rollout
454
-
455
- @logger.log Logger::ERROR, "Rollout with ID '#{rollout_id}' is not in the datafile."
456
- nil
457
- end
458
-
459
- def feature_experiment?(experiment_id)
460
- # Determines if given experiment is a feature test.
461
- #
462
- # experiment_id - String experiment ID
463
- #
464
- # Returns true if experiment belongs to any feature,
465
- # false otherwise.
466
- @experiment_feature_map.key?(experiment_id)
467
- end
468
-
469
- private
470
-
471
- def generate_key_map(array, key)
472
- # Helper method to generate map from key to hash in array of hashes
473
- #
474
- # array - Array consisting of hash
475
- # key - Key in each hash which will be key in the map
476
- #
477
- # Returns map mapping key to hash
478
-
479
- Hash[array.map { |obj| [obj[key], obj] }]
480
- end
75
+ def feature_experiment?(experiment_id); end
481
76
  end
482
77
  end