optimizely-sdk 0.1.2 → 1.0.0

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