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.
@@ -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 get_common_params(user_id, attributes)
59
- # Get params which are used in both conversion and impression events.
60
- #
61
- # user_id - +String+ ID for user
62
- # attributes - +Hash+ representing user attributes and values which need to be recorded.
63
- #
64
- # Returns +Hash+ Common event params
65
-
66
- visitor_attributes = []
67
-
68
- unless attributes.nil?
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
- ENDPOINT = 'https://logx.optimizely.com/v1/events'
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 impression Event to be sent to the logging endpoint.
75
+ # Create conversion Event to be sent to the logging endpoint.
128
76
  #
129
- # experiment - +Object+ Experiment for which impression needs to be recorded.
130
- # variation_id - +String+ ID for variation which would be presented to user.
131
- # user_id - +String+ ID for user.
132
- # attributes - +Hash+ representing user attributes and values which need to be recorded.
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 +Event+ encapsulating the impression event.
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
- Event.new(:post, ENDPOINT, event_params, POST_HEADERS)
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 - +String+ Event key representing the event which needs to be recorded.
147
- # user_id - +String+ ID for user.
148
- # attributes - +Hash+ representing user attributes and values which need to be recorded.
149
- # event_tags - +Hash+ representing metadata associated with the event.
150
- # experiment_variation_map - +Map+ of experiment ID to the ID of the variation that the user is bucketed into.
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 +Event+ encapsulating the conversion event.
153
-
154
- event_params = get_common_params(user_id, attributes)
155
- conversion_params = get_conversion_params(event_key, event_tags, experiment_variation_map)
156
- event_params[:visitors][0][:snapshots] = conversion_params;
157
-
158
- Event.new(:post, ENDPOINT, event_params, POST_HEADERS)
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 get_impression_params(experiment, variation_id)
164
- # Creates object of params specific to impression events
165
- #
166
- # experiment - +Hash+ experiment for which impression needs to be recorded
167
- # variation_id - +string+ ID for variation which would be presented to user
168
- #
169
- # Returns +Hash+ Impression event params
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
- impressionEventParams = {
175
- decisions: [{
176
- campaign_id: @config.experiment_key_map[experiment_key]['layerId'],
177
- experiment_id: experiment_id,
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 get_conversion_params(event_key, event_tags, experiment_variation_map)
192
- # Creates object of params specific to conversion events
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
- conversionEventParams = []
169
+ return if event_tags.nil?
201
170
 
202
- experiment_variation_map.each do |experiment_id, variation_id|
171
+ event_tags.each_pair do |event_tag_key, event_tag_value|
172
+ next if event_tag_value.nil?
203
173
 
204
- single_snapshot = {
205
- decisions: [{
206
- campaign_id: @config.experiment_id_map[experiment_id]['layerId'],
207
- experiment_id: experiment_id,
208
- variation_id: variation_id,
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
- event_object = {
214
- entity_id: @config.event_key_map[event_key]['id'],
215
- timestamp: get_timestamp(),
216
- uuid: get_uuid(),
217
- key: event_key,
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
- if event_tags
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
- numeric_value = Helpers::EventTagUtils.get_numeric_value(event_tags)
227
- if numeric_value
228
- event_object[:value] = numeric_value
229
- end
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
- event_object[:tags] = event_tags
232
- end
201
+ event_id = @config.event_key_map[event_key]['id']
202
+ event_name = @config.event_key_map[event_key]['key']
233
203
 
234
- single_snapshot[:events] = [event_object]
204
+ @params['eventEntityId'] = event_id
205
+ @params['eventName'] = event_name
206
+ end
235
207
 
236
- conversionEventParams.push(single_snapshot)
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 get_timestamp
243
- # Returns +Integer+ Current timestamp
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 get_uuid
249
- # Returns +String+ Random UUID
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
- # The revenue value must be an integer.
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.event_tags_valid?(event_tags)
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?(REVENUE_EVENT_METRIC_NAME)
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[REVENUE_EVENT_METRIC_NAME]
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