optimizely-sdk 2.0.0.beta → 2.0.0.beta1

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