From 58f5a950200f5957307a8552b92ce9e92f948b73 Mon Sep 17 00:00:00 2001 From: Patrick J Cherry Date: Thu, 3 May 2012 19:42:20 +0100 Subject: * Person and people lists have now become interchangeable, with notifications only recording the username, rather than the Person/PeopleList. This means that notifications in alert groups can use default #during clauses and #every values from people lists and persons. * Notify clauses can now take an array of persons/people_lists, and people_lists can refer to other people lists. * PeopleList names are now in the same namespace as usernames. * Tests to go with. --- lib/mauve/alert_group.rb | 24 +- lib/mauve/configuration_builders/alert_group.rb | 38 +--- lib/mauve/configuration_builders/people_list.rb | 15 +- lib/mauve/configuration_builders/person.rb | 1 + lib/mauve/notification.rb | 70 ++++-- lib/mauve/people_list.rb | 15 ++ lib/mauve/person.rb | 9 + test/tc_mauve_alert_changed.rb | 2 + test/tc_mauve_alert_group.rb | 129 ++++++++++- .../tc_mauve_configuration_builders_people_list.rb | 14 +- test/tc_mauve_notification.rb | 253 ++++++++++++++++++--- test/tc_mauve_people_list.rb | 4 +- 12 files changed, 452 insertions(+), 122 deletions(-) diff --git a/lib/mauve/alert_group.rb b/lib/mauve/alert_group.rb index 05488fd..76c2e98 100644 --- a/lib/mauve/alert_group.rb +++ b/lib/mauve/alert_group.rb @@ -157,9 +157,17 @@ module Mauve # # This is where we set the reminder -- i.e. on a per-alert-group basis. - + # remind_at = nil - notifications.each do |notification| + + these_notifications = self.resolve_notifications(at) + + these_notifications.each do |notification| + # + # Make sure the level is set. + # + notification.level = self.level + # # Create a new during_runner for this notification clause, and keep it # handy. @@ -208,7 +216,7 @@ module Mauve # The notifications are specified in the config file. # sent_to = [] - notifications.each do |notification| + these_notifications.each do |notification| sent_to << notification.notify(alert, sent_to, during_runners.shift) end @@ -240,6 +248,16 @@ module Mauve [LEVELS.index(self.level), self.name] <=> [LEVELS.index(other.level), other.name] end + # + # + # + def resolve_notifications(at = Time.now) + self.notifications.collect do |notification| + notification.people.collect do |person| + person.resolve_notifications(notification.every, notification.during, at) + end + end.flatten.compact + end end end diff --git a/lib/mauve/configuration_builders/alert_group.rb b/lib/mauve/configuration_builders/alert_group.rb index 851ab7f..a813ee9 100644 --- a/lib/mauve/configuration_builders/alert_group.rb +++ b/lib/mauve/configuration_builders/alert_group.rb @@ -18,22 +18,8 @@ module Mauve # @raise [ArgumentError] if a username doesn't exist. # # @return [Mauve::Notification] New notification instance. - def builder_setup(who) - who = if who.is_a?(Mauve::Person) or who.is_a?(Mauve::PeopleList) - who - - elsif @context.people[who] - @context.people[who] - - elsif @context.people_lists[who] - @context.people_lists[who] - - else - raise ArgumentError.new("You have not declared who #{who} is") - - end - - @result = Mauve::Notification.new(who) + def builder_setup(*who) + @result = Mauve::Notification.new(*who) end is_attribute "every" @@ -65,25 +51,7 @@ module Mauve # def created_notify(notification) @result.notifications ||= [] - - if notification.during.nil? and notification.every.nil? - @result.notifications += notification.person.notifications.collect do |n| - # - # Set up a new notification for each one defined for this person. - # - new_notification = Mauve::Notification.new(notification.person) - new_notification.level = @result.level - new_notification.every = n.every - new_notification.during = n.during - new_notification - end - else - # - # Set the level for this notification - # - notification.level = @result.level - @result.notifications << notification - end + @result.notifications << notification end end diff --git a/lib/mauve/configuration_builders/people_list.rb b/lib/mauve/configuration_builders/people_list.rb index fffa15c..7a5f3ab 100644 --- a/lib/mauve/configuration_builders/people_list.rb +++ b/lib/mauve/configuration_builders/people_list.rb @@ -9,9 +9,9 @@ module Mauve class PeopleList < ObjectBuilder - is_builder "notification", Notification + is_builder "notification", ConfigurationBuilders::Notification - def builder_setup(label, list) + def builder_setup(label, *list) @result = Mauve::PeopleList.new(label) @result += list @result @@ -41,14 +41,9 @@ module Mauve # @param [Mauve::PeopleList] people_list # def created_people_list(people_list) - label = people_list.label - if @result.people_lists.has_key?(label) - _logger.warn("Duplicate people_list '#{label}'") - @result.people_lists[label] += people_list.list - else - @result.people_lists[label] = people_list - end + name = people_list.username + raise ArgumentError.new("Duplicate person '#{name}'") if @result.people[name] + @result.people[name] = people_list end - end end diff --git a/lib/mauve/configuration_builders/person.rb b/lib/mauve/configuration_builders/person.rb index 5c79474..bb4fe8d 100644 --- a/lib/mauve/configuration_builders/person.rb +++ b/lib/mauve/configuration_builders/person.rb @@ -5,6 +5,7 @@ require 'mauve/configuration_builder' require 'mauve/configuration_builders/alert_group' module Mauve + module ConfigurationBuilders class Person < ObjectBuilder diff --git a/lib/mauve/notification.rb b/lib/mauve/notification.rb index fcefcbc..82da2e8 100644 --- a/lib/mauve/notification.rb +++ b/lib/mauve/notification.rb @@ -121,10 +121,10 @@ module Mauve # @return [Boolean] # def no_one_in(people_list) - return true unless Configuration.current.people_lists.has_key?(people_list) - @test_time = @time if @test_time.nil? + return true unless Configuration.current.people[people_list].respond_to?(:people) - return Configuration.current.people_lists[people_list].people(@test_time).empty? + @test_time = @time if @test_time.nil? + return Configuration.current.people[people_list].people(@test_time).empty? end # Returns true if the current hour is in the list of hours given. @@ -133,7 +133,7 @@ module Mauve # @return [Boolean] def hours_in_day(*hours) @test_time = @time if @test_time.nil? - x_in_list_of_y(@test_time.hour, hours.flatten) + x_in_list_of_y(@test_time.hour, Configuration.parse_range(hours).flatten) end # Returns true if the current day is in the list of days given @@ -142,7 +142,7 @@ module Mauve # @return [Boolean] def days_in_week(*days) @test_time = @time if @test_time.nil? - x_in_list_of_y(@test_time.wday, days.flatten) + x_in_list_of_y(@test_time.wday, Configuration.parse_range(days,0...7).flatten) end # Tests if the alert has not been acknowledged within a certain time. @@ -223,14 +223,21 @@ module Mauve # class Notification - attr_reader :during, :every, :level, :person + attr_reader :during, :every, :level, :usernames # Set up a new notification # - # @param [Array] person List of Mauve::Person to notify + # @param [Array] usernames List of Mauve::Person to notify # @param [Symbol] level Level at which to notify - def initialize(person) - @person = person + def initialize(*usernames) + @usernames = usernames.flatten.collect do |u| + if u.respond_to?(:username) + u.username + else + u.to_s + end + end.flatten + @during = nil @every = nil @level = nil @@ -238,7 +245,7 @@ module Mauve # @return [String] def to_s - "#" + "#" end # @return Log4r::Logger @@ -256,8 +263,14 @@ module Mauve @level = arg end - def person=(arg) - @person = arg + def usernames=(arg) + @usernames = arg + end + + def people + usernames.sort.collect do |username| + Configuration.current.people[username] + end.compact.uniq end # Push a notification on to the queue for this alert. The Mauve::Notifier @@ -269,8 +282,8 @@ module Mauve # @return [Array] The list of people that have received this alert. def notify(alert, already_sent_to = [], during_runner = nil) - if person.nil? - logger.warn "No person found in for notification #{list}" + if usernames.nil? or usernames.empty? + logger.warn "No usernames found for notification #{list}" return end @@ -280,15 +293,16 @@ module Mauve # Should we notify at all? return already_sent_to unless during_runner.now? - case person - when Person - [person] - when PeopleList - person.people - else - logger.warn "Unable to notify #{person} (unrecognised class #{person.class})" - [] - end.flatten.uniq.each do |person| + people.collect do |person| + case person + when PeopleList + person.people(during_runner.time) + when Person + person + else + nil + end + end.flatten.compact.uniq.each do |person| # # A bit of alert de-bouncing. # @@ -310,8 +324,16 @@ module Mauve # @return [Time or nil] The time a reminder should get sent, or nil if it # should never get sent again. def remind_at_next(alert, during_runner = nil) + # + # Don't remind on acknowledgements / clears. + # return nil unless alert.raised? + # + # Never remind if every is not set. + # + return nil unless every + # Set up a during_runner during_runner ||= DuringRunner.new(Time.now, alert, &self.during) @@ -325,4 +347,6 @@ module Mauve end + class NotificationDummy < Struct.new(:during, :every) ; end + end diff --git a/lib/mauve/people_list.rb b/lib/mauve/people_list.rb index f632f78..e6c6c36 100644 --- a/lib/mauve/people_list.rb +++ b/lib/mauve/people_list.rb @@ -78,6 +78,21 @@ module Mauve l end + def resolve_notifications(default_every=nil, default_during=nil, at = nil) + self.people(at).collect do |person| + if self.notifications.empty? + person.resolve_notifications(default_every, default_during, at) + else + self.notifications.collect do |notification| + this_notification = Notification.new(person) + this_notification.every = default_every || notification.every + this_notification.during = default_during || notification.during + this_notification + end + end + end.flatten.compact + end + end end diff --git a/lib/mauve/person.rb b/lib/mauve/person.rb index 6178fa0..d5e5ea9 100644 --- a/lib/mauve/person.rb +++ b/lib/mauve/person.rb @@ -302,6 +302,15 @@ module Mauve return CalendarInterface.is_user_off_sick?(self.username, at) end + def resolve_notifications(default_every=nil, default_during=nil, at = nil) + self.notifications.collect do |notification| + this_notification = Notification.new(self) + this_notification.every = default_every || notification.every + this_notification.during = default_during || notification.during + this_notification + end.flatten.compact + end + end end diff --git a/test/tc_mauve_alert_changed.rb b/test/tc_mauve_alert_changed.rb index 6cd51fb..679b9bb 100644 --- a/test/tc_mauve_alert_changed.rb +++ b/test/tc_mauve_alert_changed.rb @@ -43,6 +43,7 @@ alert_group("test_group") { notify("test_person") { every 5.minutes + during { true } } } @@ -118,6 +119,7 @@ alert_group("test_group") { notify("test_person") { every 5.minutes + during { true } } } diff --git a/test/tc_mauve_alert_group.rb b/test/tc_mauve_alert_group.rb index 7e7c4dd..92a9d61 100644 --- a/test/tc_mauve_alert_group.rb +++ b/test/tc_mauve_alert_group.rb @@ -1,17 +1,33 @@ $:.unshift "../lib" require 'th_mauve' -require 'mauve/alert_group' require 'th_mauve_resolv' +require 'mauve/alert_group' +require 'mauve/server' +require 'mauve/configuration' +require 'mauve/configuration_builder' +require 'mauve/configuration_builders' require 'pp' class TcMauveAlertGroup < Mauve::UnitTest + include Mauve + + def setup + super + setup_database + end + + def teardown + teardown_database + super + end + def test_matches_alert - alert = Mauve::Alert.new + alert = Alert.new - alert_group = Mauve::AlertGroup.new("test") + alert_group = AlertGroup.new("test") alert_group.includes = Proc.new { true } assert( alert_group.matches_alert?(alert) ) @@ -38,7 +54,112 @@ class TcMauveAlertGroup < Mauve::UnitTest assert( ! alert_group.matches_alert?(alert) ) end - + def test_notify + config=< "test", + :source => "test", + :subject => "test" + ) + + a.raise! + assert_equal("test1@example.com", notification_buffer.pop[2]) + assert(notification_buffer.empty?) + + Timecop.freeze(Time.now + 5.minutes) + a.acknowledge!(Configuration.current.people["test2"], Time.now + 5.minutes) + assert_equal(2, notification_buffer.length) + assert_equal(["test1@example.com", "test2@example.com"], notification_buffer.collect{|m| m[2]}.sort) + notification_buffer.pop until notification_buffer.empty? + + Timecop.freeze(Time.now + 5.minutes) + a.clear! + assert_equal(2, notification_buffer.length) + assert_equal(["test1@example.com", "test2@example.com"], notification_buffer.collect{|m| m[2]}.sort) + notification_buffer.pop until notification_buffer.empty? + + # + # If we raise it again, test2 shouldn't get notified. + # + Timecop.freeze(Time.now + 5.minutes) + a.raise! + assert_equal("test1@example.com", notification_buffer.pop[2]) + assert(notification_buffer.empty?) + + Timecop.freeze(Time.now + 5.minutes) + a.clear! + assert_equal("test1@example.com", notification_buffer.pop[2]) + assert(notification_buffer.empty?) + + # + # Freeze to 1am + # + Timecop.freeze(Time.local(2012,5,2,1,0,0)) + + a.raise! + assert_equal("test2@example.com", notification_buffer.pop[2]) + assert(notification_buffer.empty?) + + Timecop.freeze(Time.now + 5.minutes) + a.acknowledge!(Configuration.current.people["test1"], Time.now + 5.minutes) + assert_equal("test2@example.com", notification_buffer.pop[2]) + assert(notification_buffer.empty?) + + # + # Test1 shouldn't get notified, even though he ack'd it. + # + Timecop.freeze(Time.now + 5.minutes) + a.clear! + assert_equal("test2@example.com", notification_buffer.pop[2]) + assert(notification_buffer.empty?) + end end diff --git a/test/tc_mauve_configuration_builders_people_list.rb b/test/tc_mauve_configuration_builders_people_list.rb index 7e0840f..d74f25a 100644 --- a/test/tc_mauve_configuration_builders_people_list.rb +++ b/test/tc_mauve_configuration_builders_people_list.rb @@ -39,9 +39,9 @@ people_list("garmin-cervelo", %w( EOF x = nil assert_nothing_raised { x = Mauve::ConfigurationBuilder.parse(config) } - assert_equal(2, x.people_lists.keys.length) - assert_equal(["team sky","garmin-cervelo"].sort,x.people_lists.keys.sort) - assert_equal(%w(geraint edvald bradley rigoberto ben), x.people_lists["team sky"].list) + assert_equal(2, x.people.keys.length) + assert_equal(["team sky","garmin-cervelo"].sort,x.people.keys.sort) + assert_equal(%w(geraint edvald bradley rigoberto ben), x.people["team sky"].list) end def test_duplicate_people_list @@ -61,13 +61,7 @@ EOF # * duplicate list # * Lars already being on a list # - assert_nothing_raised { x = Mauve::ConfigurationBuilder.parse(config) } - - assert_match(/Lars/, logger_pop()) - assert_match(/Duplicate/, logger_pop()) - - assert_equal(1, x.people_lists.keys.length) - assert_equal(["mark c","mark r","Lars","Bernie","Danny"].sort, x.people_lists["htc-highroad"].list.sort) + assert_raise(ArgumentError) { x = Mauve::ConfigurationBuilder.parse(config) } end end diff --git a/test/tc_mauve_notification.rb b/test/tc_mauve_notification.rb index 00005e1..f78ec27 100644 --- a/test/tc_mauve_notification.rb +++ b/test/tc_mauve_notification.rb @@ -316,10 +316,12 @@ alert_group("default") { level URGENT notify("test1") { + during { true } every 10.minutes } notify("testers") { + during { true } every 15.minutes } @@ -563,48 +565,229 @@ EOF :subject => "test" ) - # - # This should only alert test1 - # - assert_equal(0, Time.now.hour) - alert.raise! - assert_equal(1, notification_buffer.size, "Wrong number of notifications sent") - assert_equal("test1@example.com", notification_buffer.pop[2]) + # At midnight just test1 + # At 1am just test2 + # At 2am, both test1 and test2 (via testers) + # At 3am both test1 and test2 (via testers clause with during) + # At 4am no-one - alert.clear! - assert_equal(1, notification_buffer.size, "Wrong number of notifications sent") - assert_equal("test1@example.com", notification_buffer.pop[2]) + [ + [0, %w(test1)], + [1, %w(test2)], + [2, %w(test1 test2)], + [3, %w(test1 test2)], + [4, []] + ].each do |hour, people| + + assert_equal(hour, Time.now.hour) + alert.raise! + assert_equal(people.length, notification_buffer.size, "Wrong number of notifications sent after a raise at #{hour} am") + sent = [] + sent << notification_buffer.pop while !notification_buffer.empty? + assert_equal(people.collect{|u| "#{u}@example.com"}.sort, sent.collect{|n| n[2]}.sort) + + alert.clear! + assert_equal(people.length, notification_buffer.size, "Wrong number of notifications sent after a clear at #{hour} am") + sent = [] + sent << notification_buffer.pop while !notification_buffer.empty? + assert_equal(people.collect{|u| "#{u}@example.com"}.sort, sent.collect{|n| n[2]}.sort) + + # + # Wind the clock forward for the next round of tests. + # + Timecop.freeze(Time.now+1.hours) + end + end + + def test_individual_notification_preferences_part_deux + config=< "test", + :source => "test", + :subject => "test" + ) + # - # Wind forward to 1am when test2 should get alerted - # - Timecop.freeze(Time.now+1.hours) + # At midnight just test1 should be notified. + # At 1am, just test2 + # At 2am both test1 and test2 + + [ + [0, %w(test1)], + [1, %w(test2)], + [2, %w(test1 test2)] + ].each do |hour, people| + + assert_equal(hour, Time.now.hour) + alert.raise! + assert_equal(people.length, notification_buffer.size, "Wrong number of notifications sent after a raise at #{hour} am") + sent = [] + sent << notification_buffer.pop while !notification_buffer.empty? + assert_equal(people.collect{|u| "#{u}@example.com"}.sort, sent.collect{|n| n[2]}.sort) + + alert.clear! + assert_equal(people.length, notification_buffer.size, "Wrong number of notifications sent after a clear at #{hour} am") + sent = [] + sent << notification_buffer.pop while !notification_buffer.empty? + assert_equal(people.collect{|u| "#{u}@example.com"}.sort, sent.collect{|n| n[2]}.sort) + + # + # Wind the clock forward for the next round of tests. + # + Timecop.freeze(Time.now+1.hours) + end - assert_equal(1, Time.now.hour) - alert.raise! - assert_equal(1, notification_buffer.size, "Wrong number of notifications sent") - assert_equal("test2@example.com", notification_buffer.pop[2]) - - alert.clear! - assert_equal(1, notification_buffer.size, "Wrong number of notifications sent") - assert_equal("test2@example.com", notification_buffer.pop[2]) + end - # - # Wind forward to 2am when the testers group should get alerted - # - Timecop.freeze(Time.now+1.hours) + def test_notify_array_of_usernames + config=< "test", + :source => "test", + :subject => "test" + ) + assert_equal("test", alert.alert_group.name) + + # At midnight no-one should be alerted + # At 1am just test1 should be alerted + # At 2am just test2 should be alerted (via the testers1 group) + # At 3am no-one should be alerted + # At 4am test1, test2, and test3 should all be alerted (via the testers2 group) + + [ + [0, []], + [1, %w(test1)], + [2, %w(test2)], + [3, []], + [4, %w(test1 test2 test3)] + ].each do |hour, people| + + assert_equal(hour, Time.now.hour) + alert.raise! + assert_equal(people.length, notification_buffer.size, "Wrong number of notifications sent after a raise at #{hour} am") + sent = [] + sent << notification_buffer.pop while !notification_buffer.empty? + assert_equal(people.collect{|u| "#{u}@example.com"}.sort, sent.collect{|n| n[2]}.sort) + + alert.clear! + assert_equal(people.length, notification_buffer.size, "Wrong number of notifications sent after a clear at #{hour} am") + sent = [] + sent << notification_buffer.pop while !notification_buffer.empty? + assert_equal(people.collect{|u| "#{u}@example.com"}.sort, sent.collect{|n| n[2]}.sort) + + # + # Wind the clock forward for the next round of tests. + # + Timecop.freeze(Time.now+1.hours) + end + + end end diff --git a/test/tc_mauve_people_list.rb b/test/tc_mauve_people_list.rb index 78586d2..2f6755a 100644 --- a/test/tc_mauve_people_list.rb +++ b/test/tc_mauve_people_list.rb @@ -60,7 +60,7 @@ EOF notification_buffer = Configuration.current.notification_methods["email"].deliver_to_queue Server.instance.setup - people_list = Configuration.current.people_lists["testers"] + people_list = Configuration.current.people["testers"] alert = Alert.new( :alert_id => "test", @@ -142,7 +142,7 @@ EOF stub_request(:get, "http://localhost/api/attendees/support_shift/2011-08-01T00:05:00"). to_return(:status => 200, :body => YAML.dump(%w(test2))) - people_list = Configuration.current.people_lists["testers"] + people_list = Configuration.current.people["testers"] assert_equal([Configuration.current.people["test1"]], people_list.people) assert_equal([Configuration.current.people["test2"]], people_list.people(Time.now + 5.minutes)) end -- cgit v1.2.1