optimizely-sdk 2.0.0.beta → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
- # Copyright 2016, Optimizely and contributors
4
+ # Copyright 2016-2017, Optimizely and contributors
3
5
  #
4
6
  # Licensed under the Apache License, Version 2.0 (the "License");
5
7
  # you may not use this file except in compliance with the License.
@@ -18,15 +20,13 @@ module Optimizely
18
20
  # Class encapsulating exception handling functionality.
19
21
  # Override with your own exception handler providing a handle_error method.
20
22
 
21
- def handle_error(_error)
22
- end
23
+ def handle_error(_error); end
23
24
  end
24
25
 
25
26
  class NoOpErrorHandler < BaseErrorHandler
26
27
  # Class providing handle_error method that suppresses errors.
27
28
 
28
- def handle_error(_error)
29
- end
29
+ def handle_error(_error); end
30
30
  end
31
31
 
32
32
  class RaiseErrorHandler < BaseErrorHandler
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
- # Copyright 2016-2017, Optimizely and contributors
4
+ # Copyright 2016-2018, Optimizely and contributors
3
5
  #
4
6
  # Licensed under the Apache License, Version 2.0 (the "License");
5
7
  # you may not use this file except in compliance with the License.
@@ -17,8 +19,11 @@ require_relative './audience'
17
19
  require_relative './params'
18
20
  require_relative './version'
19
21
  require_relative '../optimizely/helpers/event_tag_utils'
22
+ require 'securerandom'
20
23
 
21
24
  module Optimizely
25
+ RESERVED_ATTRIBUTE_KEY_BUCKETING_ID_EVENT_PARAM_KEY = 'optimizely_bucketing_id'
26
+
22
27
  class Event
23
28
  # Representation of an event which can be sent to the Optimizely logging endpoint.
24
29
 
@@ -35,204 +40,210 @@ module Optimizely
35
40
  end
36
41
 
37
42
  # Override equality operator to make two events with the same contents equal for testing purposes
38
- def ==(event)
39
- @http_verb == event.http_verb && @url == event.url && @params == event.params && @headers == event.headers
43
+ def ==(other)
44
+ @http_verb == other.http_verb && @url == other.url && @params == other.params && @headers == other.headers
40
45
  end
41
46
  end
42
47
 
43
48
  class BaseEventBuilder
49
+ CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom'
50
+
44
51
  attr_reader :config
45
- attr_reader :params
52
+ attr_reader :logger
46
53
 
47
- def initialize(config)
54
+ def initialize(config, logger)
48
55
  @config = config
49
- @params = {}
56
+ @logger = logger
50
57
  end
51
58
 
52
59
  private
53
60
 
54
- def add_common_params(user_id, attributes)
55
- # Add params which are used in both conversion and impression events.
61
+ def get_common_params(user_id, attributes)
62
+ # Get params which are used in both conversion and impression events.
63
+ #
64
+ # user_id - +String+ ID for user
65
+ # attributes - +Hash+ representing user attributes and values which need to be recorded.
56
66
  #
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
67
+ # Returns +Hash+ Common event params
68
+
69
+ visitor_attributes = []
70
+
71
+ attributes&.keys&.each do |attribute_key|
72
+ # Omit null attribute value
73
+ attribute_value = attributes[attribute_key]
74
+ next if attribute_value.nil?
75
+
76
+ if attribute_key.eql? RESERVED_ATTRIBUTE_KEY_BUCKETING_ID
77
+ # TODO: (Copied from PHP-SDK) (Alda): the type for bucketing ID attribute may change so
78
+ # that custom attributes are not overloaded
79
+ feature = {
80
+ entity_id: RESERVED_ATTRIBUTE_KEY_BUCKETING_ID,
81
+ key: RESERVED_ATTRIBUTE_KEY_BUCKETING_ID_EVENT_PARAM_KEY,
82
+ type: CUSTOM_ATTRIBUTE_FEATURE_TYPE,
83
+ value: attribute_value
84
+ }
85
+ else
86
+ # Skip attributes not in the datafile
87
+ attribute_id = @config.get_attribute_id(attribute_key)
88
+ next unless attribute_id
89
+
90
+ feature = {
91
+ entity_id: attribute_id,
92
+ key: attribute_key,
93
+ type: CUSTOM_ATTRIBUTE_FEATURE_TYPE,
94
+ value: attribute_value
95
+ }
96
+
97
+ end
98
+ visitor_attributes.push(feature)
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
66
118
  end
