optimizely-sdk 4.0.1 → 5.0.0.pre.beta
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/audience.rb +5 -5
- data/lib/optimizely/config/datafile_project_config.rb +542 -539
- data/lib/optimizely/config_manager/http_project_config_manager.rb +12 -4
- data/lib/optimizely/config_manager/project_config_manager.rb +2 -1
- data/lib/optimizely/config_manager/static_project_config_manager.rb +3 -2
- data/lib/optimizely/event_dispatcher.rb +2 -4
- data/lib/optimizely/exceptions.rb +15 -1
- data/lib/optimizely/helpers/constants.rb +45 -1
- data/lib/optimizely/helpers/http_utils.rb +3 -0
- data/lib/optimizely/helpers/sdk_settings.rb +61 -0
- data/lib/optimizely/helpers/validator.rb +55 -0
- data/lib/optimizely/notification_center_registry.rb +71 -0
- data/lib/optimizely/odp/lru_cache.rb +114 -0
- data/lib/optimizely/odp/odp_config.rb +102 -0
- data/lib/optimizely/odp/odp_event.rb +75 -0
- data/lib/optimizely/odp/odp_event_api_manager.rb +70 -0
- data/lib/optimizely/odp/odp_event_manager.rb +286 -0
- data/lib/optimizely/odp/odp_manager.rb +159 -0
- data/lib/optimizely/odp/odp_segment_api_manager.rb +122 -0
- data/lib/optimizely/odp/odp_segment_manager.rb +97 -0
- data/lib/optimizely/optimizely_config.rb +1 -1
- data/lib/optimizely/optimizely_factory.rb +7 -2
- data/lib/optimizely/optimizely_user_context.rb +40 -6
- data/lib/optimizely/user_condition_evaluator.rb +1 -1
- data/lib/optimizely/version.rb +2 -2
- data/lib/optimizely.rb +142 -10
- metadata +14 -4
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright 2022, 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 'json'
|
20
|
+
require_relative '../exceptions'
|
21
|
+
|
22
|
+
module Optimizely
|
23
|
+
class OdpSegmentApiManager
|
24
|
+
# Interface that handles fetching audience segments.
|
25
|
+
|
26
|
+
def initialize(logger: nil, proxy_config: nil, timeout: nil)
|
27
|
+
@logger = logger || NoOpLogger.new
|
28
|
+
@proxy_config = proxy_config
|
29
|
+
@timeout = timeout || Optimizely::Helpers::Constants::ODP_GRAPHQL_API_CONFIG[:REQUEST_TIMEOUT]
|
30
|
+
end
|
31
|
+
|
32
|
+
# Fetch segments from the ODP GraphQL API.
|
33
|
+
#
|
34
|
+
# @param api_key - public api key
|
35
|
+
# @param api_host - domain url of the host
|
36
|
+
# @param user_key - vuid or fs_user_id (client device id or fullstack id)
|
37
|
+
# @param user_value - value of user_key
|
38
|
+
# @param segments_to_check - array of segments to check
|
39
|
+
|
40
|
+
def fetch_segments(api_key, api_host, user_key, user_value, segments_to_check)
|
41
|
+
url = "#{api_host}/v3/graphql"
|
42
|
+
|
43
|
+
headers = {'Content-Type' => 'application/json', 'x-api-key' => api_key.to_s}
|
44
|
+
|
45
|
+
payload = {
|
46
|
+
query: 'query($userId: String, $audiences: [String]) {' \
|
47
|
+
"customer(#{user_key}: $userId) " \
|
48
|
+
'{audiences(subset: $audiences) {edges {node {name state}}}}}',
|
49
|
+
variables: {
|
50
|
+
userId: user_value.to_s,
|
51
|
+
audiences: segments_to_check || []
|
52
|
+
}
|
53
|
+
}.to_json
|
54
|
+
|
55
|
+
begin
|
56
|
+
response = Helpers::HttpUtils.make_request(
|
57
|
+
url, :post, payload, headers, @timeout, @proxy_config
|
58
|
+
)
|
59
|
+
rescue SocketError, Timeout::Error, Net::ProtocolError, Errno::ECONNRESET => e
|
60
|
+
@logger.log(Logger::DEBUG, "GraphQL download failed: #{e}")
|
61
|
+
log_segments_failure('network error')
|
62
|
+
return nil
|
63
|
+
rescue Errno::EINVAL, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, HTTPUriError => e
|
64
|
+
log_segments_failure(e)
|
65
|
+
return nil
|
66
|
+
end
|
67
|
+
|
68
|
+
status = response.code.to_i
|
69
|
+
if status >= 400
|
70
|
+
log_segments_failure(status)
|
71
|
+
return nil
|
72
|
+
end
|
73
|
+
|
74
|
+
begin
|
75
|
+
response = JSON.parse(response.body)
|
76
|
+
rescue JSON::ParserError
|
77
|
+
log_segments_failure('JSON decode error')
|
78
|
+
return nil
|
79
|
+
end
|
80
|
+
|
81
|
+
if response.include?('errors')
|
82
|
+
error = response['errors'].first if response['errors'].is_a? Array
|
83
|
+
error_code = extract_component(error, 'extensions', 'code')
|
84
|
+
if error_code == 'INVALID_IDENTIFIER_EXCEPTION'
|
85
|
+
log_segments_failure('invalid identifier', Logger::WARN)
|
86
|
+
else
|
87
|
+
error_class = extract_component(error, 'extensions', 'classification') || 'decode error'
|
88
|
+
log_segments_failure(error_class)
|
89
|
+
end
|
90
|
+
return nil
|
91
|
+
end
|
92
|
+
|
93
|
+
audiences = extract_component(response, 'data', 'customer', 'audiences', 'edges')
|
94
|
+
unless audiences
|
95
|
+
log_segments_failure('decode error')
|
96
|
+
return nil
|
97
|
+
end
|
98
|
+
|
99
|
+
audiences.filter_map do |edge|
|
100
|
+
name = extract_component(edge, 'node', 'name')
|
101
|
+
state = extract_component(edge, 'node', 'state')
|
102
|
+
unless name && state
|
103
|
+
log_segments_failure('decode error')
|
104
|
+
return nil
|
105
|
+
end
|
106
|
+
state == 'qualified' ? name : nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def log_segments_failure(message, level = Logger::ERROR)
|
113
|
+
@logger.log(level, format(Optimizely::Helpers::Constants::ODP_LOGS[:FETCH_SEGMENTS_FAILED], message))
|
114
|
+
end
|
115
|
+
|
116
|
+
def extract_component(hash, *components)
|
117
|
+
hash.dig(*components) if hash.is_a? Hash
|
118
|
+
rescue TypeError
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright 2022, 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/logger'
|
20
|
+
require_relative 'odp_segment_api_manager'
|
21
|
+
|
22
|
+
module Optimizely
|
23
|
+
class OdpSegmentManager
|
24
|
+
# Schedules connections to ODP for audience segmentation and caches the results
|
25
|
+
attr_accessor :odp_config
|
26
|
+
attr_reader :segments_cache, :api_manager, :logger
|
27
|
+
|
28
|
+
def initialize(segments_cache, api_manager = nil, logger = nil, proxy_config = nil, timeout: nil)
|
29
|
+
@odp_config = nil
|
30
|
+
@logger = logger || NoOpLogger.new
|
31
|
+
@api_manager = api_manager || OdpSegmentApiManager.new(logger: @logger, proxy_config: proxy_config, timeout: timeout)
|
32
|
+
@segments_cache = segments_cache
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns qualified segments for the user from the cache or the ODP server if not in the cache.
|
36
|
+
#
|
37
|
+
# @param user_key - The key for identifying the id type.
|
38
|
+
# @param user_value - The id itself.
|
39
|
+
# @param options - An array of OptimizelySegmentOptions used to ignore and/or reset the cache.
|
40
|
+
#
|
41
|
+
# @return - Array of qualified segments.
|
42
|
+
def fetch_qualified_segments(user_key, user_value, options)
|
43
|
+
odp_api_key = @odp_config&.api_key
|
44
|
+
odp_api_host = @odp_config&.api_host
|
45
|
+
segments_to_check = @odp_config&.segments_to_check
|
46
|
+
|
47
|
+
if odp_api_key.nil? || odp_api_host.nil?
|
48
|
+
@logger.log(Logger::ERROR, format(Optimizely::Helpers::Constants::ODP_LOGS[:FETCH_SEGMENTS_FAILED], 'ODP is not enabled'))
|
49
|
+
return nil
|
50
|
+
end
|
51
|
+
|
52
|
+
unless segments_to_check&.size&.positive?
|
53
|
+
@logger.log(Logger::DEBUG, 'No segments are used in the project. Returning empty list')
|
54
|
+
return []
|
55
|
+
end
|
56
|
+
|
57
|
+
cache_key = make_cache_key(user_key, user_value)
|
58
|
+
|
59
|
+
ignore_cache = options.include?(OptimizelySegmentOption::IGNORE_CACHE)
|
60
|
+
reset_cache = options.include?(OptimizelySegmentOption::RESET_CACHE)
|
61
|
+
|
62
|
+
reset if reset_cache
|
63
|
+
|
64
|
+
unless ignore_cache || reset_cache
|
65
|
+
segments = @segments_cache.lookup(cache_key)
|
66
|
+
unless segments.nil?
|
67
|
+
@logger.log(Logger::DEBUG, 'ODP cache hit. Returning segments from cache.')
|
68
|
+
return segments
|
69
|
+
end
|
70
|
+
@logger.log(Logger::DEBUG, 'ODP cache miss.')
|
71
|
+
end
|
72
|
+
|
73
|
+
@logger.log(Logger::DEBUG, 'Making a call to ODP server.')
|
74
|
+
|
75
|
+
segments = @api_manager.fetch_segments(odp_api_key, odp_api_host, user_key, user_value, segments_to_check)
|
76
|
+
@segments_cache.save(cache_key, segments) unless segments.nil? || ignore_cache
|
77
|
+
segments
|
78
|
+
end
|
79
|
+
|
80
|
+
def reset
|
81
|
+
@segments_cache.reset
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def make_cache_key(user_key, user_value)
|
88
|
+
"#{user_key}-$-#{user_value}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class OptimizelySegmentOption
|
93
|
+
# Options for the OdpSegmentManager
|
94
|
+
IGNORE_CACHE = :IGNORE_CACHE
|
95
|
+
RESET_CACHE = :RESET_CACHE
|
96
|
+
end
|
97
|
+
end
|
@@ -201,7 +201,7 @@ module Optimizely
|
|
201
201
|
def stringify_conditions(conditions, audiences_map)
|
202
202
|
operand = 'OR'
|
203
203
|
conditions_str = ''
|
204
|
-
length = conditions.length
|
204
|
+
length = conditions.length
|
205
205
|
return '' if length.zero?
|
206
206
|
return "\"#{lookup_name_from_id(conditions[0], audiences_map)}\"" if length == 1 && !OPERATORS.include?(conditions[0])
|
207
207
|
|
@@ -126,6 +126,7 @@ module Optimizely
|
|
126
126
|
# @param user_profile_service - Optional UserProfileServiceInterface Provides methods to store and retreive user profiles.
|
127
127
|
# @param config_manager - Optional ConfigManagerInterface Responds to 'config' method.
|
128
128
|
# @param notification_center - Optional Instance of NotificationCenter.
|
129
|
+
# @param settings: Optional instance of OptimizelySdkSettings for sdk configuration.
|
129
130
|
#
|
130
131
|
# if @max_event_batch_size and @max_event_flush_interval are nil then default batchsize and flush_interval
|
131
132
|
# will be used to setup batchEventProcessor.
|
@@ -138,7 +139,8 @@ module Optimizely
|
|
138
139
|
skip_json_validation = false, # rubocop:disable Style/OptionalBooleanParameter
|
139
140
|
user_profile_service = nil,
|
140
141
|
config_manager = nil,
|
141
|
-
notification_center = nil
|
142
|
+
notification_center = nil,
|
143
|
+
settings = nil
|
142
144
|
)
|
143
145
|
|
144
146
|
error_handler ||= NoOpErrorHandler.new
|
@@ -174,7 +176,10 @@ module Optimizely
|
|
174
176
|
sdk_key,
|
175
177
|
config_manager,
|
176
178
|
notification_center,
|
177
|
-
event_processor
|
179
|
+
event_processor,
|
180
|
+
[],
|
181
|
+
{},
|
182
|
+
settings
|
178
183
|
)
|
179
184
|
end
|
180
185
|
end
|
@@ -26,7 +26,7 @@ module Optimizely
|
|
26
26
|
|
27
27
|
OptimizelyDecisionContext = Struct.new(:flag_key, :rule_key)
|
28
28
|
OptimizelyForcedDecision = Struct.new(:variation_key)
|
29
|
-
def initialize(optimizely_client, user_id, user_attributes)
|
29
|
+
def initialize(optimizely_client, user_id, user_attributes, identify: true)
|
30
30
|
@attr_mutex = Mutex.new
|
31
31
|
@forced_decision_mutex = Mutex.new
|
32
32
|
@qualified_segment_mutex = Mutex.new
|
@@ -34,13 +34,15 @@ module Optimizely
|
|
34
34
|
@user_id = user_id
|
35
35
|
@user_attributes = user_attributes.nil? ? {} : user_attributes.clone
|
36
36
|
@forced_decisions = {}
|
37
|
-
@qualified_segments =
|
37
|
+
@qualified_segments = nil
|
38
|
+
|
39
|
+
@optimizely_client&.identify_user(user_id: user_id) if identify
|
38
40
|
end
|
39
41
|
|
40
42
|
def clone
|
41
|
-
user_context = OptimizelyUserContext.new(@optimizely_client, @user_id, user_attributes)
|
43
|
+
user_context = OptimizelyUserContext.new(@optimizely_client, @user_id, user_attributes, identify: false)
|
42
44
|
@forced_decision_mutex.synchronize { user_context.instance_variable_set('@forced_decisions', @forced_decisions.dup) unless @forced_decisions.empty? }
|
43
|
-
@qualified_segment_mutex.synchronize { user_context.instance_variable_set('@qualified_segments', @qualified_segments.dup) unless @qualified_segments.
|
45
|
+
@qualified_segment_mutex.synchronize { user_context.instance_variable_set('@qualified_segments', @qualified_segments.dup) unless @qualified_segments.nil? }
|
44
46
|
user_context
|
45
47
|
end
|
46
48
|
|
@@ -194,11 +196,43 @@ module Optimizely
|
|
194
196
|
# Checks if user is qualified for the provided segment.
|
195
197
|
#
|
196
198
|
# @param segment - A segment name
|
199
|
+
# @return true if qualified.
|
197
200
|
|
198
201
|
def qualified_for?(segment)
|
199
|
-
|
202
|
+
qualified = false
|
203
|
+
@qualified_segment_mutex.synchronize do
|
204
|
+
break if @qualified_segments.nil? || @qualified_segments.empty?
|
205
|
+
|
206
|
+
qualified = @qualified_segments.include?(segment)
|
207
|
+
end
|
208
|
+
qualified
|
209
|
+
end
|
210
|
+
|
211
|
+
# Fetch all qualified segments for the user context.
|
212
|
+
#
|
213
|
+
# The segments fetched will be saved in `@qualified_segments` and can be accessed any time.
|
214
|
+
#
|
215
|
+
# @param options - A set of options for fetching qualified segments (optional).
|
216
|
+
# @param block - An optional block to call after segments have been fetched.
|
217
|
+
# If a block is provided, segments will be fetched on a separate thread.
|
218
|
+
# Block will be called with a boolean indicating if the fetch succeeded.
|
219
|
+
# @return If no block is provided, a boolean indicating whether the fetch was successful.
|
220
|
+
# Otherwise, returns a thread handle and the status boolean is passed to the block.
|
200
221
|
|
201
|
-
|
222
|
+
def fetch_qualified_segments(options: [], &block)
|
223
|
+
fetch_segments = lambda do |opts, callback|
|
224
|
+
segments = @optimizely_client&.fetch_qualified_segments(user_id: @user_id, options: opts)
|
225
|
+
self.qualified_segments = segments
|
226
|
+
success = !segments.nil?
|
227
|
+
callback&.call(success)
|
228
|
+
success
|
229
|
+
end
|
230
|
+
|
231
|
+
if block_given?
|
232
|
+
Thread.new(options, block, &fetch_segments)
|
233
|
+
else
|
234
|
+
fetch_segments.call(options, nil)
|
235
|
+
end
|
202
236
|
end
|
203
237
|
end
|
204
238
|
end
|
@@ -331,7 +331,7 @@ module Optimizely
|
|
331
331
|
end
|
332
332
|
|
333
333
|
def qualified_evaluator(condition)
|
334
|
-
# Evaluate the given match condition for the given user
|
334
|
+
# Evaluate the given match condition for the given user qualified segments.
|
335
335
|
# Returns boolean true if condition value is in the user's qualified segments,
|
336
336
|
# false if the condition value is not in the user's qualified segments,
|
337
337
|
# nil if the condition value isn't a string.
|
data/lib/optimizely/version.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2016-
|
4
|
+
# Copyright 2016-2023, 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.
|
@@ -17,5 +17,5 @@
|
|
17
17
|
#
|
18
18
|
module Optimizely
|
19
19
|
CLIENT_ENGINE = 'ruby-sdk'
|
20
|
-
VERSION = '
|
20
|
+
VERSION = '5.0.0-beta'
|
21
21
|
end
|
data/lib/optimizely.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2016-
|
4
|
+
# Copyright 2016-2023, 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.
|
@@ -25,7 +25,7 @@ require_relative 'optimizely/decide/optimizely_decision_message'
|
|
25
25
|
require_relative 'optimizely/decision_service'
|
26
26
|
require_relative 'optimizely/error_handler'
|
27
27
|
require_relative 'optimizely/event_builder'
|
28
|
-
require_relative 'optimizely/event/
|
28
|
+
require_relative 'optimizely/event/batch_event_processor'
|
29
29
|
require_relative 'optimizely/event/event_factory'
|
30
30
|
require_relative 'optimizely/event/user_event_factory'
|
31
31
|
require_relative 'optimizely/event_dispatcher'
|
@@ -36,8 +36,12 @@ require_relative 'optimizely/helpers/validator'
|
|
36
36
|
require_relative 'optimizely/helpers/variable_type'
|
37
37
|
require_relative 'optimizely/logger'
|
38
38
|
require_relative 'optimizely/notification_center'
|
39
|
+
require_relative 'optimizely/notification_center_registry'
|
39
40
|
require_relative 'optimizely/optimizely_config'
|
40
41
|
require_relative 'optimizely/optimizely_user_context'
|
42
|
+
require_relative 'optimizely/odp/lru_cache'
|
43
|
+
require_relative 'optimizely/odp/odp_manager'
|
44
|
+
require_relative 'optimizely/helpers/sdk_settings'
|
41
45
|
|
42
46
|
module Optimizely
|
43
47
|
class Project
|
@@ -46,7 +50,7 @@ module Optimizely
|
|
46
50
|
attr_reader :notification_center
|
47
51
|
# @api no-doc
|
48
52
|
attr_reader :config_manager, :decision_service, :error_handler, :event_dispatcher,
|
49
|
-
:event_processor, :logger, :stopped
|
53
|
+
:event_processor, :logger, :odp_manager, :stopped
|
50
54
|
|
51
55
|
# Constructor for Projects.
|
52
56
|
#
|
@@ -62,6 +66,9 @@ module Optimizely
|
|
62
66
|
# @param config_manager - Optional Responds to 'config' method.
|
63
67
|
# @param notification_center - Optional Instance of NotificationCenter.
|
64
68
|
# @param event_processor - Optional Responds to process.
|
69
|
+
# @param default_decide_options: Optional default decision options.
|
70
|
+
# @param event_processor_options: Optional hash of options to be passed to the default batch event processor.
|
71
|
+
# @param settings: Optional instance of OptimizelySdkSettings for sdk configuration.
|
65
72
|
|
66
73
|
def initialize( # rubocop:disable Metrics/ParameterLists
|
67
74
|
datafile = nil,
|
@@ -74,13 +81,16 @@ module Optimizely
|
|
74
81
|
config_manager = nil,
|
75
82
|
notification_center = nil,
|
76
83
|
event_processor = nil,
|
77
|
-
default_decide_options = []
|
84
|
+
default_decide_options = [],
|
85
|
+
event_processor_options = {},
|
86
|
+
settings = nil
|
78
87
|
)
|
79
88
|
@logger = logger || NoOpLogger.new
|
80
89
|
@error_handler = error_handler || NoOpErrorHandler.new
|
81
90
|
@event_dispatcher = event_dispatcher || EventDispatcher.new(logger: @logger, error_handler: @error_handler)
|
82
91
|
@user_profile_service = user_profile_service
|
83
92
|
@default_decide_options = []
|
93
|
+
@sdk_settings = settings
|
84
94
|
|
85
95
|
if default_decide_options.is_a? Array
|
86
96
|
@default_decide_options = default_decide_options.clone
|
@@ -89,6 +99,11 @@ module Optimizely
|
|
89
99
|
@default_decide_options = []
|
90
100
|
end
|
91
101
|
|
102
|
+
unless event_processor_options.is_a? Hash
|
103
|
+
@logger.log(Logger::DEBUG, 'Provided event processor options is not a hash.')
|
104
|
+
event_processor_options = {}
|
105
|
+
end
|
106
|
+
|
92
107
|
begin
|
93
108
|
validate_instantiation_options
|
94
109
|
rescue InvalidInputError => e
|
@@ -98,7 +113,7 @@ module Optimizely
|
|
98
113
|
|
99
114
|
@notification_center = notification_center.is_a?(Optimizely::NotificationCenter) ? notification_center : NotificationCenter.new(@logger, @error_handler)
|
100
115
|
|
101
|
-
@config_manager = if config_manager.respond_to?(:config)
|
116
|
+
@config_manager = if config_manager.respond_to?(:config) && config_manager.respond_to?(:sdk_key)
|
102
117
|
config_manager
|
103
118
|
elsif sdk_key
|
104
119
|
HTTPProjectConfigManager.new(
|
@@ -113,12 +128,20 @@ module Optimizely
|
|
113
128
|
StaticProjectConfigManager.new(datafile, @logger, @error_handler, skip_json_validation)
|
114
129
|
end
|
115
130
|
|
131
|
+
setup_odp!(@config_manager.sdk_key)
|
132
|
+
|
116
133
|
@decision_service = DecisionService.new(@logger, @user_profile_service)
|
117
134
|
|
118
135
|
@event_processor = if event_processor.respond_to?(:process)
|
119
136
|
event_processor
|
120
137
|
else
|
121
|
-
|
138
|
+
BatchEventProcessor.new(
|
139
|
+
event_dispatcher: @event_dispatcher,
|
140
|
+
logger: @logger,
|
141
|
+
notification_center: @notification_center,
|
142
|
+
batch_size: event_processor_options[:batch_size] || BatchEventProcessor::DEFAULT_BATCH_SIZE,
|
143
|
+
flush_interval: event_processor_options[:flush_interval] || BatchEventProcessor::DEFAULT_BATCH_INTERVAL
|
144
|
+
)
|
122
145
|
end
|
123
146
|
end
|
124
147
|
|
@@ -507,7 +530,7 @@ module Optimizely
|
|
507
530
|
return false
|
508
531
|
end
|
509
532
|
|
510
|
-
user_context =
|
533
|
+
user_context = OptimizelyUserContext.new(self, user_id, attributes, identify: false)
|
511
534
|
decision, = @decision_service.get_variation_for_feature(config, feature_flag, user_context)
|
512
535
|
|
513
536
|
feature_enabled = false
|
@@ -747,7 +770,7 @@ module Optimizely
|
|
747
770
|
return nil
|
748
771
|
end
|
749
772
|
|
750
|
-
user_context =
|
773
|
+
user_context = OptimizelyUserContext.new(self, user_id, attributes, identify: false)
|
751
774
|
decision, = @decision_service.get_variation_for_feature(config, feature_flag, user_context)
|
752
775
|
variation = decision ? decision['variation'] : nil
|
753
776
|
feature_enabled = variation ? variation['featureEnabled'] : false
|
@@ -816,6 +839,7 @@ module Optimizely
|
|
816
839
|
@stopped = true
|
817
840
|
@config_manager.stop! if @config_manager.respond_to?(:stop!)
|
818
841
|
@event_processor.stop! if @event_processor.respond_to?(:stop!)
|
842
|
+
@odp_manager.stop!
|
819
843
|
end
|
820
844
|
|
821
845
|
def get_optimizely_config
|
@@ -869,6 +893,52 @@ module Optimizely
|
|
869
893
|
end
|
870
894
|
end
|
871
895
|
|
896
|
+
# Send an event to the ODP server.
|
897
|
+
#
|
898
|
+
# @param action - the event action name. Cannot be nil or empty string.
|
899
|
+
# @param identifiers - a hash for identifiers. The caller must provide at least one key-value pair.
|
900
|
+
# @param type - the event type (default = "fullstack").
|
901
|
+
# @param data - a hash for associated data. The default event data will be added to this data before sending to the ODP server.
|
902
|
+
|
903
|
+
def send_odp_event(action:, identifiers:, type: Helpers::Constants::ODP_MANAGER_CONFIG[:EVENT_TYPE], data: {})
|
904
|
+
unless identifiers.is_a?(Hash) && !identifiers.empty?
|
905
|
+
@logger.log(Logger::ERROR, 'ODP events must have at least one key-value pair in identifiers.')
|
906
|
+
return
|
907
|
+
end
|
908
|
+
|
909
|
+
unless is_valid
|
910
|
+
@logger.log(Logger::ERROR, InvalidProjectConfigError.new('send_odp_event').message)
|
911
|
+
return
|
912
|
+
end
|
913
|
+
|
914
|
+
if action.nil? || action.empty?
|
915
|
+
@logger.log(Logger::ERROR, Helpers::Constants::ODP_LOGS[:ODP_INVALID_ACTION])
|
916
|
+
return
|
917
|
+
end
|
918
|
+
|
919
|
+
type = Helpers::Constants::ODP_MANAGER_CONFIG[:EVENT_TYPE] if type.nil? || type.empty?
|
920
|
+
|
921
|
+
@odp_manager.send_event(type: type, action: action, identifiers: identifiers, data: data)
|
922
|
+
end
|
923
|
+
|
924
|
+
def identify_user(user_id:)
|
925
|
+
unless is_valid
|
926
|
+
@logger.log(Logger::ERROR, InvalidProjectConfigError.new('identify_user').message)
|
927
|
+
return
|
928
|
+
end
|
929
|
+
|
930
|
+
@odp_manager.identify_user(user_id: user_id)
|
931
|
+
end
|
932
|
+
|
933
|
+
def fetch_qualified_segments(user_id:, options: [])
|
934
|
+
unless is_valid
|
935
|
+
@logger.log(Logger::ERROR, InvalidProjectConfigError.new('fetch_qualified_segments').message)
|
936
|
+
return
|
937
|
+
end
|
938
|
+
|
939
|
+
@odp_manager.fetch_qualified_segments(user_id: user_id, options: options)
|
940
|
+
end
|
941
|
+
|
872
942
|
private
|
873
943
|
|
874
944
|
def get_variation_with_config(experiment_key, user_id, attributes, config)
|
@@ -888,7 +958,7 @@ module Optimizely
|
|
888
958
|
|
889
959
|
return nil unless user_inputs_valid?(attributes)
|
890
960
|
|
891
|
-
user_context =
|
961
|
+
user_context = OptimizelyUserContext.new(self, user_id, attributes, identify: false)
|
892
962
|
variation_id, = @decision_service.get_variation(config, experiment_id, user_context)
|
893
963
|
variation = config.get_variation_from_id(experiment_key, variation_id) unless variation_id.nil?
|
894
964
|
variation_key = variation['key'] if variation
|
@@ -955,7 +1025,7 @@ module Optimizely
|
|
955
1025
|
return nil
|
956
1026
|
end
|
957
1027
|
|
958
|
-
user_context =
|
1028
|
+
user_context = OptimizelyUserContext.new(self, user_id, attributes, identify: false)
|
959
1029
|
decision, = @decision_service.get_variation_for_feature(config, feature_flag, user_context)
|
960
1030
|
variation = decision ? decision['variation'] : nil
|
961
1031
|
feature_enabled = variation ? variation['featureEnabled'] : false
|
@@ -1126,5 +1196,67 @@ module Optimizely
|
|
1126
1196
|
def project_config
|
1127
1197
|
@config_manager.config
|
1128
1198
|
end
|
1199
|
+
|
1200
|
+
def update_odp_config_on_datafile_update
|
1201
|
+
# if datafile isn't ready, expects to be called again by the internal notification_center
|
1202
|
+
return if @config_manager.respond_to?(:ready?) && !@config_manager.ready?
|
1203
|
+
|
1204
|
+
config = @config_manager&.config
|
1205
|
+
return unless config
|
1206
|
+
|
1207
|
+
@odp_manager.update_odp_config(config.public_key_for_odp, config.host_for_odp, config.all_segments)
|
1208
|
+
end
|
1209
|
+
|
1210
|
+
def setup_odp!(sdk_key)
|
1211
|
+
unless @sdk_settings.is_a? Optimizely::Helpers::OptimizelySdkSettings
|
1212
|
+
@logger.log(Logger::DEBUG, 'Provided sdk_settings is not an OptimizelySdkSettings instance.') unless @sdk_settings.nil?
|
1213
|
+
@sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new
|
1214
|
+
end
|
1215
|
+
|
1216
|
+
if !@sdk_settings.odp_segment_manager.nil? && !Helpers::Validator.segment_manager_valid?(@sdk_settings.odp_segment_manager)
|
1217
|
+
@logger.log(Logger::ERROR, 'Invalid ODP segment manager, reverting to default.')
|
1218
|
+
@sdk_settings.odp_segment_manager = nil
|
1219
|
+
end
|
1220
|
+
|
1221
|
+
if !@sdk_settings.odp_event_manager.nil? && !Helpers::Validator.event_manager_valid?(@sdk_settings.odp_event_manager)
|
1222
|
+
@logger.log(Logger::ERROR, 'Invalid ODP event manager, reverting to default.')
|
1223
|
+
@sdk_settings.odp_event_manager = nil
|
1224
|
+
end
|
1225
|
+
|
1226
|
+
if !@sdk_settings.odp_segments_cache.nil? && !Helpers::Validator.segments_cache_valid?(@sdk_settings.odp_segments_cache)
|
1227
|
+
@logger.log(Logger::ERROR, 'Invalid ODP segments cache, reverting to default.')
|
1228
|
+
@sdk_settings.odp_segments_cache = nil
|
1229
|
+
end
|
1230
|
+
|
1231
|
+
# no need to instantiate a cache if a custom cache or segment manager is provided.
|
1232
|
+
if !@sdk_settings.odp_disabled && @sdk_settings.odp_segment_manager.nil?
|
1233
|
+
@sdk_settings.odp_segments_cache ||= LRUCache.new(
|
1234
|
+
@sdk_settings.segments_cache_size,
|
1235
|
+
@sdk_settings.segments_cache_timeout_in_secs
|
1236
|
+
)
|
1237
|
+
end
|
1238
|
+
|
1239
|
+
@odp_manager = OdpManager.new(
|
1240
|
+
disable: @sdk_settings.odp_disabled,
|
1241
|
+
segment_manager: @sdk_settings.odp_segment_manager,
|
1242
|
+
event_manager: @sdk_settings.odp_event_manager,
|
1243
|
+
segments_cache: @sdk_settings.odp_segments_cache,
|
1244
|
+
fetch_segments_timeout: @sdk_settings.fetch_segments_timeout,
|
1245
|
+
odp_event_timeout: @sdk_settings.odp_event_timeout,
|
1246
|
+
odp_flush_interval: @sdk_settings.odp_flush_interval,
|
1247
|
+
logger: @logger
|
1248
|
+
)
|
1249
|
+
|
1250
|
+
return if @sdk_settings.odp_disabled
|
1251
|
+
|
1252
|
+
Optimizely::NotificationCenterRegistry
|
1253
|
+
.get_notification_center(sdk_key, @logger)
|
1254
|
+
&.add_notification_listener(
|
1255
|
+
NotificationCenter::NOTIFICATION_TYPES[:OPTIMIZELY_CONFIG_UPDATE],
|
1256
|
+
method(:update_odp_config_on_datafile_update)
|
1257
|
+
)
|
1258
|
+
|
1259
|
+
update_odp_config_on_datafile_update
|
1260
|
+
end
|
1129
1261
|
end
|
1130
1262
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: optimizely-sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 5.0.0.pre.beta
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Optimizely
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-04-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -169,10 +169,20 @@ files:
|
|
169
169
|
- lib/optimizely/helpers/event_tag_utils.rb
|
170
170
|
- lib/optimizely/helpers/group.rb
|
171
171
|
- lib/optimizely/helpers/http_utils.rb
|
172
|
+
- lib/optimizely/helpers/sdk_settings.rb
|
172
173
|
- lib/optimizely/helpers/validator.rb
|
173
174
|
- lib/optimizely/helpers/variable_type.rb
|
174
175
|
- lib/optimizely/logger.rb
|
175
176
|
- lib/optimizely/notification_center.rb
|
177
|
+
- lib/optimizely/notification_center_registry.rb
|
178
|
+
- lib/optimizely/odp/lru_cache.rb
|
179
|
+
- lib/optimizely/odp/odp_config.rb
|
180
|
+
- lib/optimizely/odp/odp_event.rb
|
181
|
+
- lib/optimizely/odp/odp_event_api_manager.rb
|
182
|
+
- lib/optimizely/odp/odp_event_manager.rb
|
183
|
+
- lib/optimizely/odp/odp_manager.rb
|
184
|
+
- lib/optimizely/odp/odp_segment_api_manager.rb
|
185
|
+
- lib/optimizely/odp/odp_segment_manager.rb
|
176
186
|
- lib/optimizely/optimizely_config.rb
|
177
187
|
- lib/optimizely/optimizely_factory.rb
|
178
188
|
- lib/optimizely/optimizely_user_context.rb
|
@@ -197,9 +207,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
197
207
|
version: '2.7'
|
198
208
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
199
209
|
requirements:
|
200
|
-
- - "
|
210
|
+
- - ">"
|
201
211
|
- !ruby/object:Gem::Version
|
202
|
-
version:
|
212
|
+
version: 1.3.1
|
203
213
|
requirements: []
|
204
214
|
rubygems_version: 3.3.7
|
205
215
|
signing_key:
|