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.
@@ -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