67
119
  end
68
120
 
69
121
  class EventBuilder < BaseEventBuilder
70
- CONVERSION_EVENT_ENDPOINT = 'https://logx.optimizely.com/log/event'
71
- IMPRESSION_EVENT_ENDPOINT = 'https://logx.optimizely.com/log/decision'
72
- POST_HEADERS = { 'Content-Type' => 'application/json' }
122
+ ENDPOINT = 'https://logx.optimizely.com/v1/events'
123
+ POST_HEADERS = {'Content-Type' => 'application/json'}.freeze
124
+ ACTIVATE_EVENT_KEY = 'campaign_activated'
73
125
 
74
126
  def create_impression_event(experiment, variation_id, user_id, attributes)
75
- # Create conversion Event to be sent to the logging endpoint.
127
+ # Create impression Event to be sent to the logging endpoint.
76
128
  #
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.
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.
81
133
  #
82
- # Returns event hash encapsulating the impression event.
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)
83
139
 
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)
140
+ Event.new(:post, ENDPOINT, event_params, POST_HEADERS)
89
141
  end
90
142
 
91
143
  def create_conversion_event(event_key, user_id, attributes, event_tags, experiment_variation_map)
92
144
  # Create conversion Event to be sent to the logging endpoint.
93
145
  #
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.
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.
99
151
  #
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
111
-
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
152
+ # Returns +Event+ encapsulating the conversion event.
120
153
 
121
- def add_account_id
122
- @params['accountId'] = @config.account_id
123
- end
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
124
157
 
125
- def add_user_id(user_id)
126
- @params['visitorId'] = user_id
158
+ Event.new(:post, ENDPOINT, event_params, POST_HEADERS)
127
159
  end
128
160
 
129
- def add_attributes(attributes)
130
- @params['userFeatures'] = []
131
-
132
- return if attributes.nil?
161
+ private
133
162
 
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
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
153
170
 
154
- def add_decision(experiment, variation_id)
155
171
  experiment_key = experiment['key']
156
172
  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,
173
+
174
+ impression_event_params = {
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: create_timestamp,
183
+ key: ACTIVATE_EVENT_KEY,
184
+ uuid: create_uuid
185
+ }]
162
186
  }
187
+
188
+ impression_event_params
163
189
  end
164
190
 
165
- def add_event_tags(event_tags)
166
- @params['eventFeatures'] ||= []
167
- @params['eventMetrics'] ||= []
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
199
+
200
+ conversion_event_params = []
201
+
202
+ experiment_variation_map.each do |experiment_id, variation_id|
203
+ single_snapshot = {
204
+ decisions: [{
205
+ campaign_id: @config.experiment_id_map[experiment_id]['layerId'],
206
+ experiment_id: experiment_id,
207
+ variation_id: variation_id
208
+ }],
209
+ events: []
210
+ }
168
211
 
169
- return if event_tags.nil?
212
+ event_object = {
213
+ entity_id: @config.event_key_map[event_key]['id'],
214
+ timestamp: create_timestamp,
215
+ uuid: create_uuid,
216
+ key: event_key
217
+ }
170
218
 
171
- event_tags.each_pair do |event_tag_key, event_tag_value|
172
- next if event_tag_value.nil?
219
+ if event_tags
220
+ revenue_value = Helpers::EventTagUtils.get_revenue_value(event_tags, @logger)
221
+ event_object[:revenue] = revenue_value if revenue_value
173
222
 
