optimizely-sdk 0.1.2 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 388b7be07d59d027ef5f08ebce2fa43d62be0513
4
- data.tar.gz: 0288d19e852dce6a860ceac8c35c6b508ed3363a
3
+ metadata.gz: 9905095aba19bd32c9a64d53e412c8d0caa101fd
4
+ data.tar.gz: 4d5103f57b0371c91f17fe45cad9faeebf903ba6
5
5
  SHA512:
6
- metadata.gz: 02cd29fbf79181021dae6d19aae905e4304dc8ce6d338e7d1df2e2086fd7d381b65232ed188f41ef005a1afaefa6de610d195162b144e7c6eccd05b084f9be9f
7
- data.tar.gz: 281090b33e4e89c8ea7a32276d259a307fa9a3001ce532cc8b373a70d7cc70ec19d7e62c52a360e29e1bbabb5f44786739d05214d276558c5d0b060772ab5488
6
+ metadata.gz: 9e049b70cd0548306b2721fcf9c494a8552b6a4c47c9efac4fb4b1f1dfda77e00ff2d5166a75dddc634cf20773ca9bb88591d6a7161596c1e2a0da42025dba7d
7
+ data.tar.gz: 62de64edcce2328c1b59dcb5cdedb067362f1cd85a8218d5554af1d77f06face671d0cf5b99218d00374654333ac538104b7134e192d75707a8138e530c8ad0d
@@ -11,6 +11,10 @@ require_relative 'optimizely/project_config'
11
11
 
12
12
  module Optimizely
13
13
  class Project
14
+
15
+ # Boolean representing if the instance represents a usable Optimizely Project
16
+ attr_reader :is_valid
17
+
14
18
  attr_accessor :config
15
19
  attr_accessor :bucketer
16
20
  attr_accessor :event_builder
@@ -33,14 +37,37 @@ module Optimizely
33
37
  # By default all exceptions will be suppressed.
34
38
  # skip_json_validation - Optional boolean param to skip JSON schema validation of the provided datafile.
35
39
 
40
+ @is_valid = true
36
41
  @logger = logger || NoOpLogger.new
37
42
  @error_handler = error_handler || NoOpErrorHandler.new
38
43
  @event_dispatcher = event_dispatcher || EventDispatcher.new
39
- validate_inputs(datafile, skip_json_validation)
40
44
 
41
- @config = ProjectConfig.new(datafile, @logger, @error_handler)
42
- @bucketer = Bucketer.new(@config)
43
- @event_builder = EVENT_BUILDERS_BY_VERSION[@config.version].new(@config, @bucketer)
45
+ begin
46
+ validate_inputs(datafile, skip_json_validation)
47
+ rescue InvalidInputError => e
48
+ @is_valid = false
49
+ logger = SimpleLogger.new
50
+ logger.log(Logger::ERROR, e.message)
51
+ return
52
+ end
53
+
54
+ begin
55
+ @config = ProjectConfig.new(datafile, @logger, @error_handler)
56
+ rescue
57
+ @is_valid = false
58
+ logger = SimpleLogger.new
59
+ logger.log(Logger::ERROR, InvalidInputError.new('datafile').message)
60
+ return
61
+ end
62
+
63
+ begin
64
+ @bucketer = Bucketer.new(@config)
65
+ @event_builder = EVENT_BUILDERS_BY_VERSION[@config.version].new(@config, @bucketer)
66
+ rescue
67
+ @is_valid = false
68
+ logger = SimpleLogger.new
69
+ logger.log(Logger::ERROR, InvalidDatafileVersionError.new)
70
+ end
44
71
  end
45
72
 
46
73
  def activate(experiment_key, user_id, attributes = nil)
@@ -51,7 +78,13 @@ module Optimizely
51
78
  # attributes - Hash representing user attributes and values to be recorded.
52
79
  #
53
80
  # Returns variation key representing the variation the user will be bucketed in.
