optimizely-sdk 0.1.1 → 0.1.2
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.
- checksums.yaml +4 -4
- data/lib/optimizely.rb +8 -3
- data/lib/optimizely/event_builder.rb +187 -36
- data/lib/optimizely/event_dispatcher.rb +25 -6
- data/lib/optimizely/helpers/constants.rb +284 -2
- data/lib/optimizely/helpers/validator.rb +14 -1
- data/lib/optimizely/project_config.rb +21 -3
- data/lib/optimizely/version.rb +1 -1
- metadata +3 -4
- data/lib/start.rb +0 -131
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 388b7be07d59d027ef5f08ebce2fa43d62be0513
|
4
|
+
data.tar.gz: 0288d19e852dce6a860ceac8c35c6b508ed3363a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 02cd29fbf79181021dae6d19aae905e4304dc8ce6d338e7d1df2e2086fd7d381b65232ed188f41ef005a1afaefa6de610d195162b144e7c6eccd05b084f9be9f
|
7
|
+
data.tar.gz: 281090b33e4e89c8ea7a32276d259a307fa9a3001ce532cc8b373a70d7cc70ec19d7e62c52a360e29e1bbabb5f44786739d05214d276558c5d0b060772ab5488
|
data/lib/optimizely.rb
CHANGED
@@ -18,6 +18,11 @@ module Optimizely
|
|
18
18
|
attr_accessor :logger
|
19
19
|
attr_accessor :error_handler
|
20
20
|
|
21
|
+
EVENT_BUILDERS_BY_VERSION = {
|
22
|
+
Optimizely::V1_CONFIG_VERSION => EventBuilderV1,
|
23
|
+
Optimizely::V2_CONFIG_VERSION => EventBuilderV2
|
24
|
+
}
|
25
|
+
|
21
26
|
def initialize(datafile, event_dispatcher = nil, logger = nil, error_handler = nil, skip_json_validation = false)
|
22
27
|
# Constructor for Projects.
|
23
28
|
#
|
@@ -35,7 +40,7 @@ module Optimizely
|
|
35
40
|
|
36
41
|
@config = ProjectConfig.new(datafile, @logger, @error_handler)
|
37
42
|
@bucketer = Bucketer.new(@config)
|
38
|
-
@event_builder =
|
43
|
+
@event_builder = EVENT_BUILDERS_BY_VERSION[@config.version].new(@config, @bucketer)
|
39
44
|
end
|
40
45
|
|
41
46
|
def activate(experiment_key, user_id, attributes = nil)
|
@@ -70,7 +75,7 @@ module Optimizely
|
|
70
75
|
@logger.log(Logger::INFO,
|
71
76
|
'Dispatching impression event to URL %s with params %s.' % [impression_event.url,
|
72
77
|
impression_event.params])
|
73
|
-
@event_dispatcher.dispatch_event(impression_event
|
78
|
+
@event_dispatcher.dispatch_event(impression_event)
|
74
79
|
|
75
80
|
@config.get_variation_key_from_id(experiment_key, variation_id)
|
76
81
|
end
|
@@ -136,7 +141,7 @@ module Optimizely
|
|
136
141
|
@logger.log(Logger::INFO,
|
137
142
|
'Dispatching conversion event to URL %s with params %s.' % [conversion_event.url,
|
138
143
|
conversion_event.params])
|
139
|
-
@event_dispatcher.dispatch_event(conversion_event
|
144
|
+
@event_dispatcher.dispatch_event(conversion_event)
|
140
145
|
end
|
141
146
|
|
142
147
|
private
|
@@ -6,28 +6,198 @@ module Optimizely
|
|
6
6
|
class Event
|
7
7
|
# Representation of an event which can be sent to the Optimizely logging endpoint.
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
attr_reader :http_verb
|
10
|
+
attr_reader :params
|
11
|
+
attr_reader :url
|
12
|
+
attr_reader :headers
|
13
|
+
|
14
|
+
def initialize(http_verb, url, params, headers)
|
15
|
+
@http_verb = http_verb
|
16
|
+
@url = url
|
17
|
+
@params = params
|
18
|
+
@headers = headers
|
19
|
+
end
|
20
|
+
|
21
|
+
# Override equality operator to make two events with the same contents equal for testing purposes
|
22
|
+
def ==(event)
|
23
|
+
@http_verb == event.http_verb && @url == event.url && @params == event.params && @headers == event.headers
|
24
|
+
end
|
25
|
+
end
|
11
26
|
|
12
|
-
|
27
|
+
class BaseEventBuilder
|
28
|
+
attr_reader :config
|
29
|
+
attr_reader :bucketer
|
13
30
|
attr_accessor :params
|
14
31
|
|
15
|
-
def initialize(
|
16
|
-
@
|
32
|
+
def initialize(config, bucketer)
|
33
|
+
@config = config
|
34
|
+
@bucketer = bucketer
|
35
|
+
@params = {}
|
17
36
|
end
|
18
37
|
|
19
|
-
|
20
|
-
|
38
|
+
private
|
39
|
+
|
40
|
+
def add_common_params(user_id, attributes)
|
41
|
+
# Add params which are used in both conversion and impression events.
|
21
42
|
#
|
22
|
-
#
|
43
|
+
# user_id - ID for user.
|
44
|
+
# attributes - Hash representing user attributes and values which need to be recorded.
|
45
|
+
|
46
|
+
add_project_id
|
47
|
+
add_account_id
|
48
|
+
add_user_id(user_id)
|
49
|
+
add_attributes(attributes)
|
50
|
+
add_source
|
51
|
+
add_time
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class EventBuilderV2 < BaseEventBuilder
|
56
|
+
CONVERSION_EVENT_ENDPOINT = 'https://p13nlog.dz.optimizely.com/log/event'
|
57
|
+
IMPRESSION_EVENT_ENDPOINT = 'https://p13nlog.dz.optimizely.com/log/decision'
|
58
|
+
POST_HEADERS = { 'Content-Type' => 'application/json' }
|
59
|
+
|
60
|
+
def create_impression_event(experiment_key, variation_id, user_id, attributes)
|
61
|
+
# Create conversion Event to be sent to the logging endpoint.
|
62
|
+
#
|
63
|
+
# experiment_key - Experiment for which impression needs to be recorded.
|
64
|
+
# variation_id - ID for variation which would be presented to user.
|
65
|
+
# user_id - ID for user.
|
66
|
+
# attributes - Hash representing user attributes and values which need to be recorded.
|
67
|
+
#
|
68
|
+
# Returns event hash encapsulating the impression event.
|
69
|
+
|
70
|
+
@params = {}
|
71
|
+
add_common_params(user_id, attributes)
|
72
|
+
add_decision(experiment_key, variation_id)
|
73
|
+
add_attributes(attributes)
|
74
|
+
Event.new(:post, IMPRESSION_EVENT_ENDPOINT, @params, POST_HEADERS)
|
75
|
+
end
|
76
|
+
|
77
|
+
def create_conversion_event(event_key, user_id, attributes, event_value, experiment_keys)
|
78
|
+
# Create conversion Event to be sent to the logging endpoint.
|
79
|
+
#
|
80
|
+
# event_key - Event key representing the event which needs to be recorded.
|
81
|
+
# user_id - ID for user.
|
82
|
+
# attributes - Hash representing user attributes and values which need to be recorded.
|
83
|
+
# event_value - Value associated with the event. Can be used to represent revenue in cents.
|
84
|
+
# experiment_keys - Array of valid experiment keys for the event
|
85
|
+
#
|
86
|
+
# Returns event hash encapsulating the conversion event.
|
87
|
+
|
88
|
+
@params = {}
|
89
|
+
add_common_params(user_id, attributes)
|
90
|
+
add_conversion_event(event_key, event_value)
|
91
|
+
add_layer_states(user_id, experiment_keys)
|
92
|
+
Event.new(:post, CONVERSION_EVENT_ENDPOINT, @params, POST_HEADERS)
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def add_common_params(user_id, attributes)
|
98
|
+
super
|
99
|
+
@params['isGlobalHoldback'] = false
|
100
|
+
end
|
101
|
+
|
102
|
+
def add_project_id
|
103
|
+
@params['projectId'] = @config.project_id
|
104
|
+
end
|
105
|
+
|
106
|
+
def add_account_id
|
107
|
+
@params['accountId'] = @config.account_id
|
108
|
+
end
|
109
|
+
|
110
|
+
def add_user_id(user_id)
|
111
|
+
@params['visitorId'] = user_id
|
112
|
+
end
|
113
|
+
|
114
|
+
def add_attributes(attributes)
|
115
|
+
@params['userFeatures'] = []
|
116
|
+
|
117
|
+
return if attributes.nil?
|
118
|
+
|
119
|
+
attributes.keys.each do |attribute_key|
|
120
|
+
# Omit falsy attribute values
|
121
|
+
attribute_value = attributes[attribute_key]
|
122
|
+
next unless attribute_value
|
123
|
+
|
124
|
+
# Skip attributes not in the datafile
|
125
|
+
attribute_id = @config.get_attribute_id(attribute_key)
|
126
|
+
next unless attribute_id
|
127
|
+
|
128
|
+
feature = {
|
129
|
+
'id' => attribute_id,
|
130
|
+
'name' => attribute_key,
|
131
|
+
'type' => 'custom',
|
132
|
+
'value' => attribute_value,
|
133
|
+
'shouldIndex' => true,
|
134
|
+
}
|
135
|
+
@params['userFeatures'].push(feature)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def add_decision(experiment_key, variation_id)
|
140
|
+
experiment_id = @config.get_experiment_id(experiment_key)
|
141
|
+
@params['layerId'] = @config.experiment_key_map[experiment_key]['layerId']
|
142
|
+
@params['decision'] = {
|
143
|
+
'variationId' => variation_id,
|
144
|
+
'experimentId' => experiment_id,
|
145
|
+
'isLayerHoldback' => false,
|
146
|
+
}
|
147
|
+
end
|
148
|
+
|
149
|
+
def add_conversion_event(event_key, event_value)
|
150
|
+
# Add conversion event information to the event.
|
23
151
|
#
|
24
|
-
#
|
152
|
+
# event_key - Event key representing the event which needs to be recorded.
|
153
|
+
# event_value - Value associated with the event. Can be used to represent revenue in cents.
|
154
|
+
|
155
|
+
event_id = @config.event_key_map[event_key]['id']
|
156
|
+
event_name = @config.event_key_map[event_key]['key']
|
157
|
+
|
158
|
+
@params['eventEntityId'] = event_id
|
159
|
+
@params['eventFeatures'] = []
|
160
|
+
@params['eventName'] = event_name
|
161
|
+
@params['eventMetrics'] = []
|
25
162
|
|
26
|
-
|
163
|
+
if event_value
|
164
|
+
@params['eventMetrics'].push({
|
165
|
+
'name' => 'revenue',
|
166
|
+
'value' => event_value,
|
167
|
+
})
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def add_layer_states(user_id, experiment_keys)
|
172
|
+
@params['layerStates'] = []
|
173
|
+
|
174
|
+
experiment_keys.each do |experiment_key|
|
175
|
+
variation_id = @bucketer.bucket(experiment_key, user_id)
|
176
|
+
experiment_id = @config.experiment_key_map[experiment_key]['id']
|
177
|
+
layer_state = {
|
178
|
+
'layerId' => @config.experiment_key_map[experiment_key]['layerId'],
|
179
|
+
'decision' => {
|
180
|
+
'variationId' => variation_id,
|
181
|
+
'experimentId' => experiment_id,
|
182
|
+
'isLayerHoldback' => false,
|
183
|
+
},
|
184
|
+
'actionTriggered' => true,
|
185
|
+
}
|
186
|
+
@params['layerStates'].push(layer_state)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def add_source
|
191
|
+
@params['clientEngine'] = 'ruby-sdk'
|
192
|
+
@params['clientVersion'] = VERSION
|
193
|
+
end
|
194
|
+
|
195
|
+
def add_time
|
196
|
+
@params['timestamp'] = (Time.now.to_f * 1000).to_i
|
27
197
|
end
|
28
198
|
end
|
29
199
|
|
30
|
-
class
|
200
|
+
class EventBuilderV1 < BaseEventBuilder
|
31
201
|
# Class which encapsulates methods to build events for tracking impressions and conversions.
|
32
202
|
|
33
203
|
# Attribute mapping format
|
@@ -36,15 +206,8 @@ module Optimizely
|
|
36
206
|
# Experiment mapping format
|
37
207
|
EXPERIMENT_PARAM_FORMAT = '%{experiment_prefix}%{experiment_id}'
|
38
208
|
|
39
|
-
|
40
|
-
|
41
|
-
attr_accessor :params
|
42
|
-
|
43
|
-
def initialize(config, bucketer)
|
44
|
-
@config = config
|
45
|
-
@bucketer = bucketer
|
46
|
-
@params = {}
|
47
|
-
end
|
209
|
+
# Event endpoint path
|
210
|
+
OFFLINE_API_PATH = 'https://%{project_id}.log.optimizely.com/event'
|
48
211
|
|
49
212
|
def create_impression_event(experiment_key, variation_id, user_id, attributes)
|
50
213
|
# Create conversion Event to be sent to the logging endpoint.
|
@@ -60,7 +223,7 @@ module Optimizely
|
|
60
223
|
add_common_params(user_id, attributes)
|
61
224
|
add_impression_goal(experiment_key)
|
62
225
|
add_experiment(experiment_key, variation_id)
|
63
|
-
Event.new(@params)
|
226
|
+
Event.new(:get, sprintf(OFFLINE_API_PATH, project_id: @params[Params::PROJECT_ID]), @params, {})
|
64
227
|
end
|
65
228
|
|
66
229
|
def create_conversion_event(event_key, user_id, attributes, event_value, experiment_keys)
|
@@ -71,12 +234,14 @@ module Optimizely
|
|
71
234
|
# attributes - Hash representing user attributes and values which need to be recorded.
|
72
235
|
# event_value - Value associated with the event. Can be used to represent revenue in cents.
|
73
236
|
# experiment_keys - Array of valid experiment keys for the goal
|
237
|
+
#
|
238
|
+
# Returns event hash encapsulating the conversion event.
|
74
239
|
|
75
240
|
@params = {}
|
76
241
|
add_common_params(user_id, attributes)
|
77
242
|
add_conversion_goal(event_key, event_value)
|
78
243
|
add_experiment_variation_params(user_id, experiment_keys)
|
79
|
-
Event.new(@params)
|
244
|
+
Event.new(:get, sprintf(OFFLINE_API_PATH, project_id: @params[Params::PROJECT_ID]), @params, {})
|
80
245
|
end
|
81
246
|
|
82
247
|
private
|
@@ -128,20 +293,6 @@ module Optimizely
|
|
128
293
|
@params[Params::TIME] = Time.now.strftime('%s').to_i
|
129
294
|
end
|
130
295
|
|
131
|
-
def add_common_params(user_id, attributes)
|
132
|
-
# Add params which are used same in both conversion and impression events.
|
133
|
-
#
|
134
|
-
# user_id - ID for user.
|
135
|
-
# attributes - Hash representing user attributes and values which need to be recorded.
|
136
|
-
|
137
|
-
add_project_id
|
138
|
-
add_account_id
|
139
|
-
add_user_id(user_id)
|
140
|
-
add_attributes(attributes)
|
141
|
-
add_source
|
142
|
-
add_time
|
143
|
-
end
|
144
|
-
|
145
296
|
def add_impression_goal(experiment_key)
|
146
297
|
# Add impression goal information to the event.
|
147
298
|
#
|
@@ -1,18 +1,37 @@
|
|
1
1
|
require 'httparty'
|
2
2
|
|
3
3
|
module Optimizely
|
4
|
+
class NoOpEventDispatcher
|
5
|
+
# Class providing dispatch_event method which does nothing.
|
6
|
+
|
7
|
+
def dispatch_event(event)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
4
11
|
class EventDispatcher
|
5
12
|
REQUEST_TIMEOUT = 10
|
6
13
|
|
7
|
-
def dispatch_event(
|
14
|
+
def dispatch_event(event)
|
8
15
|
# Dispatch the event being represented by the Event object.
|
9
16
|
#
|
10
|
-
#
|
11
|
-
# params - Params to be sent to the impression/conversion event.
|
17
|
+
# event - Event object
|
12
18
|
|
13
|
-
|
14
|
-
|
15
|
-
|
19
|
+
if event.http_verb == :get
|
20
|
+
begin
|
21
|
+
HTTParty.get(event.url, headers: event.headers, query: event.params, timeout: REQUEST_TIMEOUT)
|
22
|
+
rescue Timeout::Error => e
|
23
|
+
return e
|
24
|
+
end
|
25
|
+
elsif event.http_verb == :post
|
26
|
+
begin
|
27
|
+
HTTParty.post(event.url,
|
28
|
+
body: event.params.to_json,
|
29
|
+
headers: event.headers,
|
30
|
+
timeout: REQUEST_TIMEOUT)
|
31
|
+
rescue Timeout::Error => e
|
32
|
+
return e
|
33
|
+
end
|
34
|
+
end
|
16
35
|
end
|
17
36
|
end
|
18
37
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Optimizely
|
2
2
|
module Helpers
|
3
3
|
module Constants
|
4
|
-
|
4
|
+
JSON_SCHEMA_V1 = {
|
5
5
|
'type' => 'object',
|
6
6
|
'properties' => {
|
7
7
|
'projectId' => {
|
@@ -186,7 +186,7 @@ module Optimizely
|
|
186
186
|
'trafficAllocation',
|
187
187
|
'audienceIds',
|
188
188
|
'forcedVariations',
|
189
|
-
'status'
|
189
|
+
'status',
|
190
190
|
]
|
191
191
|
}
|
192
192
|
},
|
@@ -278,6 +278,288 @@ module Optimizely
|
|
278
278
|
'revision'
|
279
279
|
]
|
280
280
|
}
|
281
|
+
|
282
|
+
JSON_SCHEMA_V2 = {
|
283
|
+
'type' => 'object',
|
284
|
+
'properties' => {
|
285
|
+
'projectId' => {
|
286
|
+
'type' => 'string'
|
287
|
+
},
|
288
|
+
'accountId' => {
|
289
|
+
'type' => 'string'
|
290
|
+
},
|
291
|
+
'groups' => {
|
292
|
+
'type' => 'array',
|
293
|
+
'items' => {
|
294
|
+
'type' => 'object',
|
295
|
+
'properties' => {
|
296
|
+
'id' => {
|
297
|
+
'type' => 'string'
|
298
|
+
},
|
299
|
+
'policy' => {
|
300
|
+
'type' => 'string'
|
301
|
+
},
|
302
|
+
'trafficAllocation' => {
|
303
|
+
'type' => 'array',
|
304
|
+
'items' => {
|
305
|
+
'type' => 'object',
|
306
|
+
'properties' => {
|
307
|
+
'entityId' => {
|
308
|
+
'type' => 'string'
|
309
|
+
},
|
310
|
+
'endOfRange' => {
|
311
|
+
'type' => 'integer'
|
312
|
+
}
|
313
|
+
},
|
314
|
+
'required' => [
|
315
|
+
'entityId',
|
316
|
+
'endOfRange'
|
317
|
+
]
|
318
|
+
}
|
319
|
+
},
|
320
|
+
'experiments' => {
|
321
|
+
'type' => 'array',
|
322
|
+
'items' => {
|
323
|
+
'type' => 'object',
|
324
|
+
'properties' => {
|
325
|
+
'id' => {
|
326
|
+
'type' => 'string'
|
327
|
+
},
|
328
|
+
'layerId' => {
|
329
|
+
'type' => 'string'
|
330
|
+
},
|
331
|
+
'key' => {
|
332
|
+
'type' => 'string'
|
333
|
+
},
|
334
|
+
'status' => {
|
335
|
+
'type' => 'string'
|
336
|
+
},
|
337
|
+
'variations' => {
|
338
|
+
'type' => 'array',
|
339
|
+
'items' => {
|
340
|
+
'type' => 'object',
|
341
|
+
'properties' => {
|
342
|
+
'id' => {
|
343
|
+
'type' => 'string'
|
344
|
+
},
|
345
|
+
'key' => {
|
346
|
+
'type' => 'string'
|
347
|
+
}
|
348
|
+
},
|
349
|
+
'required' => [
|
350
|
+
'id',
|
351
|
+
'key'
|
352
|
+
]
|
353
|
+
}
|
354
|
+
},
|
355
|
+
'trafficAllocation' => {
|
356
|
+
'type' => 'array',
|
357
|
+
'items' => {
|
358
|
+
'type' => 'object',
|
359
|
+
'properties' => {
|
360
|
+
'entityId' => {
|
361
|
+
'type' => 'string'
|
362
|
+
},
|
363
|
+
'endOfRange' => {
|
364
|
+
'type' => 'integer'
|
365
|
+
}
|
366
|
+
},
|
367
|
+
'required' => [
|
368
|
+
'entityId',
|
369
|
+
'endOfRange'
|
370
|
+
]
|
371
|
+
}
|
372
|
+
},
|
373
|
+
'audienceIds' => {
|
374
|
+
'type' => 'array',
|
375
|
+
'items' => {
|
376
|
+
'type' => 'string'
|
377
|
+
}
|
378
|
+
},
|
379
|
+
'forcedVariations' => {
|
380
|
+
'type' => 'object'
|
381
|
+
}
|
382
|
+
},
|
383
|
+
'required' => [
|
384
|
+
'id',
|
385
|
+
'layerId',
|
386
|
+
'key',
|
387
|
+
'status',
|
388
|
+
'variations',
|
389
|
+
'trafficAllocation',
|
390
|
+
'audienceIds',
|
391
|
+
'forcedVariations'
|
392
|
+
]
|
393
|
+
}
|
394
|
+
}
|
395
|
+
},
|
396
|
+
'required' => [
|
397
|
+
'id',
|
398
|
+
'policy',
|
399
|
+
'trafficAllocation',
|
400
|
+
'experiments'
|
401
|
+
]
|
402
|
+
}
|
403
|
+
},
|
404
|
+
'experiments' => {
|
405
|
+
'type' => 'array',
|
406
|
+
'items' => {
|
407
|
+
'type' => 'object',
|
408
|
+
'properties' => {
|
409
|
+
'id' => {
|
410
|
+
'type' => 'string'
|
411
|
+
},
|
412
|
+
'key' => {
|
413
|
+
'type' => 'string'
|
414
|
+
},
|
415
|
+
'status' => {
|
416
|
+
'type' => 'string'
|
417
|
+
},
|
418
|
+
'layerId' => {
|
419
|
+
'type' => 'string'
|
420
|
+
},
|
421
|
+
'variations' => {
|
422
|
+
'type' => 'array',
|
423
|
+
'items' => {
|
424
|
+
'type' => 'object',
|
425
|
+
'properties' => {
|
426
|
+
'id' => {
|
427
|
+
'type' => 'string'
|
428
|
+
},
|
429
|
+
'key' => {
|
430
|
+
'type' => 'string'
|
431
|
+
}
|
432
|
+
},
|
433
|
+
'required' => [
|
434
|
+
'id',
|
435
|
+
'key'
|
436
|
+
]
|
437
|
+
}
|
438
|
+
},
|
439
|
+
'trafficAllocation' => {
|
440
|
+
'type' => 'array',
|
441
|
+
'items' => {
|
442
|
+
'type' => 'object',
|
443
|
+
'properties' => {
|
444
|
+
'entityId' => {
|
445
|
+
'type' => 'string'
|
446
|
+
},
|
447
|
+
'endOfRange' => {
|
448
|
+
'type' => 'integer'
|
449
|
+
}
|
450
|
+
},
|
451
|
+
'required' => [
|
452
|
+
'entityId',
|
453
|
+
'endOfRange'
|
454
|
+
]
|
455
|
+
}
|
456
|
+
},
|
457
|
+
'audienceIds' => {
|
458
|
+
'type' => 'array',
|
459
|
+
'items' => {
|
460
|
+
'type' => 'string'
|
461
|
+
}
|
462
|
+
},
|
463
|
+
'forcedVariations' => {
|
464
|
+
'type' => 'object'
|
465
|
+
}
|
466
|
+
},
|
467
|
+
'required' => [
|
468
|
+
'id',
|
469
|
+
'key',
|
470
|
+
'variations',
|
471
|
+
'trafficAllocation',
|
472
|
+
'audienceIds',
|
473
|
+
'forcedVariations',
|
474
|
+
'status',
|
475
|
+
'layerId'
|
476
|
+
]
|
477
|
+
}
|
478
|
+
},
|
479
|
+
'events' => {
|
480
|
+
'type' => 'array',
|
481
|
+
'items' => {
|
482
|
+
'type' => 'object',
|
483
|
+
'properties' => {
|
484
|
+
'key' => {
|
485
|
+
'type' => 'string'
|
486
|
+
},
|
487
|
+
'experimentIds' => {
|
488
|
+
'type' => 'array',
|
489
|
+
'items' => {
|
490
|
+
'type' => 'string'
|
491
|
+
}
|
492
|
+
},
|
493
|
+
'id' => {
|
494
|
+
'type' => 'string'
|
495
|
+
}
|
496
|
+
},
|
497
|
+
'required' => [
|
498
|
+
'key',
|
499
|
+
'experimentIds',
|
500
|
+
'id'
|
501
|
+
]
|
502
|
+
}
|
503
|
+
},
|
504
|
+
'audiences' => {
|
505
|
+
'type' => 'array',
|
506
|
+
'items' => {
|
507
|
+
'type' => 'object',
|
508
|
+
'properties' => {
|
509
|
+
'id' => {
|
510
|
+
'type' => 'string'
|
511
|
+
},
|
512
|
+
'name' => {
|
513
|
+
'type' => 'string'
|
514
|
+
},
|
515
|
+
'conditions' => {
|
516
|
+
'type' => 'string'
|
517
|
+
}
|
518
|
+
},
|
519
|
+
'required' => [
|
520
|
+
'id',
|
521
|
+
'name',
|
522
|
+
'conditions'
|
523
|
+
]
|
524
|
+
}
|
525
|
+
},
|
526
|
+
'attributes' => {
|
527
|
+
'type' => 'array',
|
528
|
+
'items' => {
|
529
|
+
'type' => 'object',
|
530
|
+
'properties' => {
|
531
|
+
'id' => {
|
532
|
+
'type' => 'string'
|
533
|
+
},
|
534
|
+
'key' => {
|
535
|
+
'type' => 'string'
|
536
|
+
},
|
537
|
+
},
|
538
|
+
'required' => [
|
539
|
+
'id',
|
540
|
+
'key',
|
541
|
+
]
|
542
|
+
}
|
543
|
+
},
|
544
|
+
'version' => {
|
545
|
+
'type' => 'string'
|
546
|
+
},
|
547
|
+
'revision' => {
|
548
|
+
'type' => 'string'
|
549
|
+
}
|
550
|
+
},
|
551
|
+
'required' => [
|
552
|
+
'projectId',
|
553
|
+
'accountId',
|
554
|
+
'experiments',
|
555
|
+
'events',
|
556
|
+
'groups',
|
557
|
+
'audiences',
|
558
|
+
'attributes',
|
559
|
+
'version',
|
560
|
+
'revision'
|
561
|
+
]
|
562
|
+
}
|
281
563
|
end
|
282
564
|
end
|
283
565
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require_relative 'constants'
|
2
|
+
require 'json'
|
2
3
|
require 'json-schema'
|
3
4
|
|
4
5
|
module Optimizely
|
@@ -23,7 +24,19 @@ module Optimizely
|
|
23
24
|
#
|
24
25
|
# Returns boolean depending on validity of datafile.
|
25
26
|
|
26
|
-
|
27
|
+
begin
|
28
|
+
datafile = JSON.load(datafile)
|
29
|
+
rescue
|
30
|
+
return false
|
31
|
+
end
|
32
|
+
|
33
|
+
version = datafile['version']
|
34
|
+
|
35
|
+
if version == Optimizely::V1_CONFIG_VERSION
|
36
|
+
JSON::Validator.validate(Helpers::Constants::JSON_SCHEMA_V1, datafile)
|
37
|
+
else
|
38
|
+
JSON::Validator.validate(Helpers::Constants::JSON_SCHEMA_V2, datafile)
|
39
|
+
end
|
27
40
|
end
|
28
41
|
|
29
42
|
def error_handler_valid?(error_handler)
|
@@ -1,6 +1,10 @@
|
|
1
1
|
require 'json'
|
2
2
|
|
3
3
|
module Optimizely
|
4
|
+
|
5
|
+
V1_CONFIG_VERSION = '1'
|
6
|
+
V2_CONFIG_VERSION = '2'
|
7
|
+
|
4
8
|
class ProjectConfig
|
5
9
|
# Representation of the Optimizely project config.
|
6
10
|
|
@@ -11,8 +15,9 @@ module Optimizely
|
|
11
15
|
|
12
16
|
# Gets project config attributes.
|
13
17
|
attr_reader :error_handler
|
14
|
-
|
18
|
+
attr_reader :logger
|
15
19
|
|
20
|
+
attr_reader :version
|
16
21
|
attr_reader :account_id
|
17
22
|
attr_reader :project_id
|
18
23
|
attr_reader :attributes
|
@@ -41,9 +46,14 @@ module Optimizely
|
|
41
46
|
|
42
47
|
@error_handler = error_handler
|
43
48
|
@logger = logger
|
49
|
+
@version = config['version']
|
44
50
|
@account_id = config['accountId']
|
45
51
|
@project_id = config['projectId']
|
46
|
-
@
|
52
|
+
if @version == V1_CONFIG_VERSION
|
53
|
+
@attributes = config['dimensions']
|
54
|
+
else
|
55
|
+
@attributes = config['attributes']
|
56
|
+
end
|
47
57
|
@events = config['events']
|
48
58
|
@experiments = config['experiments']
|
49
59
|
@revision = config['revision']
|
@@ -73,7 +83,7 @@ module Optimizely
|
|
73
83
|
end
|
74
84
|
|
75
85
|
def experiment_running?(experiment_key)
|
76
|
-
# Determine if experiment
|
86
|
+
# Determine if experiment corresponding to given key is running
|
77
87
|
#
|
78
88
|
# experiment_key - String key representing the experiment
|
79
89
|
#
|
@@ -239,6 +249,14 @@ module Optimizely
|
|
239
249
|
@error_handler.handle_error InvalidExperimentError
|
240
250
|
end
|
241
251
|
|
252
|
+
def get_attribute_id(attribute_key)
|
253
|
+
attribute = @attribute_key_map[attribute_key]
|
254
|
+
return attribute['id'] if attribute
|
255
|
+
@logger.log Logger::ERROR, "Attribute key '#{attribute_key}' is not in datafile."
|
256
|
+
@error_handler.handle_error InvalidAttributeError
|
257
|
+
nil
|
258
|
+
end
|
259
|
+
|
242
260
|
private
|
243
261
|
|
244
262
|
def generate_key_map(array, key)
|
data/lib/optimizely/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: optimizely-sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Delikat
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2016-
|
13
|
+
date: 2016-09-19 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: bundler
|
@@ -133,7 +133,6 @@ files:
|
|
133
133
|
- lib/optimizely/params.rb
|
134
134
|
- lib/optimizely/project_config.rb
|
135
135
|
- lib/optimizely/version.rb
|
136
|
-
- lib/start.rb
|
137
136
|
homepage: https://www.optimizely.com/
|
138
137
|
licenses:
|
139
138
|
- Apache-2.0
|
@@ -154,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
154
153
|
version: '0'
|
155
154
|
requirements: []
|
156
155
|
rubyforge_project:
|
157
|
-
rubygems_version: 2.
|
156
|
+
rubygems_version: 2.6.6
|
158
157
|
signing_key:
|
159
158
|
specification_version: 4
|
160
159
|
summary: Ruby SDK for Optimizely's testing framework
|
data/lib/start.rb
DELETED
@@ -1,131 +0,0 @@
|
|
1
|
-
require './optimizely'
|
2
|
-
require 'benchmark'
|
3
|
-
|
4
|
-
class PerformanceTests
|
5
|
-
@error_handler = Optimizely::NoOpErrorHandler.new
|
6
|
-
@logger = Optimizely::NoOpLogger.new
|
7
|
-
|
8
|
-
def self.test_initialize(testdata, optly)
|
9
|
-
Optimizely::Project.new(testdata)
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.test_initialize_logger(testdata, optly)
|
13
|
-
Optimizely::Project.new(testdata, nil, @logger)
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.test_initialize_logger_and_error_handler(testdata, optly)
|
17
|
-
Optimizely::Project.new(testdata, nil, @logger, @error_handler)
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.test_initialize_no_schema_validation(testdata, optly)
|
21
|
-
Optimizely::Project.new(testdata, nil, nil, nil, true)
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.test_initialize_logger_no_schema_validation(testdata, optly)
|
25
|
-
Optimizely::Project.new(testdata, nil, @logger, nil, true)
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.test_initialize_error_handler_no_schema_validation(testdata, optly)
|
29
|
-
Optimizely::Project.new(testdata, nil, nil, @error_handler, true)
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.test_initialize_logger_error_handler_no_schema_validation(testdata, optly)
|
33
|
-
Optimizely::Project.new(testdata, nil, @logger, @error_handler, true)
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.test_initialize_error_handler_no_schema_validation(testdata, optly)
|
37
|
-
Optimizely::Project.new(testdata, nil, nil, @error_handler, true)
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.test_activate(testdata, optly)
|
41
|
-
optly.activate('testExperiment2', 'optimizely_user')
|
42
|
-
end
|
43
|
-
|
44
|
-
def self.test_activate_with_attributes(testdata, optly)
|
45
|
-
optly.activate('testExperimentWithFirefoxAudience', 'optimizely_user', {'browser_type' => 'firefox'})
|
46
|
-
end
|
47
|
-
|
48
|
-
def self.test_activate_with_forced_variation(testdata, optly)
|
49
|
-
optly.activate('testExperiment2', 'variation_user')
|
50
|
-
end
|
51
|
-
|
52
|
-
def self.test_activate_grouped_exp(testdata, optly)
|
53
|
-
optly.activate('mutex_exp2', 'optimizely_user')
|
54
|
-
end
|
55
|
-
|
56
|
-
def self.test_activate_grouped_exp_with_attributes(testdata, optly)
|
57
|
-
optly.activate('mutex_exp1', 'optimizely_user', {'browser_type' => 'firefox'})
|
58
|
-
end
|
59
|
-
|
60
|
-
def self.test_get_variation(testdata, optly)
|
61
|
-
optly.get_variation('testExperiment2', 'optimizely_user')
|
62
|
-
end
|
63
|
-
|
64
|
-
def self.test_get_variation_with_attributes(testdata, optly)
|
65
|
-
optly.get_variation('testExperimentWithFirefoxAudience', 'optimizely_user', {'browser_type' => 'firefox'})
|
66
|
-
end
|
67
|
-
|
68
|
-
def self.test_get_variation_with_forced_variation(testdata, optly)
|
69
|
-
optly.get_variation('testExperiment2', 'variation_user')
|
70
|
-
end
|
71
|
-
|
72
|
-
def self.test_get_variation_grouped_exp(testdata, optly)
|
73
|
-
optly.get_variation('mutex_exp2', 'optimizely_user')
|
74
|
-
end
|
75
|
-
|
76
|
-
def self.test_get_variation_grouped_exp_with_attributes(testdata, optly)
|
77
|
-
optly.get_variation('mutex_exp1', 'optimizely_user')
|
78
|
-
end
|
79
|
-
|
80
|
-
def self.test_track(testdata, optly)
|
81
|
-
optly.track('testEvent', 'optimizely_user')
|
82
|
-
end
|
83
|
-
|
84
|
-
def self.test_track_with_attributes(testdata, optly)
|
85
|
-
optly.track('testEventWithAudiences' 'optimizely_user', {'browser_type' => 'firefox'})
|
86
|
-
end
|
87
|
-
|
88
|
-
def self.test_track_with_revenue(testdata, optly)
|
89
|
-
optly.track('testEvent', 'optimizely_user', nil, 666)
|
90
|
-
end
|
91
|
-
|
92
|
-
def self.test_track_with_attributes_and_revenue(testdata, optly)
|
93
|
-
optly.track('testEventWithAudiences', 'optimizely_user', {'browser_type' => 'firefox'}, 666)
|
94
|
-
end
|
95
|
-
|
96
|
-
def self.test_track_grouped_exp(testdata, optly)
|
97
|
-
optly.track('testEventWithMultipleGroupedExperiments', 'optimizely_user')
|
98
|
-
end
|
99
|
-
|
100
|
-
def self.test_track_grouped_exp_with_attributes(testdata, optly)
|
101
|
-
optly.track('testEventWithMultipleExperiments', 'optimizely_user', {'browser_type' => 'firefox'})
|
102
|
-
end
|
103
|
-
|
104
|
-
def self.test_track_grouped_exp_with_revenue(testdata, optly)
|
105
|
-
optly.track('testEventWithMultipleGroupedExperiments', 'optimizely_user', nil, 666)
|
106
|
-
end
|
107
|
-
|
108
|
-
def self.test_track_grouped_exp_with_attributes_and_revenue(testdata, optly)
|
109
|
-
optly.track('testEventWithMultipleExperiments', 'optimizely_user', {'browser_type' => 'firefox'}, 666)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
def run_tests
|
114
|
-
testdata10 = File.read('testdata_10.json')
|
115
|
-
testdata25 = File.read('testdata_25.json')
|
116
|
-
testdata50 = File.read('testdata_50.json')
|
117
|
-
optly10 = Optimizely::Project.new(testdata10)
|
118
|
-
optly25 = Optimizely::Project.new(testdata25)
|
119
|
-
optly50 = Optimizely::Project.new(testdata50)
|
120
|
-
|
121
|
-
tests = PerformanceTests.methods(false)
|
122
|
-
tests.each do |test|
|
123
|
-
puts '', test
|
124
|
-
Benchmark.bmbm do |x|
|
125
|
-
x.report('10 exps') { PerformanceTests.send(test, testdata10, optly10) }
|
126
|
-
x.report('25 exps') { PerformanceTests.send(test, testdata25, optly25) }
|
127
|
-
x.report('50 exps') { PerformanceTests.send(test, testdata50, optly50) }
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|