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,24 @@
1
+ module Optimizely
2
+ class BaseErrorHandler
3
+ # Class encapsulating exception handling functionality.
4
+ # Override with your own exception handler providing a handle_error method.
5
+
6
+ def handle_error(_error)
7
+ end
8
+ end
9
+
10
+ class NoOpErrorHandler < BaseErrorHandler
11
+ # Class providing handle_error method that suppresses errors.
12
+
13
+ def handle_error(_error)
14
+ end
15
+ end
16
+
17
+ class RaiseErrorHandler < BaseErrorHandler
18
+ # Class providing a handle_error method that raises exceptions.
19
+
20
+ def handle_error(error)
21
+ raise error
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,200 @@
1
+ require_relative './audience'
2
+ require_relative './params'
3
+ require_relative './version'
4
+
5
+ module Optimizely
6
+ class Event
7
+ # Representation of an event which can be sent to the Optimizely logging endpoint.
8
+
9
+ # Event API format
10
+ OFFLINE_API_PATH = 'https://%{project_id}.log.optimizely.com/event'
11
+
12
+ # Gets/Sets event params.
13
+ attr_accessor :params
14
+
15
+ def initialize(params)
16
+ @params = params
17
+ end
18
+
19
+ def url
20
+ # URL for sending impression/conversion event.
21
+ #
22
+ # project_id - ID for the project.
23
+ #
24
+ # Returns URL for event API.
25
+
26
+ sprintf(OFFLINE_API_PATH, project_id: @params[Params::PROJECT_ID])
27
+ end
28
+ end
29
+
30
+ class EventBuilder
31
+ # Class which encapsulates methods to build events for tracking impressions and conversions.
32
+
33
+ # Attribute mapping format
34
+ ATTRIBUTE_PARAM_FORMAT = '%{segment_prefix}%{segment_id}'
35
+
36
+ # Experiment mapping format
37
+ EXPERIMENT_PARAM_FORMAT = '%{experiment_prefix}%{experiment_id}'
38
+
39
+ attr_accessor :config
40
+ attr_accessor :bucketer
41
+ attr_accessor :params
42
+
43
+ def initialize(config, bucketer)
44
+ @config = config
45
+ @bucketer = bucketer
46
+ @params = {}
47
+ end
48
+
49
+ def create_impression_event(experiment_key, variation_id, user_id, attributes)
50
+ # Create conversion Event to be sent to the logging endpoint.
51
+ #
52
+ # experiment_key - Experiment for which impression needs to be recorded.
53
+ # variation_id - ID for variation which would be presented to user.
54
+ # user_id - ID for user.
55
+ # attributes - Hash representing user attributes and values which need to be recorded.
56
+ #
57
+ # Returns event hash encapsulating the impression event.
58
+
59
+ @params = {}
60
+ add_common_params(user_id, attributes)
61
+ add_impression_goal(experiment_key)
62
+ add_experiment(experiment_key, variation_id)
63
+ Event.new(@params)
64
+ end
65
+
66
+ def create_conversion_event(event_key, user_id, attributes, event_value, experiment_keys)
67
+ # Create conversion Event to be sent to the logging endpoint.
68
+ #
69
+ # event_key - Goal key representing the event which needs to be recorded.
70
+ # user_id - ID for user.
71
+ # attributes - Hash representing user attributes and values which need to be recorded.
72
+ # event_value - Value associated with the event. Can be used to represent revenue in cents.
73
+ # experiment_keys - Array of valid experiment keys for the goal
74
+
75
+ @params = {}
76
+ add_common_params(user_id, attributes)
77
+ add_conversion_goal(event_key, event_value)
78
+ add_experiment_variation_params(user_id, experiment_keys)
79
+ Event.new(@params)
80
+ end
81
+
82
+ private
83
+
84
+ def add_project_id
85
+ # Add project ID to the event.
86
+
87
+ @params[Params::PROJECT_ID] = @config.project_id
88
+ end
89
+
90
+ def add_account_id
91
+ # Add account ID to the event.
92
+
93
+ @params[Params::ACCOUNT_ID] = @config.account_id
94
+ end
95
+
96
+ def add_user_id(user_id)
97
+ # Add user ID to the event.
98
+
99
+ @params[Params::END_USER_ID] = user_id
100
+ end
101
+
102
+ def add_attributes(attributes)
103
+ # Add attribute(s) information to the event.
104
+ #
105
+ # attributes - Hash representing user attributes and values which need to be recorded.
106
+
107
+ return if attributes.nil?
108
+
109
+ attributes.keys.each do |attribute_key|
110
+ attribute_value = attributes[attribute_key]
111
+ next unless attribute_value
112
+ segment_id = @config.attribute_key_map[attribute_key]['segmentId']
113
+ segment_param = sprintf(ATTRIBUTE_PARAM_FORMAT,
114
+ segment_prefix: Params::SEGMENT_PREFIX, segment_id: segment_id)
115
+ params[segment_param] = attribute_value
116
+ end
117
+ end
118
+
119
+ def add_source
120
+ # Add source information to the event.
121
+
122
+ @params[Params::SOURCE] = sprintf('ruby-sdk-%{version}', version: VERSION)
123
+ end
124
+
125
+ def add_time
126
+ # Add time information to the event.
127
+
128
+ @params[Params::TIME] = Time.now.strftime('%s').to_i
129
+ end
130
+
131
+ def add_common_params(user_id, attributes)
132
+ # Add params which are used same in both conversion and impression events.
133
+ #
134
+ # user_id - ID for user.
135
+ # attributes - Hash representing user attributes and values which need to be recorded.
136
+
137
+ add_project_id
138
+ add_account_id
139
+ add_user_id(user_id)
140
+ add_attributes(attributes)
141
+ add_source
142
+ add_time
143
+ end
144
+
145
+ def add_impression_goal(experiment_key)
146
+ # Add impression goal information to the event.
147
+ #
148
+ # experiment_key - Experiment which is being activated.
149
+
150
+ # For tracking impressions, goal ID is set equal to experiment ID of experiment being activated.
151
+ @params[Params::GOAL_ID] = @config.get_experiment_id(experiment_key)
152
+ @params[Params::GOAL_NAME] = 'visitor-event'
153
+ end
154
+
155
+ def add_experiment(experiment_key, variation_id)
156
+ # Add experiment to variation mapping to the impression event.
157
+ #
158
+ # experiment_key - Experiment which is being activated.
159
+ # variation_id - ID for variation which would be presented to user.
160
+
161
+ experiment_id = @config.get_experiment_id(experiment_key)
162
+ experiment_param = sprintf(EXPERIMENT_PARAM_FORMAT,
163
+ experiment_prefix: Params::EXPERIMENT_PREFIX, experiment_id: experiment_id)
164
+ @params[experiment_param] = variation_id
165
+ end
166
+
167
+ def add_experiment_variation_params(user_id, experiment_keys)
168
+ # Maps experiment and corresponding variation as parameters to be used in the event tracking call.
169
+ #
170
+ # user_id - ID for user.
171
+ # experiment_keys - Array of valid experiment keys for the goal
172
+
173
+ experiment_keys.each do |experiment_key|
174
+ variation_id = @bucketer.bucket(experiment_key, user_id)
175
+ experiment_id = @config.experiment_key_map[experiment_key]['id']
176
+ experiment_param = sprintf(EXPERIMENT_PARAM_FORMAT,
177
+ experiment_prefix: Params::EXPERIMENT_PREFIX, experiment_id: experiment_id)
178
+ @params[experiment_param] = variation_id
179
+ end
180
+ end
181
+
182
+ def add_conversion_goal(event_key, event_value)
183
+ # Add conversion goal information to the event.
184
+ #
185
+ # event_key - Goal key representing the event which needs to be recorded.
186
+ # event_value - Value associated with the event. Can be used to represent revenue in cents.
187
+
188
+ goal_id = @config.event_key_map[event_key]['id']
189
+ event_ids = goal_id
190
+
191
+ if event_value
192
+ event_ids = sprintf('%{goal_id},%{revenue_id}', goal_id: goal_id, revenue_id: @config.get_revenue_goal_id)
193
+ @params[Params::EVENT_VALUE] = event_value
194
+ end
195
+
196
+ @params[Params::GOAL_ID] = event_ids
197
+ @params[Params::GOAL_NAME] = event_key
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,18 @@
1
+ require 'httparty'
2
+
3
+ module Optimizely
4
+ class EventDispatcher
5
+ REQUEST_TIMEOUT = 10
6
+
7
+ def dispatch_event(url, params)
8
+ # Dispatch the event being represented by the Event object.
9
+ #
10
+ # url - URL to send impression/conversion event to.
11
+ # params - Params to be sent to the impression/conversion event.
12
+
13
+ HTTParty.get(url, query: params, timeout: REQUEST_TIMEOUT)
14
+ rescue Timeout::Error => e
15
+ return e
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,83 @@
1
+ module Optimizely
2
+ class Error < StandardError; end
3
+
4
+ class InvalidAudienceError < Error
5
+ # Raised when an invalid audience is provided
6
+
7
+ def initialize(msg = 'Provided audience is not in datafile.')
8
+ super
9
+ end
10
+ end
11
+
12
+ class InvalidAttributeError < Error
13
+ # Raised when an invalid attribute is provided
14
+
15
+ def initialize(msg = 'Provided attribute is not in datafile.')
16
+ super
17
+ end
18
+ end
19
+
20
+ class InvalidAttributeFormatError < Error
21
+ # Raised when attributes are provided in an invalid format (e.g. not a Hash)
22
+
23
+ def initialize(msg = 'Attributes provided are in an invalid format.')
24
+ super
25
+ end
26
+ end
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
38
+
39
+ def initialize(msg = 'Provided error_handler is in an invalid format.')
40
+ super
41
+ end
42
+ end
43
+
44
+ class InvalidEventDispatcherError < Error
45
+ # Raised when an invalid event dispatcher is provided
46
+
47
+ def initialize(msg = 'Provided event_dispatcher is in an invalid format.')
48
+ super
49
+ end
50
+ end
51
+
52
+ class InvalidExperimentError < Error
53
+ # Raised when an invalid experiment key is provided
54
+
55
+ def initialize(msg = 'Provided experiment is not in datafile.')
56
+ super
57
+ end
58
+ end
59
+
60
+ class InvalidGoalError < Error
61
+ # Raised when an invalid event key is provided
62
+
63
+ def initialize(msg = 'Provided event is not in datafile.')
64
+ super
65
+ end
66
+ end
67
+
68
+ class InvalidLoggerError < Error
69
+ # Raised when an invalid logger is provided
70
+
71
+ def initialize(msg = 'Provided logger is in an invalid format.')
72
+ super
73
+ end
74
+ end
75
+
76
+ class InvalidVariationError < Error
77
+ # Raised when an invalid variation key or ID is provided
78
+
79
+ def initialize(msg = 'Provided variation is not in datafile.')
80
+ super
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,173 @@
1
+ module Optimizely
2
+ module Helpers
3
+ module Constants
4
+ JSON_SCHEMA = {
5
+ 'type' => 'object',
6
+ 'properties' => {
7
+ 'projectId' => {
8
+ 'type' => 'string'
9
+ },
10
+ 'accountId' => {
11
+ 'type' => 'string'
12
+ },
13
+ 'experiments' => {
14
+ 'type' => 'array',
15
+ 'items' => {
16
+ 'type' => 'object',
17
+ 'properties' => {
18
+ 'id' => {
19
+ 'type' => 'string'
20
+ },
21
+ 'key' => {
22
+ 'type' => 'string'
23
+ },
24
+ 'variations' => {
25
+ 'type' => 'array',
26
+ 'items' => {
27
+ 'type' => 'object',
28
+ 'properties' => {
29
+ 'id' => {
30
+ 'type' => 'string'
31
+ },
32
+ 'key' => {
33
+ 'type' => 'string'
34
+ }
35
+ },
36
+ 'required' => [
37
+ 'id',
38
+ 'key'
39
+ ]
40
+ }
41
+ },
42
+ 'trafficAllocation' => {
43
+ 'type' => 'array',
44
+ 'items' => {
45
+ 'type' => 'object',
46
+ 'properties' => {
47
+ 'entityId' => {
48
+ 'type' => 'string'
49
+ },
50
+ 'endOfRange' => {
51
+ 'type' => 'integer'
52
+ }
53
+ },
54
+ 'required' => [
55
+ 'entityId',
56
+ 'endOfRange'
57
+ ]
58
+ }
59
+ },
60
+ 'percentageIncluded' => {
61
+ 'type' => 'integer'
62
+ },
63
+ 'audienceIds' => {
64
+ 'type' => 'array',
65
+ 'items' => {
66
+ 'type' => 'string'
67
+ }
68
+ },
69
+ 'forcedVariations' => {
70
+ 'type' => 'object'
71
+ }
72
+ },
73
+ 'required' => [
74
+ 'id',
75
+ 'key',
76
+ 'variations',
77
+ 'trafficAllocation',
78
+ 'percentageIncluded',
79
+ 'audienceIds',
80
+ 'forcedVariations'
81
+ ]
82
+ }
83
+ },
84
+ 'events' => {
85
+ 'type' => 'array',
86
+ 'items' => {
87
+ 'type' => 'object',
88
+ 'properties' => {
89
+ 'key' => {
90
+ 'type' => 'string'
91
+ },
92
+ 'experimentIds' => {
93
+ 'type' => 'array',
94
+ 'items' => {
95
+ 'type' => 'string'
96
+ }
97
+ },
98
+ 'id' => {
99
+ 'type' => 'string'
100
+ }
101
+ },
102
+ 'required' => [
103
+ 'key',
104
+ 'experimentIds',
105
+ 'id'
106
+ ]
107
+ }
108
+ },
109
+ 'audiences' => {
110
+ 'type' => 'array',
111
+ 'items' => {
112
+ 'type' => 'object',
113
+ 'properties' => {
114
+ 'id' => {
115
+ 'type' => 'string'
116
+ },
117
+ 'name' => {
118
+ 'type' => 'string'
119
+ },
120
+ 'conditions' => {
121
+ 'type' => 'string'
122
+ }
123
+ },
124
+ 'required' => [
125
+ 'id',
126
+ 'name',
127
+ 'conditions'
128
+ ]
129
+ }
130
+ },
131
+ 'dimensions' => {
132
+ 'type' => 'array',
133
+ 'items' => {
134
+ 'type' => 'object',
135
+ 'properties' => {
136
+ 'id' => {
137
+ 'type' => 'string'
138
+ },
139
+ 'key' => {
140
+ 'type' => 'string'
141
+ },
142
+ 'segmentId' => {
143
+ 'type' => 'string'
144
+ }
145
+ },
146
+ 'required' => [
147
+ 'id',
148
+ 'key',
149
+ 'segmentId'
150
+ ]
151
+ }
152
+ },
153
+ 'version' => {
154
+ 'type' => 'string'
155
+ },
156
+ 'revision' => {
157
+ 'type' => 'string'
158
+ }
159
+ },
160
+ 'required' => [
161
+ 'projectId',
162
+ 'accountId',
163
+ 'experiments',
164
+ 'events',
165
+ 'audiences',
166
+ 'dimensions',
167
+ 'version',
168
+ 'revision'
169
+ ]
170
+ }
171
+ end
172
+ end
173
+ end