54
- # Returns nil if experiment is not Running or if user is not in experiment.
81
+ # Returns nil if experiment is not Running, if user is not in experiment, or if datafile is invalid.
82
+
83
+ unless @is_valid
84
+ logger = SimpleLogger.new
85
+ logger.log(Logger::ERROR, InvalidDatafileError.new('activate').message)
86
+ return nil
87
+ end
55
88
 
56
89
  if attributes && !attributes_valid?(attributes)
57
90
  @logger.log(Logger::INFO, "Not activating user '#{user_id}'.")
@@ -75,7 +108,11 @@ module Optimizely
75
108
  @logger.log(Logger::INFO,
76
109
  'Dispatching impression event to URL %s with params %s.' % [impression_event.url,
77
110
  impression_event.params])
78
- @event_dispatcher.dispatch_event(impression_event)
111
+ begin
112
+ @event_dispatcher.dispatch_event(impression_event)
113
+ rescue => e
114
+ @logger.log(Logger::ERROR, "Unable to dispatch impression event. Error: #{e}")
115
+ end
79
116
 
80
117
  @config.get_variation_key_from_id(experiment_key, variation_id)
81
118
  end
@@ -88,7 +125,13 @@ module Optimizely
88
125
  # attributes - Hash representing user attributes.
89
126
  #
90
127
  # Returns variation key where visitor will be bucketed.
91
- # Returns nil if experiment is not Running or if user is not in experiment.
128
+ # Returns nil if experiment is not Running, if user is not in experiment, or if datafile is invalid.
129
+
130
+ unless @is_valid
131
+ logger = SimpleLogger.new
132
+ logger.log(Logger::ERROR, InvalidDatafileError.new('get_variation').message)
133
+ return nil
134
+ end
92
135
 
93
136
  if attributes && !attributes_valid?(attributes)
94
137
  @logger.log(Logger::INFO, "Not activating user '#{user_id}.")
@@ -112,7 +155,11 @@ module Optimizely
112
155
  # attributes - Hash representing visitor attributes and values which need to be recorded.
113
156
  # event_value - Value associated with the event. Can be used to represent revenue in cents.
114
157
 
115
- # Create and dispatch conversion event
158
+ unless @is_valid
159
+ logger = SimpleLogger.new
160
+ logger.log(Logger::ERROR, InvalidDatafileError.new('track').message)
161
+ return nil
162
+ end
116
163
 
117
164
  return nil if attributes && !attributes_valid?(attributes)
118
165
 
@@ -134,14 +181,21 @@ module Optimizely
134
181
  end
135
182
 
136
183
  # Don't track events without valid experiments attached
137
- return if valid_experiment_keys.empty?
184
+ if valid_experiment_keys.empty?
185
+ @logger.log(Logger::INFO, "There are no valid experiments for event '#{event_key}' to track.")
186
+ return nil
187
+ end
138
188
 
139
189
  conversion_event = @event_builder.create_conversion_event(event_key, user_id, attributes,
140
190
  event_value, valid_experiment_keys)
141
191
  @logger.log(Logger::INFO,
142
192
  'Dispatching conversion event to URL %s with params %s.' % [conversion_event.url,
143
193
  conversion_event.params])
144
- @event_dispatcher.dispatch_event(conversion_event)
194
+ begin
195
+ @event_dispatcher.dispatch_event(conversion_event)
196
+ rescue => e
197
+ @logger.log(Logger::ERROR, "Unable to dispatch conversion event. Error: #{e}")
198
+ end
145
199
  end
146
200
 
147
201
  private
@@ -156,13 +210,17 @@ module Optimizely
156
210
  # Returns boolean representing whether all preconditions are valid.
157
211
 
158
212
  unless @config.experiment_running?(experiment_key)
159
- @logger.log(Logger::INFO, "Experiment '#{experiment_key} is not running.")
213
+ @logger.log(Logger::INFO, "Experiment '#{experiment_key}' is not running.")
160
214
  return false
161
215
  end
162
216
 
