optimizely-sdk 3.1.1 → 3.2.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 +169 -94
- data/lib/optimizely/audience.rb +7 -7
- data/lib/optimizely/bucketer.rb +16 -17
- data/lib/optimizely/config/datafile_project_config.rb +411 -0
- data/lib/optimizely/config_manager/async_scheduler.rb +91 -0
- data/lib/optimizely/config_manager/http_project_config_manager.rb +282 -0
- data/lib/optimizely/config_manager/project_config_manager.rb +24 -0
- data/lib/optimizely/config_manager/static_project_config_manager.rb +43 -0
- data/lib/optimizely/decision_service.rb +142 -52
- data/lib/optimizely/event_builder.rb +21 -28
- data/lib/optimizely/exceptions.rb +17 -1
- data/lib/optimizely/helpers/constants.rb +19 -0
- data/lib/optimizely/notification_center.rb +1 -0
- data/lib/optimizely/optimizely_factory.rb +73 -0
- data/lib/optimizely/project_config.rb +29 -434
- data/lib/optimizely/version.rb +1 -1
- metadata +12 -6
@@ -48,26 +48,16 @@ module Optimizely
|
|
48
48
|
class BaseEventBuilder
|
49
49
|
CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom'
|
50
50
|
|
51
|
-
|
52
|
-
attr_reader :logger
|
53
|
-
|
54
|
-
def initialize(config, logger)
|
55
|
-
@config = config
|
51
|
+
def initialize(logger)
|
56
52
|
@logger = logger
|
57
53
|
end
|
58
54
|
|
59
55
|
private
|
60
56
|
|
61
|
-
def
|
62
|
-
# Get bot filtering bool
|
63
|
-
#
|
64
|
-
# Returns 'botFiltering' value in the datafile.
|
65
|
-
@config.bot_filtering
|
66
|
-
end
|
67
|
-
|
68
|
-
def get_common_params(user_id, attributes)
|
57
|
+
def get_common_params(project_config, user_id, attributes)
|
69
58
|
# Get params which are used in both conversion and impression events.
|
70
59
|
#
|
60
|
+
# project_config - +Object+ Instance of ProjectConfig
|
71
61
|
# user_id - +String+ ID for user
|
72
62
|
# attributes - +Hash+ representing user attributes and values which need to be recorded.
|
73
63
|
#
|
@@ -79,7 +69,7 @@ module Optimizely
|
|
79
69
|
# Omit attribute values that are not supported by the log endpoint.
|
80
70
|
attribute_value = attributes[attribute_key]
|
81
71
|
if Helpers::Validator.attribute_valid?(attribute_key, attribute_value)
|
82
|
-
attribute_id =
|
72
|
+
attribute_id = project_config.get_attribute_id attribute_key
|
83
73
|
if attribute_id
|
84
74
|
visitor_attributes.push(
|
85
75
|
entity_id: attribute_id,
|
@@ -91,18 +81,18 @@ module Optimizely
|
|
91
81
|
end
|
92
82
|
end
|
93
83
|
# Append Bot Filtering Attribute
|
94
|
-
if bot_filtering == true || bot_filtering == false
|
84
|
+
if project_config.bot_filtering == true || project_config.bot_filtering == false
|
95
85
|
visitor_attributes.push(
|
96
86
|
entity_id: Optimizely::Helpers::Constants::CONTROL_ATTRIBUTES['BOT_FILTERING'],
|
97
87
|
key: Optimizely::Helpers::Constants::CONTROL_ATTRIBUTES['BOT_FILTERING'],
|
98
88
|
type: CUSTOM_ATTRIBUTE_FEATURE_TYPE,
|
99
|
-
value: bot_filtering
|
89
|
+
value: project_config.bot_filtering
|
100
90
|
)
|
101
91
|
end
|
102
92
|
|
103
93
|
common_params = {
|
104
|
-
account_id:
|
105
|
-
project_id:
|
94
|
+
account_id: project_config.account_id,
|
95
|
+
project_id: project_config.project_id,
|
106
96
|
visitors: [
|
107
97
|
{
|
108
98
|
attributes: visitor_attributes,
|
@@ -110,8 +100,8 @@ module Optimizely
|
|
110
100
|
visitor_id: user_id
|
111
101
|
}
|
112
102
|
],
|
113
|
-
anonymize_ip:
|
114
|
-
revision:
|
103
|
+
anonymize_ip: project_config.anonymize_ip,
|
104
|
+
revision: project_config.revision,
|
115
105
|
client_name: CLIENT_ENGINE,
|
116
106
|
enrich_decisions: true,
|
117
107
|
client_version: VERSION
|
@@ -126,9 +116,10 @@ module Optimizely
|
|
126
116
|
POST_HEADERS = {'Content-Type' => 'application/json'}.freeze
|
127
117
|
ACTIVATE_EVENT_KEY = 'campaign_activated'
|
128
118
|
|
129
|
-
def create_impression_event(experiment, variation_id, user_id, attributes)
|
119
|
+
def create_impression_event(project_config, experiment, variation_id, user_id, attributes)
|
130
120
|
# Create impression Event to be sent to the logging endpoint.
|
131
121
|
#
|
122
|
+
# project_config - +Object+ Instance of ProjectConfig
|
132
123
|
# experiment - +Object+ Experiment for which impression needs to be recorded.
|
133
124
|
# variation_id - +String+ ID for variation which would be presented to user.
|
134
125
|
# user_id - +String+ ID for user.
|
@@ -136,16 +127,17 @@ module Optimizely
|
|
136
127
|
#
|
137
128
|
# Returns +Event+ encapsulating the impression event.
|
138
129
|
|
139
|
-
event_params = get_common_params(user_id, attributes)
|
140
|
-
impression_params = get_impression_params(experiment, variation_id)
|
130
|
+
event_params = get_common_params(project_config, user_id, attributes)
|
131
|
+
impression_params = get_impression_params(project_config, experiment, variation_id)
|
141
132
|
event_params[:visitors][0][:snapshots].push(impression_params)
|
142
133
|
|
143
134
|
Event.new(:post, ENDPOINT, event_params, POST_HEADERS)
|
144
135
|
end
|
145
136
|
|
146
|
-
def create_conversion_event(event, user_id, attributes, event_tags)
|
137
|
+
def create_conversion_event(project_config, event, user_id, attributes, event_tags)
|
147
138
|
# Create conversion Event to be sent to the logging endpoint.
|
148
139
|
#
|
140
|
+
# project_config - +Object+ Instance of ProjectConfig
|
149
141
|
# event - +Object+ Event which needs to be recorded.
|
150
142
|
# user_id - +String+ ID for user.
|
151
143
|
# attributes - +Hash+ representing user attributes and values which need to be recorded.
|
@@ -153,7 +145,7 @@ module Optimizely
|
|
153
145
|
#
|
154
146
|
# Returns +Event+ encapsulating the conversion event.
|
155
147
|
|
156
|
-
event_params = get_common_params(user_id, attributes)
|
148
|
+
event_params = get_common_params(project_config, user_id, attributes)
|
157
149
|
conversion_params = get_conversion_params(event, event_tags)
|
158
150
|
event_params[:visitors][0][:snapshots] = [conversion_params]
|
159
151
|
|
@@ -162,9 +154,10 @@ module Optimizely
|
|
162
154
|
|
163
155
|
private
|
164
156
|
|
165
|
-
def get_impression_params(experiment, variation_id)
|
157
|
+
def get_impression_params(project_config, experiment, variation_id)
|
166
158
|
# Creates object of params specific to impression events
|
167
159
|
#
|
160
|
+
# project_config - +Object+ Instance of ProjectConfig
|
168
161
|
# experiment - +Hash+ experiment for which impression needs to be recorded
|
169
162
|
# variation_id - +string+ ID for variation which would be presented to user
|
170
163
|
#
|
@@ -175,12 +168,12 @@ module Optimizely
|
|
175
168
|
|
176
169
|
impression_event_params = {
|
177
170
|
decisions: [{
|
178
|
-
campaign_id:
|
171
|
+
campaign_id: project_config.experiment_key_map[experiment_key]['layerId'],
|
179
172
|
experiment_id: experiment_id,
|
180
173
|
variation_id: variation_id
|
181
174
|
}],
|
182
175
|
events: [{
|
183
|
-
entity_id:
|
176
|
+
entity_id: project_config.experiment_key_map[experiment_key]['layerId'],
|
184
177
|
timestamp: create_timestamp,
|
185
178
|
key: ACTIVATE_EVENT_KEY,
|
186
179
|
uuid: create_uuid
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2016-
|
4
|
+
# Copyright 2016-2019, 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.
|
@@ -105,4 +105,20 @@ module Optimizely
|
|
105
105
|
super
|
106
106
|
end
|
107
107
|
end
|
108
|
+
|
109
|
+
class InvalidInputsError < Error
|
110
|
+
# Raised when an invalid inputs are provided during Project instantiation
|
111
|
+
|
112
|
+
def initialize(msg)
|
113
|
+
super msg
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class InvalidProjectConfigError < Error
|
118
|
+
# Raised when a public method fails due to an invalid datafile
|
119
|
+
|
120
|
+
def initialize(aborted_method)
|
121
|
+
super("Optimizely instance is not valid. Failing '#{aborted_method}'.")
|
122
|
+
end
|
123
|
+
end
|
108
124
|
end
|
@@ -359,6 +359,25 @@ module Optimizely
|
|
359
359
|
'FEATURE_TEST' => 'feature-test',
|
360
360
|
'FEATURE_VARIABLE' => 'feature-variable'
|
361
361
|
}.freeze
|
362
|
+
|
363
|
+
CONFIG_MANAGER = {
|
364
|
+
'DATAFILE_URL_TEMPLATE' => 'https://cdn.optimizely.com/datafiles/%s.json',
|
365
|
+
# Default time in seconds to block the get_config call until config has been initialized.
|
366
|
+
'DEFAULT_BLOCKING_TIMEOUT' => 15,
|
367
|
+
# Default config update interval of 5 minutes
|
368
|
+
'DEFAULT_UPDATE_INTERVAL' => 5 * 60,
|
369
|
+
# Maximum update interval or blocking timeout: 30 days
|
370
|
+
'MAX_SECONDS_LIMIT' => 2_592_000,
|
371
|
+
# Minimum update interval or blocking timeout: 1 second
|
372
|
+
'MIN_SECONDS_LIMIT' => 1,
|
373
|
+
# Time in seconds before which request for datafile times out
|
374
|
+
'REQUEST_TIMEOUT' => 10
|
375
|
+
}.freeze
|
376
|
+
|
377
|
+
HTTP_HEADERS = {
|
378
|
+
'IF_MODIFIED_SINCE' => 'If-Modified-Since',
|
379
|
+
'LAST_MODIFIED' => 'Last-Modified'
|
380
|
+
}.freeze
|
362
381
|
end
|
363
382
|
end
|
364
383
|
end
|
@@ -24,6 +24,7 @@ module Optimizely
|
|
24
24
|
# DEPRECATED: ACTIVATE notification type is deprecated since relase 3.1.0.
|
25
25
|
ACTIVATE: 'ACTIVATE: experiment, user_id, attributes, variation, event',
|
26
26
|
DECISION: 'DECISION: type, user_id, attributes, decision_info',
|
27
|
+
OPTIMIZELY_CONFIG_UPDATE: 'optimizely_config_update',
|
27
28
|
TRACK: 'TRACK: event_key, user_id, attributes, event_tags, event'
|
28
29
|
}.freeze
|
29
30
|
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright 2019, Optimizely and contributors
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'optimizely'
|
20
|
+
module Optimizely
|
21
|
+
class OptimizelyFactory
|
22
|
+
# Returns a new optimizely instance.
|
23
|
+
#
|
24
|
+
# @params sdk_key - Required String uniquely identifying the fallback datafile corresponding to project.
|
25
|
+
# @param fallback datafile - Optional JSON string datafile.
|
26
|
+
def self.default_instance(sdk_key, datafile = nil)
|
27
|
+
Optimizely::Project.new(datafile, nil, nil, nil, nil, nil, sdk_key)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns a new optimizely instance.
|
31
|
+
#
|
32
|
+
# @param config_manager - Required ConfigManagerInterface Responds to get_config.
|
33
|
+
def self.default_instance_with_config_manager(config_manager)
|
34
|
+
Optimizely::Project.new(nil, nil, nil, nil, nil, nil, nil, config_manager)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns a new optimizely instance.
|
38
|
+
#
|
39
|
+
# @params sdk_key - Required String uniquely identifying the datafile corresponding to project.
|
40
|
+
# @param fallback datafile - Optional JSON string datafile.
|
41
|
+
# @param event_dispatcher - Optional EventDispatcherInterface Provides a dispatch_event method which if given a URL and params sends a request to it.
|
42
|
+
# @param logger - Optional LoggerInterface Provides a log method to log messages. By default nothing would be logged.
|
43
|
+
# @param error_handler - Optional ErrorHandlerInterface which provides a handle_error method to handle exceptions.
|
44
|
+
# By default all exceptions will be suppressed.
|
45
|
+
# @param skip_json_validation - Optional Boolean param to skip JSON schema validation of the provided datafile.
|
46
|
+
# @param user_profile_service - Optional UserProfileServiceInterface Provides methods to store and retreive user profiles.
|
47
|
+
# @param config_manager - Optional ConfigManagerInterface Responds to get_config.
|
48
|
+
# @param notification_center - Optional Instance of NotificationCenter.
|
49
|
+
def self.custom_instance(
|
50
|
+
sdk_key,
|
51
|
+
datafile = nil,
|
52
|
+
event_dispatcher = nil,
|
53
|
+
logger = nil,
|
54
|
+
error_handler = nil,
|
55
|
+
skip_json_validation = false,
|
56
|
+
user_profile_service = nil,
|
57
|
+
config_manager = nil,
|
58
|
+
notification_center = nil
|
59
|
+
)
|
60
|
+
Optimizely::Project.new(
|
61
|
+
datafile,
|
62
|
+
event_dispatcher,
|
63
|
+
logger,
|
64
|
+
error_handler,
|
65
|
+
skip_json_validation,
|
66
|
+
user_profile_service,
|
67
|
+
sdk_key,
|
68
|
+
config_manager,
|
69
|
+
notification_center
|
70
|
+
)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -14,469 +14,64 @@
|
|
14
14
|
# See the License for the specific language governing permissions and
|
15
15
|
# limitations under the License.
|
16
16
|
#
|
17
|
-
require 'json'
|
18
|
-
require_relative 'helpers/constants'
|
19
|
-
require_relative 'helpers/validator'
|
20
17
|
|
21
18
|
module Optimizely
|
22
19
|
class ProjectConfig
|
23
|
-
#
|
24
|
-
|
25
|
-
RESERVED_ATTRIBUTE_PREFIX = '$opt_'
|
20
|
+
# ProjectConfig is an interface capturing the experiment, variation and feature definitions.
|
21
|
+
# The default implementation of ProjectConfig can be found in DatafileProjectConfig.
|
26
22
|
|
27
|
-
|
28
|
-
attr_reader :error_handler
|
29
|
-
attr_reader :logger
|
23
|
+
def account_id; end
|
30
24
|
|
31
|
-
|
32
|
-
attr_reader :attributes
|
33
|
-
attr_reader :audiences
|
34
|
-
attr_reader :typed_audiences
|
35
|
-
attr_reader :events
|
36
|
-
attr_reader :experiments
|
37
|
-
attr_reader :feature_flags
|
38
|
-
attr_reader :groups
|
39
|
-
attr_reader :project_id
|
40
|
-
# Boolean - denotes if Optimizely should remove the last block of visitors' IP address before storing event data
|
41
|
-
attr_reader :anonymize_ip
|
42
|
-
attr_reader :bot_filtering
|
43
|
-
attr_reader :revision
|
44
|
-
attr_reader :rollouts
|
45
|
-
attr_reader :version
|
25
|
+
def attributes; end
|
46
26
|
|
47
|
-
|
48
|
-
attr_reader :audience_id_map
|
49
|
-
attr_reader :event_key_map
|
50
|
-
attr_reader :experiment_feature_map
|
51
|
-
attr_reader :experiment_id_map
|
52
|
-
attr_reader :experiment_key_map
|
53
|
-
attr_reader :feature_flag_key_map
|
54
|
-
attr_reader :feature_variable_key_map
|
55
|
-
attr_reader :group_key_map
|
56
|
-
attr_reader :rollout_id_map
|
57
|
-
attr_reader :rollout_experiment_key_map
|
58
|
-
attr_reader :variation_id_map
|
59
|
-
attr_reader :variation_id_to_variable_usage_map
|
60
|
-
attr_reader :variation_key_map
|
27
|
+
def audiences; end
|
61
28
|
|
62
|
-
|
63
|
-
# of experiments to variations. This contains all the forced variations
|
64
|
-
# set by the user by calling setForcedVariation (it is not the same as the
|
65
|
-
# whitelisting forcedVariations data structure in the Experiments class).
|
66
|
-
attr_reader :forced_variation_map
|
29
|
+
def typed_audiences; end
|
67
30
|
|
68
|
-
def
|
69
|
-
# ProjectConfig init method to fetch and set project config data
|
70
|
-
#
|
71
|
-
# datafile - JSON string representing the project
|
31
|
+
def events; end
|
72
32
|
|
73
|
-
|
33
|
+
def experiments; end
|
74
34
|
|
75
|
-
|
76
|
-
@logger = logger
|
77
|
-
@version = config['version']
|
35
|
+
def feature_flags; end
|
78
36
|
|
79
|
-
|
37
|
+
def groups; end
|
80
38
|
|
81
|
-
|
82
|
-
@attributes = config.fetch('attributes', [])
|
83
|
-
@audiences = config.fetch('audiences', [])
|
84
|
-
@typed_audiences = config.fetch('typedAudiences', [])
|
85
|
-
@events = config.fetch('events', [])
|
86
|
-
@experiments = config['experiments']
|
87
|
-
@feature_flags = config.fetch('featureFlags', [])
|
88
|
-
@groups = config.fetch('groups', [])
|
89
|
-
@project_id = config['projectId']
|
90
|
-
@anonymize_ip = config.key?('anonymizeIP') ? config['anonymizeIP'] : false
|
91
|
-
@bot_filtering = config['botFiltering']
|
92
|
-
@revision = config['revision']
|
93
|
-
@rollouts = config.fetch('rollouts', [])
|
39
|
+
def project_id; end
|
94
40
|
|
95
|
-
|
96
|
-
@attribute_key_map = generate_key_map(@attributes, 'key')
|
97
|
-
@event_key_map = generate_key_map(@events, 'key')
|
98
|
-
@group_key_map = generate_key_map(@groups, 'id')
|
99
|
-
@group_key_map.each do |key, group|
|
100
|
-
exps = group.fetch('experiments')
|
101
|
-
exps.each do |exp|
|
102
|
-
@experiments.push(exp.merge('groupId' => key))
|
103
|
-
end
|
104
|
-
end
|
105
|
-
@experiment_key_map = generate_key_map(@experiments, 'key')
|
106
|
-
@experiment_id_map = generate_key_map(@experiments, 'id')
|
107
|
-
@audience_id_map = generate_key_map(@audiences, 'id')
|
108
|
-
@audience_id_map = @audience_id_map.merge(generate_key_map(@typed_audiences, 'id')) unless @typed_audiences.empty?
|
109
|
-
@variation_id_map = {}
|
110
|
-
@variation_key_map = {}
|
111
|
-
@forced_variation_map = {}
|
112
|
-
@variation_id_to_variable_usage_map = {}
|
113
|
-
@variation_id_to_experiment_map = {}
|
114
|
-
@experiment_key_map.each_value do |exp|
|
115
|
-
# Excludes experiments from rollouts
|
116
|
-
variations = exp.fetch('variations')
|
117
|
-
variations.each do |variation|
|
118
|
-
variation_id = variation['id']
|
119
|
-
@variation_id_to_experiment_map[variation_id] = exp
|
120
|
-
end
|
121
|
-
end
|
122
|
-
@rollout_id_map = generate_key_map(@rollouts, 'id')
|
123
|
-
# split out the experiment key map for rollouts
|
124
|
-
@rollout_experiment_key_map = {}
|
125
|
-
@rollout_id_map.each_value do |rollout|
|
126
|
-
exps = rollout.fetch('experiments')
|
127
|
-
@rollout_experiment_key_map = @rollout_experiment_key_map.merge(generate_key_map(exps, 'key'))
|
128
|
-
end
|
129
|
-
@all_experiments = @experiment_key_map.merge(@rollout_experiment_key_map)
|
130
|
-
@all_experiments.each do |key, exp|
|
131
|
-
variations = exp.fetch('variations')
|
132
|
-
variations.each do |variation|
|
133
|
-
variation_id = variation['id']
|
134
|
-
variation['featureEnabled'] = variation['featureEnabled'] == true
|
135
|
-
variation_variables = variation['variables']
|
136
|
-
next if variation_variables.nil?
|
41
|
+
def anonymize_ip; end
|
137
42
|
|
138
|
-
|
139
|
-
end
|
140
|
-
@variation_id_map[key] = generate_key_map(variations, 'id')
|
141
|
-
@variation_key_map[key] = generate_key_map(variations, 'key')
|
142
|
-
end
|
143
|
-
@feature_flag_key_map = generate_key_map(@feature_flags, 'key')
|
144
|
-
@experiment_feature_map = {}
|
145
|
-
@feature_variable_key_map = {}
|
146
|
-
@feature_flag_key_map.each do |key, feature_flag|
|
147
|
-
@feature_variable_key_map[key] = generate_key_map(feature_flag['variables'], 'key')
|
148
|
-
feature_flag['experimentIds'].each do |experiment_id|
|
149
|
-
@experiment_feature_map[experiment_id] = [feature_flag['id']]
|
150
|
-
end
|
151
|
-
end
|
152
|
-
end
|
43
|
+
def bot_filtering; end
|
153
44
|
|
154
|
-
def
|
155
|
-
# Determine if experiment corresponding to given key is running
|
156
|
-
#
|
157
|
-
# experiment - Experiment
|
158
|
-
#
|
159
|
-
# Returns true if experiment is running
|
160
|
-
RUNNING_EXPERIMENT_STATUS.include?(experiment['status'])
|
161
|
-
end
|
45
|
+
def revision; end
|
162
46
|
|
163
|
-
def
|
164
|
-
# Retrieves experiment ID for a given key
|
165
|
-
#
|
166
|
-
# experiment_key - String key representing the experiment
|
167
|
-
#
|
168
|
-
# Returns Experiment or nil if not found
|
47
|
+
def rollouts; end
|
169
48
|
|
170
|
-
|
171
|
-
return experiment if experiment
|
49
|
+
def experiment_running?(experiment); end
|
172
50
|
|
173
|
-
|
174
|
-
@error_handler.handle_error InvalidExperimentError
|
175
|
-
nil
|
176
|
-
end
|
51
|
+
def get_experiment_from_key(experiment_key); end
|
177
52
|
|
178
|
-
def get_experiment_key(experiment_id)
|
179
|
-
# Retrieves experiment key for a given ID.
|
180
|
-
#
|
181
|
-
# experiment_id - String ID representing the experiment.
|
182
|
-
#
|
183
|
-
# Returns String key.
|
53
|
+
def get_experiment_key(experiment_id); end
|
184
54
|
|
185
|
-
|
186
|
-
return experiment['key'] unless experiment.nil?
|
55
|
+
def get_event_from_key(event_key); end
|
187
56
|
|
188
|
-
|
189
|
-
@error_handler.handle_error InvalidExperimentError
|
190
|
-
nil
|
191
|
-
end
|
57
|
+
def get_audience_from_id(audience_id); end
|
192
58
|
|
193
|
-
def
|
194
|
-
# Get event for the provided event key.
|
195
|
-
#
|
196
|
-
# event_key - Event key for which event is to be determined.
|
197
|
-
#
|
198
|
-
# Returns Event corresponding to the provided event key.
|
59
|
+
def get_variation_from_id(experiment_key, variation_id); end
|
199
60
|
|
200
|
-
|
201
|
-
return event if event
|
61
|
+
def get_variation_id_from_key(experiment_key, variation_key); end
|
202
62
|
|
203
|
-
|
204
|
-
@error_handler.handle_error InvalidEventError
|
205
|
-
nil
|
206
|
-
end
|
63
|
+
def get_whitelisted_variations(experiment_key); end
|
207
64
|
|
208
|
-
def
|
209
|
-
# Get audience for the provided audience ID
|
210
|
-
#
|
211
|
-
# audience_id - ID of the audience
|
212
|
-
#
|
213
|
-
# Returns the audience
|
65
|
+
def get_attribute_id(attribute_key); end
|
214
66
|
|
215
|
-
|
216
|
-
return audience if audience
|
67
|
+
def variation_id_exists?(experiment_id, variation_id); end
|
217
68
|
|
218
|
-
|
219
|
-
@error_handler.handle_error InvalidAudienceError
|
220
|
-
nil
|
221
|
-
end
|
69
|
+
def get_feature_flag_from_key(feature_flag_key); end
|
222
70
|
|
223
|
-
def
|
224
|
-
# Get variation given experiment key and variation ID
|
225
|
-
#
|
226
|
-
# experiment_key - Key representing parent experiment of variation
|
227
|
-
# variation_id - ID of the variation
|
228
|
-
#
|
229
|
-
# Returns the variation or nil if not found
|
71
|
+
def get_feature_variable(feature_flag, variable_key); end
|
230
72
|
|
231
|
-
|
232
|
-
if variation_id_map
|
233
|
-
variation = variation_id_map[variation_id]
|
234
|
-
return variation if variation
|
73
|
+
def get_rollout_from_id(rollout_id); end
|
235
74
|
|
236
|
-
|
237
|
-
@error_handler.handle_error InvalidVariationError
|
238
|
-
return nil
|
239
|
-
end
|
240
|
-
|
241
|
-
@logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
|
242
|
-
@error_handler.handle_error InvalidExperimentError
|
243
|
-
nil
|
244
|
-
end
|
245
|
-
|
246
|
-
def get_variation_id_from_key(experiment_key, variation_key)
|
247
|
-
# Get variation ID given experiment key and variation key
|
248
|
-
#
|
249
|
-
# experiment_key - Key representing parent experiment of variation
|
250
|
-
# variation_key - Key of the variation
|
251
|
-
#
|
252
|
-
# Returns ID of the variation
|
253
|
-
|
254
|
-
variation_key_map = @variation_key_map[experiment_key]
|
255
|
-
if variation_key_map
|
256
|
-
variation = variation_key_map[variation_key]
|
257
|
-
return variation['id'] if variation
|
258
|
-
|
259
|
-
@logger.log Logger::ERROR, "Variation key '#{variation_key}' is not in datafile."
|
260
|
-
@error_handler.handle_error InvalidVariationError
|
261
|
-
return nil
|
262
|
-
end
|
263
|
-
|
264
|
-
@logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
|
265
|
-
@error_handler.handle_error InvalidExperimentError
|
266
|
-
nil
|
267
|
-
end
|
268
|
-
|
269
|
-
def get_whitelisted_variations(experiment_key)
|
270
|
-
# Retrieves whitelisted variations for a given experiment Key
|
271
|
-
#
|
272
|
-
# experiment_key - String Key representing the experiment
|
273
|
-
#
|
274
|
-
# Returns whitelisted variations for the experiment or nil
|
275
|
-
|
276
|
-
experiment = @experiment_key_map[experiment_key]
|
277
|
-
return experiment['forcedVariations'] if experiment
|
278
|
-
|
279
|
-
@logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
|
280
|
-
@error_handler.handle_error InvalidExperimentError
|
281
|
-
end
|
282
|
-
|
283
|
-
def get_forced_variation(experiment_key, user_id)
|
284
|
-
# Gets the forced variation for the given user and experiment.
|
285
|
-
#
|
286
|
-
# experiment_key - String Key for experiment.
|
287
|
-
# user_id - String ID for user
|
288
|
-
#
|
289
|
-
# Returns Variation The variation which the given user and experiment should be forced into.
|
290
|
-
|
291
|
-
return nil unless Optimizely::Helpers::Validator.inputs_valid?(
|
292
|
-
{
|
293
|
-
experiment_key: experiment_key,
|
294
|
-
user_id: user_id
|
295
|
-
}, @logger, Logger::DEBUG
|
296
|
-
)
|
297
|
-
|
298
|
-
unless @forced_variation_map.key? user_id
|
299
|
-
@logger.log(Logger::DEBUG, "User '#{user_id}' is not in the forced variation map.")
|
300
|
-
return nil
|
301
|
-
end
|
302
|
-
|
303
|
-
experiment_to_variation_map = @forced_variation_map[user_id]
|
304
|
-
experiment = get_experiment_from_key(experiment_key)
|
305
|
-
experiment_id = experiment['id'] if experiment
|
306
|
-
# check for nil and empty string experiment ID
|
307
|
-
# this case is logged in get_experiment_from_key
|
308
|
-
return nil if experiment_id.nil? || experiment_id.empty?
|
309
|
-
|
310
|
-
unless experiment_to_variation_map.key? experiment_id
|
311
|
-
@logger.log(Logger::DEBUG, "No experiment '#{experiment_key}' mapped to user '#{user_id}' "\
|
312
|
-
'in the forced variation map.')
|
313
|
-
return nil
|
314
|
-
end
|
315
|
-
|
316
|
-
variation_id = experiment_to_variation_map[experiment_id]
|
317
|
-
variation_key = ''
|
318
|
-
variation = get_variation_from_id(experiment_key, variation_id)
|
319
|
-
variation_key = variation['key'] if variation
|
320
|
-
|
321
|
-
# check if the variation exists in the datafile
|
322
|
-
# this case is logged in get_variation_from_id
|
323
|
-
return nil if variation_key.empty?
|
324
|
-
|
325
|
-
@logger.log(Logger::DEBUG, "Variation '#{variation_key}' is mapped to experiment '#{experiment_key}' "\
|
326
|
-
"and user '#{user_id}' in the forced variation map")
|
327
|
-
|
328
|
-
variation
|
329
|
-
end
|
330
|
-
|
331
|
-
def set_forced_variation(experiment_key, user_id, variation_key)
|
332
|
-
# Sets a Hash of user IDs to a Hash of experiments to forced variations.
|
333
|
-
#
|
334
|
-
# experiment_key - String Key for experiment.
|
335
|
-
# user_id - String ID for user.
|
336
|
-
# variation_key - String Key for variation. If null, then clear the existing experiment-to-variation mapping.
|
337
|
-
#
|
338
|
-
# Returns a boolean value that indicates if the set completed successfully.
|
339
|
-
|
340
|
-
input_values = {experiment_key: experiment_key, user_id: user_id}
|
341
|
-
input_values[:variation_key] = variation_key unless variation_key.nil?
|
342
|
-
return false unless Optimizely::Helpers::Validator.inputs_valid?(input_values, @logger, Logger::DEBUG)
|
343
|
-
|
344
|
-
experiment = get_experiment_from_key(experiment_key)
|
345
|
-
experiment_id = experiment['id'] if experiment
|
346
|
-
# check if the experiment exists in the datafile
|
347
|
-
return false if experiment_id.nil? || experiment_id.empty?
|
348
|
-
|
349
|
-
# clear the forced variation if the variation key is null
|
350
|
-
if variation_key.nil?
|
351
|
-
@forced_variation_map[user_id].delete(experiment_id) if @forced_variation_map.key? user_id
|
352
|
-
@logger.log(Logger::DEBUG, "Variation mapped to experiment '#{experiment_key}' has been removed for user "\
|
353
|
-
"'#{user_id}'.")
|
354
|
-
return true
|
355
|
-
end
|
356
|
-
|
357
|
-
variation_id = get_variation_id_from_key(experiment_key, variation_key)
|
358
|
-
|
359
|
-
# check if the variation exists in the datafile
|
360
|
-
unless variation_id
|
361
|
-
# this case is logged in get_variation_id_from_key
|
362
|
-
return false
|
363
|
-
end
|
364
|
-
|
365
|
-
@forced_variation_map[user_id] = {} unless @forced_variation_map.key? user_id
|
366
|
-
@forced_variation_map[user_id][experiment_id] = variation_id
|
367
|
-
@logger.log(Logger::DEBUG, "Set variation '#{variation_id}' for experiment '#{experiment_id}' and "\
|
368
|
-
"user '#{user_id}' in the forced variation map.")
|
369
|
-
true
|
370
|
-
end
|
371
|
-
|
372
|
-
def get_attribute_id(attribute_key)
|
373
|
-
# Get attribute ID for the provided attribute key.
|
374
|
-
#
|
375
|
-
# Args:
|
376
|
-
# Attribute key for which attribute is to be fetched.
|
377
|
-
#
|
378
|
-
# Returns:
|
379
|
-
# Attribute ID corresponding to the provided attribute key.
|
380
|
-
attribute = @attribute_key_map[attribute_key]
|
381
|
-
has_reserved_prefix = attribute_key.to_s.start_with?(RESERVED_ATTRIBUTE_PREFIX)
|
382
|
-
unless attribute.nil?
|
383
|
-
if has_reserved_prefix
|
384
|
-
@logger.log(Logger::WARN, "Attribute '#{attribute_key}' unexpectedly has reserved prefix '#{RESERVED_ATTRIBUTE_PREFIX}'; "\
|
385
|
-
'using attribute ID instead of reserved attribute name.')
|
386
|
-
end
|
387
|
-
return attribute['id']
|
388
|
-
end
|
389
|
-
return attribute_key if has_reserved_prefix
|
390
|
-
|
391
|
-
@logger.log Logger::ERROR, "Attribute key '#{attribute_key}' is not in datafile."
|
392
|
-
@error_handler.handle_error InvalidAttributeError
|
393
|
-
nil
|
394
|
-
end
|
395
|
-
|
396
|
-
def variation_id_exists?(experiment_id, variation_id)
|
397
|
-
# Determines if a given experiment ID / variation ID pair exists in the datafile
|
398
|
-
#
|
399
|
-
# experiment_id - String experiment ID
|
400
|
-
# variation_id - String variation ID
|
401
|
-
#
|
402
|
-
# Returns true if variation is in datafile
|
403
|
-
|
404
|
-
experiment_key = get_experiment_key(experiment_id)
|
405
|
-
variation_id_map = @variation_id_map[experiment_key]
|
406
|
-
if variation_id_map
|
407
|
-
variation = variation_id_map[variation_id]
|
408
|
-
return true if variation
|
409
|
-
|
410
|
-
@logger.log Logger::ERROR, "Variation ID '#{variation_id}' is not in datafile."
|
411
|
-
@error_handler.handle_error InvalidVariationError
|
412
|
-
end
|
413
|
-
|
414
|
-
false
|
415
|
-
end
|
416
|
-
|
417
|
-
def get_feature_flag_from_key(feature_flag_key)
|
418
|
-
# Retrieves the feature flag with the given key
|
419
|
-
#
|
420
|
-
# feature_flag_key - String feature key
|
421
|
-
#
|
422
|
-
# Returns feature flag if found, otherwise nil
|
423
|
-
feature_flag = @feature_flag_key_map[feature_flag_key]
|
424
|
-
return feature_flag if feature_flag
|
425
|
-
|
426
|
-
@logger.log Logger::ERROR, "Feature flag key '#{feature_flag_key}' is not in datafile."
|
427
|
-
nil
|
428
|
-
end
|
429
|
-
|
430
|
-
def get_feature_variable(feature_flag, variable_key)
|
431
|
-
# Retrieves the variable with the given key for the given feature
|
432
|
-
#
|
433
|
-
# feature_flag - The feature flag for which we are retrieving the variable
|
434
|
-
# variable_key - String variable key
|
435
|
-
#
|
436
|
-
# Returns variable if found, otherwise nil
|
437
|
-
feature_flag_key = feature_flag['key']
|
438
|
-
variable = @feature_variable_key_map[feature_flag_key][variable_key]
|
439
|
-
return variable if variable
|
440
|
-
|
441
|
-
@logger.log Logger::ERROR, "No feature variable was found for key '#{variable_key}' in feature flag "\
|
442
|
-
"'#{feature_flag_key}'."
|
443
|
-
nil
|
444
|
-
end
|
445
|
-
|
446
|
-
def get_rollout_from_id(rollout_id)
|
447
|
-
# Retrieves the rollout with the given ID
|
448
|
-
#
|
449
|
-
# rollout_id - String rollout ID
|
450
|
-
#
|
451
|
-
# Returns the rollout if found, otherwise nil
|
452
|
-
rollout = @rollout_id_map[rollout_id]
|
453
|
-
return rollout if rollout
|
454
|
-
|
455
|
-
@logger.log Logger::ERROR, "Rollout with ID '#{rollout_id}' is not in the datafile."
|
456
|
-
nil
|
457
|
-
end
|
458
|
-
|
459
|
-
def feature_experiment?(experiment_id)
|
460
|
-
# Determines if given experiment is a feature test.
|
461
|
-
#
|
462
|
-
# experiment_id - String experiment ID
|
463
|
-
#
|
464
|
-
# Returns true if experiment belongs to any feature,
|
465
|
-
# false otherwise.
|
466
|
-
@experiment_feature_map.key?(experiment_id)
|
467
|
-
end
|
468
|
-
|
469
|
-
private
|
470
|
-
|
471
|
-
def generate_key_map(array, key)
|
472
|
-
# Helper method to generate map from key to hash in array of hashes
|
473
|
-
#
|
474
|
-
# array - Array consisting of hash
|
475
|
-
# key - Key in each hash which will be key in the map
|
476
|
-
#
|
477
|
-
# Returns map mapping key to hash
|
478
|
-
|
479
|
-
Hash[array.map { |obj| [obj[key], obj] }]
|
480
|
-
end
|
75
|
+
def feature_experiment?(experiment_id); end
|
481
76
|
end
|
482
77
|
end
|