174
- event_feature = {
175
- 'name' => event_tag_key,
176
- 'type' => 'custom',
177
- 'value' => event_tag_value,
178
- 'shouldIndex' => false,
179
- }
180
- @params['eventFeatures'].push(event_feature)
223
+ numeric_value = Helpers::EventTagUtils.get_numeric_value(event_tags, @logger)
224
+ event_object[:value] = numeric_value if numeric_value
181
225
 
182
- end
226
+ event_object[:tags] = event_tags
227
+ end
183
228
 
184
- event_value = Helpers::EventTagUtils.get_revenue_value(event_tags)
229
+ single_snapshot[:events] = [event_object]
185
230
 
186
- if event_value
187
- event_metric = {
188
- 'name' => 'revenue',
189
- 'value' => event_value
190
- }
191
- @params['eventMetrics'].push(event_metric)
231
+ conversion_event_params.push(single_snapshot)
192
232
  end
193
233
 
234
+ conversion_event_params
194
235
  end
195
236
 
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.
200
-
201
- event_id = @config.event_key_map[event_key]['id']
202
- event_name = @config.event_key_map[event_key]['key']
237
+ def create_timestamp
238
+ # Returns +Integer+ Current timestamp
203
239
 
204
- @params['eventEntityId'] = event_id
205
- @params['eventName'] = event_name
240
+ (Time.now.to_f * 1000).to_i
206
241
  end
207
242
 
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)
226
- end
227
- end
228
-
229
- def add_source
230
- @params['clientEngine'] = 'ruby-sdk'
231
- @params['clientVersion'] = VERSION
232
- end
243
+ def create_uuid
244
+ # Returns +String+ Random UUID
233
245
 
234
- def add_time
235
- @params['timestamp'] = (Time.now.to_f * 1000).to_i
246
+ SecureRandom.uuid
236
247
  end
237
248
  end
238
249
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
- # Copyright 2016, Optimizely and contributors
4
+ # Copyright 2016-2017, Optimizely and contributors
3
5
  #
4
6
  # Licensed under the Apache License, Version 2.0 (the "License");
5
7
  # you may not use this file except in compliance with the License.
@@ -19,8 +21,7 @@ module Optimizely
19
21
  class NoOpEventDispatcher
20
22
  # Class providing dispatch_event method which does nothing.
21
23
 
22
- def dispatch_event(event)
23
- end
24
+ def dispatch_event(event); end
24
25
  end
25
26
 
26
27
  class EventDispatcher
@@ -40,9 +41,9 @@ module Optimizely
40
41
  elsif event.http_verb == :post
41
42
  begin
42
43
  HTTParty.post(event.url,
43
- body: event.params.to_json,
44
- headers: event.headers,
45
- timeout: REQUEST_TIMEOUT)
44
+ body: event.params.to_json,
45
+ headers: event.headers,
46
+ timeout: REQUEST_TIMEOUT)
46
47
  rescue Timeout::Error => e
47
48
  return e
48
49
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # Copyright 2016-2017, Optimizely and contributors
3
5
  #
@@ -83,7 +85,8 @@ module Optimizely
83
85
  class InvalidDatafileVersionError < Error
84
86
  # Raised when a datafile with an unsupported version is provided
85
87
 
86
- def initialize(msg = 'Provided datafile is an unsupported version. Please use SDK version 1.1.2 or earlier for datafile version 1.')
88
+ def initialize(msg = 'Provided datafile is an unsupported version. Please use SDK version 1.1.2 or earlier '\
89
+ 'for datafile version 1.')
87
90
  super
88
91
  end
89
92
  end
@@ -95,4 +98,12 @@ module Optimizely
95
98
  super("Provided #{type} is in an invalid format.")
96
99
  end
97
100
  end
101
+
102
+ class InvalidNotificationType < Error
103
+ # Raised when an invalid notification type is provided
104
+
105
+ def initialize(msg = 'Provided notification type is invalid.')
106
+ super
107
+ end
108
+ end
98
109
  end