optimizely-sdk 1.5.0 → 2.0.0.beta
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.
- 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
|