217
+ if @config.user_in_forced_variation?(experiment_key, user_id)
218
+ return true
219
+ end
220
+
163
221
  unless Audience.user_in_experiment?(@config, experiment_key, attributes)
164
222
  @logger.log(Logger::INFO,
165
- "User '#{user_id} does not meet the conditions to be in experiment '#{experiment_key}.")
223
+ "User '#{user_id}' does not meet the conditions to be in experiment '#{experiment_key}'.")
166
224
  return false
167
225
  end
168
226
 
@@ -180,12 +238,12 @@ module Optimizely
180
238
 
181
239
  def validate_inputs(datafile, skip_json_validation)
182
240
  unless skip_json_validation
183
- raise InvalidDatafileError unless Helpers::Validator.datafile_valid?(datafile)
241
+ raise InvalidInputError.new('datafile') unless Helpers::Validator.datafile_valid?(datafile)
184
242
  end
185
243
 
186
- raise InvalidLoggerError unless Helpers::Validator.logger_valid?(@logger)
187
- raise InvalidErrorHandlerError unless Helpers::Validator.error_handler_valid?(@error_handler)
188
- raise InvalidEventDispatcherError unless Helpers::Validator.event_dispatcher_valid?(@event_dispatcher)
244
+ raise InvalidInputError.new('logger') unless Helpers::Validator.logger_valid?(@logger)
245
+ raise InvalidInputError.new('error_handler') unless Helpers::Validator.error_handler_valid?(@error_handler)
246
+ raise InvalidInputError.new('event_dispatcher') unless Helpers::Validator.event_dispatcher_valid?(@event_dispatcher)
189
247
  end
190
248
  end
191
249
  end
@@ -274,7 +274,10 @@ module Optimizely
274
274
  attributes.keys.each do |attribute_key|
275
275
  attribute_value = attributes[attribute_key]
276
276
  next unless attribute_value
277
- segment_id = @config.attribute_key_map[attribute_key]['segmentId']
277
+
278
+ # Skip attributes not in the datafile
279
+ segment_id = @config.get_segment_id(attribute_key)
280
+ next unless segment_id
278
281
  segment_param = sprintf(ATTRIBUTE_PARAM_FORMAT,
279
282
  segment_prefix: Params::SEGMENT_PREFIX, segment_id: segment_id)
280
283
  params[segment_param] = attribute_value
@@ -25,59 +25,51 @@ module Optimizely
25
25
  end
26
26
  end
27
27
 
28
- class InvalidDatafileError < Error
29
- # Raised when an invalid datafile is provided
30
-
31
- def initialize(msg = 'Provided datafile is in an invalid format.')
32
- super
33
- end
34
- end
35
-
36
- class InvalidErrorHandlerError < Error
37
- # Raised when an invalid error handler is provided
28
+ class InvalidExperimentError < Error
29
+ # Raised when an invalid experiment key is provided
38
30
 
39
- def initialize(msg = 'Provided error_handler is in an invalid format.')
31
+ def initialize(msg = 'Provided experiment is not in datafile.')
40
32
  super
41
33
  end
42
34
  end
43
35
 
44
- class InvalidEventDispatcherError < Error
45
- # Raised when an invalid event dispatcher is provided
36
+ class InvalidEventError < Error
37
+ # Raised when an invalid event key is provided
46
38
 
47
- def initialize(msg = 'Provided event_dispatcher is in an invalid format.')
39
+ def initialize(msg = 'Provided event is not in datafile.')
48
40
  super
49
41
  end
50
42
  end
51
43
 
52
- class InvalidExperimentError < Error
53
- # Raised when an invalid experiment key is provided
44
+ class InvalidVariationError < Error
45
+ # Raised when an invalid variation key or ID is provided
54
46
 
55
- def initialize(msg = 'Provided experiment is not in datafile.')
47
+ def initialize(msg = 'Provided variation is not in datafile.')
56
48
  super
57
49
  end
58
50
  end
59
51
 
60
- class InvalidGoalError < Error
61
- # Raised when an invalid event key is provided
52
+ class InvalidDatafileError < Error
53
+ # Raised when a public method fails due to an invalid datafile
62
54
 
