optimizely-sdk 3.1.1 → 3.2.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 +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
|