Skip to content

Commit a34512a

Browse files
rashidspmjc1283
authored andcommitted
feat(notification-center): Added LogEvent notification and integration. (#197)
Summary: - Introduced LogEvent notification in notification_center.rb. - Integrated it in batch_event_dispatcher.rb and forwarding_event_processor.rb. - Added notification_count in notification_center.rb and integrated into Optimizely for notification types _ACTIVATE_ and _TRACK_. Test plan: - Added unit tests for LogEvent in notification_center_spec.rb, batch_event_dispatcher_spec.rb and forwarding_event_processor_spec.rb. - Updated Optimizely's unit tests for LogEvent and notification_count.
1 parent 2df63ad commit a34512a

8 files changed

+156
-13
lines changed

lib/optimizely.rb

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def initialize(
102102
@event_processor = if event_processor.respond_to?(:process)
103103
event_processor
104104
else
105-
ForwardingEventProcessor.new(@event_dispatcher, @logger)
105+
ForwardingEventProcessor.new(@event_dispatcher, @logger, @notification_center)
106106
end
107107
end
108108

@@ -258,11 +258,13 @@ def track(event_key, user_id, attributes = nil, event_tags = nil)
258258
@event_processor.process(user_event)
259259
@logger.log(Logger::INFO, "Tracking event '#{event_key}' for user '#{user_id}'.")
260260

261-
log_event = EventFactory.create_log_event(user_event, @logger)
262-
@notification_center.send_notifications(
263-
NotificationCenter::NOTIFICATION_TYPES[:TRACK],
264-
event_key, user_id, attributes, event_tags, log_event
265-
)
261+
if @notification_center.notification_count(NotificationCenter::NOTIFICATION_TYPES[:TRACK]).positive?
262+
log_event = EventFactory.create_log_event(user_event, @logger)
263+
@notification_center.send_notifications(
264+
NotificationCenter::NOTIFICATION_TYPES[:TRACK],
265+
event_key, user_id, attributes, event_tags, log_event
266+
)
267+
end
266268
nil
267269
end
268270

@@ -708,6 +710,7 @@ def send_impression(config, experiment, variation_key, user_id, attributes = nil
708710
variation_id = config.get_variation_id_from_key(experiment_key, variation_key)
709711
user_event = UserEventFactory.create_impression_event(config, experiment, variation_id, user_id, attributes)
710712
@event_processor.process(user_event)
713+
return unless @notification_center.notification_count(NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE]).positive?
711714

712715
@logger.log(Logger::INFO, "Activating user '#{user_id}' in experiment '#{experiment_key}'.")
713716
variation = config.get_variation_from_id(experiment_key, variation_id)

lib/optimizely/event/batch_event_processor.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ def initialize(
3939
event_dispatcher:,
4040
batch_size: DEFAULT_BATCH_SIZE,
4141
flush_interval: DEFAULT_BATCH_INTERVAL,
42-
logger: NoOpLogger.new
42+
logger: NoOpLogger.new,
43+
notification_center: nil
4344
)
4445
@event_queue = event_queue
4546
@logger = logger
@@ -51,6 +52,7 @@ def initialize(
5152
DEFAULT_BATCH_SIZE
5253
end
5354
@flush_interval = positive_number?(flush_interval) ? flush_interval : DEFAULT_BATCH_INTERVAL
55+
@notification_center = notification_center
5456
@mutex = Mutex.new
5557
@received = ConditionVariable.new
5658
@current_batch = []
@@ -152,6 +154,10 @@ def flush_queue!
152154
log_event = Optimizely::EventFactory.create_log_event(@current_batch, @logger)
153155
begin
154156
@event_dispatcher.dispatch_event(log_event)
157+
@notification_center&.send_notifications(
158+
NotificationCenter::NOTIFICATION_TYPES[:LOG_EVENT],
159+
log_event
160+
)
155161
rescue StandardError => e
156162
@logger.log(Logger::ERROR, "Error dispatching event: #{log_event} #{e.message}.")
157163
end

lib/optimizely/event/forwarding_event_processor.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,21 @@ module Optimizely
2020
class ForwardingEventProcessor < EventProcessor
2121
# ForwardingEventProcessor is a basic transformation stage for converting
2222
# the event batch into a LogEvent to be dispatched.
23-
def initialize(event_dispatcher, logger = nil)
23+
def initialize(event_dispatcher, logger = nil, notification_center = nil)
2424
@event_dispatcher = event_dispatcher
2525
@logger = logger || NoOpLogger.new
26+
@notification_center = notification_center
2627
end
2728

2829
def process(user_event)
2930
log_event = Optimizely::EventFactory.create_log_event(user_event, @logger)
3031

3132
begin
3233
@event_dispatcher.dispatch_event(log_event)
34+
@notification_center&.send_notifications(
35+
NotificationCenter::NOTIFICATION_TYPES[:LOG_EVENT],
36+
log_event
37+
)
3338
rescue StandardError => e
3439
@logger.log(Logger::ERROR, "Error dispatching event: #{log_event} #{e.message}.")
3540
end

lib/optimizely/notification_center.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class NotificationCenter
2424
# DEPRECATED: ACTIVATE notification type is deprecated since relase 3.1.0.
2525
ACTIVATE: 'ACTIVATE: experiment, user_id, attributes, variation, event',
2626
DECISION: 'DECISION: type, user_id, attributes, decision_info',
27+
LOG_EVENT: 'LOG_EVENT: type, log_event',
2728
OPTIMIZELY_CONFIG_UPDATE: 'optimizely_config_update',
2829
TRACK: 'TRACK: event_key, user_id, attributes, event_tags, event'
2930
}.freeze
@@ -137,6 +138,10 @@ def send_notifications(notification_type, *args)
137138
end
138139
end
139140

141+
def notification_count(notification_type)
142+
@notifications.include?(notification_type) ? @notifications[notification_type].count : 0
143+
end
144+
140145
private
141146

142147
def notification_type_valid?(notification_type)

spec/event/batch_event_processor_spec.rb

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,16 @@
4141
@event_queue = SizedQueue.new(100)
4242
@event_dispatcher = Optimizely::EventDispatcher.new
4343
allow(@event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event))
44+
@notification_center = Optimizely::NotificationCenter.new(spy_logger, error_handler)
45+
allow(@notification_center).to receive(:send_notifications)
4446

4547
@event_processor = Optimizely::BatchEventProcessor.new(
4648
event_queue: @event_queue,
4749
event_dispatcher: @event_dispatcher,
4850
batch_size: MAX_BATCH_SIZE,
4951
flush_interval: MAX_DURATION_MS,
50-
logger: spy_logger
52+
logger: spy_logger,
53+
notification_center: @notification_center
5154
)
5255
end
5356

@@ -258,4 +261,38 @@
258261
expect(event_processor.flush_interval).to eq(2000.5)
259262
event_processor.stop!
260263
end
264+
265+
it 'should send log event notification when event is dispatched' do
266+
conversion_event = Optimizely::UserEventFactory.create_conversion_event(project_config, event, 'test_user', nil, nil)
267+
log_event = Optimizely::EventFactory.create_log_event(conversion_event, spy_logger)
268+
269+
@event_processor.process(conversion_event)
270+
sleep 1.5
271+
272+
expect(@notification_center).to have_received(:send_notifications).with(
273+
Optimizely::NotificationCenter::NOTIFICATION_TYPES[:LOG_EVENT],
274+
log_event
275+
).once
276+
277+
expect(@event_dispatcher).to have_received(:dispatch_event).with(log_event).once
278+
end
279+
280+
it 'should log an error when dispatch event raises timeout exception' do
281+
conversion_event = Optimizely::UserEventFactory.create_conversion_event(project_config, event, 'test_user', nil, nil)
282+
log_event = Optimizely::EventFactory.create_log_event(conversion_event, spy_logger)
283+
allow(Optimizely::EventFactory).to receive(:create_log_event).and_return(log_event)
284+
285+
timeout_error = Timeout::Error.new
286+
allow(@event_dispatcher).to receive(:dispatch_event).and_raise(timeout_error)
287+
288+
@event_processor.process(conversion_event)
289+
sleep 1.5
290+
291+
expect(@notification_center).not_to have_received(:send_notifications)
292+
293+
expect(spy_logger).to have_received(:log).once.with(
294+
Logger::ERROR,
295+
"Error dispatching event: #{log_event} Timeout::Error."
296+
)
297+
end
261298
end

spec/event/forwarding_event_processor_spec.rb

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,31 +69,43 @@
6969
end
7070

7171
describe '.process' do
72-
it 'should dispatch log event when valid event is provided' do
72+
it 'should dispatch and send log event when valid event is provided' do
73+
notification_center = Optimizely::NotificationCenter.new(spy_logger, error_handler)
74+
allow(notification_center).to receive(:send_notifications)
7375
forwarding_event_processor = Optimizely::ForwardingEventProcessor.new(
74-
@event_dispatcher, spy_logger
76+
@event_dispatcher, spy_logger, notification_center
7577
)
7678

7779
forwarding_event_processor.process(@conversion_event)
7880

81+
expect(notification_center).to have_received(:send_notifications).with(
82+
Optimizely::NotificationCenter::NOTIFICATION_TYPES[:LOG_EVENT],
83+
Optimizely::Event.new(:post, log_url, @expected_conversion_params, post_headers)
84+
).once
85+
7986
expect(@event_dispatcher).to have_received(:dispatch_event).with(
8087
Optimizely::Event.new(:post, log_url, @expected_conversion_params, post_headers)
8188
).once
8289
end
8390

8491
it 'should log an error when dispatch event raises timeout exception' do
92+
notification_center = Optimizely::NotificationCenter.new(spy_logger, error_handler)
93+
allow(notification_center).to receive(:send_notifications)
94+
8595
log_event = Optimizely::Event.new(:post, log_url, @expected_conversion_params, post_headers)
8696
allow(Optimizely::EventFactory).to receive(:create_log_event).and_return(log_event)
8797

8898
timeout_error = Timeout::Error.new
8999
allow(@event_dispatcher).to receive(:dispatch_event).and_raise(timeout_error)
90100

91101
forwarding_event_processor = Optimizely::ForwardingEventProcessor.new(
92-
@event_dispatcher, spy_logger
102+
@event_dispatcher, spy_logger, notification_center
93103
)
94104

95105
forwarding_event_processor.process(@conversion_event)
96106

107+
expect(notification_center).not_to have_received(:send_notifications)
108+
97109
expect(spy_logger).to have_received(:log).once.with(
98110
Logger::ERROR,
99111
"Error dispatching event: #{log_event} Timeout::Error."

spec/notification_center_spec.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,37 @@ def deliver_three; end
486486
end
487487
end
488488

489+
describe '.notification_count' do
490+
it 'should return count of added notification types' do
491+
notification_center.add_notification_listener(
492+
Optimizely::NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE],
493+
@callback_reference
494+
)
495+
496+
notification_center.add_notification_listener(
497+
Optimizely::NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE],
498+
method(:test)
499+
)
500+
501+
notification_center.add_notification_listener(
502+
Optimizely::NotificationCenter::NOTIFICATION_TYPES[:TRACK],
503+
method(:test)
504+
)
505+
506+
expect(
507+
notification_center.notification_count(Optimizely::NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE])
508+
).to eq(2)
509+
510+
expect(
511+
notification_center.notification_count(Optimizely::NotificationCenter::NOTIFICATION_TYPES[:TRACK])
512+
).to eq(1)
513+
514+
expect(
515+
notification_center.notification_count(Optimizely::NotificationCenter::NOTIFICATION_TYPES[:LOG_EVENT])
516+
).to eq(0)
517+
end
518+
end
519+
489520
describe '@error_handler' do
490521
let(:raise_error_handler) { Optimizely::RaiseErrorHandler.new }
491522
let(:notification_center) { Optimizely::NotificationCenter.new(spy_logger, raise_error_handler) }

