optimizely-sdk 0.0.12

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.
@@ -0,0 +1,14 @@
1
+ module Optimizely
2
+ module Helpers
3
+ module Group
4
+ ORDERED_POLICY = 'ordered'
5
+ RANDOM_POLICY = 'random'
6
+
7
+ module_function
8
+
9
+ def random_policy?(group)
10
+ group['policy'] == RANDOM_POLICY
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,60 @@
1
+ require_relative 'constants'
2
+ require 'json-schema'
3
+
4
+ module Optimizely
5
+ module Helpers
6
+ module Validator
7
+ module_function
8
+
9
+ def attributes_valid?(attributes)
10
+ # Determines if provided attributes are valid.
11
+ #
12
+ # attributes - User attributes to be validated.
13
+ #
14
+ # Returns boolean depending on validity of attributes.
15
+
16
+ attributes.is_a?(Hash)
17
+ end
18
+
19
+ def datafile_valid?(datafile)
20
+ # Determines if a given datafile is valid.
21
+ #
22
+ # datafile - String JSON representing the project.
23
+ #
24
+ # Returns boolean depending on validity of datafile.
25
+
26
+ JSON::Validator.validate(Helpers::Constants::JSON_SCHEMA, datafile)
27
+ end
28
+
29
+ def error_handler_valid?(error_handler)
30
+ # Determines if a given error handler is valid.
31
+ #
32
+ # error_handler - error_handler to be validated.
33
+ #
34
+ # Returns boolean depending on whether error_handler has a handle_error method.
35
+
36
+ error_handler.respond_to?(:handle_error)
37
+ end
38
+
39
+ def event_dispatcher_valid?(event_dispatcher)
40
+ # Determines if a given event dispatcher is valid.
41
+ #
42
+ # event_dispatcher - event_dispatcher to be validated.
43
+ #
44
+ # Returns boolean depending on whether event_dispatcher has a dispatch_event method.
45
+
46
+ event_dispatcher.respond_to?(:dispatch_event)
47
+ end
48
+
49
+ def logger_valid?(logger)
50
+ # Determines if a given logger is valid.
51
+ #
52
+ # logger - logger to be validated.
53
+ #
54
+ # Returns boolean depending on whether logger has a log method.
55
+
56
+ logger.respond_to?(:log)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,30 @@
1
+ require 'logger'
2
+
3
+ module Optimizely
4
+ class BaseLogger
5
+ # Class encapsulating logging functionality. Override with your own logger providing log method.
6
+
7
+ def log(_level, _message)
8
+ end
9
+ end
10
+
11
+ class NoOpLogger < BaseLogger
12
+ # Class providing log method which logs nothing.
13
+
14
+ def log(_level, _message)
15
+ end
16
+ end
17
+
18
+ class SimpleLogger < BaseLogger
19
+ # Simple wrapper around Logger.
20
+
21
+ def initialize(min_level = Logger::INFO)
22
+ @logger = Logger.new(STDOUT)
23
+ @logger.level = min_level
24
+ end
25
+
26
+ def log(level, message)
27
+ @logger.add(level, message)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,14 @@
1
+ module Optimizely
2
+ class Params
3
+ ACCOUNT_ID = 'd'
4
+ PROJECT_ID = 'a'
5
+ EXPERIMENT_PREFIX = 'x'
6
+ GOAL_ID = 'g'
7
+ GOAL_NAME = 'n'
8
+ SEGMENT_PREFIX = 's'
9
+ END_USER_ID = 'u'
10
+ EVENT_VALUE = 'v'
11
+ SOURCE = 'src'
12
+ TIME = 'time'
13
+ end
14
+ end
@@ -0,0 +1,255 @@
1
+ require 'json'
2
+
3
+ module Optimizely
4
+ class ProjectConfig
5
+ # Representation of the Optimizely project config.
6
+
7
+ PROJECT_CONFIG_LINK_TEMPLATE = 'https://cdn.optimizely.com/json/%{project_id}.json'
8
+ REVENUE_GOAL_KEY = 'Total Revenue'
9
+ REQUEST_TIMEOUT = 10
10
+ RUNNING_EXPERIMENT_STATUS = ['Running']
11
+
12
+ # Gets project config attributes.
13
+ attr_reader :error_handler
14
+ attr_accessor :logger
15
+
16
+ attr_reader :account_id
17
+ attr_reader :project_id
18
+ attr_reader :attributes
19
+ attr_reader :events
20
+ attr_reader :experiments
21
+ attr_reader :groups
22
+ attr_reader :revision
23
+ attr_reader :audiences
24
+
25
+ attr_reader :attribute_key_map
26
+ attr_reader :audience_id_map
27
+ attr_reader :event_key_map
28
+ attr_reader :experiment_id_map
29
+ attr_reader :experiment_key_map
30
+ attr_reader :group_key_map
31
+ attr_reader :audience_id_map
32
+ attr_reader :variation_id_map
33
+ attr_reader :variation_key_map
34
+
35
+ def initialize(datafile, logger, error_handler)
36
+ # ProjectConfig init method to fetch and set project config data
37
+ #
38
+ # datafile - JSON string representing the project
39
+
40
+ config = JSON.load(datafile)
41
+
42
+ @error_handler = error_handler
43
+ @logger = logger
44
+ @account_id = config['accountId']
45
+ @project_id = config['projectId']
46
+ @attributes = config['dimensions']
47
+ @events = config['events']
48
+ @experiments = config['experiments']
49
+ @revision = config['revision']
50
+ @audiences = config['audiences']
51
+ @groups = config.fetch('groups', [])
52
+
53
+ # Utility maps for quick lookup
54
+ @attribute_key_map = generate_key_map(@attributes, 'key')
55
+ @event_key_map = generate_key_map(@events, 'key')
56
+ @group_key_map = generate_key_map(@groups, 'id')
57
+ @group_key_map.each do |key, group|
58
+ exps = group.fetch('experiments')
59
+ exps.each do |exp|
60
+ @experiments.push(exp.merge('groupId' => key))
61
+ end
62
+ end
63
+ @experiment_key_map = generate_key_map(@experiments, 'key')
64
+ @experiment_id_map = generate_key_map(@experiments, 'id')
65
+ @audience_id_map = generate_key_map(@audiences, 'id')
66
+ @variation_id_map = {}
67
+ @variation_key_map = {}
68
+ @experiment_key_map.each do |key, exp|
69
+ variations = exp.fetch('variations')
70
+ @variation_id_map[key] = generate_key_map(variations, 'id')
71
+ @variation_key_map[key] = generate_key_map(variations, 'key')
72
+ end
73
+ end
74
+
75
+ def experiment_running?(experiment_key)
76
+ # Determine if experiment coresponding to given key is running
77
+ #
78
+ # experiment_key - String key representing the experiment
79
+ #
80
+ # Returns true if experiment is running
81
+ experiment = @experiment_key_map[experiment_key]
82
+ return RUNNING_EXPERIMENT_STATUS.include?(experiment['status']) if experiment
83
+ @logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
84
+ @error_handler.handle_error InvalidExperimentError
85
+ nil
86
+ end
87
+
88
+ def get_experiment_id(experiment_key)
89
+ # Retrieves experiment ID for a given key
90
+ #
91
+ # experiment_key - String key representing the experiment
92
+ #
93
+ # Returns String ID
94
+
95
+ experiment = @experiment_key_map[experiment_key]
96
+ return experiment['id'] if experiment
97
+ @logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
98
+ @error_handler.handle_error InvalidExperimentError
99
+ nil
100
+ end
101
+
102
+ def get_goal_keys
103
+ # Retrieves all goals in the project except 'Total Revenue'
104
+ #
105
+ # Returns array of all goal keys except 'Total Revenue'
106
+
107
+ goal_keys = @event_key_map.keys
108
+ goal_keys.delete(REVENUE_GOAL_KEY) if goal_keys.include?(REVENUE_GOAL_KEY)
109
+ goal_keys
110
+ end
111
+
112
+ def get_revenue_goal_id
113
+ # Get ID of the revenue goal for the project
114
+ #
115
+ # Returns revenue goal ID
116
+
117
+ revenue_goal = @event_key_map[REVENUE_GOAL_KEY]
118
+ return revenue_goal['id'] if revenue_goal
119
+ nil
120
+ end
121
+
122
+ def get_experiment_ids_for_goal(goal_key)
123
+ # Get experiment IDs for the provided goal key.
124
+ #
125
+ # goal_key - Goal key for which experiment IDs are to be retrieved.
126
+ #
127
+ # Returns array of all experiment IDs for the goal.
128
+
129
+ goal = @event_key_map[goal_key]
130
+ return goal['experimentIds'] if goal
131
+ @logger.log Logger::ERROR, "Event '#{goal_key}' is not in datafile."
132
+ @error_handler.handle_error InvalidGoalError
133
+ []
134
+ end
135
+
136
+ def get_traffic_allocation(experiment_key)
137
+ # Retrieves traffic allocation for a given experiment Key
138
+ #
139
+ # experiment_key - String Key representing the experiment
140
+ #
141
+ # Returns traffic allocation for the experiment or nil
142
+
143
+ experiment = @experiment_key_map[experiment_key]
144
+ return experiment['trafficAllocation'] if experiment
145
+ @logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
146
+ @error_handler.handle_error InvalidExperimentError
147
+ nil
148
+ end
149
+
150
+ def get_audience_ids_for_experiment(experiment_key)
151
+ # Get audience IDs for the experiment
152
+ #
153
+ # experiment_key - Experiment key for which audience IDs are to be determined
154
+ #
155
+ # Returns audience IDs corresponding to the experiment.
156
+
157
+ experiment = @experiment_key_map[experiment_key]
158
+ return experiment['audienceIds'] if experiment
159
+ @logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
160
+ @error_handler.handle_error InvalidExperimentError
161
+ nil
162
+ end
163
+
164
+ def get_audience_conditions_from_id(audience_id)
165
+ # Get audience conditions for the provided audience ID
166
+ #
167
+ # audience_id - ID of the audience
168
+ #
169
+ # Returns conditions for the audience
170
+
171
+ audience = @audience_id_map[audience_id]
172
+ return audience['conditions'] if audience
173
+ @logger.log Logger::ERROR, "Audience '#{audience_id}' is not in datafile."
174
+ @error_handler.handle_error InvalidAudienceError
175
+ nil
176
+ end
177
+
178
+ def get_variation_key_from_id(experiment_key, variation_id)
179
+ # Get variation key given experiment key and variation ID
180
+ #
181
+ # experiment_key - Key representing parent experiment of variation
182
+ # variation_id - ID of the variation
183
+ #
184
+ # Returns key of the variation
185
+
186
+ variation_id_map = @variation_id_map[experiment_key]
187
+ if variation_id_map
188
+ variation = variation_id_map[variation_id]
189
+ return variation['key'] if variation
190
+ @logger.log Logger::ERROR, "Variation id '#{variation_id}' is not in datafile."
191
+ @error_handler.handle_error InvalidVariationError
192
+ return nil
193
+ end
194
+
195
+ @logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
196
+ @error_handler.handle_error InvalidExperimentError
197
+ nil
198
+ end
199
+
200
+ def get_variation_id_from_key(experiment_key, variation_key)
201
+ # Get variation ID given experiment key and variation key
202
+ #
203
+ # experiment_key - Key representing parent experiment of variation
204
+ # variation_key - Key of the variation
205
+ #
206
+ # Returns ID of the variation
207
+
208
+ variation_key_map = @variation_key_map[experiment_key]
209
+ if variation_key_map
210
+ variation = variation_key_map[variation_key]
211
+ return variation['id'] if variation
212
+ @logger.log Logger::ERROR, "Variation key '#{variation_key}' is not in datafile."
213
+ @error_handler.handle_error InvalidVariationError
214
+ return nil
215
+ end
216
+
217
+ @logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
218
+ @error_handler.handle_error InvalidExperimentError
219
+ nil
220
+ end
221
+
222
+ def get_forced_variations(experiment_key)
223
+ # Retrieves forced variations for a given experiment Key
224
+ #
225
+ # experiment_key - String Key representing the experiment
226
+ #
227
+ # Returns forced variations for the experiment or nil
228
+
229
+ experiment = @experiment_key_map[experiment_key]
230
+ return experiment['forcedVariations'] if experiment
231
+ @logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
232
+ @error_handler.handle_error InvalidExperimentError
233
+ end
234
+
235
+ def get_experiment_group_id(experiment_key)
236
+ experiment = @experiment_key_map[experiment_key]
237
+ return experiment['groupId'] if experiment
238
+ @logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
239
+ @error_handler.handle_error InvalidExperimentError
240
+ end
241
+
242
+ private
243
+
244
+ def generate_key_map(array, key)
245
+ # Helper method to generate map from key to hash in array of hashes
246
+ #
247
+ # array - Array consisting of hash
248
+ # key - Key in each hash which will be key in the map
249
+ #
250
+ # Returns map mapping key to hash
251
+
252
+ Hash[array.map { |obj| [obj[key], obj] }]
253
+ end
254
+ end
255
+ end
@@ -0,0 +1,3 @@
1
+ module Optimizely
2
+ VERSION = '0.0.12'
3
+ end
@@ -0,0 +1,6 @@
1
+ require "./optimizely.rb"
2
+ require 'json'
3
+ require 'httparty'
4
+ # conf = '{"projectId": "53020", "events": [{"key": "Total Revenue", "id": "5641834680287232", "experimentIds": ["53033"]}], "experiments": [], "dimensions": [], "audiences": [], "revision": "5", "groups": [{"policy": "random", "id": "53025", "experiments": [{"variations": [{"key": "BLAH_1", "id": "53027"}, {"key": "BLAH_2", "id": "53028"}], "trafficAllocation": [{"endOfRange": 5000, "entityId": "53027"}, {"endOfRange": 10000, "entityId": "53028"}], "forcedVariations": {}, "audienceIds": [], "percentageIncluded": 3000, "status": "Running", "key": "exp_1", "id": "53026"}, {"variations": [{"key": "BLAH_1", "id": "53034"}, {"key": "BLAH_2", "id": "53035"}], "trafficAllocation": [{"endOfRange": 3000, "entityId": "53034"}, {"endOfRange": 10000, "entityId": "53035"}], "forcedVariations": {}, "audienceIds": [], "percentageIncluded": 4000, "status": "Running", "key": "exp_2", "id": "53033"}], "trafficAllocation": [{"endOfRange": 3000, "entityId": "53026"}, {"endOfRange": 7000, "entityId": "53033"}]}], "accountId": "23001", "version": "1"}'
5
+ conf = HTTParty.get('https://cdn.optimizely.com/json/6377970066.json').body
6
+ @optly = Optimizely::Project.new(conf, nil, Optimizely::SimpleLogger.new)
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: optimizely-sdk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.12
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Delikat
8
+ - Haley Bash
9
+ - Optimizely
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2016-07-07 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: bundler
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - "~>"
20
+ - !ruby/object:Gem::Version
21
+ version: '1.10'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '1.10'
29
+ - !ruby/object:Gem::Dependency
30
+ name: rake
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: '10.0'
36
+ type: :development
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '10.0'
43
+ - !ruby/object:Gem::Dependency
44
+ name: rspec
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: 3.4.0
50
+ type: :development
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: 3.4.0
57
+ - !ruby/object:Gem::Dependency
58
+ name: rubocop
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: 0.41.1
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - "~>"
69
+ - !ruby/object:Gem::Version
70
+ version: 0.41.1
71
+ - !ruby/object:Gem::Dependency
72
+ name: murmurhash3
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - "~>"
76
+ - !ruby/object:Gem::Version
77
+ version: 0.1.6
78
+ type: :runtime
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - "~>"
83
+ - !ruby/object:Gem::Version
84
+ version: 0.1.6
85
+ - !ruby/object:Gem::Dependency
86
+ name: httparty
87
+ requirement: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - "~>"
90
+ - !ruby/object:Gem::Version
91
+ version: 0.13.7
92
+ type: :runtime
93
+ prerelease: false
94
+ version_requirements: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - "~>"
97
+ - !ruby/object:Gem::Version
98
+ version: 0.13.7
99
+ - !ruby/object:Gem::Dependency
100
+ name: json-schema
101
+ requirement: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - "~>"
104
+ - !ruby/object:Gem::Version
105
+ version: 2.6.2
106
+ type: :runtime
107
+ prerelease: false
108
+ version_requirements: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - "~>"
111
+ - !ruby/object:Gem::Version
112
+ version: 2.6.2
113
+ description: A Ruby SDK for Optimizely's server-side testing product. For more information,
114
+ please visit http://developers.optimizely.com/server.
115
+ email:
116
+ - server-side-testing@optimizely.com
117
+ executables: []
118
+ extensions: []
119
+ extra_rdoc_files: []
120
+ files:
121
+ - lib/optimizely.rb
122
+ - lib/optimizely/audience.rb
123
+ - lib/optimizely/bucketer.rb
124
+ - lib/optimizely/condition.rb
125
+ - lib/optimizely/error_handler.rb
126
+ - lib/optimizely/event_builder.rb
127
+ - lib/optimizely/event_dispatcher.rb
128
+ - lib/optimizely/exceptions.rb
129
+ - lib/optimizely/helpers/constants.rb
130
+ - lib/optimizely/helpers/group.rb
131
+ - lib/optimizely/helpers/validator.rb
132
+ - lib/optimizely/logger.rb
133
+ - lib/optimizely/params.rb
134
+ - lib/optimizely/project_config.rb
135
+ - lib/optimizely/version.rb
136
+ - lib/start.rb
137
+ homepage: http://developers.optimizely.com/server
138
+ licenses:
139
+ - Apache-2.0
140
+ metadata: {}
141
+ post_install_message:
142
+ rdoc_options: []
143
+ require_paths:
144
+ - lib
145
+ required_ruby_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ required_rubygems_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ requirements: []
156
+ rubyforge_project:
157
+ rubygems_version: 2.2.5
158
+ signing_key:
159
+ specification_version: 4
160
+ summary: Ruby SDK for Optimizely's testing framework
161
+ test_files: []