optimizely-sdk 3.8.0 → 3.8.1
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 +9 -6
- data/lib/optimizely/bucketer.rb +2 -2
- data/lib/optimizely/config/datafile_project_config.rb +80 -13
- data/lib/optimizely/decision_service.rb +20 -20
- data/lib/optimizely/project_config.rb +7 -1
- data/lib/optimizely/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5751701cecb265d27a62c72b268fcb01bb5a37f8696f97a6f56ad29776b226cc
|
4
|
+
data.tar.gz: 32533f70bdd2861e2250b3c1b8e274d61f04395dd4ff5cf7ff0776d5f49c2a90
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f7258d91de18c854b91f1d938c4624bf227307a2f01aec82aa23d1f2978ca8510209171f4dc62a3d2dcc606784a741dd9f6572d7c6a81475e2b791e70b7e45be
|
7
|
+
data.tar.gz: 1471cf812fbebc51fe2207e599f44b60597c3e6e15209af400d4ecabe0333bcd285269b3b421ad80f043c33012aecc54d3d41e59c53dffe64b09b93039b36254
|
data/lib/optimizely.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2016-
|
4
|
+
# Copyright 2016-2021, Optimizely and contributors
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -877,12 +877,14 @@ module Optimizely
|
|
877
877
|
experiment = config.get_experiment_from_key(experiment_key)
|
878
878
|
return nil if experiment.nil?
|
879
879
|
|
880
|
+
experiment_id = experiment['id']
|
881
|
+
|
880
882
|
return nil unless user_inputs_valid?(attributes)
|
881
883
|
|
882
|
-
variation_id, = @decision_service.get_variation(config,
|
884
|
+
variation_id, = @decision_service.get_variation(config, experiment_id, user_id, attributes)
|
883
885
|
variation = config.get_variation_from_id(experiment_key, variation_id) unless variation_id.nil?
|
884
886
|
variation_key = variation['key'] if variation
|
885
|
-
decision_notification_type = if config.feature_experiment?(
|
887
|
+
decision_notification_type = if config.feature_experiment?(experiment_id)
|
886
888
|
Helpers::Constants::DECISION_NOTIFICATION_TYPES['FEATURE_TEST']
|
887
889
|
else
|
888
890
|
Helpers::Constants::DECISION_NOTIFICATION_TYPES['AB_TEST']
|
@@ -1078,10 +1080,11 @@ module Optimizely
|
|
1078
1080
|
}
|
1079
1081
|
end
|
1080
1082
|
|
1083
|
+
experiment_id = experiment['id']
|
1081
1084
|
experiment_key = experiment['key']
|
1082
1085
|
|
1083
1086
|
variation_id = ''
|
1084
|
-
variation_id = config.
|
1087
|
+
variation_id = config.get_variation_id_from_key_by_experiment_id(experiment_id, variation_key) if experiment_id != ''
|
1085
1088
|
|
1086
1089
|
metadata = {
|
1087
1090
|
flag_key: flag_key,
|
@@ -1097,9 +1100,9 @@ module Optimizely
|
|
1097
1100
|
|
1098
1101
|
@logger.log(Logger::INFO, "Activating user '#{user_id}' in experiment '#{experiment_key}'.")
|
1099
1102
|
|
1100
|
-
experiment = nil if
|
1103
|
+
experiment = nil if experiment_id == ''
|
1101
1104
|
variation = nil
|
1102
|
-
variation = config.
|
1105
|
+
variation = config.get_variation_from_id_by_experiment_id(experiment_id, variation_id) unless experiment.nil?
|
1103
1106
|
log_event = EventFactory.create_log_event(user_event, @logger)
|
1104
1107
|
@notification_center.send_notifications(
|
1105
1108
|
NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE],
|
data/lib/optimizely/bucketer.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2016-2017, 2019-
|
4
|
+
# Copyright 2016-2017, 2019-2021 Optimizely and contributors
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -88,7 +88,7 @@ module Optimizely
|
|
88
88
|
decide_reasons.push(*find_bucket_reasons)
|
89
89
|
|
90
90
|
if variation_id && variation_id != ''
|
91
|
-
variation = project_config.
|
91
|
+
variation = project_config.get_variation_from_id_by_experiment_id(experiment_id, variation_id)
|
92
92
|
return variation, decide_reasons
|
93
93
|
end
|
94
94
|
|
@@ -52,10 +52,12 @@ module Optimizely
|
|
52
52
|
attr_reader :feature_variable_key_map
|
53
53
|
attr_reader :group_id_map
|
54
54
|
attr_reader :rollout_id_map
|
55
|
-
attr_reader :
|
55
|
+
attr_reader :rollout_experiment_id_map
|
56
56
|
attr_reader :variation_id_map
|
57
57
|
attr_reader :variation_id_to_variable_usage_map
|
58
58
|
attr_reader :variation_key_map
|
59
|
+
attr_reader :variation_id_map_by_experiment_id
|
60
|
+
attr_reader :variation_key_map_by_experiment_id
|
59
61
|
|
60
62
|
def initialize(datafile, logger, error_handler)
|
61
63
|
# ProjectConfig init method to fetch and set project config data
|
@@ -113,9 +115,11 @@ module Optimizely
|
|
113
115
|
@audience_id_map = @audience_id_map.merge(generate_key_map(@typed_audiences, 'id')) unless @typed_audiences.empty?
|
114
116
|
@variation_id_map = {}
|
115
117
|
@variation_key_map = {}
|
118
|
+
@variation_id_map_by_experiment_id = {}
|
119
|
+
@variation_key_map_by_experiment_id = {}
|
116
120
|
@variation_id_to_variable_usage_map = {}
|
117
121
|
@variation_id_to_experiment_map = {}
|
118
|
-
@
|
122
|
+
@experiment_id_map.each_value do |exp|
|
119
123
|
# Excludes experiments from rollouts
|
120
124
|
variations = exp.fetch('variations')
|
121
125
|
variations.each do |variation|
|
@@ -125,13 +129,13 @@ module Optimizely
|
|
125
129
|
end
|
126
130
|
@rollout_id_map = generate_key_map(@rollouts, 'id')
|
127
131
|
# split out the experiment key map for rollouts
|
128
|
-
@
|
132
|
+
@rollout_experiment_id_map = {}
|
129
133
|
@rollout_id_map.each_value do |rollout|
|
130
134
|
exps = rollout.fetch('experiments')
|
131
|
-
@
|
135
|
+
@rollout_experiment_id_map = @rollout_experiment_id_map.merge(generate_key_map(exps, 'id'))
|
132
136
|
end
|
133
|
-
@all_experiments = @
|
134
|
-
@all_experiments.each do |
|
137
|
+
@all_experiments = @experiment_id_map.merge(@rollout_experiment_id_map)
|
138
|
+
@all_experiments.each do |id, exp|
|
135
139
|
variations = exp.fetch('variations')
|
136
140
|
variations.each do |variation|
|
137
141
|
variation_id = variation['id']
|
@@ -141,8 +145,10 @@ module Optimizely
|
|
141
145
|
|
142
146
|
@variation_id_to_variable_usage_map[variation_id] = generate_key_map(variation_variables, 'id')
|
143
147
|
end
|
144
|
-
@variation_id_map[key] = generate_key_map(variations, 'id')
|
145
|
-
@variation_key_map[key] = generate_key_map(variations, 'key')
|
148
|
+
@variation_id_map[exp['key']] = generate_key_map(variations, 'id')
|
149
|
+
@variation_key_map[exp['key']] = generate_key_map(variations, 'key')
|
150
|
+
@variation_id_map_by_experiment_id[id] = generate_key_map(variations, 'id')
|
151
|
+
@variation_key_map_by_experiment_id[id] = generate_key_map(variations, 'key')
|
146
152
|
end
|
147
153
|
@feature_flag_key_map = generate_key_map(@feature_flags, 'key')
|
148
154
|
@experiment_feature_map = {}
|
@@ -209,6 +215,21 @@ module Optimizely
|
|
209
215
|
nil
|
210
216
|
end
|
211
217
|
|
218
|
+
def get_experiment_from_id(experiment_id)
|
219
|
+
# Retrieves experiment ID for a given key
|
220
|
+
#
|
221
|
+
# experiment_id - String id representing the experiment
|
222
|
+
#
|
223
|
+
# Returns Experiment or nil if not found
|
224
|
+
|
225
|
+
experiment = @experiment_id_map[experiment_id]
|
226
|
+
return experiment if experiment
|
227
|
+
|
228
|
+
@logger.log Logger::ERROR, "Experiment id '#{experiment_id}' is not in datafile."
|
229
|
+
@error_handler.handle_error InvalidExperimentError
|
230
|
+
nil
|
231
|
+
end
|
232
|
+
|
212
233
|
def get_experiment_key(experiment_id)
|
213
234
|
# Retrieves experiment key for a given ID.
|
214
235
|
#
|
@@ -277,6 +298,52 @@ module Optimizely
|
|
277
298
|
nil
|
278
299
|
end
|
279
300
|
|
301
|
+
def get_variation_from_id_by_experiment_id(experiment_id, variation_id)
|
302
|
+
# Get variation given experiment ID and variation ID
|
303
|
+
#
|
304
|
+
# experiment_id - ID representing parent experiment of variation
|
305
|
+
# variation_id - ID of the variation
|
306
|
+
#
|
307
|
+
# Returns the variation or nil if not found
|
308
|
+
|
309
|
+
variation_id_map_by_experiment_id = @variation_id_map_by_experiment_id[experiment_id]
|
310
|
+
if variation_id_map_by_experiment_id
|
311
|
+
variation = variation_id_map_by_experiment_id[variation_id]
|
312
|
+
return variation if variation
|
313
|
+
|
314
|
+
@logger.log Logger::ERROR, "Variation id '#{variation_id}' is not in datafile."
|
315
|
+
@error_handler.handle_error InvalidVariationError
|
316
|
+
return nil
|
317
|
+
end
|
318
|
+
|
319
|
+
@logger.log Logger::ERROR, "Experiment id '#{experiment_id}' is not in datafile."
|
320
|
+
@error_handler.handle_error InvalidExperimentError
|
321
|
+
nil
|
322
|
+
end
|
323
|
+
|
324
|
+
def get_variation_id_from_key_by_experiment_id(experiment_id, variation_key)
|
325
|
+
# Get variation given experiment ID and variation key
|
326
|
+
#
|
327
|
+
# experiment_id - ID representing parent experiment of variation
|
328
|
+
# variation_key - Key of the variation
|
329
|
+
#
|
330
|
+
# Returns the variation or nil if not found
|
331
|
+
|
332
|
+
variation_key_map = @variation_key_map_by_experiment_id[experiment_id]
|
333
|
+
if variation_key_map
|
334
|
+
variation = variation_key_map[variation_key]
|
335
|
+
return variation['id'] if variation
|
336
|
+
|
337
|
+
@logger.log Logger::ERROR, "Variation key '#{variation_key}' is not in datafile."
|
338
|
+
@error_handler.handle_error InvalidVariationError
|
339
|
+
return nil
|
340
|
+
end
|
341
|
+
|
342
|
+
@logger.log Logger::ERROR, "Experiment id '#{experiment_id}' is not in datafile."
|
343
|
+
@error_handler.handle_error InvalidExperimentError
|
344
|
+
nil
|
345
|
+
end
|
346
|
+
|
280
347
|
def get_variation_id_from_key(experiment_key, variation_key)
|
281
348
|
# Get variation ID given experiment key and variation key
|
282
349
|
#
|
@@ -300,17 +367,17 @@ module Optimizely
|
|
300
367
|
nil
|
301
368
|
end
|
302
369
|
|
303
|
-
def get_whitelisted_variations(
|
304
|
-
# Retrieves whitelisted variations for a given experiment
|
370
|
+
def get_whitelisted_variations(experiment_id)
|
371
|
+
# Retrieves whitelisted variations for a given experiment id
|
305
372
|
#
|
306
|
-
#
|
373
|
+
# experiment_id - String id representing the experiment
|
307
374
|
#
|
308
375
|
# Returns whitelisted variations for the experiment or nil
|
309
376
|
|
310
|
-
experiment = @
|
377
|
+
experiment = @experiment_id_map[experiment_id]
|
311
378
|
return experiment['forcedVariations'] if experiment
|
312
379
|
|
313
|
-
@logger.log Logger::ERROR, "Experiment
|
380
|
+
@logger.log Logger::ERROR, "Experiment ID '#{experiment_id}' is not in datafile."
|
314
381
|
@error_handler.handle_error InvalidExperimentError
|
315
382
|
end
|
316
383
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2017-
|
4
|
+
# Copyright 2017-2021, Optimizely and contributors
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -52,11 +52,11 @@ module Optimizely
|
|
52
52
|
@forced_variation_map = {}
|
53
53
|
end
|
54
54
|
|
55
|
-
def get_variation(project_config,
|
55
|
+
def get_variation(project_config, experiment_id, user_id, attributes = nil, decide_options = [])
|
56
56
|
# Determines variation into which user will be bucketed.
|
57
57
|
#
|
58
58
|
# project_config - project_config - Instance of ProjectConfig
|
59
|
-
#
|
59
|
+
# experiment_id - Experiment for which visitor variation needs to be determined
|
60
60
|
# user_id - String ID for user
|
61
61
|
# attributes - Hash representing user attributes
|
62
62
|
#
|
@@ -68,10 +68,10 @@ module Optimizely
|
|
68
68
|
bucketing_id, bucketing_id_reasons = get_bucketing_id(user_id, attributes)
|
69
69
|
decide_reasons.push(*bucketing_id_reasons)
|
70
70
|
# Check to make sure experiment is active
|
71
|
-
experiment = project_config.
|
71
|
+
experiment = project_config.get_experiment_from_id(experiment_id)
|
72
72
|
return nil, decide_reasons if experiment.nil?
|
73
73
|
|
74
|
-
|
74
|
+
experiment_key = experiment['key']
|
75
75
|
unless project_config.experiment_running?(experiment)
|
76
76
|
message = "Experiment '#{experiment_key}' is not running."
|
77
77
|
@logger.log(Logger::INFO, message)
|
@@ -80,12 +80,12 @@ module Optimizely
|
|
80
80
|
end
|
81
81
|
|
82
82
|
# Check if a forced variation is set for the user
|
83
|
-
forced_variation, reasons_received = get_forced_variation(project_config,
|
83
|
+
forced_variation, reasons_received = get_forced_variation(project_config, experiment['key'], user_id)
|
84
84
|
decide_reasons.push(*reasons_received)
|
85
85
|
return forced_variation['id'], decide_reasons if forced_variation
|
86
86
|
|
87
87
|
# Check if user is in a white-listed variation
|
88
|
-
whitelisted_variation_id, reasons_received = get_whitelisted_variation_id(project_config,
|
88
|
+
whitelisted_variation_id, reasons_received = get_whitelisted_variation_id(project_config, experiment_id, user_id)
|
89
89
|
decide_reasons.push(*reasons_received)
|
90
90
|
return whitelisted_variation_id, decide_reasons if whitelisted_variation_id
|
91
91
|
|
@@ -122,7 +122,7 @@ module Optimizely
|
|
122
122
|
message = ''
|
123
123
|
if variation_id
|
124
124
|
variation_key = variation['key']
|
125
|
-
message = "User '#{user_id}' is in variation '#{variation_key}' of experiment '#{
|
125
|
+
message = "User '#{user_id}' is in variation '#{variation_key}' of experiment '#{experiment_id}'."
|
126
126
|
else
|
127
127
|
message = "User '#{user_id}' is in no variation."
|
128
128
|
end
|
@@ -186,13 +186,13 @@ module Optimizely
|
|
186
186
|
return nil, decide_reasons
|
187
187
|
end
|
188
188
|
|
189
|
-
|
190
|
-
variation_id, reasons_received = get_variation(project_config,
|
189
|
+
experiment_id = experiment['id']
|
190
|
+
variation_id, reasons_received = get_variation(project_config, experiment_id, user_id, attributes, decide_options)
|
191
191
|
decide_reasons.push(*reasons_received)
|
192
192
|
|
193
193
|
next unless variation_id
|
194
194
|
|
195
|
-
variation = project_config.
|
195
|
+
variation = project_config.get_variation_from_id_by_experiment_id(experiment_id, variation_id)
|
196
196
|
|
197
197
|
return Decision.new(experiment, variation, DECISION_SOURCES['FEATURE_TEST']), decide_reasons
|
198
198
|
end
|
@@ -315,7 +315,7 @@ module Optimizely
|
|
315
315
|
return true
|
316
316
|
end
|
317
317
|
|
318
|
-
variation_id = project_config.
|
318
|
+
variation_id = project_config.get_variation_id_from_key_by_experiment_id(experiment_id, variation_key)
|
319
319
|
|
320
320
|
# check if the variation exists in the datafile
|
321
321
|
unless variation_id
|
@@ -334,7 +334,7 @@ module Optimizely
|
|
334
334
|
# Gets the forced variation for the given user and experiment.
|
335
335
|
#
|
336
336
|
# project_config - Instance of ProjectConfig
|
337
|
-
# experiment_key - String
|
337
|
+
# experiment_key - String key for experiment
|
338
338
|
# user_id - String ID for user
|
339
339
|
#
|
340
340
|
# Returns Variation The variation which the given user and experiment should be forced into
|
@@ -354,7 +354,7 @@ module Optimizely
|
|
354
354
|
return nil, decide_reasons if experiment_id.nil? || experiment_id.empty?
|
355
355
|
|
356
356
|
unless experiment_to_variation_map.key? experiment_id
|
357
|
-
message = "No experiment '#{
|
357
|
+
message = "No experiment '#{experiment_id}' mapped to user '#{user_id}' in the forced variation map."
|
358
358
|
@logger.log(Logger::DEBUG, message)
|
359
359
|
decide_reasons.push(message)
|
360
360
|
return nil, decide_reasons
|
@@ -362,14 +362,14 @@ module Optimizely
|
|
362
362
|
|
363
363
|
variation_id = experiment_to_variation_map[experiment_id]
|
364
364
|
variation_key = ''
|
365
|
-
variation = project_config.
|
365
|
+
variation = project_config.get_variation_from_id_by_experiment_id(experiment_id, variation_id)
|
366
366
|
variation_key = variation['key'] if variation
|
367
367
|
|
368
368
|
# check if the variation exists in the datafile
|
369
369
|
# this case is logged in get_variation_from_id
|
370
370
|
return nil, decide_reasons if variation_key.empty?
|
371
371
|
|
372
|
-
message = "Variation '#{variation_key}' is mapped to experiment '#{
|
372
|
+
message = "Variation '#{variation_key}' is mapped to experiment '#{experiment_id}' and user '#{user_id}' in the forced variation map"
|
373
373
|
@logger.log(Logger::DEBUG, message)
|
374
374
|
decide_reasons.push(message)
|
375
375
|
|
@@ -378,7 +378,7 @@ module Optimizely
|
|
378
378
|
|
379
379
|
private
|
380
380
|
|
381
|
-
def get_whitelisted_variation_id(project_config,
|
381
|
+
def get_whitelisted_variation_id(project_config, experiment_id, user_id)
|
382
382
|
# Determine if a user is whitelisted into a variation for the given experiment and return the ID of that variation
|
383
383
|
#
|
384
384
|
# project_config - project_config - Instance of ProjectConfig
|
@@ -387,7 +387,7 @@ module Optimizely
|
|
387
387
|
#
|
388
388
|
# Returns variation ID into which user_id is whitelisted (nil if no variation)
|
389
389
|
|
390
|
-
whitelisted_variations = project_config.get_whitelisted_variations(
|
390
|
+
whitelisted_variations = project_config.get_whitelisted_variations(experiment_id)
|
391
391
|
|
392
392
|
return nil, nil unless whitelisted_variations
|
393
393
|
|
@@ -395,7 +395,7 @@ module Optimizely
|
|
395
395
|
|
396
396
|
return nil, nil unless whitelisted_variation_key
|
397
397
|
|
398
|
-
whitelisted_variation_id = project_config.
|
398
|
+
whitelisted_variation_id = project_config.get_variation_id_from_key_by_experiment_id(experiment_id, whitelisted_variation_key)
|
399
399
|
|
400
400
|
unless whitelisted_variation_id
|
401
401
|
message = "User '#{user_id}' is whitelisted into variation '#{whitelisted_variation_key}', which is not in the datafile."
|
@@ -403,7 +403,7 @@ module Optimizely
|
|
403
403
|
return nil, message
|
404
404
|
end
|
405
405
|
|
406
|
-
message = "User '#{user_id}' is whitelisted into variation '#{whitelisted_variation_key}' of experiment '#{
|
406
|
+
message = "User '#{user_id}' is whitelisted into variation '#{whitelisted_variation_key}' of experiment '#{experiment_id}'."
|
407
407
|
@logger.log(Logger::INFO, message)
|
408
408
|
|
409
409
|
[whitelisted_variation_id, message]
|
@@ -54,6 +54,8 @@ module Optimizely
|
|
54
54
|
|
55
55
|
def get_experiment_from_key(experiment_key); end
|
56
56
|
|
57
|
+
def get_experiment_from_id(experiment_id); end
|
58
|
+
|
57
59
|
def get_experiment_key(experiment_id); end
|
58
60
|
|
59
61
|
def get_event_from_key(event_key); end
|
@@ -62,9 +64,13 @@ module Optimizely
|
|
62
64
|
|
63
65
|
def get_variation_from_id(experiment_key, variation_id); end
|
64
66
|
|
67
|
+
def get_variation_from_id_by_experiment_id(experiment_id, variation_id); end
|
68
|
+
|
69
|
+
def get_variation_id_from_key_by_experiment_id(experiment_id, variation_key); end
|
70
|
+
|
65
71
|
def get_variation_id_from_key(experiment_key, variation_key); end
|
66
72
|
|
67
|
-
def get_whitelisted_variations(
|
73
|
+
def get_whitelisted_variations(experiment_id); end
|
68
74
|
|
69
75
|
def get_attribute_id(attribute_key); end
|
70
76
|
|
data/lib/optimizely/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: optimizely-sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.8.
|
4
|
+
version: 3.8.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Optimizely
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-02
|
11
|
+
date: 2021-08-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -200,7 +200,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
200
200
|
- !ruby/object:Gem::Version
|
201
201
|
version: '0'
|
202
202
|
requirements: []
|
203
|
-
rubygems_version: 3.0.
|
203
|
+
rubygems_version: 3.0.1
|
204
204
|
signing_key:
|
205
205
|
specification_version: 4
|
206
206
|
summary: Ruby SDK for Optimizely's testing framework
|