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,75 @@
|
|
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 '../helpers/constants'
|
21
|
+
|
22
|
+
module Optimizely
|
23
|
+
class OdpEvent
|
24
|
+
# Representation of an odp event which can be sent to the Optimizely odp platform.
|
25
|
+
|
26
|
+
KEY_FOR_USER_ID = Helpers::Constants::ODP_MANAGER_CONFIG[:KEY_FOR_USER_ID]
|
27
|
+
|
28
|
+
def initialize(type:, action:, identifiers:, data:)
|
29
|
+
@type = type
|
30
|
+
@action = action
|
31
|
+
@identifiers = convert_identifiers(identifiers)
|
32
|
+
@data = add_common_event_data(data)
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_common_event_data(custom_data)
|
36
|
+
data = {
|
37
|
+
idempotence_id: SecureRandom.uuid,
|
38
|
+
data_source_type: 'sdk',
|
39
|
+
data_source: 'ruby-sdk',
|
40
|
+
data_source_version: VERSION
|
41
|
+
}
|
42
|
+
data.update(custom_data)
|
43
|
+
data
|
44
|
+
end
|
45
|
+
|
46
|
+
def convert_identifiers(identifiers)
|
47
|
+
# Convert incorrect case/separator of identifier key `fs_user_id`
|
48
|
+
# (ie. `fs-user-id`, `FS_USER_ID`).
|
49
|
+
|
50
|
+
identifiers.clone.each_key do |key|
|
51
|
+
break if key == KEY_FOR_USER_ID
|
52
|
+
|
53
|
+
if ['fs-user-id', KEY_FOR_USER_ID].include?(key.downcase)
|
54
|
+
identifiers[KEY_FOR_USER_ID] = identifiers.delete(key)
|
55
|
+
break
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
identifiers
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_json(*_args)
|
63
|
+
{
|
64
|
+
type: @type,
|
65
|
+
action: @action,
|
66
|
+
identifiers: @identifiers,
|
67
|
+
data: @data
|
68
|
+
}.to_json
|
69
|
+
end
|
70
|
+
|
71
|
+
def ==(other)
|
72
|
+
to_json == other.to_json
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,70 @@
|
|
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
|
+
|
21
|
+
module Optimizely
|
22
|
+
class OdpEventApiManager
|
23
|
+
# Interface that handles sending ODP events.
|
24
|
+
|
25
|
+
def initialize(logger: nil, proxy_config: nil, timeout: nil)
|
26
|
+
@logger = logger || NoOpLogger.new
|
27
|
+
@proxy_config = proxy_config
|
28
|
+
@timeout = timeout || Optimizely::Helpers::Constants::ODP_REST_API_CONFIG[:REQUEST_TIMEOUT]
|
29
|
+
end
|
30
|
+
|
31
|
+
# Send events to the ODP Events API.
|
32
|
+
#
|
33
|
+
# @param api_key - public api key
|
34
|
+
# @param api_host - domain url of the host
|
35
|
+
# @param events - array of events to send
|
36
|
+
|
37
|
+
def send_odp_events(api_key, api_host, events)
|
38
|
+
should_retry = false
|
39
|
+
url = "#{api_host}/v3/events"
|
40
|
+
|
41
|
+
headers = {'Content-Type' => 'application/json', 'x-api-key' => api_key.to_s}
|
42
|
+
|
43
|
+
begin
|
44
|
+
response = Helpers::HttpUtils.make_request(
|
45
|
+
url, :post, events.to_json, headers, @timeout, @proxy_config
|
46
|
+
)
|
47
|
+
rescue SocketError, Timeout::Error, Errno::ECONNRESET, Errno::EHOSTUNREACH, Errno::EFAULT, Errno::ENETUNREACH, Errno::ENETDOWN, Errno::ECONNREFUSED
|
48
|
+
log_failure('network error')
|
49
|
+
should_retry = true
|
50
|
+
return should_retry
|
51
|
+
rescue StandardError => e
|
52
|
+
log_failure(e)
|
53
|
+
return should_retry
|
54
|
+
end
|
55
|
+
|
56
|
+
status = response.code.to_i
|
57
|
+
if status >= 400
|
58
|
+
log_failure(!response.body.empty? ? response.body : "#{status}: #{response.message}")
|
59
|
+
should_retry = status >= 500
|
60
|
+
end
|
61
|
+
should_retry
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def log_failure(message, level = Logger::ERROR)
|
67
|
+
@logger.log(level, format(Optimizely::Helpers::Constants::ODP_LOGS[:ODP_EVENT_FAILED], message))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,286 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright 2019, 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
|
+
require_relative 'odp_event_api_manager'
|
19
|
+
require_relative '../helpers/constants'
|
20
|
+
require_relative 'odp_event'
|
21
|
+
|
22
|
+
module Optimizely
|
23
|
+
class OdpEventManager
|
24
|
+
# Events passed to the OdpEventManager are immediately added to an EventQueue.
|
25
|
+
# The OdpEventManager maintains a single consumer thread that pulls events off of
|
26
|
+
# the BlockingQueue and buffers them for either a configured batch size or for a
|
27
|
+
# maximum duration before the resulting OdpEvent is sent to Odp.
|
28
|
+
|
29
|
+
attr_reader :batch_size, :api_manager, :logger
|
30
|
+
attr_accessor :odp_config
|
31
|
+
|
32
|
+
def initialize(
|
33
|
+
api_manager: nil,
|
34
|
+
logger: NoOpLogger.new,
|
35
|
+
proxy_config: nil,
|
36
|
+
request_timeout: nil,
|
37
|
+
flush_interval: nil
|
38
|
+
)
|
39
|
+
@odp_config = nil
|
40
|
+
@api_host = nil
|
41
|
+
@api_key = nil
|
42
|
+
|
43
|
+
@mutex = Mutex.new
|
44
|
+
@event_queue = SizedQueue.new(Optimizely::Helpers::Constants::ODP_EVENT_MANAGER[:DEFAULT_QUEUE_CAPACITY])
|
45
|
+
@queue_capacity = Helpers::Constants::ODP_EVENT_MANAGER[:DEFAULT_QUEUE_CAPACITY]
|
46
|
+
# received signal should be sent after adding item to event_queue
|
47
|
+
@received = ConditionVariable.new
|
48
|
+
@logger = logger
|
49
|
+
@api_manager = api_manager || OdpEventApiManager.new(logger: @logger, proxy_config: proxy_config, timeout: request_timeout)
|
50
|
+
@flush_interval = flush_interval || Helpers::Constants::ODP_EVENT_MANAGER[:DEFAULT_FLUSH_INTERVAL_SECONDS]
|
51
|
+
@batch_size = @flush_interval&.zero? ? 1 : Helpers::Constants::ODP_EVENT_MANAGER[:DEFAULT_BATCH_SIZE]
|
52
|
+
@flush_deadline = 0
|
53
|
+
@retry_count = Helpers::Constants::ODP_EVENT_MANAGER[:DEFAULT_RETRY_COUNT]
|
54
|
+
# current_batch should only be accessed by processing thread
|
55
|
+
@current_batch = []
|
56
|
+
@thread = nil
|
57
|
+
@thread_exception = false
|
58
|
+
end
|
59
|
+
|
60
|
+
def start!(odp_config)
|
61
|
+
if running?
|
62
|
+
@logger.log(Logger::WARN, 'Service already started.')
|
63
|
+
return
|
64
|
+
end
|
65
|
+
|
66
|
+
@odp_config = odp_config
|
67
|
+
@api_host = odp_config.api_host
|
68
|
+
@api_key = odp_config.api_key
|
69
|
+
|
70
|
+
@thread = Thread.new { run }
|
71
|
+
@logger.log(Logger::INFO, 'Starting scheduler.')
|
72
|
+
end
|
73
|
+
|
74
|
+
def flush
|
75
|
+
begin
|
76
|
+
@event_queue.push(:FLUSH_SIGNAL, true)
|
77
|
+
rescue ThreadError
|
78
|
+
@logger.log(Logger::ERROR, 'Error flushing ODP event queue.')
|
79
|
+
return
|
80
|
+
end
|
81
|
+
|
82
|
+
@mutex.synchronize do
|
83
|
+
@received.signal
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def update_config
|
88
|
+
begin
|
89
|
+
# Adds update config signal to event_queue.
|
90
|
+
@event_queue.push(:UPDATE_CONFIG, true)
|
91
|
+
rescue ThreadError
|
92
|
+
@logger.log(Logger::ERROR, 'Error updating ODP config for the event queue')
|
93
|
+
end
|
94
|
+
|
95
|
+
@mutex.synchronize do
|
96
|
+
@received.signal
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def dispatch(event)
|
101
|
+
if @thread_exception
|
102
|
+
@logger.log(Logger::ERROR, format(Helpers::Constants::ODP_LOGS[:ODP_EVENT_FAILED], 'Queue is down'))
|
103
|
+
return
|
104
|
+
end
|
105
|
+
|
106
|
+
# if the processor has been explicitly stopped. Don't accept tasks
|
107
|
+
unless running?
|
108
|
+
@logger.log(Logger::WARN, 'ODP event queue is shutdown, not accepting events.')
|
109
|
+
return
|
110
|
+
end
|
111
|
+
|
112
|
+
begin
|
113
|
+
@logger.log(Logger::DEBUG, 'ODP event queue: adding event.')
|
114
|
+
@event_queue.push(event, true)
|
115
|
+
rescue => e
|
116
|
+
@logger.log(Logger::WARN, format(Helpers::Constants::ODP_LOGS[:ODP_EVENT_FAILED], e.message))
|
117
|
+
return
|
118
|
+
end
|
119
|
+
|
120
|
+
@mutex.synchronize do
|
121
|
+
@received.signal
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def send_event(type:, action:, identifiers:, data:)
|
126
|
+
case @odp_config&.odp_state
|
127
|
+
when nil
|
128
|
+
@logger.log(Logger::DEBUG, 'ODP event queue: cannot send before config has been set.')
|
129
|
+
return
|
130
|
+
when OdpConfig::ODP_CONFIG_STATE[:UNDETERMINED]
|
131
|
+
@logger.log(Logger::DEBUG, 'ODP event queue: cannot send before the datafile has loaded.')
|
132
|
+
return
|
133
|
+
when OdpConfig::ODP_CONFIG_STATE[:NOT_INTEGRATED]
|
134
|
+
@logger.log(Logger::DEBUG, Helpers::Constants::ODP_LOGS[:ODP_NOT_INTEGRATED])
|
135
|
+
return
|
136
|
+
end
|
137
|
+
|
138
|
+
event = Optimizely::OdpEvent.new(type: type, action: action, identifiers: identifiers, data: data)
|
139
|
+
dispatch(event)
|
140
|
+
end
|
141
|
+
|
142
|
+
def stop!
|
143
|
+
return unless running?
|
144
|
+
|
145
|
+
begin
|
146
|
+
@event_queue.push(:SHUTDOWN_SIGNAL, true)
|
147
|
+
rescue ThreadError
|
148
|
+
@logger.log(Logger::ERROR, 'Error stopping ODP event queue.')
|
149
|
+
return
|
150
|
+
end
|
151
|
+
|
152
|
+
@event_queue.close
|
153
|
+
|
154
|
+
@mutex.synchronize do
|
155
|
+
@received.signal
|
156
|
+
end
|
157
|
+
|
158
|
+
@logger.log(Logger::INFO, 'Stopping ODP event queue.')
|
159
|
+
|
160
|
+
@thread.join
|
161
|
+
|
162
|
+
@logger.log(Logger::ERROR, format(Helpers::Constants::ODP_LOGS[:ODP_EVENT_FAILED], @current_batch.to_json)) unless @current_batch.empty?
|
163
|
+
end
|
164
|
+
|
165
|
+
def running?
|
166
|
+
!!@thread && !!@thread.status && !@event_queue.closed?
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
def run
|
172
|
+
loop do
|
173
|
+
@mutex.synchronize do
|
174
|
+
@received.wait(@mutex, queue_timeout) if @event_queue.empty?
|
175
|
+
end
|
176
|
+
|
177
|
+
begin
|
178
|
+
item = @event_queue.pop(true)
|
179
|
+
rescue ThreadError => e
|
180
|
+
raise unless e.message == 'queue empty'
|
181
|
+
|
182
|
+
item = nil
|
183
|
+
end
|
184
|
+
|
185
|
+
case item
|
186
|
+
when :SHUTDOWN_SIGNAL
|
187
|
+
@logger.log(Logger::DEBUG, 'ODP event queue: received shutdown signal.')
|
188
|
+
break
|
189
|
+
|
190
|
+
when :FLUSH_SIGNAL
|
191
|
+
@logger.log(Logger::DEBUG, 'ODP event queue: received flush signal.')
|
192
|
+
flush_batch!
|
193
|
+
|
194
|
+
when :UPDATE_CONFIG
|
195
|
+
@logger.log(Logger::DEBUG, 'ODP event queue: received update config signal.')
|
196
|
+
process_config_update
|
197
|
+
|
198
|
+
when Optimizely::OdpEvent
|
199
|
+
add_to_batch(item)
|
200
|
+
|
201
|
+
when nil && !@current_batch.empty?
|
202
|
+
@logger.log(Logger::DEBUG, 'ODP event queue: flushing on interval.')
|
203
|
+
flush_batch!
|
204
|
+
end
|
205
|
+
end
|
206
|
+
rescue SignalException
|
207
|
+
@thread_exception = true
|
208
|
+
@logger.log(Logger::ERROR, 'Interrupted while processing ODP events.')
|
209
|
+
rescue => e
|
210
|
+
@thread_exception = true
|
211
|
+
@logger.log(Logger::ERROR, "Uncaught exception processing ODP events. Error: #{e.message}")
|
212
|
+
ensure
|
213
|
+
@logger.log(Logger::INFO, 'Exiting ODP processing loop. Attempting to flush pending events.')
|
214
|
+
flush_batch!
|
215
|
+
end
|
216
|
+
|
217
|
+
def flush_batch!
|
218
|
+
if @current_batch.empty?
|
219
|
+
@logger.log(Logger::DEBUG, 'ODP event queue: nothing to flush.')
|
220
|
+
return
|
221
|
+
end
|
222
|
+
|
223
|
+
if @api_key.nil? || @api_host.nil?
|
224
|
+
@logger.log(Logger::DEBUG, Helpers::Constants::ODP_LOGS[:ODP_NOT_INTEGRATED])
|
225
|
+
@current_batch.clear
|
226
|
+
return
|
227
|
+
end
|
228
|
+
|
229
|
+
@logger.log(Logger::DEBUG, "ODP event queue: flushing batch size #{@current_batch.length}.")
|
230
|
+
should_retry = false
|
231
|
+
|
232
|
+
i = 0
|
233
|
+
while i < @retry_count
|
234
|
+
begin
|
235
|
+
should_retry = @api_manager.send_odp_events(@api_key, @api_host, @current_batch)
|
236
|
+
rescue StandardError => e
|
237
|
+
should_retry = false
|
238
|
+
@logger.log(Logger::ERROR, format(Helpers::Constants::ODP_LOGS[:ODP_EVENT_FAILED], "Error: #{e.message} #{@current_batch.to_json}"))
|
239
|
+
end
|
240
|
+
break unless should_retry
|
241
|
+
|
242
|
+
@logger.log(Logger::DEBUG, 'Error dispatching ODP events, scheduled to retry.') if i < @retry_count
|
243
|
+
i += 1
|
244
|
+
end
|
245
|
+
|
246
|
+
@logger.log(Logger::ERROR, format(Helpers::Constants::ODP_LOGS[:ODP_EVENT_FAILED], "Failed after #{i} retries: #{@current_batch.to_json}")) if should_retry
|
247
|
+
|
248
|
+
@current_batch.clear
|
249
|
+
end
|
250
|
+
|
251
|
+
def add_to_batch(event)
|
252
|
+
set_flush_deadline if @current_batch.empty?
|
253
|
+
|
254
|
+
@current_batch << event
|
255
|
+
return unless @current_batch.length >= @batch_size
|
256
|
+
|
257
|
+
@logger.log(Logger::DEBUG, 'ODP event queue: flushing on batch size.')
|
258
|
+
flush_batch!
|
259
|
+
end
|
260
|
+
|
261
|
+
def set_flush_deadline
|
262
|
+
# Sets time that next flush will occur.
|
263
|
+
@flush_deadline = Time.new + @flush_interval
|
264
|
+
end
|
265
|
+
|
266
|
+
def time_till_flush
|
267
|
+
# Returns seconds until next flush; no less than 0.
|
268
|
+
[0, @flush_deadline - Time.new].max
|
269
|
+
end
|
270
|
+
|
271
|
+
def queue_timeout
|
272
|
+
# Returns seconds until next flush or None if current batch is empty.
|
273
|
+
return nil if @current_batch.empty?
|
274
|
+
|
275
|
+
time_till_flush
|
276
|
+
end
|
277
|
+
|
278
|
+
def process_config_update
|
279
|
+
# Updates the configuration used to send events.
|
280
|
+
flush_batch! unless @current_batch.empty?
|
281
|
+
|
282
|
+
@api_key = @odp_config&.api_key
|
283
|
+
@api_host = @odp_config&.api_host
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
@@ -0,0 +1,159 @@
|
|
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 '../helpers/constants'
|
21
|
+
require_relative '../helpers/validator'
|
22
|
+
require_relative '../exceptions'
|
23
|
+
require_relative 'odp_config'
|
24
|
+
require_relative 'lru_cache'
|
25
|
+
require_relative 'odp_segment_manager'
|
26
|
+
require_relative 'odp_event_manager'
|
27
|
+
|
28
|
+
module Optimizely
|
29
|
+
class OdpManager
|
30
|
+
ODP_LOGS = Helpers::Constants::ODP_LOGS
|
31
|
+
ODP_MANAGER_CONFIG = Helpers::Constants::ODP_MANAGER_CONFIG
|
32
|
+
ODP_CONFIG_STATE = Helpers::Constants::ODP_CONFIG_STATE
|
33
|
+
|
34
|
+
# update_odp_config must be called to complete initialization
|
35
|
+
def initialize(
|
36
|
+
disable:,
|
37
|
+
segments_cache: nil,
|
38
|
+
segment_manager: nil,
|
39
|
+
event_manager: nil,
|
40
|
+
fetch_segments_timeout: nil,
|
41
|
+
odp_event_timeout: nil,
|
42
|
+
odp_flush_interval: nil,
|
43
|
+
logger: nil
|
44
|
+
)
|
45
|
+
@enabled = !disable
|
46
|
+
@segment_manager = segment_manager
|
47
|
+
@event_manager = event_manager
|
48
|
+
@logger = logger || NoOpLogger.new
|
49
|
+
@odp_config = OdpConfig.new
|
50
|
+
|
51
|
+
unless @enabled
|
52
|
+
@logger.log(Logger::INFO, ODP_LOGS[:ODP_NOT_ENABLED])
|
53
|
+
return
|
54
|
+
end
|
55
|
+
|
56
|
+
unless @segment_manager
|
57
|
+
segments_cache ||= LRUCache.new(
|
58
|
+
Helpers::Constants::ODP_SEGMENTS_CACHE_CONFIG[:DEFAULT_CAPACITY],
|
59
|
+
Helpers::Constants::ODP_SEGMENTS_CACHE_CONFIG[:DEFAULT_TIMEOUT_SECONDS]
|
60
|
+
)
|
61
|
+
@segment_manager = Optimizely::OdpSegmentManager.new(segments_cache, nil, @logger, timeout: fetch_segments_timeout)
|
62
|
+
end
|
63
|
+
|
64
|
+
@event_manager ||= Optimizely::OdpEventManager.new(logger: @logger, request_timeout: odp_event_timeout, flush_interval: odp_flush_interval)
|
65
|
+
|
66
|
+
@segment_manager.odp_config = @odp_config
|
67
|
+
end
|
68
|
+
|
69
|
+
def fetch_qualified_segments(user_id:, options:)
|
70
|
+
# Returns qualified segments for the user from the cache or the ODP server if not in the cache.
|
71
|
+
#
|
72
|
+
# @param user_id - The user id.
|
73
|
+
# @param options - An array of OptimizelySegmentOptions used to ignore and/or reset the cache.
|
74
|
+
#
|
75
|
+
# @return - Array of qualified segments or nil.
|
76
|
+
options ||= []
|
77
|
+
unless @enabled
|
78
|
+
@logger.log(Logger::ERROR, ODP_LOGS[:ODP_NOT_ENABLED])
|
79
|
+
return nil
|
80
|
+
end
|
81
|
+
|
82
|
+
if @odp_config.odp_state == ODP_CONFIG_STATE[:UNDETERMINED]
|
83
|
+
@logger.log(Logger::ERROR, 'Cannot fetch segments before the datafile has loaded.')
|
84
|
+
return nil
|
85
|
+
end
|
86
|
+
|
87
|
+
@segment_manager.fetch_qualified_segments(ODP_MANAGER_CONFIG[:KEY_FOR_USER_ID], user_id, options)
|
88
|
+
end
|
89
|
+
|
90
|
+
def identify_user(user_id:)
|
91
|
+
unless @enabled
|
92
|
+
@logger.log(Logger::DEBUG, 'ODP identify event is not dispatched (ODP disabled).')
|
93
|
+
return
|
94
|
+
end
|
95
|
+
|
96
|
+
case @odp_config.odp_state
|
97
|
+
when ODP_CONFIG_STATE[:UNDETERMINED]
|
98
|
+
@logger.log(Logger::DEBUG, 'ODP identify event is not dispatched (datafile not ready).')
|
99
|
+
return
|
100
|
+
when ODP_CONFIG_STATE[:NOT_INTEGRATED]
|
101
|
+
@logger.log(Logger::DEBUG, 'ODP identify event is not dispatched (ODP not integrated).')
|
102
|
+
return
|
103
|
+
end
|
104
|
+
|
105
|
+
@event_manager.send_event(
|
106
|
+
type: ODP_MANAGER_CONFIG[:EVENT_TYPE],
|
107
|
+
action: 'identified',
|
108
|
+
identifiers: {ODP_MANAGER_CONFIG[:KEY_FOR_USER_ID] => user_id},
|
109
|
+
data: {}
|
110
|
+
)
|
111
|
+
end
|
112
|
+
|
113
|
+
def send_event(type:, action:, identifiers:, data:)
|
114
|
+
# Send an event to the ODP server.
|
115
|
+
#
|
116
|
+
# @param type - the event type.
|
117
|
+
# @param action - the event action name.
|
118
|
+
# @param identifiers - a hash for identifiers.
|
119
|
+
# @param data - a hash for associated data. The default event data will be added to this data before sending to the ODP server.
|
120
|
+
unless @enabled
|
121
|
+
@logger.log(Logger::ERROR, ODP_LOGS[:ODP_NOT_ENABLED])
|
122
|
+
return
|
123
|
+
end
|
124
|
+
|
125
|
+
unless Helpers::Validator.odp_data_types_valid?(data)
|
126
|
+
@logger.log(Logger::ERROR, ODP_LOGS[:ODP_INVALID_DATA])
|
127
|
+
return
|
128
|
+
end
|
129
|
+
|
130
|
+
@event_manager.send_event(type: type, action: action, identifiers: identifiers, data: data)
|
131
|
+
end
|
132
|
+
|
133
|
+
def update_odp_config(api_key, api_host, segments_to_check)
|
134
|
+
# Update the odp config, reset the cache and send signal to the event processor to update its config.
|
135
|
+
# Start the event manager if odp is integrated.
|
136
|
+
return unless @enabled
|
137
|
+
|
138
|
+
config_changed = @odp_config.update(api_key, api_host, segments_to_check)
|
139
|
+
unless config_changed
|
140
|
+
@logger.log(Logger::DEBUG, 'Odp config was not changed.')
|
141
|
+
return
|
142
|
+
end
|
143
|
+
|
144
|
+
@segment_manager.reset
|
145
|
+
|
146
|
+
if @event_manager.running?
|
147
|
+
@event_manager.update_config
|
148
|
+
elsif @odp_config.odp_state == ODP_CONFIG_STATE[:INTEGRATED]
|
149
|
+
@event_manager.start!(@odp_config)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def stop!
|
154
|
+
return unless @enabled
|
155
|
+
|
156
|
+
@event_manager.stop!
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|