Skip to content

Commit 89fd7b4

Browse files
feat: add odp flush interval to sdk_settings (#324)
* add odp flush interval to sdk_settings
1 parent 05ebb60 commit 89fd7b4

File tree

6 files changed

+97
-14
lines changed

6 files changed

+97
-14
lines changed

lib/optimizely.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,6 +1203,7 @@ def setup_odp!(sdk_key)
12031203
segments_cache: @sdk_settings.odp_segments_cache,
12041204
fetch_segments_timeout: @sdk_settings.fetch_segments_timeout,
12051205
odp_event_timeout: @sdk_settings.odp_event_timeout,
1206+
odp_flush_interval: @sdk_settings.odp_flush_interval,
12061207
logger: @logger
12071208
)
12081209

lib/optimizely/helpers/sdk_settings.rb

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
module Optimizely
2222
module Helpers
2323
class OptimizelySdkSettings
24-
attr_accessor :odp_disabled, :segments_cache_size, :segments_cache_timeout_in_secs, :odp_segments_cache, :odp_segment_manager, :odp_event_manager, :fetch_segments_timeout, :odp_event_timeout
24+
attr_accessor :odp_disabled, :segments_cache_size, :segments_cache_timeout_in_secs, :odp_segments_cache, :odp_segment_manager,
25+
:odp_event_manager, :fetch_segments_timeout, :odp_event_timeout, :odp_flush_interval
2526

2627
# Contains configuration used for Optimizely Project initialization.
2728
#
@@ -31,26 +32,29 @@ class OptimizelySdkSettings
3132
# @param odp_segments_cache - A custom odp segments cache. Required methods include: `save(key, value)`, `lookup(key) -> value`, and `reset()`
3233
# @param odp_segment_manager - A custom odp segment manager. Required method is: `fetch_qualified_segments(user_key, user_value, options)`.
3334
# @param odp_event_manager - A custom odp event manager. Required method is: `send_event(type:, action:, identifiers:, data:)`
34-
# @param fetch_segments_timeout - The timeout in seconds of to fetch odp segments (optional. default = 10).
35-
# @param odp_event_timeout - The timeout in seconds of to send odp events (optional. default = 10).
35+
# @param odp_segment_request_timeout - Time to wait in seconds for fetch_qualified_segments (optional. default = 10).
36+
# @param odp_event_request_timeout - Time to wait in seconds for send_odp_events (optional. default = 10).
37+
# @param odp_event_flush_interval - Time to wait in seconds for odp events to accumulate before sending (optional. default = 1).
3638
def initialize(
3739
disable_odp: false,
3840
segments_cache_size: Constants::ODP_SEGMENTS_CACHE_CONFIG[:DEFAULT_CAPACITY],
3941
segments_cache_timeout_in_secs: Constants::ODP_SEGMENTS_CACHE_CONFIG[:DEFAULT_TIMEOUT_SECONDS],
4042
odp_segments_cache: nil,
4143
odp_segment_manager: nil,
4244
odp_event_manager: nil,
43-
fetch_segments_timeout: nil,
44-
odp_event_timeout: nil
45+
odp_segment_request_timeout: nil,
46+
odp_event_request_timeout: nil,
47+
odp_event_flush_interval: nil
4548
)
4649
@odp_disabled = disable_odp
4750
@segments_cache_size = segments_cache_size
4851
@segments_cache_timeout_in_secs = segments_cache_timeout_in_secs
4952
@odp_segments_cache = odp_segments_cache
5053
@odp_segment_manager = odp_segment_manager
5154
@odp_event_manager = odp_event_manager
52-
@fetch_segments_timeout = fetch_segments_timeout
53-
@odp_event_timeout = odp_event_timeout
55+
@fetch_segments_timeout = odp_segment_request_timeout
56+
@odp_event_timeout = odp_event_request_timeout
57+
@odp_flush_interval = odp_event_flush_interval
5458
end
5559
end
5660
end

