optimizely-sdk 1.3.0 → 1.4.0
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 +38 -2
- data/lib/optimizely/audience.rb +3 -3
- data/lib/optimizely/bucketer.rb +22 -22
- data/lib/optimizely/decision_service.rb +36 -26
- data/lib/optimizely/event_builder.rb +147 -145
- data/lib/optimizely/helpers/event_tag_utils.rb +66 -5
- data/lib/optimizely/helpers/validator.rb +5 -0
- data/lib/optimizely/project_config.rb +132 -66
- data/lib/optimizely/version.rb +4 -1
- metadata +3 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '05059bf31a28b9a72a3f4ecb95808bf46671438b'
|
4
|
+
data.tar.gz: 481be969d392df0eb93bf8157f13d7625912d613
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5f5099fc8956cdeec7add609b9c13ec2cb1f7e0524889c626001afb9c24ec456f642bcf2307da4114c0747b1714faa70861820d3e0ed9fcb901f164f5400034b
|
7
|
+
data.tar.gz: f327bb0d528621dbcddea84d7a0495e1ceec473a616d5ce3afb5a26386794a45452cf1ce83deca7f0d2f0bab0ed2af6dfa3e08a6ec5313558169c25c78a6772a
|
data/lib/optimizely.rb
CHANGED
@@ -80,7 +80,7 @@ module Optimizely
|
|
80
80
|
end
|
81
81
|
|
82
82
|
@decision_service = DecisionService.new(@config, @user_profile_service)
|
83
|
-
@event_builder =
|
83
|
+
@event_builder = EventBuilder.new(@config)
|
84
84
|
end
|
85
85
|
|
86
86
|
def activate(experiment_key, user_id, attributes = nil)
|
@@ -108,7 +108,8 @@ module Optimizely
|
|
108
108
|
|
109
109
|
# Create and dispatch impression event
|
110
110
|
variation_id = @config.get_variation_id_from_key(experiment_key, variation_key)
|
111
|
-
|
111
|
+
experiment = @config.get_experiment_from_key(experiment_key)
|
112
|
+
impression_event = @event_builder.create_impression_event(experiment, variation_id, user_id, attributes)
|
112
113
|
@logger.log(Logger::INFO,
|
113
114
|
'Dispatching impression event to URL %s with params %s.' % [impression_event.url,
|
114
115
|
impression_event.params])
|
@@ -137,6 +138,11 @@ module Optimizely
|
|
137
138
|
return nil
|
138
139
|
end
|
139
140
|
|
141
|
+
unless user_id.is_a? String
|
142
|
+
@logger.log(Logger::ERROR, "User id: #{user_id} is not a string")
|
143
|
+
return nil
|
144
|
+
end
|
145
|
+
|
140
146
|
unless user_inputs_valid?(attributes)
|
141
147
|
@logger.log(Logger::INFO, "Not activating user '#{user_id}.")
|
142
148
|
return nil
|
@@ -150,6 +156,36 @@ module Optimizely
|
|
150
156
|
nil
|
151
157
|
end
|
152
158
|
|
159
|
+
def set_forced_variation(experiment_key, user_id, variation_key)
|
160
|
+
# Force a user into a variation for a given experiment.
|
161
|
+
#
|
162
|
+
# experiment_key - String - key identifying the experiment.
|
163
|
+
# user_id - String - The user ID to be used for bucketing.
|
164
|
+
# variation_key - The variation key specifies the variation which the user will
|
165
|
+
# be forced into. If nil, then clear the existing experiment-to-variation mapping.
|
166
|
+
#
|
167
|
+
# Returns - Boolean - indicates if the set completed successfully.
|
168
|
+
|
169
|
+
@config.set_forced_variation(experiment_key, user_id, variation_key);
|
170
|
+
end
|
171
|
+
|
172
|
+
def get_forced_variation(experiment_key, user_id)
|
173
|
+
# Gets the forced variation for a given user and experiment.
|
174
|
+
#
|
175
|
+
# experiment_key - String - Key identifying the experiment.
|
176
|
+
# user_id - String - The user ID to be used for bucketing.
|
177
|
+
#
|
178
|
+
# Returns String|nil The forced variation key.
|
179
|
+
|
180
|
+
forced_variation_key = nil
|
181
|
+
forced_variation = @config.get_forced_variation(experiment_key, user_id);
|
182
|
+
if forced_variation
|
183
|
+
forced_variation_key = forced_variation['key']
|
184
|
+
end
|
185
|
+
|
186
|
+
forced_variation_key
|
187
|
+
end
|
188
|
+
|
153
189
|
def track(event_key, user_id, attributes = nil, event_tags = nil)
|
154
190
|
# Send conversion event to Optimizely.
|
155
191
|
#
|
data/lib/optimizely/audience.rb
CHANGED
@@ -20,17 +20,17 @@ module Optimizely
|
|
20
20
|
module Audience
|
21
21
|
module_function
|
22
22
|
|
23
|
-
def user_in_experiment?(config,
|
23
|
+
def user_in_experiment?(config, experiment, attributes)
|
24
24
|
# Determine for given experiment if user satisfies the audiences for the experiment.
|
25
25
|
#
|
26
26
|
# config - Representation of the Optimizely project config.
|
27
|
-
#
|
27
|
+
# experiment - Experiment for which visitor is to be bucketed.
|
28
28
|
# attributes - Hash representing user attributes which will be used in determining if
|
29
29
|
# the audience conditions are met.
|
30
30
|
#
|
31
31
|
# Returns boolean representing if user satisfies audience conditions for any of the audiences or not.
|
32
32
|
|
33
|
-
audience_ids =
|
33
|
+
audience_ids = experiment['audienceIds']
|
34
34
|
|
35
35
|
# Return true if there are no audiences
|
36
36
|
return true if audience_ids.empty?
|
data/lib/optimizely/bucketer.rb
CHANGED
@@ -35,26 +35,23 @@ module Optimizely
|
|
35
35
|
@config = config
|
36
36
|
end
|
37
37
|
|
38
|
-
def bucket(
|
38
|
+
def bucket(experiment, user_id)
|
39
39
|
# Determines ID of variation to be shown for a given experiment key and user ID.
|
40
40
|
#
|
41
|
-
#
|
41
|
+
# experiment - Experiment for which visitor is to be bucketed.
|
42
42
|
# user_id - String ID for user.
|
43
43
|
#
|
44
|
-
# Returns
|
44
|
+
# Returns variation in which visitor with ID user_id has been placed. Nil if no variation.
|
45
45
|
|
46
46
|
# check if experiment is in a group; if so, check if user is bucketed into specified experiment
|
47
|
-
experiment_id =
|
48
|
-
|
47
|
+
experiment_id = experiment['id']
|
48
|
+
experiment_key = experiment['key']
|
49
|
+
group_id = experiment['groupId']
|
49
50
|
if group_id
|
50
51
|
group = @config.group_key_map.fetch(group_id)
|
51
52
|
if Helpers::Group.random_policy?(group)
|
52
|
-
bucketing_id = sprintf(BUCKETING_ID_TEMPLATE, user_id: user_id, entity_id: group_id)
|
53
53
|
traffic_allocations = group.fetch('trafficAllocation')
|
54
|
-
|
55
|
-
@config.logger.log(Logger::DEBUG, "Assigned experiment bucket #{bucket_value} to user '#{user_id}'.")
|
56
|
-
bucketed_experiment_id = find_bucket(bucket_value, traffic_allocations)
|
57
|
-
|
54
|
+
bucketed_experiment_id = find_bucket(user_id, group_id, traffic_allocations)
|
58
55
|
# return if the user is not bucketed into any experiment
|
59
56
|
unless bucketed_experiment_id
|
60
57
|
@config.logger.log(Logger::INFO, "User '#{user_id}' is in no experiment.")
|
@@ -78,18 +75,16 @@ module Optimizely
|
|
78
75
|
end
|
79
76
|
end
|
80
77
|
|
81
|
-
|
82
|
-
|
83
|
-
@config.logger.log(Logger::DEBUG, "Assigned variation bucket #{bucket_value} to user '#{user_id}'.")
|
84
|
-
traffic_allocations = @config.get_traffic_allocation(experiment_key)
|
85
|
-
variation_id = find_bucket(bucket_value, traffic_allocations)
|
78
|
+
traffic_allocations = experiment['trafficAllocation']
|
79
|
+
variation_id = find_bucket(user_id, experiment_id, traffic_allocations)
|
86
80
|
if variation_id && variation_id != ''
|
87
|
-
|
81
|
+
variation = @config.get_variation_from_id(experiment_key, variation_id)
|
82
|
+
variation_key = variation ? variation['key'] : nil
|
88
83
|
@config.logger.log(
|
89
84
|
Logger::INFO,
|
90
85
|
"User '#{user_id}' is in variation '#{variation_key}' of experiment '#{experiment_key}'."
|
91
86
|
)
|
92
|
-
return
|
87
|
+
return variation
|
93
88
|
end
|
94
89
|
|
95
90
|
# Handle the case when the traffic range is empty due to sticky bucketing
|
@@ -101,16 +96,19 @@ module Optimizely
|
|
101
96
|
nil
|
102
97
|
end
|
103
98
|
|
104
|
-
|
105
|
-
|
106
|
-
def find_bucket(bucket_value, traffic_allocations)
|
99
|
+
def find_bucket(user_id, parent_id, traffic_allocations)
|
107
100
|
# Helper function to find the matching entity ID for a given bucketing value in a list of traffic allocations.
|
108
101
|
#
|
109
|
-
#
|
102
|
+
# user_id - String ID for user
|
103
|
+
# parent_id - String entity ID to use for bucketing ID
|
110
104
|
# traffic_allocations - Array of traffic allocations
|
111
105
|
#
|
112
106
|
# Returns entity ID corresponding to the provided bucket value or nil if no match is found.
|
113
107
|
|
108
|
+
bucketing_id = sprintf(BUCKETING_ID_TEMPLATE, user_id: user_id, entity_id: parent_id)
|
109
|
+
bucket_value = generate_bucket_value(bucketing_id)
|
110
|
+
@config.logger.log(Logger::DEBUG, "Assigned bucket #{bucket_value} to user '#{user_id}'.")
|
111
|
+
|
114
112
|
traffic_allocations.each do |traffic_allocation|
|
115
113
|
current_end_of_range = traffic_allocation['endOfRange']
|
116
114
|
if bucket_value < current_end_of_range
|
@@ -122,6 +120,8 @@ module Optimizely
|
|
122
120
|
nil
|
123
121
|
end
|
124
122
|
|
123
|
+
private
|
124
|
+
|
125
125
|
def generate_bucket_value(bucketing_id)
|
126
126
|
# Helper function to generate bucket value in half-closed interval [0, MAX_TRAFFIC_VALUE).
|
127
127
|
#
|
@@ -143,4 +143,4 @@ module Optimizely
|
|
143
143
|
MurmurHash3::V32.str_hash(bucketing_id, @bucket_seed) & UNSIGNED_MAX_32_BIT_VALUE
|
144
144
|
end
|
145
145
|
end
|
146
|
-
end
|
146
|
+
end
|
@@ -22,11 +22,12 @@ module Optimizely
|
|
22
22
|
# The decision service contains all logic relating to how a user bucketing decisions is made.
|
23
23
|
# This includes all of the following (in order):
|
24
24
|
#
|
25
|
-
# 1.
|
26
|
-
# 2.
|
27
|
-
# 3.
|
28
|
-
#
|
29
|
-
#
|
25
|
+
# 1. Check experiment status
|
26
|
+
# 2. Check forced bucketing
|
27
|
+
# 3. Check whitelisting
|
28
|
+
# 4. Check user profile service for past bucketing decisions (sticky bucketing)
|
29
|
+
# 5. Check audience targeting
|
30
|
+
# 6. Use Murmurhash3 to bucket the user
|
30
31
|
|
31
32
|
attr_reader :bucketer
|
32
33
|
attr_reader :config
|
@@ -47,16 +48,24 @@ module Optimizely
|
|
47
48
|
# Returns variation ID where visitor will be bucketed (nil if experiment is inactive or user does not meet audience conditions)
|
48
49
|
|
49
50
|
# Check to make sure experiment is active
|
50
|
-
|
51
|
+
experiment = @config.get_experiment_from_key(experiment_key)
|
52
|
+
if experiment.nil?
|
53
|
+
return nil
|
54
|
+
end
|
55
|
+
|
56
|
+
experiment_id = experiment['id']
|
57
|
+
unless @config.experiment_running?(experiment)
|
51
58
|
@config.logger.log(Logger::INFO, "Experiment '#{experiment_key}' is not running.")
|
52
59
|
return nil
|
53
60
|
end
|
54
61
|
|
55
|
-
|
62
|
+
# Check if a forced variation is set for the user
|
63
|
+
forced_variation = @config.get_forced_variation(experiment_key, user_id)
|
64
|
+
return forced_variation['id'] if forced_variation
|
56
65
|
|
57
|
-
# Check if user is in a
|
58
|
-
|
59
|
-
return
|
66
|
+
# Check if user is in a white-listed variation
|
67
|
+
whitelisted_variation_id = get_whitelisted_variation_id(experiment_key, user_id)
|
68
|
+
return whitelisted_variation_id if whitelisted_variation_id
|
60
69
|
|
61
70
|
# Check for saved bucketing decisions
|
62
71
|
user_profile = get_user_profile(user_id)
|
@@ -70,7 +79,7 @@ module Optimizely
|
|
70
79
|
end
|
71
80
|
|
72
81
|
# Check audience conditions
|
73
|
-
unless Audience.user_in_experiment?(@config,
|
82
|
+
unless Audience.user_in_experiment?(@config, experiment, attributes)
|
74
83
|
@config.logger.log(
|
75
84
|
Logger::INFO,
|
76
85
|
"User '#{user_id}' does not meet the conditions to be in experiment '#{experiment_key}'."
|
@@ -79,7 +88,8 @@ module Optimizely
|
|
79
88
|
end
|
80
89
|
|
81
90
|
# Bucket normally
|
82
|
-
|
91
|
+
variation = @bucketer.bucket(experiment, user_id)
|
92
|
+
variation_id = variation ? variation['id'] : nil
|
83
93
|
|
84
94
|
# Persist bucketing decision
|
85
95
|
save_user_profile(user_profile, experiment_id, variation_id)
|
@@ -87,38 +97,38 @@ module Optimizely
|
|
87
97
|
end
|
88
98
|
|
89
99
|
private
|
90
|
-
|
91
|
-
def
|
92
|
-
# Determine if a user is
|
100
|
+
|
101
|
+
def get_whitelisted_variation_id(experiment_key, user_id)
|
102
|
+
# Determine if a user is whitelisted into a variation for the given experiment and return the ID of that variation
|
93
103
|
#
|
94
104
|
# experiment_key - Key representing the experiment for which user is to be bucketed
|
95
105
|
# user_id - ID for the user
|
96
106
|
#
|
97
|
-
# Returns variation ID into which user_id is
|
107
|
+
# Returns variation ID into which user_id is whitelisted (nil if no variation)
|
98
108
|
|
99
|
-
|
109
|
+
whitelisted_variations = @config.get_whitelisted_variations(experiment_key)
|
100
110
|
|
101
|
-
return nil unless
|
111
|
+
return nil unless whitelisted_variations
|
102
112
|
|
103
|
-
|
113
|
+
whitelisted_variation_key = whitelisted_variations[user_id]
|
104
114
|
|
105
|
-
return nil unless
|
115
|
+
return nil unless whitelisted_variation_key
|
106
116
|
|
107
|
-
|
117
|
+
whitelisted_variation_id = @config.get_variation_id_from_key(experiment_key, whitelisted_variation_key)
|
108
118
|
|
109
|
-
unless
|
119
|
+
unless whitelisted_variation_id
|
110
120
|
@config.logger.log(
|
111
121
|
Logger::INFO,
|
112
|
-
"User '#{user_id}' is whitelisted into variation '#{
|
122
|
+
"User '#{user_id}' is whitelisted into variation '#{whitelisted_variation_key}', which is not in the datafile."
|
113
123
|
)
|
114
124
|
return nil
|
115
125
|
end
|
116
126
|
|
117
127
|
@config.logger.log(
|
118
128
|
Logger::INFO,
|
119
|
-
"User '#{user_id}' is whitelisted into variation '#{
|
129
|
+
"User '#{user_id}' is whitelisted into variation '#{whitelisted_variation_key}' of experiment '#{experiment_key}'."
|
120
130
|
)
|
121
|
-
|
131
|
+
whitelisted_variation_id
|
122
132
|
end
|
123
133
|
|
124
134
|
def get_saved_variation_id(experiment_id, user_profile)
|
@@ -147,7 +157,7 @@ module Optimizely
|
|
147
157
|
#
|
148
158
|
# user_id - String ID for the user
|
149
159
|
#
|
150
|
-
# Returns Hash stored user profile (or a default one if lookup fails or user profile service not provided)
|
160
|
+
# Returns Hash stored user profile (or a default one if lookup fails or user profile service not provided)
|
151
161
|
|
152
162
|
user_profile = {
|
153
163
|
:user_id => user_id,
|
@@ -17,6 +17,7 @@ require_relative './audience'
|
|
17
17
|
require_relative './params'
|
18
18
|
require_relative './version'
|
19
19
|
require_relative '../optimizely/helpers/event_tag_utils'
|
20
|
+
require 'securerandom'
|
20
21
|
|
21
22
|
module Optimizely
|
22
23
|
class Event
|
@@ -41,197 +42,198 @@ module Optimizely
|
|
41
42
|
end
|
42
43
|
|
43
44
|
class BaseEventBuilder
|
45
|
+
CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom'
|
46
|
+
|
44
47
|
attr_reader :config
|
45
|
-
attr_reader :params
|
46
48
|
|
47
49
|
def initialize(config)
|
48
50
|
@config = config
|
49
|
-
@params = {}
|
50
51
|
end
|
51
52
|
|
52
53
|
private
|
53
54
|
|
54
|
-
def
|
55
|
-
#
|
56
|
-
#
|
57
|
-
# user_id - ID for user
|
58
|
-
# attributes - Hash representing user attributes and values which need to be recorded.
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
55
|
+
def get_common_params(user_id, attributes)
|
56
|
+
# Get params which are used in both conversion and impression events.
|
57
|
+
#
|
58
|
+
# user_id - +String+ ID for user
|
59
|
+
# attributes - +Hash+ representing user attributes and values which need to be recorded.
|
60
|
+
#
|
61
|
+
# Returns +Hash+ Common event params
|
62
|
+
|
63
|
+
visitor_attributes = []
|
64
|
+
|
65
|
+
unless attributes.nil?
|
66
|
+
|
67
|
+
attributes.keys.each do |attribute_key|
|
68
|
+
# Omit null attribute value
|
69
|
+
attribute_value = attributes[attribute_key]
|
70
|
+
next if attribute_value.nil?
|
71
|
+
|
72
|
+
# Skip attributes not in the datafile
|
73
|
+
attribute_id = @config.get_attribute_id(attribute_key)
|
74
|
+
next unless attribute_id
|
75
|
+
|
76
|
+
feature = {
|
77
|
+
entity_id: attribute_id,
|
78
|
+
key: attribute_key,
|
79
|
+
type: CUSTOM_ATTRIBUTE_FEATURE_TYPE,
|
80
|
+
value: attribute_value
|
81
|
+
}
|
82
|
+
|
83
|
+
visitor_attributes.push(feature)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
common_params = {
|
88
|
+
account_id: @config.account_id,
|
89
|
+
project_id: @config.project_id,
|
90
|
+
visitors: [
|
91
|
+
{
|
92
|
+
attributes: visitor_attributes,
|
93
|
+
snapshots: [],
|
94
|
+
visitor_id: user_id
|
95
|
+
}
|
96
|
+
],
|
97
|
+
revision: @config.revision,
|
98
|
+
client_name: CLIENT_ENGINE,
|
99
|
+
client_version: VERSION
|
100
|
+
}
|
101
|
+
|
102
|
+
common_params
|
66
103
|
end
|
67
104
|
end
|
68
105
|
|
69
|
-
class
|
70
|
-
|
71
|
-
IMPRESSION_EVENT_ENDPOINT = 'https://logx.optimizely.com/log/decision'
|
106
|
+
class EventBuilder < BaseEventBuilder
|
107
|
+
ENDPOINT = 'https://logx.optimizely.com/v1/events'
|
72
108
|
POST_HEADERS = { 'Content-Type' => 'application/json' }
|
109
|
+
ACTIVATE_EVENT_KEY = 'campaign_activated'
|
73
110
|
|
74
|
-
def create_impression_event(
|
75
|
-
# Create
|
111
|
+
def create_impression_event(experiment, variation_id, user_id, attributes)
|
112
|
+
# Create impression Event to be sent to the logging endpoint.
|
76
113
|
#
|
77
|
-
#
|
78
|
-
# variation_id - ID for variation which would be presented to user.
|
79
|
-
# user_id - ID for user.
|
80
|
-
# attributes -
|
114
|
+
# experiment - +Object+ Experiment for which impression needs to be recorded.
|
115
|
+
# variation_id - +String+ ID for variation which would be presented to user.
|
116
|
+
# user_id - +String+ ID for user.
|
117
|
+
# attributes - +Hash+ representing user attributes and values which need to be recorded.
|
81
118
|
#
|
82
|
-
# Returns
|
119
|
+
# Returns +Event+ encapsulating the impression event.
|
83
120
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
Event.new(:post,
|
121
|
+
event_params = get_common_params(user_id, attributes)
|
122
|
+
impression_params = get_impression_params(experiment, variation_id)
|
123
|
+
event_params[:visitors][0][:snapshots].push(impression_params)
|
124
|
+
|
125
|
+
Event.new(:post, ENDPOINT, event_params, POST_HEADERS)
|
89
126
|
end
|
90
127
|
|
91
128
|
def create_conversion_event(event_key, user_id, attributes, event_tags, experiment_variation_map)
|
92
129
|
# Create conversion Event to be sent to the logging endpoint.
|
93
130
|
#
|
94
|
-
# event_key - Event key representing the event which needs to be recorded.
|
95
|
-
# user_id - ID for user.
|
96
|
-
# attributes -
|
97
|
-
# event_tags -
|
98
|
-
# experiment_variation_map - Map of experiment ID to the ID of the variation that the user is bucketed into.
|
131
|
+
# event_key - +String+ Event key representing the event which needs to be recorded.
|
132
|
+
# user_id - +String+ ID for user.
|
133
|
+
# attributes - +Hash+ representing user attributes and values which need to be recorded.
|
134
|
+
# event_tags - +Hash+ representing metadata associated with the event.
|
135
|
+
# experiment_variation_map - +Map+ of experiment ID to the ID of the variation that the user is bucketed into.
|
99
136
|
#
|
100
|
-
# Returns
|
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
|
137
|
+
# Returns +Event+ encapsulating the conversion event.
|
111
138
|
|
112
|
-
|
113
|
-
|
114
|
-
|
139
|
+
event_params = get_common_params(user_id, attributes)
|
140
|
+
conversion_params = get_conversion_params(event_key, event_tags, experiment_variation_map)
|
141
|
+
event_params[:visitors][0][:snapshots] = conversion_params;
|
142
|
+
|
143
|
+
Event.new(:post, ENDPOINT, event_params, POST_HEADERS)
|
115
144
|
end
|
116
145
|
|
117
|
-
|
118
|
-
@params['projectId'] = @config.project_id
|
119
|
-
end
|
146
|
+
private
|
120
147
|
|
121
|
-
def
|
122
|
-
|
123
|
-
|
148
|
+
def get_impression_params(experiment, variation_id)
|
149
|
+
# Creates object of params specific to impression events
|
150
|
+
#
|
151
|
+
# experiment - +Hash+ experiment for which impression needs to be recorded
|
152
|
+
# variation_id - +string+ ID for variation which would be presented to user
|
153
|
+
#
|
154
|
+
# Returns +Hash+ Impression event params
|
155
|
+
|
156
|
+
experiment_key = experiment['key']
|
157
|
+
experiment_id = experiment['id']
|
158
|
+
|
159
|
+
impressionEventParams = {
|
160
|
+
decisions: [{
|
161
|
+
campaign_id: @config.experiment_key_map[experiment_key]['layerId'],
|
162
|
+
experiment_id: experiment_id,
|
163
|
+
variation_id: variation_id,
|
164
|
+
}],
|
165
|
+
events: [{
|
166
|
+
entity_id: @config.experiment_key_map[experiment_key]['layerId'],
|
167
|
+
timestamp: get_timestamp(),
|
168
|
+
key: ACTIVATE_EVENT_KEY,
|
169
|
+
uuid: get_uuid()
|
170
|
+
}]
|
171
|
+
}
|
124
172
|
|
125
|
-
|
126
|
-
@params['visitorId'] = user_id
|
173
|
+
impressionEventParams;
|
127
174
|
end
|
128
175
|
|
129
|
-
def
|
130
|
-
|
131
|
-
|
132
|
-
|
176
|
+
def get_conversion_params(event_key, event_tags, experiment_variation_map)
|
177
|
+
# Creates object of params specific to conversion events
|
178
|
+
#
|
179
|
+
# event_key - +String+ Key representing the event which needs to be recorded
|
180
|
+
# event_tags - +Hash+ Values associated with the event.
|
181
|
+
# experiment_variation_map - +Hash+ Map of experiment IDs to bucketed variation IDs
|
182
|
+
#
|
183
|
+
# Returns +Hash+ Impression event params
|
133
184
|
|
134
|
-
|
135
|
-
# Omit falsy attribute values
|
136
|
-
attribute_value = attributes[attribute_key]
|
137
|
-
next unless attribute_value
|
185
|
+
conversionEventParams = []
|
138
186
|
|
139
|
-
|
140
|
-
attribute_id = @config.get_attribute_id(attribute_key)
|
141
|
-
next unless attribute_id
|
187
|
+
experiment_variation_map.each do |experiment_id, variation_id|
|
142
188
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
189
|
+
single_snapshot = {
|
190
|
+
decisions: [{
|
191
|
+
campaign_id: @config.experiment_id_map[experiment_id]['layerId'],
|
192
|
+
experiment_id: experiment_id,
|
193
|
+
variation_id: variation_id,
|
194
|
+
}],
|
195
|
+
events: [],
|
149
196
|
}
|
150
|
-
@params['userFeatures'].push(feature)
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
def add_decision(experiment_key, variation_id)
|
155
|
-
experiment_id = @config.get_experiment_id(experiment_key)
|
156
|
-
@params['layerId'] = @config.experiment_key_map[experiment_key]['layerId']
|
157
|
-
@params['decision'] = {
|
158
|
-
'variationId' => variation_id,
|
159
|
-
'experimentId' => experiment_id,
|
160
|
-
'isLayerHoldback' => false,
|
161
|
-
}
|
162
|
-
end
|
163
|
-
|
164
|
-
def add_event_tags(event_tags)
|
165
|
-
@params['eventFeatures'] ||= []
|
166
|
-
@params['eventMetrics'] ||= []
|
167
197
|
|
168
|
-
|
198
|
+
event_object = {
|
199
|
+
entity_id: @config.event_key_map[event_key]['id'],
|
200
|
+
timestamp: get_timestamp(),
|
201
|
+
uuid: get_uuid(),
|
202
|
+
key: event_key,
|
203
|
+
}
|
169
204
|
|
170
|
-
|
171
|
-
|
205
|
+
if event_tags
|
206
|
+
revenue_value = Helpers::EventTagUtils.get_revenue_value(event_tags)
|
207
|
+
if revenue_value
|
208
|
+
event_object[:revenue] = revenue_value
|
209
|
+
end
|
172
210
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
'shouldIndex' => false,
|
178
|
-
}
|
179
|
-
@params['eventFeatures'].push(event_feature)
|
211
|
+
numeric_value = Helpers::EventTagUtils.get_numeric_value(event_tags)
|
212
|
+
if numeric_value
|
213
|
+
event_object[:value] = numeric_value
|
214
|
+
end
|
180
215
|
|
181
|
-
|
216
|
+
event_object[:tags] = event_tags
|
217
|
+
end
|
182
218
|
|
183
|
-
|
219
|
+
single_snapshot[:events] = [event_object]
|
184
220
|
|
185
|
-
|
186
|
-
event_metric = {
|
187
|
-
'name' => 'revenue',
|
188
|
-
'value' => event_value
|
189
|
-
}
|
190
|
-
@params['eventMetrics'].push(event_metric)
|
221
|
+
conversionEventParams.push(single_snapshot)
|
191
222
|
end
|
192
|
-
|
223
|
+
|
224
|
+
conversionEventParams
|
193
225
|
end
|
194
226
|
|
195
|
-
def
|
196
|
-
#
|
197
|
-
#
|
198
|
-
# event_key - Event key representing the event which needs to be recorded.
|
199
|
-
|
200
|
-
event_id = @config.event_key_map[event_key]['id']
|
201
|
-
event_name = @config.event_key_map[event_key]['key']
|
227
|
+
def get_timestamp
|
228
|
+
# Returns +Integer+ Current timestamp
|
202
229
|
|
203
|
-
|
204
|
-
@params['eventName'] = event_name
|
230
|
+
(Time.now.to_f * 1000).to_i
|
205
231
|
end
|
206
232
|
|
207
|
-
def
|
208
|
-
#
|
209
|
-
#
|
210
|
-
# experiments_map - Hash with experiment ID as a key and variation ID as a value.
|
211
|
-
|
212
|
-
@params['layerStates'] = []
|
213
|
-
|
214
|
-
experiments_map.each do |experiment_id, variation_id|
|
215
|
-
layer_state = {
|
216
|
-
'layerId' => @config.experiment_id_map[experiment_id]['layerId'],
|
217
|
-
'decision' => {
|
218
|
-
'variationId' => variation_id,
|
219
|
-
'experimentId' => experiment_id,
|
220
|
-
'isLayerHoldback' => false,
|
221
|
-
},
|
222
|
-
'actionTriggered' => true,
|
223
|
-
}
|
224
|
-
@params['layerStates'].push(layer_state)
|
225
|
-
end
|
226
|
-
end
|
227
|
-
|
228
|
-
def add_source
|
229
|
-
@params['clientEngine'] = 'ruby-sdk'
|
230
|
-
@params['clientVersion'] = VERSION
|
231
|
-
end
|
233
|
+
def get_uuid
|
234
|
+
# Returns +String+ Random UUID
|
232
235
|
|
233
|
-
|
234
|
-
@params['timestamp'] = (Time.now.to_f * 1000).to_i
|
236
|
+
SecureRandom.uuid
|
235
237
|
end
|
236
238
|
end
|
237
239
|
end
|
@@ -21,23 +21,31 @@ module Optimizely
|
|
21
21
|
module EventTagUtils
|
22
22
|
module_function
|
23
23
|
|
24
|
+
REVENUE_EVENT_METRIC_NAME = 'revenue';
|
25
|
+
NUMERIC_EVENT_METRIC_NAME = 'value';
|
26
|
+
|
27
|
+
def isStringNumeric(str)
|
28
|
+
Float(str) != nil rescue false
|
29
|
+
end
|
30
|
+
|
24
31
|
def get_revenue_value(event_tags)
|
25
32
|
# Grab the revenue value from the event tags. "revenue" is a reserved keyword.
|
26
|
-
#
|
33
|
+
# The revenue value must be an integer.
|
34
|
+
#
|
27
35
|
# event_tags - Hash representing metadata associated with the event.
|
28
36
|
# Returns revenue value as an integer number
|
29
37
|
# Returns nil if revenue can't be retrieved from the event tags.
|
30
38
|
|
31
|
-
if event_tags.nil? or !Helpers::Validator.
|
39
|
+
if event_tags.nil? or !Helpers::Validator.event_tags_valid?(event_tags)
|
32
40
|
return nil
|
33
41
|
end
|
34
42
|
|
35
|
-
unless event_tags.has_key?(
|
43
|
+
unless event_tags.has_key?(REVENUE_EVENT_METRIC_NAME)
|
36
44
|
return nil
|
37
45
|
end
|
38
46
|
|
39
47
|
logger = SimpleLogger.new
|
40
|
-
raw_value = event_tags[
|
48
|
+
raw_value = event_tags[REVENUE_EVENT_METRIC_NAME]
|
41
49
|
|
42
50
|
unless raw_value.is_a? Numeric
|
43
51
|
logger.log(Logger::WARN, "Failed to parse revenue value #{raw_value} from event tags.")
|
@@ -52,6 +60,59 @@ module Optimizely
|
|
52
60
|
logger.log(Logger::INFO, "Parsed revenue value #{raw_value} from event tags.")
|
53
61
|
raw_value
|
54
62
|
end
|
63
|
+
|
64
|
+
def get_numeric_value(event_tags, logger = nil)
|
65
|
+
# Grab the numeric event value from the event tags. "value" is a reserved keyword.
|
66
|
+
# The value of 'value' can be a float or a numeric string
|
67
|
+
#
|
68
|
+
# event_tags - +Hash+ representing metadata associated with the event.
|
69
|
+
# Returns +Number+ | +nil+ if value can't be retrieved from the event tags.
|
70
|
+
logger = SimpleLogger.new if logger.nil?
|
71
|
+
|
72
|
+
if event_tags.nil?
|
73
|
+
logger.log(Logger::DEBUG,"Event tags is undefined.")
|
74
|
+
return nil
|
75
|
+
end
|
76
|
+
|
77
|
+
if !Helpers::Validator.event_tags_valid?(event_tags)
|
78
|
+
logger.log(Logger::DEBUG,"Event tags is not a dictionary.")
|
79
|
+
return nil
|
80
|
+
end
|
81
|
+
|
82
|
+
if !event_tags.has_key?(NUMERIC_EVENT_METRIC_NAME)
|
83
|
+
logger.log(Logger::DEBUG,"The numeric metric key is not defined in the event tags.")
|
84
|
+
return nil
|
85
|
+
end
|
86
|
+
|
87
|
+
if event_tags[NUMERIC_EVENT_METRIC_NAME].nil?
|
88
|
+
logger.log(Logger::DEBUG,"The numeric metric key is null.")
|
89
|
+
return nil
|
90
|
+
end
|
91
|
+
|
92
|
+
raw_value = event_tags[NUMERIC_EVENT_METRIC_NAME]
|
93
|
+
|
94
|
+
if raw_value === true or raw_value === false
|
95
|
+
logger.log(Logger::DEBUG,"Provided numeric value is a boolean, which is an invalid format.")
|
96
|
+
return nil
|
97
|
+
end
|
98
|
+
|
99
|
+
if raw_value.is_a? Array or raw_value.is_a? Hash or raw_value.to_f.nan? or raw_value.to_f.infinite?
|
100
|
+
logger.log(Logger::DEBUG,"Provided numeric value is in an invalid format.")
|
101
|
+
return nil
|
102
|
+
end
|
103
|
+
|
104
|
+
if !Helpers::Validator.string_numeric?(raw_value)
|
105
|
+
logger.log(Logger::DEBUG,"Provided numeric value is not a numeric string.")
|
106
|
+
return nil
|
107
|
+
end
|
108
|
+
|
109
|
+
raw_value = raw_value.to_f
|
110
|
+
|
111
|
+
logger.log(Logger::INFO,"The numeric metric value #{raw_value} will be sent to results.")
|
112
|
+
|
113
|
+
raw_value
|
114
|
+
end
|
115
|
+
|
55
116
|
end
|
56
117
|
end
|
57
|
-
end
|
118
|
+
end
|
@@ -25,10 +25,6 @@ module Optimizely
|
|
25
25
|
|
26
26
|
class ProjectConfig
|
27
27
|
# Representation of the Optimizely project config.
|
28
|
-
|
29
|
-
PROJECT_CONFIG_LINK_TEMPLATE = 'https://cdn.optimizely.com/json/%{project_id}.json'
|
30
|
-
REVENUE_GOAL_KEY = 'Total Revenue'
|
31
|
-
REQUEST_TIMEOUT = 10
|
32
28
|
RUNNING_EXPERIMENT_STATUS = ['Running']
|
33
29
|
|
34
30
|
# Gets project config attributes.
|
@@ -56,6 +52,12 @@ module Optimizely
|
|
56
52
|
attr_reader :variation_id_map
|
57
53
|
attr_reader :variation_key_map
|
58
54
|
|
55
|
+
# Hash of user IDs to a Hash
|
56
|
+
# of experiments to variations. This contains all the forced variations
|
57
|
+
# set by the user by calling setForcedVariation (it is not the same as the
|
58
|
+
# whitelisting forcedVariations data structure in the Experiments class).
|
59
|
+
attr_reader :forced_variation_map
|
60
|
+
|
59
61
|
def initialize(datafile, logger, error_handler)
|
60
62
|
# ProjectConfig init method to fetch and set project config data
|
61
63
|
#
|
@@ -96,6 +98,9 @@ module Optimizely
|
|
96
98
|
@audience_id_map = generate_key_map(@audiences, 'id')
|
97
99
|
@variation_id_map = {}
|
98
100
|
@variation_key_map = {}
|
101
|
+
@forced_variation_map = {}
|
102
|
+
@variation_id_to_variable_usage_map = {}
|
103
|
+
@variation_id_to_experiment_map = {}
|
99
104
|
@experiment_key_map.each do |key, exp|
|
100
105
|
variations = exp.fetch('variations')
|
101
106
|
@variation_id_map[key] = generate_key_map(variations, 'id')
|
@@ -104,28 +109,24 @@ module Optimizely
|
|
104
109
|
@parsing_succeeded = true
|
105
110
|
end
|
106
111
|
|
107
|
-
def experiment_running?(
|
112
|
+
def experiment_running?(experiment)
|
108
113
|
# Determine if experiment corresponding to given key is running
|
109
114
|
#
|
110
|
-
#
|
115
|
+
# experiment - Experiment
|
111
116
|
#
|
112
117
|
# Returns true if experiment is running
|
113
|
-
experiment
|
114
|
-
return RUNNING_EXPERIMENT_STATUS.include?(experiment['status']) if experiment
|
115
|
-
@logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
|
116
|
-
@error_handler.handle_error InvalidExperimentError
|
117
|
-
nil
|
118
|
+
return RUNNING_EXPERIMENT_STATUS.include?(experiment['status'])
|
118
119
|
end
|
119
120
|
|
120
|
-
def
|
121
|
+
def get_experiment_from_key(experiment_key)
|
121
122
|
# Retrieves experiment ID for a given key
|
122
123
|
#
|
123
124
|
# experiment_key - String key representing the experiment
|
124
125
|
#
|
125
|
-
# Returns
|
126
|
+
# Returns Experiment
|
126
127
|
|
127
128
|
experiment = @experiment_key_map[experiment_key]
|
128
|
-
return experiment
|
129
|
+
return experiment if experiment
|
129
130
|
@logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
|
130
131
|
@error_handler.handle_error InvalidExperimentError
|
131
132
|
nil
|
@@ -159,34 +160,6 @@ module Optimizely
|
|
159
160
|
[]
|
160
161
|
end
|
161
162
|
|
162
|
-
def get_traffic_allocation(experiment_key)
|
163
|
-
# Retrieves traffic allocation for a given experiment Key
|
164
|
-
#
|
165
|
-
# experiment_key - String Key representing the experiment
|
166
|
-
#
|
167
|
-
# Returns traffic allocation for the experiment or nil
|
168
|
-
|
169
|
-
experiment = @experiment_key_map[experiment_key]
|
170
|
-
return experiment['trafficAllocation'] if experiment
|
171
|
-
@logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
|
172
|
-
@error_handler.handle_error InvalidExperimentError
|
173
|
-
nil
|
174
|
-
end
|
175
|
-
|
176
|
-
def get_audience_ids_for_experiment(experiment_key)
|
177
|
-
# Get audience IDs for the experiment
|
178
|
-
#
|
179
|
-
# experiment_key - Experiment key for which audience IDs are to be determined
|
180
|
-
#
|
181
|
-
# Returns audience IDs corresponding to the experiment.
|
182
|
-
|
183
|
-
experiment = @experiment_key_map[experiment_key]
|
184
|
-
return experiment['audienceIds'] if experiment
|
185
|
-
@logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
|
186
|
-
@error_handler.handle_error InvalidExperimentError
|
187
|
-
nil
|
188
|
-
end
|
189
|
-
|
190
163
|
def get_audience_conditions_from_id(audience_id)
|
191
164
|
# Get audience conditions for the provided audience ID
|
192
165
|
#
|
@@ -223,6 +196,28 @@ module Optimizely
|
|
223
196
|
nil
|
224
197
|
end
|
225
198
|
|
199
|
+
def get_variation_from_id(experiment_key, variation_id)
|
200
|
+
# Get variation given experiment key and variation ID
|
201
|
+
#
|
202
|
+
# experiment_key - Key representing parent experiment of variation
|
203
|
+
# variation_id - ID of the variation
|
204
|
+
#
|
205
|
+
# Returns the variation or nil if not found
|
206
|
+
|
207
|
+
variation_id_map = @variation_id_map[experiment_key]
|
208
|
+
if variation_id_map
|
209
|
+
variation = variation_id_map[variation_id]
|
210
|
+
return variation if variation
|
211
|
+
@logger.log Logger::ERROR, "Variation id '#{variation_id}' is not in datafile."
|
212
|
+
@error_handler.handle_error InvalidVariationError
|
213
|
+
return nil
|
214
|
+
end
|
215
|
+
|
216
|
+
@logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
|
217
|
+
@error_handler.handle_error InvalidExperimentError
|
218
|
+
nil
|
219
|
+
end
|
220
|
+
|
226
221
|
def get_variation_id_from_key(experiment_key, variation_key)
|
227
222
|
# Get variation ID given experiment key and variation key
|
228
223
|
#
|
@@ -245,12 +240,12 @@ module Optimizely
|
|
245
240
|
nil
|
246
241
|
end
|
247
242
|
|
248
|
-
def
|
249
|
-
# Retrieves
|
243
|
+
def get_whitelisted_variations(experiment_key)
|
244
|
+
# Retrieves whitelisted variations for a given experiment Key
|
250
245
|
#
|
251
246
|
# experiment_key - String Key representing the experiment
|
252
247
|
#
|
253
|
-
# Returns
|
248
|
+
# Returns whitelisted variations for the experiment or nil
|
254
249
|
|
255
250
|
experiment = @experiment_key_map[experiment_key]
|
256
251
|
return experiment['forcedVariations'] if experiment
|
@@ -258,11 +253,98 @@ module Optimizely
|
|
258
253
|
@error_handler.handle_error InvalidExperimentError
|
259
254
|
end
|
260
255
|
|
261
|
-
def
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
256
|
+
def get_forced_variation(experiment_key, user_id)
|
257
|
+
# Gets the forced variation for the given user and experiment.
|
258
|
+
#
|
259
|
+
# experiment_key - String Key for experiment.
|
260
|
+
# user_id - String ID for user
|
261
|
+
#
|
262
|
+
# Returns Variation The variation which the given user and experiment should be forced into.
|
263
|
+
|
264
|
+
# check for nil and empty string user ID
|
265
|
+
if user_id.nil? or user_id.empty?
|
266
|
+
@logger.log(Logger::DEBUG, "User ID is invalid")
|
267
|
+
return nil
|
268
|
+
end
|
269
|
+
|
270
|
+
unless @forced_variation_map.has_key? (user_id)
|
271
|
+
@logger.log(Logger::DEBUG, "User '#{user_id}' is not in the forced variation map.")
|
272
|
+
return nil
|
273
|
+
end
|
274
|
+
|
275
|
+
experimentToVariationMap = @forced_variation_map[user_id]
|
276
|
+
experiment = get_experiment_from_key(experiment_key)
|
277
|
+
experiment_id = experiment["id"] if experiment
|
278
|
+
# check for nil and empty string experiment ID
|
279
|
+
if experiment_id.nil? or experiment_id.empty?
|
280
|
+
# this case is logged in get_experiment_from_key
|
281
|
+
return nil
|
282
|
+
end
|
283
|
+
|
284
|
+
unless experimentToVariationMap.has_key? (experiment_id)
|
285
|
+
@logger.log(Logger::DEBUG, "No experiment '#{experiment_key}' mapped to user '#{user_id}' in the forced variation map.")
|
286
|
+
return nil
|
287
|
+
end
|
288
|
+
|
289
|
+
variation_id = experimentToVariationMap[experiment_id]
|
290
|
+
variation_key = ""
|
291
|
+
variation = get_variation_from_id(experiment_key,variation_id)
|
292
|
+
variation_key = variation["key"] if variation
|
293
|
+
|
294
|
+
# check if the variation exists in the datafile
|
295
|
+
if variation_key.empty?
|
296
|
+
# this case is logged in get_variation_from_id
|
297
|
+
return nil
|
298
|
+
end
|
299
|
+
|
300
|
+
@logger.log(Logger::DEBUG, "Variation '#{variation_key}' is mapped to experiment '#{experiment_key}' and user '#{user_id}' in the forced variation map")
|
301
|
+
|
302
|
+
variation
|
303
|
+
end
|
304
|
+
|
305
|
+
def set_forced_variation(experiment_key, user_id, variation_key)
|
306
|
+
# Sets a Hash of user IDs to a Hash of experiments to forced variations.
|
307
|
+
#
|
308
|
+
# experiment_key - String Key for experiment.
|
309
|
+
# user_id - String ID for user.
|
310
|
+
# variation_key - String Key for variation. If null, then clear the existing experiment-to-variation mapping.
|
311
|
+
#
|
312
|
+
# Returns a boolean value that indicates if the set completed successfully.
|
313
|
+
|
314
|
+
# check for null and empty string user ID
|
315
|
+
if user_id.nil? or user_id.empty?
|
316
|
+
@logger.log(Logger::DEBUG, "User ID is invalid")
|
317
|
+
return false
|
318
|
+
end
|
319
|
+
|
320
|
+
experiment = get_experiment_from_key(experiment_key)
|
321
|
+
experiment_id = experiment["id"] if experiment
|
322
|
+
# check if the experiment exists in the datafile
|
323
|
+
if experiment_id.nil? or experiment_id.empty?
|
324
|
+
return false
|
325
|
+
end
|
326
|
+
|
327
|
+
# clear the forced variation if the variation key is null
|
328
|
+
if variation_key.nil? or variation_key.empty?
|
329
|
+
@forced_variation_map[user_id].delete(experiment_id) if @forced_variation_map.has_key? (user_id)
|
330
|
+
@logger.log(Logger::DEBUG, "Variation mapped to experiment '#{experiment_key}' has been removed for user '#{user_id}'.")
|
331
|
+
return true
|
332
|
+
end
|
333
|
+
|
334
|
+
variation_id = get_variation_id_from_key(experiment_key, variation_key)
|
335
|
+
|
336
|
+
# check if the variation exists in the datafile
|
337
|
+
unless variation_id
|
338
|
+
# this case is logged in get_variation_id_from_key
|
339
|
+
return false
|
340
|
+
end
|
341
|
+
|
342
|
+
unless @forced_variation_map.has_key? user_id
|
343
|
+
@forced_variation_map[user_id] = {}
|
344
|
+
end
|
345
|
+
@forced_variation_map[user_id][experiment_id] = variation_id
|
346
|
+
@logger.log(Logger::DEBUG, "Set variation '#{variation_id}' for experiment '#{experiment_id}' and user '#{user_id}' in the forced variation map.")
|
347
|
+
return true
|
266
348
|
end
|
267
349
|
|
268
350
|
def get_attribute_id(attribute_key)
|
@@ -273,19 +355,6 @@ module Optimizely
|
|
273
355
|
nil
|
274
356
|
end
|
275
357
|
|
276
|
-
def user_in_forced_variation?(experiment_key, user_id)
|
277
|
-
# Determines if a given user is in a forced variation
|
278
|
-
#
|
279
|
-
# experiment_key - String experiment key
|
280
|
-
# user_id - String user ID
|
281
|
-
#
|
282
|
-
# Returns true if user is in a forced variation
|
283
|
-
|
284
|
-
forced_variations = get_forced_variations(experiment_key)
|
285
|
-
return forced_variations.include?(user_id) if forced_variations
|
286
|
-
false
|
287
|
-
end
|
288
|
-
|
289
358
|
def parsing_succeeded?
|
290
359
|
# Helper method to determine if parsing the datafile was successful.
|
291
360
|
#
|
@@ -309,11 +378,8 @@ module Optimizely
|
|
309
378
|
return true if variation
|
310
379
|
@logger.log Logger::ERROR, "Variation ID '#{variation_id}' is not in datafile."
|
311
380
|
@error_handler.handle_error InvalidVariationError
|
312
|
-
return false
|
313
381
|
end
|
314
382
|
|
315
|
-
@logger.log Logger::ERROR, "Experiment ID '#{experiment_id}' is not in datafile."
|
316
|
-
@error_handler.handle_error InvalidExperimentError
|
317
383
|
false
|
318
384
|
end
|
319
385
|
|
data/lib/optimizely/version.rb
CHANGED
metadata
CHANGED
@@ -1,16 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: optimizely-sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
- Andrew Delikat
|
8
|
-
- Haley Bash
|
9
7
|
- Optimizely
|
10
8
|
autorequire:
|
11
9
|
bindir: bin
|
12
10
|
cert_chain: []
|
13
|
-
date: 2017-
|
11
|
+
date: 2017-10-04 00:00:00.000000000 Z
|
14
12
|
dependencies:
|
15
13
|
- !ruby/object:Gem::Dependency
|
16
14
|
name: bundler
|
@@ -155,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
155
153
|
version: '0'
|
156
154
|
requirements: []
|
157
155
|
rubyforge_project:
|
158
|
-
rubygems_version: 2.6.
|
156
|
+
rubygems_version: 2.6.13
|
159
157
|
signing_key:
|
160
158
|
specification_version: 4
|
161
159
|
summary: Ruby SDK for Optimizely's testing framework
|