diff options
Diffstat (limited to 'lib/mauve/notification.rb')
-rw-r--r-- | lib/mauve/notification.rb | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/lib/mauve/notification.rb b/lib/mauve/notification.rb new file mode 100644 index 0000000..2220211 --- /dev/null +++ b/lib/mauve/notification.rb @@ -0,0 +1,165 @@ +# encoding: UTF-8 +require 'mauve/person' +require 'mauve/notifiers' + +module Mauve + # This class provides an execution context for the code found in 'during' + # blocks in the configuration file. This code specifies when an alert + # should cause notifications to be generated, and can access @time and + # @alert variables. There are also some helper methods to provide + # oft-needed functionality such as hours_in_day. + # + # e.g. to send alerts only between 10 and 11 am: + # + # during = Proc.new { @time.hour == 10 } + # + # ... later on ... + # + # DuringRunner.new(MauveTime.now, my_alert, &during).inside? + # + # ... or to ask when an alert will next be cued ... + # + # DuringRunner.new(MauveTime.now, my_alert, &during).find_next + # + # which will return a MauveTime object, or nil if the time period will + # not be valid again, at least not in the next week. + # + class DuringRunner + + def initialize(time, alert=nil, &during) + raise ArgumentError.new("Must supply time (not #{time.inspect})") unless time.is_a?(Time) + @time = time + @alert = alert + @during = during || Proc.new { true } + @logger = Log4r::Logger.new "mauve::DuringRunner" + end + + def now? + instance_eval(&@during) + end + + def find_next(interval) + interval = 300 if true == interval.nil? + offset = (@time.nil?)? MauveTime.now : @time + plus_one_week = MauveTime.now + 604800 # ish + while offset < plus_one_week + offset += interval + if DuringRunner.new(offset, @alert, &@during).now? + @logger.debug("Found reminder time of #{offset}") + return offset + end + end + @logger.info("Could not find a reminder time less than a week "+ + "for #{@alert}.") + nil # never again + end + + protected + def hours_in_day(*hours) + x_in_list_of_y(@time.hour, hours.flatten) + end + + def days_in_week(*days) + x_in_list_of_y(@time.wday, days.flatten) + end + + ## Return true if the alert has not been acknowledged within a certain time. + # + def unacknowledged(seconds) + @alert && + @alert.raised? && + !@alert.acknowledged? && + (@time - @alert.raised_at.to_time) > seconds + end + + def x_in_list_of_y(x,y) + y.any? do |range| + if range.respond_to?("include?") + range.include?(x) + else + range == x + end + end + end + + def working_hours? + now = (@time || MauveTime.now) + (8..17).include?(now.hour) and (1..5).include?(now.wday) + end + + # Return true in the dead zone between 3 and 7 in the morning. + # + # Nota bene that this is used with different times in the reminder section. + # + # @return [Boolean] Whether now is a in the dead zone or not. + def dead_zone? + now = (@time || MauveTime.now) + (3..6).include?(now.hour) + end + + end + + # A Notification is an instruction to notify a list of people, at a + # particular alert level, on a periodic basis, and optionally under + # certain conditions specified by a block of code. + # + class Notification < Struct.new(:people, :level, :every, :during, :list) + + def to_s + "#<Notification:of #{people.map { |p| p.username }.join(',')} at level #{level} every #{every}>" + end + + attr_reader :thread_list + + def initialize(people, level) + + self.level = level + self.every = 300 + self.people = people + end + + def logger ; Log4r::Logger.new self.class.to_s ; end + + + # Updated code, now takes account of lists of people. + # + # @TODO refactor so we can test this more easily. + # + # @TODO Make sure that if no notifications is send at all, we log this + # as an error so that an email is send to the developers. Hum, we + # could have person.alert_changed return true if a notification was + # send (false otherwise) and add it to a queue. Then, dequeue till + # we see a "true" and abort. However, this needs a timeout loop + # around it and we will slow down the whole notificatin since it + # will have to wait untill such a time as it gets a true or timeout. + # Not ideal. A quick fix is to make sure that the clause in the + # configuration has a fall back that will send an alert in all cases. + # + def alert_changed(alert) + + # Should we notificy at all? + is_relevant = DuringRunner.new(MauveTime.now, alert, &during).now? + + to_notify = people.collect do |person| + case person + when Person + person + when PeopleList + person.people + else + logger.warn "Unable to notify #{person} (unrecognised class #{person.class})" + [] + end + end.flatten.uniq.each do |person| + person.alert_changed(level, alert, is_relevant, remind_at_next(alert)) + end + end + + def remind_at_next(alert) + return nil unless alert.raised? + DuringRunner.new(MauveTime.now, alert, &during).find_next(every) + end + + end + +end |