optimizely-sdk 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
|