optimizely-sdk 3.4.0 → 3.8.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 +383 -49
- data/lib/optimizely/audience.rb +31 -43
- data/lib/optimizely/bucketer.rb +36 -33
- data/lib/optimizely/config/datafile_project_config.rb +19 -3
- data/lib/optimizely/config/proxy_config.rb +34 -0
- data/lib/optimizely/config_manager/async_scheduler.rb +6 -2
- data/lib/optimizely/config_manager/http_project_config_manager.rb +40 -23
- data/lib/optimizely/custom_attribute_condition_evaluator.rb +133 -37
- data/lib/optimizely/decide/optimizely_decide_option.rb +28 -0
- data/lib/optimizely/decide/optimizely_decision.rb +60 -0
- data/lib/optimizely/decide/optimizely_decision_message.rb +26 -0
- data/lib/optimizely/decision_service.rb +163 -139
- data/lib/optimizely/event/entity/decision.rb +6 -4
- data/lib/optimizely/event/entity/impression_event.rb +4 -2
- data/lib/optimizely/event/event_factory.rb +4 -3
- data/lib/optimizely/event/user_event_factory.rb +4 -3
- data/lib/optimizely/event_dispatcher.rb +8 -14
- data/lib/optimizely/exceptions.rb +17 -9
- data/lib/optimizely/helpers/constants.rb +19 -5
- data/lib/optimizely/helpers/http_utils.rb +64 -0
- data/lib/optimizely/helpers/variable_type.rb +8 -1
- data/lib/optimizely/optimizely_config.rb +2 -1
- data/lib/optimizely/optimizely_factory.rb +54 -5
- data/lib/optimizely/optimizely_user_context.rb +107 -0
- data/lib/optimizely/project_config.rb +5 -1
- data/lib/optimizely/semantic_version.rb +166 -0
- data/lib/optimizely/version.rb +1 -1
- metadata +9 -16
data/lib/optimizely/audience.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2016-2017, 2019, Optimizely and contributors
|
4
|
+
# Copyright 2016-2017, 2019-2020, 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.
|
@@ -24,38 +24,37 @@ module Optimizely
|
|
24
24
|
module Audience
|
25
25
|
module_function
|
26
26
|
|
27
|
-
def
|
28
|
-
# Determine for given experiment if user satisfies the
|
27
|
+
def user_meets_audience_conditions?(config, experiment, attributes, logger, logging_hash = nil, logging_key = nil)
|
28
|
+
# Determine for given experiment/rollout rule if user satisfies the audience conditions.
|
29
29
|
#
|
30
30
|
# config - Representation of the Optimizely project config.
|
31
|
-
# experiment - Experiment
|
31
|
+
# experiment - Experiment/Rollout rule in which user is to be bucketed.
|
32
32
|
# attributes - Hash representing user attributes which will be used in determining if
|
33
33
|
# the audience conditions are met.
|
34
|
+
# logger - Provides a logger instance.
|
35
|
+
# logging_hash - Optional string representing logs hash inside Helpers::Constants.
|
36
|
+
# This defaults to 'EXPERIMENT_AUDIENCE_EVALUATION_LOGS'.
|
37
|
+
# logging_key - Optional string to be logged as an identifier of experiment under evaluation.
|
38
|
+
# This defaults to experiment['key'].
|
34
39
|
#
|
35
40
|
# Returns boolean representing if user satisfies audience conditions for the audiences or not.
|
41
|
+
decide_reasons = []
|
42
|
+
logging_hash ||= 'EXPERIMENT_AUDIENCE_EVALUATION_LOGS'
|
43
|
+
logging_key ||= experiment['key']
|
44
|
+
|
45
|
+
logs_hash = Object.const_get "Optimizely::Helpers::Constants::#{logging_hash}"
|
36
46
|
|
37
47
|
audience_conditions = experiment['audienceConditions'] || experiment['audienceIds']
|
38
48
|
|
39
|
-
|
40
|
-
|
41
|
-
format(
|
42
|
-
Helpers::Constants::AUDIENCE_EVALUATION_LOGS['EVALUATING_AUDIENCES_COMBINED'],
|
43
|
-
experiment['key'],
|
44
|
-
audience_conditions
|
45
|
-
)
|
46
|
-
)
|
49
|
+
message = format(logs_hash['EVALUATING_AUDIENCES_COMBINED'], logging_key, audience_conditions)
|
50
|
+
logger.log(Logger::DEBUG, message)
|
47
51
|
|
48
52
|
# Return true if there are no audiences
|
49
53
|
if audience_conditions.empty?
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
experiment['key'],
|
55
|
-
'TRUE'
|
56
|
-
)
|
57
|
-
)
|
58
|
-
return true
|
54
|
+
message = format(logs_hash['AUDIENCE_EVALUATION_RESULT_COMBINED'], logging_key, 'TRUE')
|
55
|
+
logger.log(Logger::INFO, message)
|
56
|
+
decide_reasons.push(message)
|
57
|
+
return true, decide_reasons
|
59
58
|
end
|
60
59
|
|
61
60
|
attributes ||= {}
|
@@ -71,39 +70,28 @@ module Optimizely
|
|
71
70
|
return nil unless audience
|
72
71
|
|
73
72
|
audience_conditions = audience['conditions']
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
Helpers::Constants::AUDIENCE_EVALUATION_LOGS['EVALUATING_AUDIENCE'],
|
78
|
-
audience_id,
|
79
|
-
audience_conditions
|
80
|
-
)
|
81
|
-
)
|
73
|
+
message = format(logs_hash['EVALUATING_AUDIENCE'], audience_id, audience_conditions)
|
74
|
+
logger.log(Logger::DEBUG, message)
|
75
|
+
decide_reasons.push(message)
|
82
76
|
|
83
77
|
audience_conditions = JSON.parse(audience_conditions) if audience_conditions.is_a?(String)
|
84
78
|
result = ConditionTreeEvaluator.evaluate(audience_conditions, evaluate_custom_attr)
|
85
79
|
result_str = result.nil? ? 'UNKNOWN' : result.to_s.upcase
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
80
|
+
message = format(logs_hash['AUDIENCE_EVALUATION_RESULT'], audience_id, result_str)
|
81
|
+
logger.log(Logger::DEBUG, message)
|
82
|
+
decide_reasons.push(message)
|
83
|
+
|
90
84
|
result
|
91
85
|
end
|
92
86
|
|
93
87
|
eval_result = ConditionTreeEvaluator.evaluate(audience_conditions, evaluate_audience)
|
94
|
-
|
95
88
|
eval_result ||= false
|
96
89
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
Helpers::Constants::AUDIENCE_EVALUATION_LOGS['AUDIENCE_EVALUATION_RESULT_COMBINED'],
|
101
|
-
experiment['key'],
|
102
|
-
eval_result.to_s.upcase
|
103
|
-
)
|
104
|
-
)
|
90
|
+
message = format(logs_hash['AUDIENCE_EVALUATION_RESULT_COMBINED'], logging_key, eval_result.to_s.upcase)
|
91
|
+
logger.log(Logger::INFO, message)
|
92
|
+
decide_reasons.push(message)
|
105
93
|
|
106
|
-
eval_result
|
94
|
+
[eval_result, decide_reasons]
|
107
95
|
end
|
108
96
|
end
|
109
97
|
end
|
data/lib/optimizely/bucketer.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2016-2017, 2019 Optimizely and contributors
|
4
|
+
# Copyright 2016-2017, 2019-2020 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.
|
@@ -39,14 +39,17 @@ module Optimizely
|
|
39
39
|
# Determines ID of variation to be shown for a given experiment key and user ID.
|
40
40
|
#
|
41
41
|
# project_config - Instance of ProjectConfig
|
42
|
-
# experiment - Experiment for which visitor is to be bucketed.
|
42
|
+
# experiment - Experiment or Rollout rule for which visitor is to be bucketed.
|
43
43
|
# bucketing_id - String A customer-assigned value used to generate the bucketing key
|
44
44
|
# user_id - String ID for user.
|
45
45
|
#
|
46
46
|
# Returns variation in which visitor with ID user_id has been placed. Nil if no variation.
|
47
|
-
return nil if experiment.nil?
|
47
|
+
return nil, [] if experiment.nil?
|
48
|
+
|
49
|
+
decide_reasons = []
|
48
50
|
|
49
51
|
# check if experiment is in a group; if so, check if user is bucketed into specified experiment
|
52
|
+
# this will not affect evaluation of rollout rules.
|
50
53
|
experiment_id = experiment['id']
|
51
54
|
experiment_key = experiment['key']
|
52
55
|
group_id = experiment['groupId']
|
@@ -54,52 +57,49 @@ module Optimizely
|
|
54
57
|
group = project_config.group_id_map.fetch(group_id)
|
55
58
|
if Helpers::Group.random_policy?(group)
|
56
59
|
traffic_allocations = group.fetch('trafficAllocation')
|
57
|
-
bucketed_experiment_id = find_bucket(bucketing_id, user_id, group_id, traffic_allocations)
|
60
|
+
bucketed_experiment_id, find_bucket_reasons = find_bucket(bucketing_id, user_id, group_id, traffic_allocations)
|
61
|
+
decide_reasons.push(*find_bucket_reasons)
|
62
|
+
|
58
63
|
# return if the user is not bucketed into any experiment
|
59
64
|
unless bucketed_experiment_id
|
60
|
-
|
61
|
-
|
65
|
+
message = "User '#{user_id}' is in no experiment."
|
66
|
+
@logger.log(Logger::INFO, message)
|
67
|
+
decide_reasons.push(message)
|
68
|
+
return nil, decide_reasons
|
62
69
|
end
|
63
70
|
|
64
71
|
# return if the user is bucketed into a different experiment than the one specified
|
65
72
|
if bucketed_experiment_id != experiment_id
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
return nil
|
73
|
+
message = "User '#{user_id}' is not in experiment '#{experiment_key}' of group #{group_id}."
|
74
|
+
@logger.log(Logger::INFO, message)
|
75
|
+
decide_reasons.push(message)
|
76
|
+
return nil, decide_reasons
|
71
77
|
end
|
72
78
|
|
73
79
|
# continue bucketing if the user is bucketed into the experiment specified
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
)
|
80
|
+
message = "User '#{user_id}' is in experiment '#{experiment_key}' of group #{group_id}."
|
81
|
+
@logger.log(Logger::INFO, message)
|
82
|
+
decide_reasons.push(message)
|
78
83
|
end
|
79
84
|
end
|
80
85
|
|
81
86
|
traffic_allocations = experiment['trafficAllocation']
|
82
|
-
variation_id = find_bucket(bucketing_id, user_id, experiment_id, traffic_allocations)
|
87
|
+
variation_id, find_bucket_reasons = find_bucket(bucketing_id, user_id, experiment_id, traffic_allocations)
|
88
|
+
decide_reasons.push(*find_bucket_reasons)
|
89
|
+
|
83
90
|
if variation_id && variation_id != ''
|
84
91
|
variation = project_config.get_variation_from_id(experiment_key, variation_id)
|
85
|
-
|
86
|
-
@logger.log(
|
87
|
-
Logger::INFO,
|
88
|
-
"User '#{user_id}' is in variation '#{variation_key}' of experiment '#{experiment_key}'."
|
89
|
-
)
|
90
|
-
return variation
|
92
|
+
return variation, decide_reasons
|
91
93
|
end
|
92
94
|
|
93
95
|
# Handle the case when the traffic range is empty due to sticky bucketing
|
94
96
|
if variation_id == ''
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
)
|
97
|
+
message = 'Bucketed into an empty traffic range. Returning nil.'
|
98
|
+
@logger.log(Logger::DEBUG, message)
|
99
|
+
decide_reasons.push(message)
|
99
100
|
end
|
100
101
|
|
101
|
-
|
102
|
-
nil
|
102
|
+
[nil, decide_reasons]
|
103
103
|
end
|
104
104
|
|
105
105
|
def find_bucket(bucketing_id, user_id, parent_id, traffic_allocations)
|
@@ -110,21 +110,24 @@ module Optimizely
|
|
110
110
|
# parent_id - String entity ID to use for bucketing ID
|
111
111
|
# traffic_allocations - Array of traffic allocations
|
112
112
|
#
|
113
|
-
# Returns entity ID corresponding to the provided bucket value
|
113
|
+
# Returns and array of two values where first value is the entity ID corresponding to the provided bucket value
|
114
|
+
# or nil if no match is found. The second value contains the array of reasons stating how the deicision was taken
|
115
|
+
decide_reasons = []
|
114
116
|
bucketing_key = format(BUCKETING_ID_TEMPLATE, bucketing_id: bucketing_id, entity_id: parent_id)
|
115
117
|
bucket_value = generate_bucket_value(bucketing_key)
|
116
|
-
|
117
|
-
|
118
|
+
|
119
|
+
message = "Assigned bucket #{bucket_value} to user '#{user_id}' with bucketing ID: '#{bucketing_id}'."
|
120
|
+
@logger.log(Logger::DEBUG, message)
|
118
121
|
|
119
122
|
traffic_allocations.each do |traffic_allocation|
|
120
123
|
current_end_of_range = traffic_allocation['endOfRange']
|
121
124
|
if bucket_value < current_end_of_range
|
122
125
|
entity_id = traffic_allocation['entityId']
|
123
|
-
return entity_id
|
126
|
+
return entity_id, decide_reasons
|
124
127
|
end
|
125
128
|
end
|
126
129
|
|
127
|
-
nil
|
130
|
+
[nil, decide_reasons]
|
128
131
|
end
|
129
132
|
|
130
133
|
private
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright 2019, Optimizely and contributors
|
3
|
+
# Copyright 2019-2020, Optimizely and contributors
|
4
4
|
#
|
5
5
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
6
|
# you may not use this file except in compliance with the License.
|
@@ -24,6 +24,7 @@ module Optimizely
|
|
24
24
|
RUNNING_EXPERIMENT_STATUS = ['Running'].freeze
|
25
25
|
RESERVED_ATTRIBUTE_PREFIX = '$opt_'
|
26
26
|
|
27
|
+
attr_reader :datafile
|
27
28
|
attr_reader :account_id
|
28
29
|
attr_reader :attributes
|
29
30
|
attr_reader :audiences
|
@@ -39,6 +40,7 @@ module Optimizely
|
|
39
40
|
attr_reader :revision
|
40
41
|
attr_reader :rollouts
|
41
42
|
attr_reader :version
|
43
|
+
attr_reader :send_flag_decisions
|
42
44
|
|
43
45
|
attr_reader :attribute_key_map
|
44
46
|
attr_reader :audience_id_map
|
@@ -62,6 +64,7 @@ module Optimizely
|
|
62
64
|
|
63
65
|
config = JSON.parse(datafile)
|
64
66
|
|
67
|
+
@datafile = datafile
|
65
68
|
@error_handler = error_handler
|
66
69
|
@logger = logger
|
67
70
|
@version = config['version']
|
@@ -81,6 +84,18 @@ module Optimizely
|
|
81
84
|
@bot_filtering = config['botFiltering']
|
82
85
|
@revision = config['revision']
|
83
86
|
@rollouts = config.fetch('rollouts', [])
|
87
|
+
@send_flag_decisions = config.fetch('sendFlagDecisions', false)
|
88
|
+
|
89
|
+
# Json type is represented in datafile as a subtype of string for the sake of backwards compatibility.
|
90
|
+
# Converting it to a first-class json type while creating Project Config
|
91
|
+
@feature_flags.each do |feature_flag|
|
92
|
+
feature_flag['variables'].each do |variable|
|
93
|
+
if variable['type'] == 'string' && variable['subType'] == 'json'
|
94
|
+
variable['type'] = 'json'
|
95
|
+
variable.delete('subType')
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
84
99
|
|
85
100
|
# Utility maps for quick lookup
|
86
101
|
@attribute_key_map = generate_key_map(@attributes, 'key')
|
@@ -159,8 +174,9 @@ module Optimizely
|
|
159
174
|
config = new(datafile, logger, error_handler)
|
160
175
|
rescue StandardError => e
|
161
176
|
default_logger = SimpleLogger.new
|
162
|
-
|
163
|
-
|
177
|
+
error_to_handle = e.class == InvalidDatafileVersionError ? e : InvalidInputError.new('datafile')
|
178
|
+
error_msg = error_to_handle.message
|
179
|
+
|
164
180
|
default_logger.log(Logger::ERROR, error_msg)
|
165
181
|
error_handler.handle_error error_to_handle
|
166
182
|
return nil
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2020, Optimizely and contributors
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
#
|
18
|
+
|
19
|
+
module Optimizely
|
20
|
+
class ProxyConfig
|
21
|
+
attr_reader :host, :port, :username, :password
|
22
|
+
|
23
|
+
def initialize(host, port = nil, username = nil, password = nil)
|
24
|
+
# host - DNS name or IP address of proxy
|
25
|
+
# port - port to use to acess the proxy
|
26
|
+
# username - username if authorization is required
|
27
|
+
# password - password if authorization is required
|
28
|
+
@host = host
|
29
|
+
@port = port
|
30
|
+
@username = username
|
31
|
+
@password = password
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2019, Optimizely and contributors
|
4
|
+
# Copyright 2019-2020, 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.
|
@@ -19,13 +19,14 @@ module Optimizely
|
|
19
19
|
class AsyncScheduler
|
20
20
|
attr_reader :running
|
21
21
|
|
22
|
-
def initialize(callback, interval, auto_update, logger = nil)
|
22
|
+
def initialize(callback, interval, auto_update, logger = nil, error_handler = nil)
|
23
23
|
# Sets up AsyncScheduler to execute a callback periodically.
|
24
24
|
#
|
25
25
|
# callback - Main function to be executed periodically.
|
26
26
|
# interval - How many seconds to wait between executions.
|
27
27
|
# auto_update - boolean indicates to run infinitely or only once.
|
28
28
|
# logger - Optional Provides a logger instance.
|
29
|
+
# error_handler - Optional Provides a handle_error method to handle exceptions.
|
29
30
|
|
30
31
|
@callback = callback
|
31
32
|
@interval = interval
|
@@ -33,6 +34,7 @@ module Optimizely
|
|
33
34
|
@running = false
|
34
35
|
@thread = nil
|
35
36
|
@logger = logger || NoOpLogger.new
|
37
|
+
@error_handler = error_handler || NoOpErrorHandler.new
|
36
38
|
end
|
37
39
|
|
38
40
|
def start!
|
@@ -54,6 +56,7 @@ module Optimizely
|
|
54
56
|
Logger::ERROR,
|
55
57
|
"Couldn't create a new thread for async scheduler. #{e.message}"
|
56
58
|
)
|
59
|
+
@error_handler.handle_error(e)
|
57
60
|
end
|
58
61
|
end
|
59
62
|
|
@@ -80,6 +83,7 @@ module Optimizely
|
|
80
83
|
Logger::ERROR,
|
81
84
|
"Something went wrong when executing passed callback. #{e.message}"
|
82
85
|
)
|
86
|
+
@error_handler.handle_error(e)
|
83
87
|
stop!
|
84
88
|
end
|
85
89
|
break unless @auto_update
|
@@ -19,14 +19,16 @@ require_relative '../config/datafile_project_config'
|
|
19
19
|
require_relative '../error_handler'
|
20
20
|
require_relative '../exceptions'
|
21
21
|
require_relative '../helpers/constants'
|
22
|
+
require_relative '../helpers/http_utils'
|
22
23
|
require_relative '../logger'
|
23
24
|
require_relative '../notification_center'
|
24
25
|
require_relative '../project_config'
|
25
26
|
require_relative '../optimizely_config'
|
26
27
|
require_relative 'project_config_manager'
|
27
28
|
require_relative 'async_scheduler'
|
28
|
-
|
29
|
+
|
29
30
|
require 'json'
|
31
|
+
|
30
32
|
module Optimizely
|
31
33
|
class HTTPProjectConfigManager < ProjectConfigManager
|
32
34
|
# Config manager that polls for the datafile and updated ProjectConfig based on an update interval.
|
@@ -49,6 +51,8 @@ module Optimizely
|
|
49
51
|
# error_handler - Provides a handle_error method to handle exceptions.
|
50
52
|
# skip_json_validation - Optional boolean param which allows skipping JSON schema
|
51
53
|
# validation upon object invocation. By default JSON schema validation will be performed.
|
54
|
+
# datafile_access_token - access token used to fetch private datafiles
|
55
|
+
# proxy_config - Optional proxy config instancea to configure making web requests through a proxy server.
|
52
56
|
def initialize(
|
53
57
|
sdk_key: nil,
|
54
58
|
url: nil,
|
@@ -61,10 +65,13 @@ module Optimizely
|
|
61
65
|
logger: nil,
|
62
66
|
error_handler: nil,
|
63
67
|
skip_json_validation: false,
|
64
|
-
notification_center: nil
|
68
|
+
notification_center: nil,
|
69
|
+
datafile_access_token: nil,
|
70
|
+
proxy_config: nil
|
65
71
|
)
|
66
72
|
@logger = logger || NoOpLogger.new
|
67
73
|
@error_handler = error_handler || NoOpErrorHandler.new
|
74
|
+
@access_token = datafile_access_token
|
68
75
|
@datafile_url = get_datafile_url(sdk_key, url, url_template)
|
69
76
|
@polling_interval = nil
|
70
77
|
polling_interval(polling_interval)
|
@@ -81,6 +88,7 @@ module Optimizely
|
|
81
88
|
# Start async scheduler in the end to avoid race condition where scheduler executes
|
82
89
|
# callback which makes use of variables not yet initialized by the main thread.
|
83
90
|
@async_scheduler.start! if start_by_default == true
|
91
|
+
@proxy_config = proxy_config
|
84
92
|
@stopped = false
|
85
93
|
end
|
86
94
|
|
@@ -143,21 +151,20 @@ module Optimizely
|
|
143
151
|
end
|
144
152
|
|
145
153
|
def request_config
|
146
|
-
@logger.log(
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
headers = {
|
152
|
-
'Content-Type' => 'application/json'
|
153
|
-
}
|
154
|
+
@logger.log(Logger::DEBUG, "Fetching datafile from #{@datafile_url}")
|
155
|
+
headers = {}
|
156
|
+
headers['Content-Type'] = 'application/json'
|
157
|
+
headers['If-Modified-Since'] = @last_modified if @last_modified
|
158
|
+
headers['Authorization'] = "Bearer #{@access_token}" unless @access_token.nil?
|
154
159
|
|
155
|
-
|
160
|
+
# Cleaning headers before logging to avoid exposing authorization token
|
161
|
+
cleansed_headers = {}
|
162
|
+
headers.each { |key, value| cleansed_headers[key] = key == 'Authorization' ? '********' : value }
|
163
|
+
@logger.log(Logger::DEBUG, "Datafile request headers: #{cleansed_headers}")
|
156
164
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
timeout: Helpers::Constants::CONFIG_MANAGER['REQUEST_TIMEOUT']
|
165
|
+
begin
|
166
|
+
response = Helpers::HttpUtils.make_request(
|
167
|
+
@datafile_url, :get, nil, headers, Helpers::Constants::CONFIG_MANAGER['REQUEST_TIMEOUT'], @proxy_config
|
161
168
|
)
|
162
169
|
rescue StandardError => e
|
163
170
|
@logger.log(
|
@@ -167,6 +174,9 @@ module Optimizely
|
|
167
174
|
return nil
|
168
175
|
end
|
169
176
|
|
177
|
+
response_code = response.code.to_i
|
178
|
+
@logger.log(Logger::DEBUG, "Datafile response status code #{response_code}")
|
179
|
+
|
170
180
|
# Leave datafile and config unchanged if it has not been modified.
|
171
181
|
if response.code == '304'
|
172
182
|
@logger.log(
|
@@ -176,9 +186,14 @@ module Optimizely
|
|
176
186
|
return
|
177
187
|
end
|
178
188
|
|
179
|
-
|
180
|
-
|
181
|
-
|
189
|
+
if response_code >= 200 && response_code < 400
|
190
|
+
@logger.log(Logger::DEBUG, 'Successfully fetched datafile, generating Project config')
|
191
|
+
config = DatafileProjectConfig.create(response.body, @logger, @error_handler, @skip_json_validation)
|
192
|
+
@last_modified = response[Helpers::Constants::HTTP_HEADERS['LAST_MODIFIED']]
|
193
|
+
@logger.log(Logger::DEBUG, "Saved last modified header value from response: #{@last_modified}.")
|
194
|
+
else
|
195
|
+
@logger.log(Logger::DEBUG, "Datafile fetch failed, status: #{response.code}, message: #{response.message}")
|
196
|
+
end
|
182
197
|
|
183
198
|
config
|
184
199
|
end
|
@@ -284,17 +299,19 @@ module Optimizely
|
|
284
299
|
# SDK key to determine URL from which to fetch the datafile.
|
285
300
|
# Returns String representing URL to fetch datafile from.
|
286
301
|
if sdk_key.nil? && url.nil?
|
287
|
-
|
288
|
-
@
|
302
|
+
error_msg = 'Must provide at least one of sdk_key or url.'
|
303
|
+
@logger.log(Logger::ERROR, error_msg)
|
304
|
+
@error_handler.handle_error(InvalidInputsError.new(error_msg))
|
289
305
|
end
|
290
306
|
|
291
307
|
unless url
|
292
|
-
url_template ||= Helpers::Constants::CONFIG_MANAGER['DATAFILE_URL_TEMPLATE']
|
308
|
+
url_template ||= @access_token.nil? ? Helpers::Constants::CONFIG_MANAGER['DATAFILE_URL_TEMPLATE'] : Helpers::Constants::CONFIG_MANAGER['AUTHENTICATED_DATAFILE_URL_TEMPLATE']
|
293
309
|
begin
|
294
310
|
return (url_template % sdk_key)
|
295
311
|
rescue
|
296
|
-
|
297
|
-
@
|
312
|
+
error_msg = "Invalid url_template #{url_template} provided."
|
313
|
+
@logger.log(Logger::ERROR, error_msg)
|
314
|
+
@error_handler.handle_error(InvalidInputsError.new(error_msg))
|
298
315
|
end
|
299
316
|
end
|
300
317
|
|