63
- def initialize(msg = 'Provided event is not in datafile.')
64
- super
55
+ def initialize(aborted_method)
56
+ super("Provided datafile is in an invalid format. Aborting #{aborted_method}.")
65
57
  end
66
58
  end
67
59
 
68
- class InvalidLoggerError < Error
69
- # Raised when an invalid logger is provided
60
+ class InvalidDatafileVersionError < Error
61
+ # Raised when a datafile with an unsupported version is provided
70
62
 
71
- def initialize(msg = 'Provided logger is in an invalid format.')
63
+ def initialize(msg = 'Provided datafile is an unsupported version.')
72
64
  super
73
65
  end
74
66
  end
75
67
 
76
- class InvalidVariationError < Error
77
- # Raised when an invalid variation key or ID is provided
68
+ class InvalidInputError < Error
69
+ # Abstract error raised when an invalid input is provided during Project instantiation
78
70
 
79
- def initialize(msg = 'Provided variation is not in datafile.')
80
- super
71
+ def initialize(type)
72
+ super("Provided #{type} is in an invalid format.")
81
73
  end
82
74
  end
83
75
  end
@@ -139,7 +139,7 @@ module Optimizely
139
139
  goal = @event_key_map[goal_key]
140
140
  return goal['experimentIds'] if goal
141
141
  @logger.log Logger::ERROR, "Event '#{goal_key}' is not in datafile."
142
- @error_handler.handle_error InvalidGoalError
142
+ @error_handler.handle_error InvalidEventError
143
143
  []
144
144
  end
145
145
 
@@ -257,6 +257,27 @@ module Optimizely
257
257
  nil
258
258
  end
259
259
 
260
+ def get_segment_id(attribute_key)
261
+ attribute = @attribute_key_map[attribute_key]
262
+ return attribute['segmentId'] if attribute
263
+ @logger.log Logger::ERROR, "Attribute key '#{attribute_key}' is not in datafile."
264
+ @error_handler.handle_error InvalidAttributeError
265
+ nil
266
+ end
267
+
268
+ def user_in_forced_variation?(experiment_key, user_id)
269
+ # Determines if a given user is in a forced variation
270
+ #
271
+ # experiment_key - String experiment key
272
+ # user_id - String user ID
273
+ #
274
+ # Returns true if user is in a forced variation
275
+
276
+ forced_variations = get_forced_variations(experiment_key)
277
+ return forced_variations.include?(user_id) if forced_variations
278
+ false
279
+ end
280
+
260
281
  private
261
282
 
262
283
  def generate_key_map(array, key)
@@ -1,3 +1,3 @@
1
1
  module Optimizely
2
- VERSION = '0.1.2'.freeze
2
+ VERSION = '1.0.0'.freeze
3
3
  end
@@ -0,0 +1,3 @@
1
+ require 'optimizely'
2
+ @df = HTTParty.get('https://cdn.optimizely.com/json/7566680716.json').body
3
+ @o = Optimizely::Project.new(@df, nil, Optimizely::SimpleLogger.new)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: optimizely-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Delikat
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2016-09-19 00:00:00.000000000 Z
13
+ date: 2016-10-04 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bundler
@@ -110,8 +110,7 @@ dependencies:
110
110
  - - "~>"
111
111
  - !ruby/object:Gem::Version
112
112
  version: 2.6.2
113
- description: A Ruby SDK for Optimizely's server-side testing product, which is currently
114
- in private beta.
113
+ description: A Ruby SDK for Optimizely's Full Stack product.
115
114
  email:
116
115
  - developers@optimizely.com
117
116
  executables: []
@@ -133,6 +132,7 @@ files:
133
132
  - lib/optimizely/params.rb
134
133
  - lib/optimizely/project_config.rb
135
134
  - lib/optimizely/version.rb
135
+ - lib/start.rb
136
136
  homepage: https://www.optimizely.com/
137
137
  licenses:
138
138
  - Apache-2.0