DEV: Make problem check registration more explicit (#26413)

Previously the problem check registry simply looked at the subclasses of ProblemCheck. This was causing some confusion in environments where eager loading is not enabled, as the registry would appear empty as a result of the classes never being referenced (and thus never loaded.)

This PR changes the approach to a more explicit one. I followed other implementations (bookmarkable and hashtag autocomplete.) As a bonus, this now has a neat plugin entry point as well.
This commit is contained in:
Ted Johansson 2024-03-28 14:00:47 +08:00 committed by GitHub
parent e04b35a184
commit 0c875cb4d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 95 additions and 19 deletions

View File

@ -28,6 +28,37 @@ class ProblemCheck
# #
config_accessor :max_blips, default: 0, instance_writer: false config_accessor :max_blips, default: 0, instance_writer: false
# Problem check classes need to be registered here in order to be enabled.
#
# Note: This list must come after the `config_accessor` declarations.
#
CORE_PROBLEM_CHECKS = [
ProblemCheck::EmailPollingErroredRecently,
ProblemCheck::FacebookConfig,
ProblemCheck::FailingEmails,
ProblemCheck::ForceHttps,
ProblemCheck::GithubConfig,
ProblemCheck::GoogleAnalyticsVersion,
ProblemCheck::GoogleOauth2Config,
ProblemCheck::GroupEmailCredentials,
ProblemCheck::HostNames,
ProblemCheck::ImageMagick,
ProblemCheck::MissingMailgunApiKey,
ProblemCheck::OutOfDateThemes,
ProblemCheck::RailsEnv,
ProblemCheck::Ram,
ProblemCheck::S3BackupConfig,
ProblemCheck::S3Cdn,
ProblemCheck::S3UploadConfig,
ProblemCheck::SidekiqCheck,
ProblemCheck::SubfolderEndsInSlash,
ProblemCheck::TranslationOverrides,
ProblemCheck::TwitterConfig,
ProblemCheck::TwitterLogin,
ProblemCheck::UnreachableThemes,
ProblemCheck::WatchedWords,
].freeze
def self.[](key) def self.[](key)
key = key.to_sym key = key.to_sym
@ -35,7 +66,7 @@ class ProblemCheck
end end
def self.checks def self.checks
descendants CORE_PROBLEM_CHECKS | DiscoursePluginRegistry.problem_checks
end end
def self.scheduled def self.scheduled

View File

@ -121,6 +121,8 @@ class DiscoursePluginRegistry
define_filtered_register :post_strippers define_filtered_register :post_strippers
define_filtered_register :problem_checks
def self.register_auth_provider(auth_provider) def self.register_auth_provider(auth_provider)
self.auth_providers << auth_provider self.auth_providers << auth_provider
end end

View File

@ -317,6 +317,10 @@ class Plugin::Instance
Site.preloaded_category_custom_fields << field Site.preloaded_category_custom_fields << field
end end
def register_problem_check(klass)
DiscoursePluginRegistry.register_problem_check(klass, self)
end
def custom_avatar_column(column) def custom_avatar_column(column)
reloadable_patch do |plugin| reloadable_patch do |plugin|
UserLookup.lookup_columns << column UserLookup.lookup_columns << column

View File

@ -1,14 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Jobs::RunProblemCheck do RSpec.describe Jobs::RunProblemCheck do
after do after { Discourse.redis.flushdb }
Discourse.redis.flushdb
ProblemCheck.send(:remove_const, "TestCheck")
end
context "when there are problems" do context "when there are problems" do
before do around do |example|
ProblemCheck::TestCheck = ProblemCheck::TestCheck =
Class.new(ProblemCheck) do Class.new(ProblemCheck) do
self.perform_every = 30.minutes self.perform_every = 30.minutes
@ -25,6 +21,10 @@ RSpec.describe Jobs::RunProblemCheck do
] ]
end end
end end
stub_const(ProblemCheck, "CORE_PROBLEM_CHECKS", [ProblemCheck::TestCheck], &example)
ProblemCheck.send(:remove_const, "TestCheck")
end end
it "adds the messages to the Redis problems array" do it "adds the messages to the Redis problems array" do
@ -37,7 +37,7 @@ RSpec.describe Jobs::RunProblemCheck do
end end
context "with multiple problems with the same identifier" do context "with multiple problems with the same identifier" do
before do around do |example|
ProblemCheck::TestCheck = ProblemCheck::TestCheck =
Class.new(ProblemCheck) do Class.new(ProblemCheck) do
self.perform_every = 30.minutes self.perform_every = 30.minutes
@ -58,6 +58,10 @@ RSpec.describe Jobs::RunProblemCheck do
] ]
end end
end end
stub_const(ProblemCheck, "CORE_PROBLEM_CHECKS", [ProblemCheck::TestCheck], &example)
ProblemCheck.send(:remove_const, "TestCheck")
end end
it "does not add the same problem twice" do it "does not add the same problem twice" do
@ -70,7 +74,7 @@ RSpec.describe Jobs::RunProblemCheck do
end end
context "when there are retries remaining" do context "when there are retries remaining" do
before do around do |example|
ProblemCheck::TestCheck = ProblemCheck::TestCheck =
Class.new(ProblemCheck) do Class.new(ProblemCheck) do
self.perform_every = 30.minutes self.perform_every = 30.minutes
@ -80,6 +84,10 @@ RSpec.describe Jobs::RunProblemCheck do
[ProblemCheck::Problem.new("Yuge problem")] [ProblemCheck::Problem.new("Yuge problem")]
end end
end end
stub_const(ProblemCheck, "CORE_PROBLEM_CHECKS", [ProblemCheck::TestCheck], &example)
ProblemCheck.send(:remove_const, "TestCheck")
end end
it "does not yet update the problem check tracker" do it "does not yet update the problem check tracker" do
@ -100,7 +108,7 @@ RSpec.describe Jobs::RunProblemCheck do
end end
context "when there are no retries remaining" do context "when there are no retries remaining" do
before do around do |example|
ProblemCheck::TestCheck = ProblemCheck::TestCheck =
Class.new(ProblemCheck) do Class.new(ProblemCheck) do
self.perform_every = 30.minutes self.perform_every = 30.minutes
@ -110,6 +118,10 @@ RSpec.describe Jobs::RunProblemCheck do
[ProblemCheck::Problem.new("Yuge problem")] [ProblemCheck::Problem.new("Yuge problem")]
end end
end end
stub_const(ProblemCheck, "CORE_PROBLEM_CHECKS", [ProblemCheck::TestCheck], &example)
ProblemCheck.send(:remove_const, "TestCheck")
end end
it "updates the problem check tracker" do it "updates the problem check tracker" do
@ -126,7 +138,7 @@ RSpec.describe Jobs::RunProblemCheck do
end end
context "when the check unexpectedly errors out" do context "when the check unexpectedly errors out" do
before do around do |example|
ProblemCheck::TestCheck = ProblemCheck::TestCheck =
Class.new(ProblemCheck) do Class.new(ProblemCheck) do
self.max_retries = 1 self.max_retries = 1
@ -135,6 +147,10 @@ RSpec.describe Jobs::RunProblemCheck do
raise StandardError.new("Something went wrong") raise StandardError.new("Something went wrong")
end end
end end
stub_const(ProblemCheck, "CORE_PROBLEM_CHECKS", [ProblemCheck::TestCheck], &example)
ProblemCheck.send(:remove_const, "TestCheck")
end end
it "does not add a problem to the Redis array" do it "does not add a problem to the Redis array" do

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Jobs::RunProblemChecks do RSpec.describe Jobs::RunProblemChecks do
before do around do |example|
ProblemCheck::ScheduledCheck = ProblemCheck::ScheduledCheck =
Class.new(ProblemCheck) do Class.new(ProblemCheck) do
self.perform_every = 30.minutes self.perform_every = 30.minutes
@ -10,9 +10,14 @@ RSpec.describe Jobs::RunProblemChecks do
end end
ProblemCheck::NonScheduledCheck = Class.new(ProblemCheck) { def call = [] } ProblemCheck::NonScheduledCheck = Class.new(ProblemCheck) { def call = [] }
end
after do stub_const(
ProblemCheck,
"CORE_PROBLEM_CHECKS",
[ProblemCheck::ScheduledCheck, ProblemCheck::NonScheduledCheck],
&example
)
Discourse.redis.flushdb Discourse.redis.flushdb
AdminDashboardData.reset_problem_checks AdminDashboardData.reset_problem_checks