lib/optimizely/odp/odp_event_manager.rb

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ def initialize(
3434
api_manager: nil,
3535
logger: NoOpLogger.new,
3636
proxy_config: nil,
37-
timeout: nil
37+
request_timeout: nil,
38+
flush_interval: nil
3839
)
3940
super()
4041

@@ -48,9 +49,9 @@ def initialize(
4849
# received signal should be sent after adding item to event_queue
4950
@received = ConditionVariable.new
5051
@logger = logger
51-
@api_manager = api_manager || OdpEventApiManager.new(logger: @logger, proxy_config: proxy_config, timeout: timeout)
52-
@batch_size = Helpers::Constants::ODP_EVENT_MANAGER[:DEFAULT_BATCH_SIZE]
53-
@flush_interval = Helpers::Constants::ODP_EVENT_MANAGER[:DEFAULT_FLUSH_INTERVAL_SECONDS]
52+
@api_manager = api_manager || OdpEventApiManager.new(logger: @logger, proxy_config: proxy_config, timeout: request_timeout)
53+
@flush_interval = flush_interval || Helpers::Constants::ODP_EVENT_MANAGER[:DEFAULT_FLUSH_INTERVAL_SECONDS]
54+
@batch_size = @flush_interval&.zero? ? 1 : Helpers::Constants::ODP_EVENT_MANAGER[:DEFAULT_BATCH_SIZE]
5455
@flush_deadline = 0
5556
@retry_count = Helpers::Constants::ODP_EVENT_MANAGER[:DEFAULT_RETRY_COUNT]
5657
# current_batch should only be accessed by processing thread

lib/optimizely/odp/odp_manager.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,16 @@ class OdpManager
3232
ODP_CONFIG_STATE = Helpers::Constants::ODP_CONFIG_STATE
3333

3434
# update_odp_config must be called to complete initialization
35-
def initialize(disable:, segments_cache: nil, segment_manager: nil, event_manager: nil, fetch_segments_timeout: nil, odp_event_timeout: nil, logger: nil)
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+
)
3645
@enabled = !disable
3746
@segment_manager = segment_manager
3847
@event_manager = event_manager
@@ -52,7 +61,7 @@ def initialize(disable:, segments_cache: nil, segment_manager: nil, event_manage
5261
@segment_manager = Optimizely::OdpSegmentManager.new(segments_cache, nil, @logger, timeout: fetch_segments_timeout)
5362
end
5463

55-
@event_manager ||= Optimizely::OdpEventManager.new(logger: @logger, timeout: odp_event_timeout)
64+
@event_manager ||= Optimizely::OdpEventManager.new(logger: @logger, request_timeout: odp_event_timeout, flush_interval: odp_flush_interval)
5665

5766
@segment_manager.odp_config = @odp_config
5867
end

spec/odp/odp_event_manager_spec.rb

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@
368368
event_manager.stop!
369369
end
370370

371-
it 'should flush when timeout is reached' do
371+
it 'should flush when flush interval is reached' do
372372
allow(SecureRandom).to receive(:uuid).and_return(test_uuid)
373373
event_manager = Optimizely::OdpEventManager.new(logger: spy_logger)
374374
allow(event_manager.api_manager).to receive(:send_odp_events).once.with(api_key, api_host, odp_events).and_return(false)
@@ -385,6 +385,22 @@
385385
event_manager.stop!
386386
end
387387

388+
it 'should flush when flush interval is zero' do
389+
allow(SecureRandom).to receive(:uuid).and_return(test_uuid)
390+
event_manager = Optimizely::OdpEventManager.new(logger: spy_logger)
391+
allow(event_manager.api_manager).to receive(:send_odp_events).once.with(api_key, api_host, odp_events).and_return(false)
392+
event_manager.instance_variable_set('@flush_interval', 0.0)
393+
event_manager.start!(odp_config)
394+
395+
event_manager.send_event(**events[0])
396+
event_manager.send_event(**events[1])
397+
sleep(0.1) until event_manager.instance_variable_get('@event_queue').empty?
398+
399+
expect(spy_logger).not_to have_received(:log).with(Logger::ERROR, anything)
400+
expect(spy_logger).to have_received(:log).once.with(Logger::DEBUG, 'ODP event queue: flushing on interval.')
401+
event_manager.stop!
402+
end
403+
388404
it 'should discard events received before datafile is ready and process normally' do
389405
allow(SecureRandom).to receive(:uuid).and_return(test_uuid)
390406
odp_config = Optimizely::OdpConfig.new

spec/project_spec.rb

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4463,6 +4463,30 @@ def callback(_args); end
44634463
expect(spy_logger).to have_received(:log).once.with(Logger::INFO, 'ODP is not enabled.')
44644464
end
44654465

4466+
it 'should accept zero for flush interval' do
4467+
stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json")
4468+
.to_return(status: 200, body: config_body_integrations_JSON)
4469+
sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(odp_event_flush_interval: 0)
4470+
project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], sdk_settings)
4471+
event_manager = project.odp_manager.instance_variable_get('@event_manager')
4472+
expect(event_manager.instance_variable_get('@flush_interval')).to eq 0
4473+
project.close
4474+
4475+
expect(spy_logger).not_to have_received(:log).with(Logger::ERROR, anything)
4476+
end
4477+
4478+
it 'should use default for flush interval when nil' do
4479+
stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json")
4480+
.to_return(status: 200, body: config_body_integrations_JSON)
4481+
sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(odp_event_flush_interval: nil)
4482+
project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], sdk_settings)
4483+
event_manager = project.odp_manager.instance_variable_get('@event_manager')
4484+
expect(event_manager.instance_variable_get('@flush_interval')).to eq 1
4485+
project.close
4486+
4487+
expect(spy_logger).not_to have_received(:log).with(Logger::ERROR, anything)
4488+
end
4489+
44664490
it 'should accept cache_size' do
44674491
stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json")
44684492
.to_return(status: 200, body: config_body_integrations_JSON)
@@ -4502,6 +4526,34 @@ def callback(_args); end
45024526
expect(spy_logger).not_to have_received(:log).with(Logger::ERROR, anything)
45034527
end
45044528

