discourse/app/models/problem_check_tracker.rb
Ted Johansson 8ca5fb706a
DEV: Remove admin notices when destroying problem check trackers (#35976)
When a problem check is run with an invalid target, which can happen if
a target is dynamically removed from the list, we destroy the problem
check tracker.

This makes it so that we also clean up any associated admin notices when
this happens.

It also marks `target` as required. This is because we now use
`__NULL__` to indicate "no target". This allows us to clean up the Ruby
implementation a bit. (Context: this is required for uniqueness checks
to work, since PostgreSQL considers `NULL` values to be distinct.)
2025-11-13 10:34:28 +08:00

104 lines
2.4 KiB
Ruby

# frozen_string_literal: true
class ProblemCheckTracker < ActiveRecord::Base
validates :identifier, presence: true, uniqueness: { scope: :target }
validates :target, presence: true
validates :blips, presence: true, numericality: { greater_than_or_equal_to: 0 }
scope :failing, -> { where("last_problem_at = last_run_at") }
scope :passing, -> { where("last_success_at = last_run_at") }
before_destroy :silence_the_alarm
def self.[](identifier, target = ProblemCheck::NO_TARGET)
find_or_create_by(identifier:, target:)
end
def ready_to_run?
next_run_at.blank? || next_run_at.past?
end
def failing?
last_problem_at == last_run_at
end
def passing?
last_success_at == last_run_at
end
def problem!(next_run_at: nil, details: {})
now = Time.current
update!(blips: blips + 1, details:, last_run_at: now, last_problem_at: now, next_run_at:)
update_notice_details(details)
sound_the_alarm if sound_the_alarm?
end
def no_problem!(next_run_at: nil)
reset(next_run_at:)
silence_the_alarm
end
def reset(next_run_at: nil)
now = Time.current
update!(blips: 0, last_run_at: now, last_success_at: now, next_run_at:)
end
def check
check = ProblemCheck[identifier]
return check if check.present?
silence_the_alarm
destroy
nil
end
private
def update_notice_details(details)
admin_notice.where(identifier:).update_all(details: details.merge(target:))
end
def sound_the_alarm?
failing? && blips > check.max_blips
end
def sound_the_alarm
admin_notice.create_with(
priority: check.priority,
details: details.merge(target:),
).find_or_create_by(identifier:)
end
def silence_the_alarm
admin_notice.where(identifier:).delete_all
end
def admin_notice
AdminNotice.problem.where("details->>'target' = ?", target)
end
end
# == Schema Information
#
# Table name: problem_check_trackers
#
# id :bigint not null, primary key
# identifier :string not null
# blips :integer default(0), not null
# last_run_at :datetime
# next_run_at :datetime
# last_success_at :datetime
# last_problem_at :datetime
# details :json
# target :string default("__NULL__")
#
# Indexes
#
# index_problem_check_trackers_on_identifier_and_target (identifier,target) UNIQUE
#