optimizely-sdk 1.3.0 → 1.4.0
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 +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
|