optimizely-sdk 0.0.12

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