optimizely-sdk 4.0.1 → 5.0.0.pre.beta
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|