spec/project_spec.rb

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,11 @@ class InvalidErrorHandler; end
587587
end
588588

589589
it 'should log and send activate notification when an impression event is dispatched' do
590+
def callback(_args); end
591+
project_instance.notification_center.add_notification_listener(
592+
Optimizely::NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE],
593+
method(:callback)
594+
)
590595
variation_to_return = project_instance.config_manager.config.get_variation_from_id('test_experiment', '111128')
591596
allow(project_instance.decision_service.bucketer).to receive(:bucket).and_return(variation_to_return)
592597
allow(project_instance.event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event))
@@ -600,6 +605,11 @@ class InvalidErrorHandler; end
600605
Optimizely::NotificationCenter::NOTIFICATION_TYPES[:DECISION], any_args
601606
).ordered
602607

608+
# Log event
609+
expect(project_instance.notification_center).to receive(:send_notifications).with(
610+
Optimizely::NotificationCenter::NOTIFICATION_TYPES[:LOG_EVENT], any_args
611+
).ordered
612+
603613
# Activate listener
604614
expect(project_instance.notification_center).to receive(:send_notifications).with(
605615
Optimizely::NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE],
@@ -886,13 +896,26 @@ class InvalidErrorHandler; end
886896
params = @expected_track_event_params
887897
params[:visitors][0][:snapshots][0][:events][0].merge!(revenue: 42,
888898
tags: {'revenue' => 42})
899+
900+
def callback(_args); end
901+
project_instance.notification_center.add_notification_listener(
902+
Optimizely::NotificationCenter::NOTIFICATION_TYPES[:TRACK],
903+
method(:callback)
904+
)
889905
allow(project_instance.event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event))
890906
conversion_event = Optimizely::Event.new(:post, conversion_log_url, params, post_headers)
907+
908+
expect(project_instance.notification_center).to receive(:send_notifications)
909+
.with(
910+
Optimizely::NotificationCenter::NOTIFICATION_TYPES[:LOG_EVENT], any_args
911+
).ordered
912+
891913
expect(project_instance.notification_center).to receive(:send_notifications)
892914
.with(
893915
Optimizely::NotificationCenter::NOTIFICATION_TYPES[:TRACK],
894916
'test_event', 'test_user', nil, {'revenue' => 42}, conversion_event
895-
).once
917+
).ordered
918+
896919
project_instance.track('test_event', 'test_user', nil, 'revenue' => 42)
897920
expect(project_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, conversion_log_url, params, post_headers)).once
898921
end
@@ -1463,6 +1486,12 @@ class InvalidErrorHandler; end
14631486
end
14641487

