Skip to content

Commit 647e72f

Browse files
oakbaniMatt Auerbach
authored andcommitted
New Event Builder Payload + Numeric Event Support (#61)
1 parent b8fc834 commit 647e72f

File tree

7 files changed

+746
-577
lines changed

7 files changed

+746
-577
lines changed

lib/optimizely/event_builder.rb

Lines changed: 143 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
require_relative './params'
1818
require_relative './version'
1919
require_relative '../optimizely/helpers/event_tag_utils'
20+
require 'securerandom'
2021

2122
module Optimizely
2223
class Event
@@ -41,198 +42,198 @@ def ==(event)
4142
end
4243

4344
class BaseEventBuilder
45+
CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom'
46+
4447
attr_reader :config
45-
attr_reader :params
4648

4749
def initialize(config)
4850
@config = config
49-
@params = {}
5051
end
5152

5253
private
5354

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
55+
def get_common_params(user_id, attributes)
56+
# Get params which are used in both conversion and impression events.
57+
#
58+
# user_id - +String+ ID for user
59+
# attributes - +Hash+ representing user attributes and values which need to be recorded.
60+
#
61+
# Returns +Hash+ Common event params
62+
63+
visitor_attributes = []
64+
65+
unless attributes.nil?
66+
67+
attributes.keys.each do |attribute_key|
68+
# Omit null attribute value
69+
attribute_value = attributes[attribute_key]
70+
next if attribute_value.nil?
71+
72+
# Skip attributes not in the datafile
73+
attribute_id = @config.get_attribute_id(attribute_key)
74+
next unless attribute_id
75+
76+
feature = {
77+
entity_id: attribute_id,
78+
key: attribute_key,
79+
type: CUSTOM_ATTRIBUTE_FEATURE_TYPE,
80+
value: attribute_value
81+
}
82+
83+
visitor_attributes.push(feature)
84+
end
85+
end
86+
87+
common_params = {
88+
account_id: @config.account_id,
89+
project_id: @config.project_id,
90+
visitors: [
91+
{
92+
attributes: visitor_attributes,
93+
snapshots: [],
94+
visitor_id: user_id
95+
}
96+
],
97+
revision: @config.revision,
98+
client_name: CLIENT_ENGINE,
99+
client_version: VERSION
100+
}
101+
102+
common_params
66103
end
67104
end
68105

69106
class EventBuilder < BaseEventBuilder
70-
CONVERSION_EVENT_ENDPOINT = 'https://logx.optimizely.com/log/event'
71-
IMPRESSION_EVENT_ENDPOINT = 'https://logx.optimizely.com/log/decision'
107+
ENDPOINT = 'https://logx.optimizely.com/v1/events'
72108
POST_HEADERS = { 'Content-Type' => 'application/json' }
109+
ACTIVATE_EVENT_KEY = 'campaign_activated'
73110

74111
def create_impression_event(experiment, variation_id, user_id, attributes)
75-
# Create conversion Event to be sent to the logging endpoint.
112+
# Create impression Event to be sent to the logging endpoint.
76113
#
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.
114+
# experiment - +Object+ Experiment for which impression needs to be recorded.
115+
# variation_id - +String+ ID for variation which would be presented to user.
116+
# user_id - +String+ ID for user.
117+
# attributes - +Hash+ representing user attributes and values which need to be recorded.
81118
#
82-
# Returns event hash encapsulating the impression event.
119+
# Returns +Event+ encapsulating the impression event.
120+
121+
event_params = get_common_params(user_id, attributes)
122+
impression_params = get_impression_params(experiment, variation_id)
123+
event_params[:visitors][0][:snapshots].push(impression_params)
83124

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)
125+
Event.new(:post, ENDPOINT, event_params, POST_HEADERS)
89126
end
90127

91128
def create_conversion_event(event_key, user_id, attributes, event_tags, experiment_variation_map)
92129
# Create conversion Event to be sent to the logging endpoint.
93130
#
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.
131+
# event_key - +String+ Event key representing the event which needs to be recorded.
132+
# user_id - +String+ ID for user.
133+
# attributes - +Hash+ representing user attributes and values which need to be recorded.
134+
# event_tags - +Hash+ representing metadata associated with the event.
135+
# experiment_variation_map - +Map+ of experiment ID to the ID of the variation that the user is bucketed into.
99136
#
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)
108-
end
109-
110-
private
137+
# Returns +Event+ encapsulating the conversion event.
111138

