From 038c5857575b51f9716e26e13a2d2d2184a07aa7 Mon Sep 17 00:00:00 2001 From: Andy Leap Date: Wed, 22 Feb 2023 13:15:30 -0500 Subject: [PATCH 1/9] make batch event processor default --- lib/optimizely.rb | 16 ++++++++++++++-- lib/optimizely/optimizely_factory.rb | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/optimizely.rb b/lib/optimizely.rb index 63753a32..95159605 100644 --- a/lib/optimizely.rb +++ b/lib/optimizely.rb @@ -25,7 +25,7 @@ require_relative 'optimizely/decision_service' require_relative 'optimizely/error_handler' require_relative 'optimizely/event_builder' -require_relative 'optimizely/event/forwarding_event_processor' +require_relative 'optimizely/event/batch_event_processor' require_relative 'optimizely/event/event_factory' require_relative 'optimizely/event/user_event_factory' require_relative 'optimizely/event_dispatcher' @@ -67,6 +67,7 @@ class Project # @param notification_center - Optional Instance of NotificationCenter. # @param event_processor - Optional Responds to process. # @param default_decide_options: Optional default decision options. + # @param event_processor_options: Optional hash of options to be passed to the default batch event processor. # @param settings: Optional instance of OptimizelySdkSettings for sdk configuration. def initialize( # rubocop:disable Metrics/ParameterLists @@ -81,6 +82,7 @@ def initialize( # rubocop:disable Metrics/ParameterLists notification_center = nil, event_processor = nil, default_decide_options = [], + event_processor_options = {}, settings = nil ) @logger = logger || NoOpLogger.new @@ -97,6 +99,11 @@ def initialize( # rubocop:disable Metrics/ParameterLists @default_decide_options = [] end + unless event_processor_options.is_a? Hash + @logger.log(Logger::DEBUG, 'Provided event processor options is not a hash.') + event_processor_options = {} + end + begin validate_instantiation_options rescue InvalidInputError => e @@ -128,7 +135,12 @@ def initialize( # rubocop:disable Metrics/ParameterLists @event_processor = if event_processor.respond_to?(:process) event_processor else - ForwardingEventProcessor.new(@event_dispatcher, @logger, @notification_center) + BatchEventProcessor.new( + event_dispatcher: @event_dispatcher, + logger: @logger, + notification_center: @notification_center, + **event_processor_options + ) end end diff --git a/lib/optimizely/optimizely_factory.rb b/lib/optimizely/optimizely_factory.rb index 7fab1bfd..b6734872 100644 --- a/lib/optimizely/optimizely_factory.rb +++ b/lib/optimizely/optimizely_factory.rb @@ -178,6 +178,7 @@ def self.custom_instance( # rubocop:disable Metrics/ParameterLists notification_center, event_processor, [], + {}, settings ) end From c11e7ad5928ae53a1b5885b9471bb81fb6f30f0e Mon Sep 17 00:00:00 2001 From: Andy Leap Date: Wed, 22 Feb 2023 13:16:41 -0500 Subject: [PATCH 2/9] fix tests --- spec/optimizely_user_context_spec.rb | 14 +- spec/project_spec.rb | 204 +++++++++++++++++++++++--- spec/user_condition_evaluator_spec.rb | 5 +- 3 files changed, 198 insertions(+), 25 deletions(-) diff --git a/spec/optimizely_user_context_spec.rb b/spec/optimizely_user_context_spec.rb index 0f2a3d3c..6a99c57b 100644 --- a/spec/optimizely_user_context_spec.rb +++ b/spec/optimizely_user_context_spec.rb @@ -28,7 +28,7 @@ let(:error_handler) { Optimizely::RaiseErrorHandler.new } let(:spy_logger) { spy('logger') } let(:project_instance) { Optimizely::Project.new(config_body_JSON, nil, spy_logger, error_handler) } - let(:forced_decision_project_instance) { Optimizely::Project.new(forced_decision_JSON, nil, spy_logger, error_handler) } + let(:forced_decision_project_instance) { Optimizely::Project.new(forced_decision_JSON, nil, spy_logger, error_handler, false, nil, nil, nil, nil, nil, [], {batch_size: 1}) } let(:integration_project_instance) { Optimizely::Project.new(integration_JSON, nil, spy_logger, error_handler) } let(:impression_log_url) { 'https://logx.optimizely.com/v1/events' } let(:good_response_data) do @@ -258,6 +258,10 @@ forced_decision = Optimizely::OptimizelyUserContext::OptimizelyForcedDecision.new('3324490562') user_context_obj.set_forced_decision(context, forced_decision) decision = user_context_obj.decide(feature_key) + + # wait for batch processing thread to send event + sleep 0.1 until forced_decision_project_instance.event_processor.event_queue.empty? + expect(forced_decision_project_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, impression_log_url, expected_params, post_headers)) expect(decision.variation_key).to eq('3324490562') expect(decision.rule_key).to be_nil @@ -350,6 +354,10 @@ forced_decision = Optimizely::OptimizelyUserContext::OptimizelyForcedDecision.new('b') user_context_obj.set_forced_decision(context, forced_decision) decision = user_context_obj.decide(feature_key, [Optimizely::Decide::OptimizelyDecideOption::INCLUDE_REASONS]) + + # wait for batch processing thread to send event + sleep 0.1 until forced_decision_project_instance.event_processor.event_queue.empty? + expect(forced_decision_project_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, impression_log_url, expected_params, post_headers)) expect(decision.variation_key).to eq('b') expect(decision.rule_key).to eq('exp_with_audience') @@ -471,6 +479,10 @@ user_context_obj.remove_forced_decision(context_with_rule) # decision should be based on flag forced decision decision = user_context_obj.decide(feature_key) + + # wait for batch processing thread to send event + sleep 0.1 until forced_decision_project_instance.event_processor.event_queue.empty? + expect(forced_decision_project_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, impression_log_url, expected_params, post_headers)) expect(decision.variation_key).to eq('3324490562') expect(decision.rule_key).to be_nil diff --git a/spec/project_spec.rb b/spec/project_spec.rb index 36ca3363..0874f749 100644 --- a/spec/project_spec.rb +++ b/spec/project_spec.rb @@ -48,7 +48,7 @@ let(:version) { Optimizely::VERSION } let(:impression_log_url) { 'https://logx.optimizely.com/v1/events' } let(:conversion_log_url) { 'https://logx.optimizely.com/v1/events' } - let(:project_instance) { Optimizely::Project.new(config_body_JSON, nil, spy_logger, error_handler) } + let(:project_instance) { Optimizely::Project.new(config_body_JSON, nil, spy_logger, error_handler, false, nil, nil, nil, nil, nil, [], {batch_size: 1}) } let(:project_config) { project_instance.config_manager.config } let(:time_now) { Time.now } let(:post_headers) { {'Content-Type' => 'application/json'} } @@ -287,6 +287,10 @@ class InvalidErrorHandler; end # rubocop:disable Lint/ConstantDefinitionInBlock stub_request(:post, impression_log_url).with(query: params) expect(project_instance.activate('test_experiment', 'test_user')).to eq('control') + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(project_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, impression_log_url, params, post_headers)).once expect(project_instance.decision_service.bucketer).to have_received(:bucket).once end @@ -305,6 +309,10 @@ class InvalidErrorHandler; end # rubocop:disable Lint/ConstantDefinitionInBlock stub_request(:post, impression_log_url).with(query: params) expect(project_instance.activate('test_experiment', 'test_user')).to eq('control') + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(project_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, impression_log_url, params, post_headers)).once end @@ -337,13 +345,17 @@ class InvalidErrorHandler; end # rubocop:disable Lint/ConstantDefinitionInBlock expect(project_instance.activate('test_experiment_with_audience', 'test_user', 'browser_type' => 'firefox')) .to eq('control_with_audience') + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(project_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, impression_log_url, params, post_headers)).once expect(project_instance.decision_service.bucketer).to have_received(:bucket).once end describe '.typed audiences' do before(:example) do - @project_typed_audience_instance = Optimizely::Project.new(JSON.dump(OptimizelySpec::CONFIG_DICT_WITH_TYPED_AUDIENCES), nil, spy_logger, error_handler) + @project_typed_audience_instance = Optimizely::Project.new(JSON.dump(OptimizelySpec::CONFIG_DICT_WITH_TYPED_AUDIENCES), nil, spy_logger, error_handler, false, nil, nil, nil, nil, nil, [], {batch_size: 1}) @project_config = @project_typed_audience_instance.config_manager.config @expected_activate_params = { account_id: '4879520872', @@ -417,6 +429,10 @@ class InvalidErrorHandler; end # rubocop:disable Lint/ConstantDefinitionInBlock # Should be included via exact match string audience with id '3468206642' expect(@project_typed_audience_instance.activate('typed_audience_experiment', 'test_user', 'house' => 'Gryffindor')) .to eq('A') + + # wait for batch processing thread to send event + sleep 0.1 until @project_typed_audience_instance.event_processor.event_queue.empty? + expect(@project_typed_audience_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, impression_log_url, params, post_headers)).once expect(@project_typed_audience_instance.decision_service.bucketer).to have_received(:bucket).once end @@ -453,6 +469,10 @@ class InvalidErrorHandler; end # rubocop:disable Lint/ConstantDefinitionInBlock # Should be included via exact match number audience with id '3468206646' expect(@project_typed_audience_instance.activate('typed_audience_experiment', 'test_user', 'lasers' => 45.5)) .to eq('A') + + # wait for batch processing thread to send event + sleep 0.1 until @project_typed_audience_instance.event_processor.event_queue.empty? + expect(@project_typed_audience_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, impression_log_url, params, post_headers)).once expect(@project_typed_audience_instance.decision_service.bucketer).to have_received(:bucket).once end @@ -510,6 +530,10 @@ class InvalidErrorHandler; end # rubocop:disable Lint/ConstantDefinitionInBlock expect(@project_typed_audience_instance.activate('audience_combinations_experiment', 'test_user', user_attributes)) .to eq('A') + + # wait for batch processing thread to send event + sleep 0.1 until @project_typed_audience_instance.event_processor.event_queue.empty? + expect(@project_typed_audience_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, impression_log_url, params, post_headers)).once expect(@project_typed_audience_instance.decision_service.bucketer).to have_received(:bucket).once end @@ -522,6 +546,10 @@ class InvalidErrorHandler; end # rubocop:disable Lint/ConstantDefinitionInBlock expect(@project_typed_audience_instance.activate('audience_combinations_experiment', 'test_user', user_attributes)) .to eq(nil) + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(@project_typed_audience_instance.event_dispatcher).not_to have_received(:dispatch_event) expect(@project_typed_audience_instance.decision_service.bucketer).not_to have_received(:bucket) end @@ -579,6 +607,10 @@ class InvalidErrorHandler; end # rubocop:disable Lint/ConstantDefinitionInBlock expect(project_instance.activate('test_experiment_with_audience', 'test_user', attributes)) .to eq('control_with_audience') + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(project_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, impression_log_url, params, post_headers)).once expect(project_instance.decision_service.bucketer).to have_received(:bucket).once end @@ -625,6 +657,10 @@ class InvalidErrorHandler; end # rubocop:disable Lint/ConstantDefinitionInBlock expect(project_instance.activate('test_experiment_with_audience', 'test_user', attributes)) .to eq('control_with_audience') + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(project_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, impression_log_url, params, post_headers)).once expect(project_instance.decision_service.bucketer).to have_received(:bucket).once end @@ -660,6 +696,10 @@ class InvalidErrorHandler; end # rubocop:disable Lint/ConstantDefinitionInBlock expect(project_instance.activate('test_experiment_with_audience', 'test_user', 'browser_type' => 'firefox')) .to eq('variation_with_audience') + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(project_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, impression_log_url, params, post_headers)).once end @@ -748,6 +788,10 @@ class InvalidErrorHandler; end # rubocop:disable Lint/ConstantDefinitionInBlock allow(project_instance.decision_service.bucketer).to receive(:bucket).and_return(nil) expect(project_instance.activate('test_experiment', 'test_user')).to eq(nil) + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(spy_logger).to have_received(:log).once.with(Logger::INFO, "Not activating user 'test_user'.") expect(project_instance.event_dispatcher).to_not have_received(:dispatch_event) end @@ -781,10 +825,13 @@ def callback(_args); end Optimizely::NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE], experiment, 'test_user', nil, variation_to_return, instance_of(Optimizely::Event) - ).ordered + ) project_instance.activate('test_experiment', 'test_user') + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(spy_logger).to have_received(:log).once.with(Logger::INFO, "Activating user 'test_user' in experiment 'test_experiment'.") end @@ -798,12 +845,19 @@ def callback(_args); end allow(project_instance.decision_service.bucketer).to receive(:bucket).and_return(variation_to_return) allow(project_instance.event_dispatcher).to receive(:dispatch_event).with(any_args).and_raise(RuntimeError) project_instance.activate('test_experiment', 'test_user') + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(spy_logger).to have_received(:log).once.with(Logger::ERROR, "Error dispatching event: #{log_event} RuntimeError.") end it 'should raise an exception when called with invalid attributes' do expect { project_instance.activate('test_experiment', 'test_user', 'invalid') } .to raise_error(Optimizely::InvalidAttributeFormatError) + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? end it 'should override the audience check if the user is whitelisted to a specific variation' do @@ -833,6 +887,10 @@ def callback(_args); end expect(project_instance.activate('test_experiment_with_audience', 'forced_audience_user', 'browser_type' => 'wrong_browser')) .to eq('variation_with_audience') + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(project_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, impression_log_url, params, post_headers)).once expect(Optimizely::Audience).to_not have_received(:user_in_experiment?) end @@ -1054,6 +1112,10 @@ def callback(_args); end allow(project_instance.event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event)) project_instance.track('test_event', 'test_user') + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(project_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, conversion_log_url, params, post_headers)).once end @@ -1061,11 +1123,15 @@ def callback(_args); end project_instance.decision_service.set_forced_variation(project_config, 'test_experiment', 'test_user', 'variation') allow(project_instance.event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event)) project_instance.track('test_event', 'test_user') + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(project_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, conversion_log_url, @expected_track_event_params, post_headers)).once end it 'should properly track an event with tags even when the project does not have a custom logger' do - custom_project_instance = Optimizely::Project.new(config_body_JSON) + custom_project_instance = Optimizely::Project.new(config_body_JSON, nil, spy_logger, error_handler, false, nil, nil, nil, nil, nil, [], {batch_size: 1}) params = @expected_track_event_params params[:visitors][0][:snapshots][0][:events][0][:tags] = {revenue: 42} @@ -1073,6 +1139,10 @@ def callback(_args); end custom_project_instance.decision_service.set_forced_variation(project_config, 'test_experiment', 'test_user', 'variation') allow(custom_project_instance.event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event)) custom_project_instance.track('test_event', 'test_user', nil, revenue: 42) + + # wait for batch processing thread to send event + sleep 0.1 until custom_project_instance.event_processor.event_queue.empty? + expect(custom_project_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, conversion_log_url, params, post_headers)).once custom_project_instance.close end @@ -1085,6 +1155,10 @@ def callback(_args); end allow(project_instance.event_dispatcher).to receive(:dispatch_event).with(any_args).and_raise(RuntimeError) project_instance.track('test_event', 'test_user') + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(spy_logger).to have_received(:log).once.with(Logger::ERROR, "Error dispatching event: #{log_event} RuntimeError.") end @@ -1110,9 +1184,13 @@ def callback(_args); end .with( Optimizely::NotificationCenter::NOTIFICATION_TYPES[:TRACK], 'test_event', 'test_user', nil, {'revenue' => 42}, conversion_event - ).ordered + ) project_instance.track('test_event', 'test_user', nil, 'revenue' => 42) + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(project_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, conversion_log_url, params, post_headers)).once end @@ -1129,12 +1207,16 @@ def callback(_args); end allow(project_instance.event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event)) project_instance.track('test_event_with_audience', 'test_user', 'browser_type' => 'firefox') + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(project_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, conversion_log_url, params, post_headers)).once end describe '.typed audiences' do before(:example) do - @project_typed_audience_instance = Optimizely::Project.new(JSON.dump(OptimizelySpec::CONFIG_DICT_WITH_TYPED_AUDIENCES), nil, spy_logger, error_handler) + @project_typed_audience_instance = Optimizely::Project.new(JSON.dump(OptimizelySpec::CONFIG_DICT_WITH_TYPED_AUDIENCES), nil, spy_logger, error_handler, false, nil, nil, nil, nil, nil, [], {batch_size: 1}) @expected_event_params = { account_id: '4879520872', project_id: '11624721371', @@ -1179,6 +1261,10 @@ def callback(_args); end # Should be included via substring match string audience with id '3988293898' allow(@project_typed_audience_instance.event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event)) @project_typed_audience_instance.track('item_bought', 'test_user', 'house' => 'Welcome to Slytherin!') + + # wait for batch processing thread to send event + sleep 0.1 until @project_typed_audience_instance.event_processor.event_queue.empty? + expect(@project_typed_audience_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, conversion_log_url, @expected_event_params, post_headers)).once end @@ -1187,6 +1273,10 @@ def callback(_args); end params[:visitors][0][:attributes][0][:value] = 'Welcome to Hufflepuff!' allow(@project_typed_audience_instance.event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event)) @project_typed_audience_instance.track('item_bought', 'test_user', 'house' => 'Welcome to Hufflepuff!') + + # wait for batch processing thread to send event + sleep 0.1 until @project_typed_audience_instance.event_processor.event_queue.empty? + expect(@project_typed_audience_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, conversion_log_url, params, post_headers)).once end @@ -1217,6 +1307,10 @@ def callback(_args); end params[:visitors][0][:snapshots][0][:events][0][:key] = 'user_signed_up' allow(@project_typed_audience_instance.event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event)) @project_typed_audience_instance.track('user_signed_up', 'test_user', user_attributes) + + # wait for batch processing thread to send event + sleep 0.1 until @project_typed_audience_instance.event_processor.event_queue.empty? + expect(@project_typed_audience_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, conversion_log_url, params, post_headers)).once end end @@ -1234,6 +1328,10 @@ def callback(_args); end allow(project_instance.event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event)) project_instance.track('test_event_with_audience', 'test_user', 'browser_type' => 'cyberdog') + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(project_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, conversion_log_url, params, post_headers)).once end @@ -1243,6 +1341,10 @@ def callback(_args); end params[:visitors][0][:snapshots][0][:events][0][:key] = 'test_event_not_running' allow(project_instance.event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event)) project_instance.track('test_event_not_running', 'test_user') + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(project_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, conversion_log_url, params, post_headers)).once end @@ -1312,6 +1414,10 @@ def callback(_args); end allow(Optimizely::Audience).to receive(:user_in_experiment?) project_instance.track('test_event_with_audience', 'forced_audience_user', 'browser_type' => 'wrong_browser') + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(Optimizely::Audience).to_not have_received(:user_in_experiment?) expect(project_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, conversion_log_url, params, post_headers)).once end @@ -1586,6 +1692,10 @@ def callback(_args); end allow(project_instance.decision_service).to receive(:get_variation_for_feature).and_return(nil) expect(project_instance.is_feature_enabled('multi_variate_feature', 'test_user')).to be(false) + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(spy_logger).to have_received(:log).once.with(Logger::INFO, "Feature 'multi_variate_feature' is not enabled for user 'test_user'.") expect(project_instance.event_dispatcher).to have_received(:dispatch_event).with(instance_of(Optimizely::Event)).once end @@ -1604,6 +1714,10 @@ def callback(_args); end allow(project_instance.decision_service).to receive(:get_variation_for_feature).and_return(decision_to_return) expect(project_instance.is_feature_enabled('boolean_single_variable_feature', 'test_user')).to be true + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(spy_logger).to have_received(:log).once.with(Logger::INFO, "Feature 'boolean_single_variable_feature' is enabled for user 'test_user'.") expect(project_instance.event_dispatcher).to have_received(:dispatch_event).with(instance_of(Optimizely::Event)).once end @@ -1621,6 +1735,10 @@ def callback(_args); end expect(variation_to_return['featureEnabled']).to be false expect(project_instance.is_feature_enabled('boolean_single_variable_feature', 'test_user')).to be false + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(spy_logger).to have_received(:log).once.with(Logger::INFO, "Feature 'boolean_single_variable_feature' is not enabled for user 'test_user'.") expect(project_instance.event_dispatcher).to have_received(:dispatch_event).with(instance_of(Optimizely::Event)).once end @@ -1638,6 +1756,10 @@ def callback(_args); end expect(variation_to_return['featureEnabled']).to be true expect(project_instance.is_feature_enabled('boolean_single_variable_feature', 'test_user')).to be true + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(spy_logger).to have_received(:log).once.with(Logger::INFO, "Feature 'boolean_single_variable_feature' is enabled for user 'test_user'.") expect(project_instance.event_dispatcher).to have_received(:dispatch_event).with(instance_of(Optimizely::Event)).once end @@ -1713,7 +1835,7 @@ def callback(_args); end expect(project_instance.notification_center).to receive(:send_notifications) .with( Optimizely::NotificationCenter::NOTIFICATION_TYPES[:LOG_EVENT], any_args - ).ordered + ) expect(project_instance.notification_center).to receive(:send_notifications) .with( @@ -1730,6 +1852,10 @@ def callback(_args); end allow(project_instance.decision_service).to receive(:get_variation_for_feature).and_return(decision_to_return) expect(project_instance.is_feature_enabled('multi_variate_feature', 'test_user')).to be true + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(spy_logger).to have_received(:log).once.with(Logger::INFO, "Activating user 'test_user' in experiment 'test_experiment_multivariate'.") expect(spy_logger).to have_received(:log).once.with(Logger::INFO, "Feature 'multi_variate_feature' is enabled for user 'test_user'.") end @@ -1747,6 +1873,10 @@ def callback(_args); end allow(project_instance.decision_service).to receive(:get_variation_for_feature).and_return(decision_to_return) expect(project_instance.is_feature_enabled('multi_variate_feature', 'test_user')).to be false + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(project_instance.event_dispatcher).to have_received(:dispatch_event).with(instance_of(Optimizely::Event)).once expect(spy_logger).to have_received(:log).once.with(Logger::INFO, "Feature 'multi_variate_feature' is not enabled for user 'test_user'.") end @@ -1769,7 +1899,9 @@ def callback(_args); end allow(project_instance.decision_service).to receive(:get_variation_for_feature).and_return(decision_to_return) # Activate listener - expect(project_instance.notification_center).to receive(:send_notifications).ordered + expect(project_instance.notification_center).to receive(:send_notifications).once.with( + Optimizely::NotificationCenter::NOTIFICATION_TYPES[:LOG_EVENT], any_args + ) # Decision listener called when the user is in experiment with variation feature on. expect(variation_to_return['featureEnabled']).to be true @@ -1786,6 +1918,9 @@ def callback(_args); end ).ordered project_instance.is_feature_enabled('multi_variate_feature', 'test_user') + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? end it 'should call decision listener when user is bucketed into a feature experiment with featureEnabled property is false' do @@ -1800,7 +1935,9 @@ def callback(_args); end allow(project_instance.decision_service).to receive(:get_variation_for_feature).and_return(decision_to_return) - expect(project_instance.notification_center).to receive(:send_notifications).ordered + expect(project_instance.notification_center).to receive(:send_notifications).once.with( + Optimizely::NotificationCenter::NOTIFICATION_TYPES[:LOG_EVENT], any_args + ).ordered # DECISION listener called when the user is in experiment with variation feature off. expect(variation_to_return['featureEnabled']).to be false @@ -1817,6 +1954,9 @@ def callback(_args); end ) project_instance.is_feature_enabled('multi_variate_feature', 'test_user', 'browser_type' => 'chrome') + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? end it 'should call decision listener when user is bucketed into rollout with featureEnabled property is true' do @@ -1832,7 +1972,9 @@ def callback(_args); end # DECISION listener called when the user is in rollout with variation feature true. expect(variation_to_return['featureEnabled']).to be true - expect(project_instance.notification_center).to receive(:send_notifications).ordered + expect(project_instance.notification_center).to receive(:send_notifications).once.with( + Optimizely::NotificationCenter::NOTIFICATION_TYPES[:LOG_EVENT], any_args + ).ordered expect(project_instance.notification_center).to receive(:send_notifications).once.with( Optimizely::NotificationCenter::NOTIFICATION_TYPES[:DECISION], 'feature', 'test_user', {'browser_type' => 'firefox'}, @@ -1843,6 +1985,9 @@ def callback(_args); end ) project_instance.is_feature_enabled('boolean_single_variable_feature', 'test_user', 'browser_type' => 'firefox') + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? end it 'should call decision listener when user is bucketed into rollout with featureEnabled property is false' do @@ -1863,7 +2008,9 @@ def callback(_args); end it 'call decision listener when the user is not bucketed into any experiment or rollout' do allow(project_instance.decision_service).to receive(:get_variation_for_feature).and_return(nil) - expect(project_instance.notification_center).to receive(:send_notifications).ordered + expect(project_instance.notification_center).to receive(:send_notifications).once.with( + Optimizely::NotificationCenter::NOTIFICATION_TYPES[:LOG_EVENT], any_args + ).ordered expect(project_instance.notification_center).to receive(:send_notifications).with( Optimizely::NotificationCenter::NOTIFICATION_TYPES[:DECISION], @@ -1875,6 +2022,9 @@ def callback(_args); end ) project_instance.is_feature_enabled('multi_variate_feature', 'test_user', 'browser_type' => 'firefox') + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? end end end @@ -3698,6 +3848,10 @@ def callback(_args); end allow(project_instance.decision_service).to receive(:get_variation_for_feature).and_return(decision_to_return) user_context = project_instance.create_user_context('user1') decision = project_instance.decide(user_context, 'multi_variate_feature') + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(decision.as_json).to include( flag_key: 'multi_variate_feature', enabled: true, @@ -3844,6 +3998,10 @@ def callback(_args); end allow(project_instance.decision_service).to receive(:get_variation_for_feature).and_return(decision_to_return) user_context = project_instance.create_user_context('user1') decision = project_instance.decide(user_context, 'multi_variate_feature') + + # wait for batch processing thread to send event + sleep 0.1 until project_instance.event_processor.event_queue.empty? + expect(decision.as_json).to include( flag_key: 'multi_variate_feature', enabled: false, @@ -4454,7 +4612,7 @@ def callback(_args); end stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json") .to_return(status: 200, body: config_body_integrations_JSON) sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(disable_odp: true) - project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], sdk_settings) + project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], {}, sdk_settings) expect(project.odp_manager.instance_variable_get('@event_manager')).to be_nil expect(project.odp_manager.instance_variable_get('@segment_manager')).to be_nil project.close @@ -4468,7 +4626,7 @@ def callback(_args); end .to_return(status: 200, body: config_body_integrations_JSON) sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(segments_cache_size: 5) - project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], sdk_settings) + project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], {}, sdk_settings) segment_manager = project.odp_manager.instance_variable_get('@segment_manager') expect(segment_manager.instance_variable_get('@segments_cache').capacity).to eq 5 project.close @@ -4480,7 +4638,7 @@ def callback(_args); end stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json") .to_return(status: 200, body: config_body_integrations_JSON) sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(segments_cache_timeout_in_secs: 5) - project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], sdk_settings) + project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], {}, sdk_settings) segment_manager = project.odp_manager.instance_variable_get('@segment_manager') expect(segment_manager.instance_variable_get('@segments_cache').timeout).to eq 5 project.close @@ -4492,7 +4650,7 @@ def callback(_args); end stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json") .to_return(status: 200, body: config_body_integrations_JSON) sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(segments_cache_size: 10, segments_cache_timeout_in_secs: 5) - project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], sdk_settings) + project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], {}, sdk_settings) segment_manager = project.odp_manager.instance_variable_get('@segment_manager') segments_cache = segment_manager.instance_variable_get('@segments_cache') expect(segments_cache.capacity).to eq 10 @@ -4512,7 +4670,7 @@ def save(key, value); end stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json") .to_return(status: 200, body: config_body_integrations_JSON) sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(odp_segments_cache: CustomCache.new) - project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], sdk_settings) + project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], {}, sdk_settings) segment_manager = project.odp_manager.instance_variable_get('@segment_manager') expect(segment_manager.instance_variable_get('@segments_cache')).to be_a CustomCache project.close @@ -4526,7 +4684,7 @@ class InvalidCustomCache; end # rubocop:disable Lint/ConstantDefinitionInBlock stub_request(:get, 'https://cdn.optimizely.com/datafiles/sdk-key.json') .to_return(status: 200, body: config_body_integrations_JSON) sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(odp_segments_cache: InvalidCustomCache.new) - project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, 'sdk-key', nil, nil, nil, [], sdk_settings) + project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, 'sdk-key', nil, nil, nil, [], {}, sdk_settings) segment_manager = project.odp_manager.instance_variable_get('@segment_manager') expect(segment_manager.instance_variable_get('@segments_cache')).to be_a Optimizely::LRUCache @@ -4550,7 +4708,7 @@ def fetch_qualified_segments(user_key, user_value, options); end stub_request(:get, 'https://cdn.optimizely.com/datafiles/sdk-key.json') .to_return(status: 200, body: config_body_integrations_JSON) sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(odp_segment_manager: CustomSegmentManager.new) - project = Optimizely::Project.new(config_body_integrations_JSON, nil, spy_logger, error_handler, false, nil, nil, nil, nil, nil, [], sdk_settings) + project = Optimizely::Project.new(config_body_integrations_JSON, nil, spy_logger, error_handler, false, nil, nil, nil, nil, nil, [], {}, sdk_settings) segment_manager = project.odp_manager.instance_variable_get('@segment_manager') expect(segment_manager).to be_a CustomSegmentManager project.fetch_qualified_segments(user_id: 'test') @@ -4566,7 +4724,7 @@ class InvalidSegmentManager; end # rubocop:disable Lint/ConstantDefinitionInBloc stub_request(:get, 'https://cdn.optimizely.com/datafiles/sdk-key.json') .to_return(status: 200, body: config_body_integrations_JSON) sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(odp_segment_manager: InvalidSegmentManager.new) - project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, 'sdk-key', nil, nil, nil, [], sdk_settings) + project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, 'sdk-key', nil, nil, nil, [], {}, sdk_settings) segment_manager = project.odp_manager.instance_variable_get('@segment_manager') expect(segment_manager).to be_a Optimizely::OdpSegmentManager @@ -4588,7 +4746,7 @@ def stop!; end stub_request(:get, 'https://cdn.optimizely.com/datafiles/sdk-key.json') .to_return(status: 200, body: config_body_integrations_JSON) sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(odp_event_manager: CustomEventManager.new) - project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, 'sdk-key', nil, nil, nil, [], sdk_settings) + project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, 'sdk-key', nil, nil, nil, [], {}, sdk_settings) event_manager = project.odp_manager.instance_variable_get('@event_manager') expect(event_manager).to be_a CustomEventManager project.send_odp_event(action: 'test') @@ -4603,7 +4761,7 @@ class InvalidEventManager; end # rubocop:disable Lint/ConstantDefinitionInBlock stub_request(:get, 'https://cdn.optimizely.com/datafiles/sdk-key.json') .to_return(status: 200, body: config_body_integrations_JSON) sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(odp_event_manager: InvalidEventManager.new) - project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, 'sdk-key', nil, nil, nil, [], sdk_settings) + project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, 'sdk-key', nil, nil, nil, [], {}, sdk_settings) event_manager = project.odp_manager.instance_variable_get('@event_manager') expect(event_manager).to be_a Optimizely::OdpEventManager @@ -4643,7 +4801,7 @@ class InvalidEventManager; end # rubocop:disable Lint/ConstantDefinitionInBlock it 'should log error when odp disabled' do expect(spy_logger).to receive(:log).once.with(Logger::ERROR, 'ODP is not enabled.') sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(disable_odp: true) - custom_project_instance = Optimizely::Project.new(config_body_integrations_JSON, nil, spy_logger, error_handler, false, nil, nil, nil, nil, nil, [], sdk_settings) + custom_project_instance = Optimizely::Project.new(config_body_integrations_JSON, nil, spy_logger, error_handler, false, nil, nil, nil, nil, nil, [], {}, sdk_settings) custom_project_instance.send_odp_event(type: 'wow', action: 'great', identifiers: {}, data: {}) custom_project_instance.close end @@ -4660,7 +4818,7 @@ class InvalidEventManager; end # rubocop:disable Lint/ConstantDefinitionInBlock .to_return(status: 200, body: config_body_integrations_JSON) expect(spy_logger).to receive(:log).once.with(Logger::ERROR, 'ODP is not enabled.') sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(disable_odp: true) - project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], sdk_settings) + project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], {}, sdk_settings) sleep 0.1 until project.config_manager.ready? project.send_odp_event(type: 'wow', action: 'great', identifiers: {}, data: {}) project.close diff --git a/spec/user_condition_evaluator_spec.rb b/spec/user_condition_evaluator_spec.rb index a25cc0fc..7aef929e 100644 --- a/spec/user_condition_evaluator_spec.rb +++ b/spec/user_condition_evaluator_spec.rb @@ -18,6 +18,8 @@ require 'json' require 'spec_helper' require 'optimizely/helpers/validator' +require 'optimizely/event/forwarding_event_processor' +require 'optimizely/event_dispatcher' require 'optimizely/logger' describe Optimizely::UserConditionEvaluator do @@ -25,7 +27,8 @@ let(:config_body_JSON) { OptimizelySpec::VALID_CONFIG_BODY_JSON } let(:error_handler) { Optimizely::NoOpErrorHandler.new } let(:spy_logger) { spy('logger') } - let(:project_instance) { Optimizely::Project.new(config_body_JSON, nil, spy_logger, error_handler) } + let(:event_processor) { Optimizely::ForwardingEventProcessor.new(Optimizely::EventDispatcher.new) } + let(:project_instance) { Optimizely::Project.new(config_body_JSON, nil, spy_logger, error_handler, false, nil, nil, nil, nil, event_processor) } let(:user_context) { project_instance.create_user_context('some-user', {}) } after(:example) { project_instance.close } From 831a4de9185ae27e8eab4bbb980dfbc83ec5547f Mon Sep 17 00:00:00 2001 From: Andy Leap Date: Wed, 22 Feb 2023 15:45:08 -0500 Subject: [PATCH 3/9] fix tests --- spec/project_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/project_spec.rb b/spec/project_spec.rb index 49cede2b..27e9d463 100644 --- a/spec/project_spec.rb +++ b/spec/project_spec.rb @@ -4625,7 +4625,7 @@ def callback(_args); end stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json") .to_return(status: 200, body: config_body_integrations_JSON) sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(odp_event_flush_interval: 0) - project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], sdk_settings) + project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], {}, sdk_settings) event_manager = project.odp_manager.instance_variable_get('@event_manager') expect(event_manager.instance_variable_get('@flush_interval')).to eq 0 project.close @@ -4637,7 +4637,7 @@ def callback(_args); end stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json") .to_return(status: 200, body: config_body_integrations_JSON) sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(odp_event_flush_interval: nil) - project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], sdk_settings) + project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], {}, sdk_settings) event_manager = project.odp_manager.instance_variable_get('@event_manager') expect(event_manager.instance_variable_get('@flush_interval')).to eq 1 project.close @@ -4688,7 +4688,7 @@ def callback(_args); end stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json") .to_return(status: 200, body: config_body_integrations_JSON) sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new - project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], sdk_settings) + project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], {}, sdk_settings) segment_manager = project.odp_manager.instance_variable_get('@segment_manager') segments_cache = segment_manager.instance_variable_get('@segments_cache') expect(segments_cache.capacity).to eq 10_000 @@ -4702,7 +4702,7 @@ def callback(_args); end stub_request(:get, "https://cdn.optimizely.com/datafiles/#{sdk_key}.json") .to_return(status: 200, body: config_body_integrations_JSON) sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new(segments_cache_size: 0, segments_cache_timeout_in_secs: 0) - project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], sdk_settings) + project = Optimizely::Project.new(nil, nil, spy_logger, error_handler, false, nil, sdk_key, nil, nil, nil, [], {}, sdk_settings) segment_manager = project.odp_manager.instance_variable_get('@segment_manager') segments_cache = segment_manager.instance_variable_get('@segments_cache') expect(segments_cache.capacity).to eq 0 From 8316f2c6282d9f0a2cf92db46efdc094aa6f3b89 Mon Sep 17 00:00:00 2001 From: Andy Leap Date: Wed, 22 Feb 2023 15:59:10 -0500 Subject: [PATCH 4/9] documentation fix --- lib/optimizely/odp/odp_event_manager.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/optimizely/odp/odp_event_manager.rb b/lib/optimizely/odp/odp_event_manager.rb index ebf4963c..031200b7 100644 --- a/lib/optimizely/odp/odp_event_manager.rb +++ b/lib/optimizely/odp/odp_event_manager.rb @@ -21,11 +21,10 @@ module Optimizely class OdpEventManager - # BatchEventProcessor is a batched implementation of the Interface EventProcessor. - # Events passed to the BatchEventProcessor are immediately added to an EventQueue. - # The BatchEventProcessor maintains a single consumer thread that pulls events off of + # Events passed to the OdpEventManager are immediately added to an EventQueue. + # The OdpEventManager maintains a single consumer thread that pulls events off of # the BlockingQueue and buffers them for either a configured batch size or for a - # maximum duration before the resulting LogEvent is sent to the NotificationCenter. + # maximum duration before the resulting LogEvent is sent to the Odp. attr_reader :batch_size, :api_manager, :logger attr_accessor :odp_config From 4ca7bbd5a61501d8d80212ce24f221a2f5f29b27 Mon Sep 17 00:00:00 2001 From: Andy Leap Date: Wed, 22 Feb 2023 15:59:47 -0500 Subject: [PATCH 5/9] remove unnecessary super --- lib/optimizely/odp/odp_event_manager.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/optimizely/odp/odp_event_manager.rb b/lib/optimizely/odp/odp_event_manager.rb index 031200b7..0ee4cbf2 100644 --- a/lib/optimizely/odp/odp_event_manager.rb +++ b/lib/optimizely/odp/odp_event_manager.rb @@ -36,8 +36,6 @@ def initialize( request_timeout: nil, flush_interval: nil ) - super() - @odp_config = nil @api_host = nil @api_key = nil From f2707beca06f314ffea49a506ce01d949cb86707 Mon Sep 17 00:00:00 2001 From: Andy Leap Date: Wed, 22 Feb 2023 16:00:00 -0500 Subject: [PATCH 6/9] fix tests --- spec/project_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/project_spec.rb b/spec/project_spec.rb index 27e9d463..170d91e3 100644 --- a/spec/project_spec.rb +++ b/spec/project_spec.rb @@ -4793,6 +4793,7 @@ def send_event(extra_param = nil, action:, type:, identifiers:, data:, other_ext def start!(odp_config); end def update_config; end def stop!; end + def running?; end end stub_request(:get, 'https://cdn.optimizely.com/datafiles/sdk-key.json') From fe99a0c5fc2cbb3e8beb1cb9279ff59ae000a7e4 Mon Sep 17 00:00:00 2001 From: Andy Leap Date: Wed, 22 Feb 2023 17:35:29 -0500 Subject: [PATCH 7/9] fix option unpacking --- lib/optimizely.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/optimizely.rb b/lib/optimizely.rb index fd35a793..7138e6de 100644 --- a/lib/optimizely.rb +++ b/lib/optimizely.rb @@ -139,7 +139,8 @@ def initialize( # rubocop:disable Metrics/ParameterLists event_dispatcher: @event_dispatcher, logger: @logger, notification_center: @notification_center, - **event_processor_options + batch_size: event_processor_options[:batch_size] || BatchEventProcessor::DEFAULT_BATCH_SIZE, + flush_interval: event_processor_options[:flush_interval] || BatchEventProcessor::DEFAULT_BATCH_INTERVAL ) end end From 015e5e9f395a14efc48a1aa5d06439a8065458ab Mon Sep 17 00:00:00 2001 From: Andy Leap <104936100+andrewleap-optimizely@users.noreply.github.com> Date: Thu, 23 Feb 2023 12:42:01 -0500 Subject: [PATCH 8/9] cleanup --- lib/optimizely/odp/odp_event_manager.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optimizely/odp/odp_event_manager.rb b/lib/optimizely/odp/odp_event_manager.rb index 0ee4cbf2..2f290115 100644 --- a/lib/optimizely/odp/odp_event_manager.rb +++ b/lib/optimizely/odp/odp_event_manager.rb @@ -24,7 +24,7 @@ class OdpEventManager # Events passed to the OdpEventManager are immediately added to an EventQueue. # The OdpEventManager maintains a single consumer thread that pulls events off of # the BlockingQueue and buffers them for either a configured batch size or for a - # maximum duration before the resulting LogEvent is sent to the Odp. + # maximum duration before the resulting LogEvent is sent to Odp. attr_reader :batch_size, :api_manager, :logger attr_accessor :odp_config From 76b800e124027e6938ffc0fe483c1e9cd5d59e6d Mon Sep 17 00:00:00 2001 From: Andy Leap <104936100+andrewleap-optimizely@users.noreply.github.com> Date: Thu, 23 Feb 2023 12:42:55 -0500 Subject: [PATCH 9/9] cleanup --- lib/optimizely/odp/odp_event_manager.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optimizely/odp/odp_event_manager.rb b/lib/optimizely/odp/odp_event_manager.rb index 2f290115..81defab9 100644 --- a/lib/optimizely/odp/odp_event_manager.rb +++ b/lib/optimizely/odp/odp_event_manager.rb @@ -24,7 +24,7 @@ class OdpEventManager # Events passed to the OdpEventManager are immediately added to an EventQueue. # The OdpEventManager maintains a single consumer thread that pulls events off of # the BlockingQueue and buffers them for either a configured batch size or for a - # maximum duration before the resulting LogEvent is sent to Odp. + # maximum duration before the resulting OdpEvent is sent to Odp. attr_reader :batch_size, :api_manager, :logger attr_accessor :odp_config