aboutsummaryrefslogtreecommitdiff
path: root/lib/mauve/person.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/mauve/person.rb')
-rw-r--r--lib/mauve/person.rb230
1 files changed, 230 insertions, 0 deletions
diff --git a/lib/mauve/person.rb b/lib/mauve/person.rb
new file mode 100644
index 0000000..6e9fcb4
--- /dev/null
+++ b/lib/mauve/person.rb
@@ -0,0 +1,230 @@
+# encoding: UTF-8
+require 'timeout'
+require 'log4r'
+
+module Mauve
+ class Person < Struct.new(:username, :password, :holiday_url, :urgent, :normal, :low)
+
+ attr_reader :notification_thresholds
+
+ def initialize(*args)
+ @notification_thresholds = { 60 => Array.new(10) }
+ @suppressed = false
+ super(*args)
+ end
+
+ def logger ; @logger ||= Log4r::Logger.new self.class.to_s ; end
+
+ def suppressed?
+ @suppressed
+ end
+
+ # This class implements an instance_eval context to execute the blocks
+ # for running a notification block for each person.
+ #
+ class NotificationCaller
+
+ def initialize(alert, other_alerts, notification_methods, base_conditions={})
+ logger = Log4r::Logger.new "mauve::NotificationCaller"
+ @alert = alert
+ @other_alerts = other_alerts
+ @notification_methods = notification_methods
+ @base_conditions = base_conditions
+ end
+
+ def method_missing(name, destination, *args)
+ conditions = @base_conditions.merge(args[0] ? args[0] : {})
+ notification_method = @notification_methods[name.to_s]
+
+ unless notification_method
+ raise NoMethodError.new("#{name} not defined as a notification method")
+ end
+ # Methods are expected to return true or false so the user can chain
+ # them together with || as fallbacks. So we have to catch exceptions
+ # and turn them into false.
+ #
+ notification_method.send_alert(destination, @alert, @other_alerts, conditions)
+ end
+
+ end
+
+ ## Deals with changes in an alert.
+ #
+ # == Old comments by Matthew.
+ #
+ # An AlertGroup tells a Person that an alert has changed. Within
+ # this alert group, the alert may or may not be "relevant" to this
+ # person, but it is ultimately up to the Person to decide whether to
+ # send a notification. (i.e. notification of acks/clears should
+ # always go out to a Person who was notified of the original alert,
+ # even if the alert is no longer relevant to them).
+ #
+ # == New comment
+ #
+ # The old code works like this: An alert arrives, with a relevance. An
+ # AlertChanged is created and the alert may or may not be send. The
+ # problem is that alerts can be relevant AFTER the initial raise and this
+ # code (due to AlertChange.was_relevant_when_raised?()) will ignore it.
+ # This is wrong.
+ #
+ #
+ # The Thread.exclusive wrapper around the AlertChanged creation makes
+ # sure that two AlertChanged are not created at the same time. This
+ # caused both instances to set the remind_at time of the other to nil.
+ # Thus reminders were never seen which is clearly wrong. This bug was
+ # only showing on jruby due to green threads in MRI.
+ #
+ #
+ # @author Matthew Bloch, Yann Golanski
+ # @param [symb] level Level of the alert.
+ # @param [Alert] alert An alert object.
+ # @param [Boolean] Whether the alert is relevant as defined by notification
+ # class.
+ # @param [MauveTime] When to send remind.
+ # @return [NULL] nada
+ def alert_changed(level, alert, is_relevant=true, remind_at=nil)
+ # User should get notified but will not since on holiday.
+ str = String.new
+# if is_on_holiday?
+# is_relevant = false
+# str = ' (user on holiday)'
+# end
+
+ # Deals with AlertChange database entry.
+ last_change = AlertChanged.first(:alert_id => alert.id, :person => username)
+ if not last_change.nil?
+ if not last_change.remind_at.nil? and not remind_at.nil?
+ if last_change.remind_at.to_time < remind_at
+ remind_at = last_change.remind_at.to_time
+ end
+ end
+ end
+
+ new_change = AlertChanged.create(
+ :level => level.to_s,
+ :alert_id => alert.id,
+ :at => MauveTime.now,
+ :person => username,
+ :update_type => alert.update_type,
+ :remind_at => remind_at,
+ :was_relevant => is_relevant)
+
+ # We need to look at the AlertChanged objects to reset them to
+ # the right value. What is the right value? Well...
+ if true == is_relevant
+ last_change.was_relevant = true if false == last_change.nil?
+ end
+
+ # Send the notification is need be.
+ if !last_change || last_change.update_type.to_sym == :cleared
+ # Person has never heard of this alert before, or previously cleared.
+ #
+ # We don't send any alert if such a change isn't relevant to this
+ # Person at this time.
+ send_alert(level, alert) if is_relevant and [:raised, :changed].include?(alert.update_type.to_sym)
+
+ else
+ # Relevance is determined by whether the user heard of this alert
+ # being raised.
+ send_alert(level, alert) if last_change.was_relevant_when_raised?
+ end
+ end
+ end
+
+ def remind(alert, level)
+ logger.debug("Reminder for #{alert} send at level #{level}.")
+ send_alert(level, alert)
+ end
+
+ #
+ # This just wraps send_alert by sending the job to a queue.
+ #
+ def send_alert(level, alert)
+ Notifier.push([self, level, alert])
+ end
+
+ def do_send_alert(level, alert)
+ now = MauveTime.now
+ suppressed_changed = nil
+ threshold_breached = @notification_thresholds.any? do |period, previous_alert_times|
+ first = previous_alert_times.first
+ first.is_a?(MauveTime) and (now - first) < period
+ end
+
+ this_alert_suppressed = false
+
+ if Server.instance.started_at > alert.updated_at.to_time and (Server.instance.started_at + Server.instance.initial_sleep) > MauveTime.now
+ logger.warn("Alert last updated in prior run of mauve -- ignoring for initial sleep period.")
+ this_alert_suppressed = true
+ elsif threshold_breached
+ unless suppressed?
+ logger.warn("Suspending notifications to #{username} until further notice.")
+ suppressed_changed = true
+ end
+ @suppressed = true
+ else
+ if suppressed?
+ suppressed_changed = false
+ logger.warn "Starting to send notifications again for #{username}."
+ else
+ logger.info "Notifying #{username} of #{alert} at level #{level}"
+ end
+ @suppressed = false
+ end
+
+ return if suppressed? or this_alert_suppressed
+
+ result = NotificationCaller.new(
+ alert,
+ current_alerts,
+ Configuration.current.notification_methods,
+ :suppressed_changed => suppressed_changed
+ ).instance_eval(&__send__(level))
+
+ if result
+ #
+ # Remember that we've sent an alert
+ #
+ @notification_thresholds.each do |period, previous_alert_times|
+ @notification_thresholds[period].replace(previous_alert_times[1..period-1] + [now])
+ end
+
+ logger.info("Notification for #{username} of #{alert} at level #{level} has been successful")
+ else
+ logger.error("Failed to notify #{username} about #{alert} at level #{level}")
+ end
+ end
+
+ # Returns the subset of current alerts that are relevant to this Person.
+ #
+ def current_alerts
+ Alert.all_current.select do |alert|
+ my_last_update = AlertChanged.first(:person => username, :alert_id => alert.id)
+ my_last_update && my_last_update.update_type != :cleared
+ end
+ end
+
+ protected
+ # Remembers that an alert has been sent so that we can later check whether
+ # too many alerts have been sent in a particular period.
+ #
+ def remember_alert(now=MauveTime.now)
+ end
+
+ # Returns time period over which "too many" alerts have been sent, or nil
+ # if none.
+ #
+ def threshold_breached(now=MauveTime.now)
+ end
+
+ # Whether the person is on holiday or not.
+ #
+ # @return [Boolean] True if person on holiday, false otherwise.
+ def is_on_holiday? ()
+ return false if true == holiday_url.nil? or '' == holiday_url
+ return CalendarInterface.is_user_on_holiday?(holiday_url, username)
+ end
+
+ end
+
+end