optimizely-sdk 3.1.1 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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