4529+
it 'should use default cache_size and cache_timeout when not provided' do
4530+
stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json")
4531+
.to_return(status: 200, body: config_body_integrations_JSON)
4532+
sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new
4533+
project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], sdk_settings)
4534+
segment_manager = project.odp_manager.instance_variable_get('@segment_manager')
4535+
segments_cache = segment_manager.instance_variable_get('@segments_cache')
4536+
expect(segments_cache.capacity).to eq 10_000
4537+
expect(segments_cache.timeout).to eq 600
4538+
project.close
4539+
4540+
expect(spy_logger).not_to have_received(:log).with(Logger::ERROR, anything)
4541+
end
4542+
4543+
it 'should accept zero cache_size and cache_timeout' do
4544+
stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json")
4545+
.to_return(status: 200, body: config_body_integrations_JSON)
4546+
sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(segments_cache_size: 0, segments_cache_timeout_in_secs: 0)
4547+
project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], sdk_settings)
4548+
segment_manager = project.odp_manager.instance_variable_get('@segment_manager')
4549+
segments_cache = segment_manager.instance_variable_get('@segments_cache')
4550+
expect(segments_cache.capacity).to eq 0
4551+
expect(segments_cache.timeout).to eq 0
4552+
project.close
4553+
4554+
expect(spy_logger).not_to have_received(:log).with(Logger::ERROR, anything)
4555+
end
4556+
45054557
it 'should accept valid custom cache' do
45064558
class CustomCache # rubocop:disable Lint/ConstantDefinitionInBlock
45074559
def reset; end

0 commit comments

Comments
 (0)