112-
def add_common_params(user_id, attributes)
113-
super
114-
@params['isGlobalHoldback'] = false
139+
event_params = get_common_params(user_id, attributes)
140+
conversion_params = get_conversion_params(event_key, event_tags, experiment_variation_map)
141+
event_params[:visitors][0][:snapshots] = conversion_params;
142+
143+
Event.new(:post, ENDPOINT, event_params, POST_HEADERS)
115144
end
116145

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 or nil 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
146+
private
142147

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
148+
def get_impression_params(experiment, variation_id)
149+
# Creates object of params specific to impression events
150+
#
151+
# experiment - +Hash+ experiment for which impression needs to be recorded
152+
# variation_id - +string+ ID for variation which would be presented to user
153+
#
154+
# Returns +Hash+ Impression event params
153155

154-
def add_decision(experiment, variation_id)
155156
experiment_key = experiment['key']
156157
experiment_id = experiment['id']
157-
@params['layerId'] = @config.experiment_key_map[experiment_key]['layerId']
158-
@params['decision'] = {
159-
'variationId' => variation_id,
160-
'experimentId' => experiment_id,
161-
'isLayerHoldback' => false,
158+
159+
impressionEventParams = {
160+
decisions: [{
161+
campaign_id: @config.experiment_key_map[experiment_key]['layerId'],
162+
experiment_id: experiment_id,
163+
variation_id: variation_id,
164+
}],
165+
events: [{
166+
entity_id: @config.experiment_key_map[experiment_key]['layerId'],
167+
timestamp: get_timestamp(),
168+
key: ACTIVATE_EVENT_KEY,
169+
uuid: get_uuid()
170+
}]
162171
}
172+
173+
impressionEventParams;
163174
end
164175

165-
def add_event_tags(event_tags)
166-
@params['eventFeatures'] ||= []
167-
@params['eventMetrics'] ||= []
176+
def get_conversion_params(event_key, event_tags, experiment_variation_map)
177+
# Creates object of params specific to conversion events
178+
#
179+
# event_key - +String+ Key representing the event which needs to be recorded
180+
# event_tags - +Hash+ Values associated with the event.
181+
# experiment_variation_map - +Hash+ Map of experiment IDs to bucketed variation IDs
182+
#
183+
# Returns +Hash+ Impression event params
168184

169-
return if event_tags.nil?
185+
conversionEventParams = []
170186

171-
event_tags.each_pair do |event_tag_key, event_tag_value|
172-
next if event_tag_value.nil?
187+
experiment_variation_map.each do |experiment_id, variation_id|
173188

174-
event_feature = {
175-
'name' => event_tag_key,
176-
'type' => 'custom',
177-
'value' => event_tag_value,
178-
'shouldIndex' => false,
189+
single_snapshot = {
190+
decisions: [{
191+
campaign_id: @config.experiment_id_map[experiment_id]['layerId'],
192+
experiment_id: experiment_id,
193+
variation_id: variation_id,
194+
}],
195+
events: [],
179196
}
180-
@params['eventFeatures'].push(event_feature)
181197

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
198+
event_object = {
199+
entity_id: @config.event_key_map[event_key]['id'],
200+
timestamp: get_timestamp(),
201+
uuid: get_uuid(),
202+
key: event_key,
190203
}
191-
@params['eventMetrics'].push(event_metric)
192-
end
193204

194-
end
205+
if event_tags
206+
revenue_value = Helpers::EventTagUtils.get_revenue_value(event_tags)
207+
if revenue_value
208+
event_object[:revenue] = revenue_value
209+
end
195210

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.
211+
numeric_value = Helpers::EventTagUtils.get_numeric_value(event_tags)
212+
if numeric_value
213+
event_object[:value] = numeric_value
214+
end
200215

201-
event_id = @config.event_key_map[event_key]['id']
202-
event_name = @config.event_key_map[event_key]['key']
216+
event_object[:tags] = event_tags
217+
end
203218

204-
@params['eventEntityId'] = event_id
205-
@params['eventName'] = event_name
206-
end
219+
single_snapshot[:events] = [event_object]
207220

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)
221+
conversionEventParams.push(single_snapshot)
226222
end
223+
224+
conversionEventParams
227225
end
228226

229-
def add_source
230-
@params['clientEngine'] = 'ruby-sdk'
231-
@params['clientVersion'] = VERSION
227+
def get_timestamp
228+
# Returns +Integer+ Current timestamp
229+
230+
(Time.now.to_f * 1000).to_i
232231
end
233232

234-
def add_time
235-
@params['timestamp'] = (Time.now.to_f * 1000).to_i
233+
def get_uuid
234+
# Returns +String+ Random UUID
235+
236+
SecureRandom.uuid
236237
end
237238
end
238239
end

0 commit comments

Comments
 (0)