View File

@ -1,17 +1,17 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe ProblemCheck do RSpec.describe ProblemCheck do
# rubocop:disable RSpec/BeforeAfterAll around do |example|
before(:all) do
ScheduledCheck = Class.new(described_class) { self.perform_every = 30.minutes } ScheduledCheck = Class.new(described_class) { self.perform_every = 30.minutes }
RealtimeCheck = Class.new(described_class) RealtimeCheck = Class.new(described_class)
end PluginCheck = Class.new(described_class)
stub_const(described_class, "CORE_PROBLEM_CHECKS", [ScheduledCheck, RealtimeCheck], &example)
after(:all) do
Object.send(:remove_const, ScheduledCheck.name) Object.send(:remove_const, ScheduledCheck.name)
Object.send(:remove_const, RealtimeCheck.name) Object.send(:remove_const, RealtimeCheck.name)
Object.send(:remove_const, PluginCheck.name)
end end
# rubocop:enable RSpec/BeforeAfterAll
let(:scheduled_check) { ScheduledCheck } let(:scheduled_check) { ScheduledCheck }
let(:realtime_check) { RealtimeCheck } let(:realtime_check) { RealtimeCheck }
@ -48,4 +48,22 @@ RSpec.describe ProblemCheck do
it { expect(realtime_check).to be_realtime } it { expect(realtime_check).to be_realtime }
it { expect(scheduled_check).to_not be_realtime } it { expect(scheduled_check).to_not be_realtime }
end end
describe "plugin problem check registration" do
before { DiscoursePluginRegistry.register_problem_check(PluginCheck, stub(enabled?: enabled)) }
after { DiscoursePluginRegistry.reset! }
context "when the plugin is enabled" do
let(:enabled) { true }
it { expect(described_class.checks).to include(PluginCheck) }
end
context "when the plugin is disabled" do
let(:enabled) { false }
it { expect(described_class.checks).not_to include(PluginCheck) }
end
end
end end