canvas-lms/app/models/observer_alert_threshold.rb

114 lines
4.6 KiB
Ruby

# frozen_string_literal: true
#
# Copyright (C) 2018 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
class ObserverAlertThreshold < ActiveRecord::Base
belongs_to :student, :class_name => 'User', inverse_of: :as_student_observer_alert_thresholds, :foreign_key => :user_id
belongs_to :observer, :class_name => 'User', inverse_of: :as_observer_observer_alert_thresholds
has_many :observer_alerts, :inverse_of => :observer_alert_threshold
ALERT_TYPES_WITH_THRESHOLD = %w(
assignment_grade_high
assignment_grade_low
course_grade_high
course_grade_low
).freeze
ALERT_TYPES_WITHOUT_THRESHOLD = %w(
assignment_missing
course_announcement
institution_announcement
).freeze
ALERT_TYPES = (ALERT_TYPES_WITH_THRESHOLD | ALERT_TYPES_WITHOUT_THRESHOLD).freeze
validates :alert_type, inclusion: { in: ALERT_TYPES }
validates :user_id, :observer_id, :alert_type, presence: true
validates :alert_type, uniqueness: { scope: [:user_id, :observer_id] }
validates :threshold, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 100 }, allow_nil: true
validate :validate_threshold_type
validate :validate_threshold_low_high
validate :validate_users_link
scope :active, -> { where.not(workflow_state: 'deleted') }
def validate_users_link
unless users_are_still_linked?
errors.add(:observer_id, "Observer must be linked to Student")
end
end
def users_are_still_linked?
return true if observer.as_observer_observation_links.active.where(student: student).exists?
return true if observer.enrollments.active.where(associated_user: student).shard(observer).exists?
false
end
# Validates alert types that require a treshold
# Also enforces _not_ passing a threshold if one is not required
def validate_threshold_type
# If a threshold is provided, there are only 4 applicable types of alert
if self.threshold
unless ALERT_TYPES_WITH_THRESHOLD.include? self.alert_type
errors.add(:threshold, "Threshold is only applicable to the following alert types: #{ALERT_TYPES_WITH_THRESHOLD.join(', ')}")
end
else
unless ALERT_TYPES_WITHOUT_THRESHOLD.include? self.alert_type
errors.add(:threshold, "Threshold is required for the provided alert_type.")
end
end
end
# Validates the highs and lows of a single alert type, enforcing that a high threshold cannot be lower than a low threshold, or vica versa
# For example:
# If the user sets assignment_grade_high to be 40, and then tries to set assignment_grade_low to 50, that would be rejected.
# On the flip side, if assignment_grade_low is set to 50, and then assignment_grade_high is set to 20, will be rejected
def validate_threshold_low_high
if ALERT_TYPES_WITH_THRESHOLD.include? self.alert_type
opposite_type = if self.alert_type.include? 'high'
self.alert_type.gsub('high', 'low')
else
self.alert_type.gsub('low', 'high')
end
opposite = observer.as_observer_observer_alert_thresholds.where(alert_type: opposite_type)
if opposite.any?
if (self.alert_type.include? 'high') && (self.threshold.to_i <= opposite.first.threshold.to_i)
errors.add(:threshold, 'You cannot set a high threshold that is lower or equal to a previously set low threshold.')
elsif (self.alert_type.include? 'low') && (self.threshold.to_i >= opposite.first.threshold.to_i)
errors.add(:threshold, 'You cannot set a low threshold that is higher or equal to a previously set high threshold.')
end
end
end
end
def destroy
self.workflow_state = 'deleted'
self.save!
end
def did_pass_threshold(previous_value, new_value)
t = self.threshold.to_i
if self.alert_type.include? 'high'
return (previous_value.nil? || previous_value < t) && (!new_value.nil? && new_value > t)
elsif self.alert_type.include? 'low'
return (previous_value.nil? || previous_value > t) && (!new_value.nil? && new_value < t)
end
end
end