optimizely-sdk 1.5.0 → 2.0.0.beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/optimizely.rb +0 -47
- data/lib/optimizely/bucketer.rb +16 -18
- data/lib/optimizely/decision_service.rb +120 -135
- data/lib/optimizely/event_builder.rb +142 -158
- data/lib/optimizely/exceptions.rb +0 -9
- data/lib/optimizely/helpers/event_tag_utils.rb +5 -66
- data/lib/optimizely/helpers/validator.rb +0 -5
- data/lib/optimizely/project_config.rb +4 -107
- data/lib/optimizely/version.rb +1 -4
- metadata +7 -6
- data/lib/optimizely/notification_center.rb +0 -148
@@ -17,12 +17,8 @@ require_relative './audience'
|
|
17
17
|
require_relative './params'
|
18
18
|
require_relative './version'
|
19
19
|
require_relative '../optimizely/helpers/event_tag_utils'
|
20
|
-
require 'securerandom'
|
21
20
|
|
22
21
|
module Optimizely
|
23
|
-
|
24
|
-
RESERVED_ATTRIBUTE_KEY_BUCKETING_ID_EVENT_PARAM_KEY = "optimizely_bucketing_id".freeze
|
25
|
-
|
26
22
|
class Event
|
27
23
|
# Representation of an event which can be sent to the Optimizely logging endpoint.
|
28
24
|
|
@@ -45,210 +41,198 @@ module Optimizely
|
|
45
41
|
end
|
46
42
|
|
47
43
|
class BaseEventBuilder
|
48
|
-
CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom'
|
49
|
-
|
50
44
|
attr_reader :config
|
45
|
+
attr_reader :params
|
51
46
|
|
52
47
|
def initialize(config)
|
53
48
|
@config = config
|
49
|
+
@params = {}
|
54
50
|
end
|
55
51
|
|
56
52
|
private
|
57
53
|
|
58
|
-
def
|
59
|
-
#
|
60
|
-
#
|
61
|
-
# user_id -
|
62
|
-
# attributes -
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
attributes.keys.each do |attribute_key|
|
71
|
-
# Omit null attribute value
|
72
|
-
attribute_value = attributes[attribute_key]
|
73
|
-
next if attribute_value.nil?
|
74
|
-
|
75
|
-
if attribute_key.eql? RESERVED_ATTRIBUTE_KEY_BUCKETING_ID
|
76
|
-
# TODO (Copied from PHP-SDK) (Alda): the type for bucketing ID attribute may change so
|
77
|
-
# that custom attributes are not overloaded
|
78
|
-
feature = {
|
79
|
-
entity_id: RESERVED_ATTRIBUTE_KEY_BUCKETING_ID,
|
80
|
-
key: RESERVED_ATTRIBUTE_KEY_BUCKETING_ID_EVENT_PARAM_KEY,
|
81
|
-
type: CUSTOM_ATTRIBUTE_FEATURE_TYPE,
|
82
|
-
value: attribute_value
|
83
|
-
}
|
84
|
-
else
|
85
|
-
# Skip attributes not in the datafile
|
86
|
-
attribute_id = @config.get_attribute_id(attribute_key)
|
87
|
-
next unless attribute_id
|
88
|
-
|
89
|
-
feature = {
|
90
|
-
entity_id: attribute_id,
|
91
|
-
key: attribute_key,
|
92
|
-
type: CUSTOM_ATTRIBUTE_FEATURE_TYPE,
|
93
|
-
value: attribute_value
|
94
|
-
}
|
95
|
-
|
96
|
-
end
|
97
|
-
visitor_attributes.push(feature)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
common_params = {
|
102
|
-
account_id: @config.account_id,
|
103
|
-
project_id: @config.project_id,
|
104
|
-
visitors: [
|
105
|
-
{
|
106
|
-
attributes: visitor_attributes,
|
107
|
-
snapshots: [],
|
108
|
-
visitor_id: user_id
|
109
|
-
}
|
110
|
-
],
|
111
|
-
anonymize_ip: @config.anonymize_ip,
|
112
|
-
revision: @config.revision,
|
113
|
-
client_name: CLIENT_ENGINE,
|
114
|
-
client_version: VERSION
|
115
|
-
}
|
116
|
-
|
117
|
-
common_params
|
54
|
+
def add_common_params(user_id, attributes)
|
55
|
+
# Add params which are used in both conversion and impression events.
|
56
|
+
#
|
57
|
+
# user_id - ID for user.
|
58
|
+
# attributes - Hash representing user attributes and values which need to be recorded.
|
59
|
+
|
60
|
+
add_project_id
|
61
|
+
add_account_id
|
62
|
+
add_user_id(user_id)
|
63
|
+
add_attributes(attributes)
|
64
|
+
add_source
|
65
|
+
add_time
|
118
66
|
end
|
119
67
|
end
|
120
68
|
|
121
69
|
class EventBuilder < BaseEventBuilder
|
122
|
-
|
70
|
+
CONVERSION_EVENT_ENDPOINT = 'https://logx.optimizely.com/log/event'
|
71
|
+
IMPRESSION_EVENT_ENDPOINT = 'https://logx.optimizely.com/log/decision'
|
123
72
|
POST_HEADERS = { 'Content-Type' => 'application/json' }
|
124
|
-
ACTIVATE_EVENT_KEY = 'campaign_activated'
|
125
73
|
|
126
74
|
def create_impression_event(experiment, variation_id, user_id, attributes)
|
127
|
-
# Create
|
75
|
+
# Create conversion Event to be sent to the logging endpoint.
|
128
76
|
#
|
129
|
-
# experiment -
|
130
|
-
# variation_id -
|
131
|
-
# user_id -
|
132
|
-
# attributes -
|
77
|
+
# experiment - Experiment for which impression needs to be recorded.
|
78
|
+
# variation_id - ID for variation which would be presented to user.
|
79
|
+
# user_id - ID for user.
|
80
|
+
# attributes - Hash representing user attributes and values which need to be recorded.
|
133
81
|
#
|
134
|
-
# Returns
|
135
|
-
|
136
|
-
event_params = get_common_params(user_id, attributes)
|
137
|
-
impression_params = get_impression_params(experiment, variation_id)
|
138
|
-
event_params[:visitors][0][:snapshots].push(impression_params)
|
82
|
+
# Returns event hash encapsulating the impression event.
|
139
83
|
|
140
|
-
|
84
|
+
@params = {}
|
85
|
+
add_common_params(user_id, attributes)
|
86
|
+
add_decision(experiment, variation_id)
|
87
|
+
add_attributes(attributes)
|
88
|
+
Event.new(:post, IMPRESSION_EVENT_ENDPOINT, @params, POST_HEADERS)
|
141
89
|
end
|
142
90
|
|
143
91
|
def create_conversion_event(event_key, user_id, attributes, event_tags, experiment_variation_map)
|
144
92
|
# Create conversion Event to be sent to the logging endpoint.
|
145
93
|
#
|
146
|
-
# event_key -
|
147
|
-
# user_id -
|
148
|
-
# attributes -
|
149
|
-
# event_tags -
|
150
|
-
# experiment_variation_map -
|
94
|
+
# event_key - Event key representing the event which needs to be recorded.
|
95
|
+
# user_id - ID for user.
|
96
|
+
# attributes - Hash representing user attributes and values which need to be recorded.
|
97
|
+
# event_tags - Hash representing metadata associated with the event.
|
98
|
+
# experiment_variation_map - Map of experiment ID to the ID of the variation that the user is bucketed into.
|
151
99
|
#
|
152
|
-
# Returns
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
100
|
+
# Returns event hash encapsulating the conversion event.
|
101
|
+
|
102
|
+
@params = {}
|
103
|
+
add_common_params(user_id, attributes)
|
104
|
+
add_conversion_event(event_key)
|
105
|
+
add_event_tags(event_tags)
|
106
|
+
add_layer_states(experiment_variation_map)
|
107
|
+
Event.new(:post, CONVERSION_EVENT_ENDPOINT, @params, POST_HEADERS)
|
159
108
|
end
|
160
109
|
|
161
110
|
private
|
162
111
|
|
163
|
-
def
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
112
|
+
def add_common_params(user_id, attributes)
|
113
|
+
super
|
114
|
+
@params['isGlobalHoldback'] = false
|
115
|
+
end
|
116
|
+
|
117
|
+
def add_project_id
|
118
|
+
@params['projectId'] = @config.project_id
|
119
|
+
end
|
120
|
+
|
121
|
+
def add_account_id
|
122
|
+
@params['accountId'] = @config.account_id
|
123
|
+
end
|
124
|
+
|
125
|
+
def add_user_id(user_id)
|
126
|
+
@params['visitorId'] = user_id
|
127
|
+
end
|
128
|
+
|
129
|
+
def add_attributes(attributes)
|
130
|
+
@params['userFeatures'] = []
|
131
|
+
|
132
|
+
return if attributes.nil?
|
133
|
+
|
134
|
+
attributes.keys.each do |attribute_key|
|
135
|
+
# Omit falsy attribute values
|
136
|
+
attribute_value = attributes[attribute_key]
|
137
|
+
next unless attribute_value
|
138
|
+
|
139
|
+
# Skip attributes not in the datafile
|
140
|
+
attribute_id = @config.get_attribute_id(attribute_key)
|
141
|
+
next unless attribute_id
|
142
|
+
|
143
|
+
feature = {
|
144
|
+
'id' => attribute_id,
|
145
|
+
'name' => attribute_key,
|
146
|
+
'type' => 'custom',
|
147
|
+
'value' => attribute_value,
|
148
|
+
'shouldIndex' => true,
|
149
|
+
}
|
150
|
+
@params['userFeatures'].push(feature)
|
151
|
+
end
|
152
|
+
end
|
170
153
|
|
154
|
+
def add_decision(experiment, variation_id)
|
171
155
|
experiment_key = experiment['key']
|
172
156
|
experiment_id = experiment['id']
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
variation_id: variation_id,
|
179
|
-
}],
|
180
|
-
events: [{
|
181
|
-
entity_id: @config.experiment_key_map[experiment_key]['layerId'],
|
182
|
-
timestamp: get_timestamp(),
|
183
|
-
key: ACTIVATE_EVENT_KEY,
|
184
|
-
uuid: get_uuid()
|
185
|
-
}]
|
157
|
+
@params['layerId'] = @config.experiment_key_map[experiment_key]['layerId']
|
158
|
+
@params['decision'] = {
|
159
|
+
'variationId' => variation_id,
|
160
|
+
'experimentId' => experiment_id,
|
161
|
+
'isLayerHoldback' => false,
|
186
162
|
}
|
187
|
-
|
188
|
-
impressionEventParams;
|
189
163
|
end
|
190
164
|
|
191
|
-
def
|
192
|
-
|
193
|
-
|
194
|
-
# event_key - +String+ Key representing the event which needs to be recorded
|
195
|
-
# event_tags - +Hash+ Values associated with the event.
|
196
|
-
# experiment_variation_map - +Hash+ Map of experiment IDs to bucketed variation IDs
|
197
|
-
#
|
198
|
-
# Returns +Hash+ Impression event params
|
165
|
+
def add_event_tags(event_tags)
|
166
|
+
@params['eventFeatures'] ||= []
|
167
|
+
@params['eventMetrics'] ||= []
|
199
168
|
|
200
|
-
|
169
|
+
return if event_tags.nil?
|
201
170
|
|
202
|
-
|
171
|
+
event_tags.each_pair do |event_tag_key, event_tag_value|
|
172
|
+
next if event_tag_value.nil?
|
203
173
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
}],
|
210
|
-
events: [],
|
174
|
+
event_feature = {
|
175
|
+
'name' => event_tag_key,
|
176
|
+
'type' => 'custom',
|
177
|
+
'value' => event_tag_value,
|
178
|
+
'shouldIndex' => false,
|
211
179
|
}
|
180
|
+
@params['eventFeatures'].push(event_feature)
|
212
181
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
182
|
+
end
|
183
|
+
|
184
|
+
event_value = Helpers::EventTagUtils.get_revenue_value(event_tags)
|
185
|
+
|
186
|
+
if event_value
|
187
|
+
event_metric = {
|
188
|
+
'name' => 'revenue',
|
189
|
+
'value' => event_value
|
218
190
|
}
|
191
|
+
@params['eventMetrics'].push(event_metric)
|
192
|
+
end
|
219
193
|
|
220
|
-
|
221
|
-
revenue_value = Helpers::EventTagUtils.get_revenue_value(event_tags)
|
222
|
-
if revenue_value
|
223
|
-
event_object[:revenue] = revenue_value
|
224
|
-
end
|
194
|
+
end
|
225
195
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
196
|
+
def add_conversion_event(event_key)
|
197
|
+
# Add conversion event information to the event.
|
198
|
+
#
|
199
|
+
# event_key - Event key representing the event which needs to be recorded.
|
230
200
|
|
231
|
-
|
232
|
-
|
201
|
+
event_id = @config.event_key_map[event_key]['id']
|
202
|
+
event_name = @config.event_key_map[event_key]['key']
|
233
203
|
|
234
|
-
|
204
|
+
@params['eventEntityId'] = event_id
|
205
|
+
@params['eventName'] = event_name
|
206
|
+
end
|
235
207
|
|
236
|
-
|
208
|
+
def add_layer_states(experiments_map)
|
209
|
+
# Add layer states information to the event.
|
210
|
+
#
|
211
|
+
# experiments_map - Hash with experiment ID as a key and variation ID as a value.
|
212
|
+
|
213
|
+
@params['layerStates'] = []
|
214
|
+
|
215
|
+
experiments_map.each do |experiment_id, variation_id|
|
216
|
+
layer_state = {
|
217
|
+
'layerId' => @config.experiment_id_map[experiment_id]['layerId'],
|
218
|
+
'decision' => {
|
219
|
+
'variationId' => variation_id,
|
220
|
+
'experimentId' => experiment_id,
|
221
|
+
'isLayerHoldback' => false,
|
222
|
+
},
|
223
|
+
'actionTriggered' => true,
|
224
|
+
}
|
225
|
+
@params['layerStates'].push(layer_state)
|
237
226
|
end
|
238
|
-
|
239
|
-
conversionEventParams
|
240
227
|
end
|
241
228
|
|
242
|
-
def
|
243
|
-
|
244
|
-
|
245
|
-
(Time.now.to_f * 1000).to_i
|
229
|
+
def add_source
|
230
|
+
@params['clientEngine'] = 'ruby-sdk'
|
231
|
+
@params['clientVersion'] = VERSION
|
246
232
|
end
|
247
233
|
|
248
|
-
def
|
249
|
-
|
250
|
-
|
251
|
-
SecureRandom.uuid
|
234
|
+
def add_time
|
235
|
+
@params['timestamp'] = (Time.now.to_f * 1000).to_i
|
252
236
|
end
|
253
237
|
end
|
254
238
|
end
|
@@ -95,13 +95,4 @@ module Optimizely
|
|
95
95
|
super("Provided #{type} is in an invalid format.")
|
96
96
|
end
|
97
97
|
end
|
98
|
-
|
99
|
-
class InvalidNotificationType < Error
|
100
|
-
# Raised when an invalid notification type is provided
|
101
|
-
|
102
|
-
def initialize(msg = 'Provided notification type is invalid.')
|
103
|
-
super
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
98
|
end
|
@@ -21,31 +21,23 @@ module Optimizely
|
|
21
21
|
module EventTagUtils
|
22
22
|
module_function
|
23
23
|
|
24
|
-
REVENUE_EVENT_METRIC_NAME = 'revenue';
|
25
|
-
NUMERIC_EVENT_METRIC_NAME = 'value';
|
26
|
-
|
27
|
-
def isStringNumeric(str)
|
28
|
-
Float(str) != nil rescue false
|
29
|
-
end
|
30
|
-
|
31
24
|
def get_revenue_value(event_tags)
|
32
25
|
# Grab the revenue value from the event tags. "revenue" is a reserved keyword.
|
33
|
-
#
|
34
|
-
#
|
26
|
+
#
|
35
27
|
# event_tags - Hash representing metadata associated with the event.
|
36
28
|
# Returns revenue value as an integer number
|
37
29
|
# Returns nil if revenue can't be retrieved from the event tags.
|
38
30
|
|
39
|
-
if event_tags.nil? or !Helpers::Validator.
|
31
|
+
if event_tags.nil? or !Helpers::Validator.attributes_valid?(event_tags)
|
40
32
|
return nil
|
41
33
|
end
|
42
34
|
|
43
|
-
unless event_tags.has_key?(
|
35
|
+
unless event_tags.has_key?('revenue')
|
44
36
|
return nil
|
45
37
|
end
|
46
38
|
|
47
39
|
logger = SimpleLogger.new
|
48
|
-
raw_value = event_tags[
|
40
|
+
raw_value = event_tags['revenue']
|
49
41
|
|
50
42
|
unless raw_value.is_a? Numeric
|
51
43
|
logger.log(Logger::WARN, "Failed to parse revenue value #{raw_value} from event tags.")
|
@@ -60,59 +52,6 @@ module Optimizely
|
|
60
52
|
logger.log(Logger::INFO, "Parsed revenue value #{raw_value} from event tags.")
|
61
53
|
raw_value
|
62
54
|
end
|
63
|
-
|
64
|
-
def get_numeric_value(event_tags, logger = nil)
|
65
|
-
# Grab the numeric event value from the event tags. "value" is a reserved keyword.
|
66
|
-
# The value of 'value' can be a float or a numeric string
|
67
|
-
#
|
68
|
-
# event_tags - +Hash+ representing metadata associated with the event.
|
69
|
-
# Returns +Number+ | +nil+ if value can't be retrieved from the event tags.
|
70
|
-
logger = SimpleLogger.new if logger.nil?
|
71
|
-
|
72
|
-
if event_tags.nil?
|
73
|
-
logger.log(Logger::DEBUG,"Event tags is undefined.")
|
74
|
-
return nil
|
75
|
-
end
|
76
|
-
|
77
|
-
if !Helpers::Validator.event_tags_valid?(event_tags)
|
78
|
-
logger.log(Logger::DEBUG,"Event tags is not a dictionary.")
|
79
|
-
return nil
|
80
|
-
end
|
81
|
-
|
82
|
-
if !event_tags.has_key?(NUMERIC_EVENT_METRIC_NAME)
|
83
|
-
logger.log(Logger::DEBUG,"The numeric metric key is not defined in the event tags.")
|
84
|
-
return nil
|
85
|
-
end
|
86
|
-
|
87
|
-
if event_tags[NUMERIC_EVENT_METRIC_NAME].nil?
|
88
|
-
logger.log(Logger::DEBUG,"The numeric metric key is null.")
|
89
|
-
return nil
|
90
|
-
end
|
91
|
-
|
92
|
-
raw_value = event_tags[NUMERIC_EVENT_METRIC_NAME]
|
93
|
-
|
94
|
-
if raw_value === true or raw_value === false
|
95
|
-
logger.log(Logger::DEBUG,"Provided numeric value is a boolean, which is an invalid format.")
|
96
|
-
return nil
|
97
|
-
end
|
98
|
-
|
99
|
-
if raw_value.is_a? Array or raw_value.is_a? Hash or raw_value.to_f.nan? or raw_value.to_f.infinite?
|
100
|
-
logger.log(Logger::DEBUG,"Provided numeric value is in an invalid format.")
|
101
|
-
return nil
|
102
|
-
end
|
103
|
-
|
104
|
-
if !Helpers::Validator.string_numeric?(raw_value)
|
105
|
-
logger.log(Logger::DEBUG,"Provided numeric value is not a numeric string.")
|
106
|
-
return nil
|
107
|
-
end
|
108
|
-
|
109
|
-
raw_value = raw_value.to_f
|
110
|
-
|
111
|
-
logger.log(Logger::INFO,"The numeric metric value #{raw_value} will be sent to results.")
|
112
|
-
|
113
|
-
raw_value
|
114
|
-
end
|
115
|
-
|
116
55
|
end
|
117
56
|
end
|
118
|
-
end
|
57
|
+
end
|