14651488
it 'should return true, send activate notification and an impression if the user is bucketed into a feature experiment' do
1489+
def callback(_args); end
1490+
project_instance.notification_center.add_notification_listener(
1491+
Optimizely::NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE],
1492+
method(:callback)
1493+
)
1494+
14661495
allow(project_instance.event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event))
14671496
experiment_to_return = config_body['experiments'][3]
14681497
variation_to_return = experiment_to_return['variations'][0]
@@ -1472,6 +1501,11 @@ class InvalidErrorHandler; end
14721501
Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
14731502
)
14741503

1504+
expect(project_instance.notification_center).to receive(:send_notifications)
1505+
.with(
1506+
Optimizely::NotificationCenter::NOTIFICATION_TYPES[:LOG_EVENT], any_args
1507+
).ordered
1508+
14751509
expect(project_instance.notification_center).to receive(:send_notifications)
14761510
.with(
14771511
Optimizely::NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE],
@@ -1709,6 +1743,12 @@ class InvalidErrorHandler; end
17091743

17101744
describe '.decision listener' do
17111745
it 'should return enabled features and call decision listener for all features' do
1746+
def callback(_args); end
1747+
project_instance.notification_center.add_notification_listener(
1748+
Optimizely::NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE],
1749+
method(:callback)
1750+
)
1751+
17121752
allow(project_instance.event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event))
17131753

17141754
enabled_features = %w[boolean_feature integer_single_variable_feature]
@@ -1739,6 +1779,10 @@ class InvalidErrorHandler; end
17391779
nil
17401780
)
17411781

1782+
expect(project_instance.notification_center).to receive(:send_notifications).twice.with(
1783+
Optimizely::NotificationCenter::NOTIFICATION_TYPES[:LOG_EVENT], any_args
1784+
)
1785+
17421786
expect(project_instance.notification_center).to receive(:send_notifications).twice.with(
17431787
Optimizely::NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE], any_args
17441788
)

0 commit comments

Comments
 (0)