0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-11 02:59:07 +08:00
discourse/app/services/admin_dashboard_section_loader.rb
Krzysztof Kotlarek 87247653e2
FIX: Don't fail the dashboard when one section errors (#41720)
Previously, an exception in a single section's loader failed the entire
admin dashboard request, so one broken section left admins with no
dashboard at all.

This change rescues and logs per-section build errors in
`AdminDashboardSectionLoader` and returns `error: true` for the affected
section, so the rest of the dashboard renders normally while the failed
section shows an inline "couldn't load" message.

Dashboard when the whole endpoint is 404
<img width="1388" height="1307" alt="Screenshot 2026-07-15 at 10 17
25 am"
src="https://github.com/user-attachments/assets/a6a0c0ea-53eb-402f-b940-ecf4015e1757"
/>

Dashboard when 2 sections are broken
<img width="1456" height="1489" alt="Screenshot 2026-07-15 at 10 43
14 am"
src="https://github.com/user-attachments/assets/46e11855-bcc3-41b1-ad45-aa3a80dd367a"
/>
2026-07-15 17:20:51 +08:00

94 lines
2.6 KiB
Ruby
Vendored

# frozen_string_literal: true
class AdminDashboardSectionLoader
def self.build(section_ids:, current_user:, start_date:, end_date:)
new(
section_ids: section_ids,
current_user: current_user,
start_date: start_date,
end_date: end_date,
).build
end
def self.pool_size
desired =
AdminDashboardSectionConfiguration::KNOWN_SECTIONS.size +
DiscoursePluginRegistry.admin_dashboard_sections.size
available = [ActiveRecord::Base.connection_pool.size - 1, 1].max
[desired, available].min
end
def self.thread_pool
@thread_pool ||=
Scheduler::ThreadPool.new(min_threads: 0, max_threads: pool_size, idle_time: 30)
end
def initialize(section_ids:, current_user:, start_date:, end_date:)
@section_ids = section_ids
@current_user = current_user
@start_date = start_date
@end_date = end_date
end
def build
results = Queue.new
section_ids.each do |id|
self.class.thread_pool.post do
ActiveRecord::Base.with_connection(prevent_permanent_checkout: true) do
results << { id: id, data: section_data(id, current_user) }
end
rescue StandardError => e
results << { id: id, error: e }
end
end
results_by_id = {}
section_ids.size.times do
result = results.pop
if result[:error]
Discourse.warn_exception(
result[:error],
message: "Failed to build admin dashboard section",
env: {
section_id: result[:id],
},
)
result = { id: result[:id], data: nil, error: true }
end
results_by_id[result[:id]] = result
end
section_ids.map { |id| results_by_id.fetch(id) }
end
private
attr_reader :section_ids, :current_user, :start_date, :end_date
def section_data(id, user)
case id
when "highlights"
AdminDashboardHighlights.build(start_date: start_date, end_date: end_date)
when "traffic"
AdminDashboardSiteTraffic.build(
start_date: start_date,
end_date: end_date,
guardian: user.guardian,
)
when "engagement"
AdminDashboardEngagement.build(start_date: start_date, end_date: end_date, current_user: user)
when "reports"
AdminDashboard::Reports::Section.build(guardian: user.guardian)
when "search"
AdminDashboardSearch.build(start_date: start_date, end_date: end_date)
else
section = DiscoursePluginRegistry.admin_dashboard_sections.find { |s| s[:id] == id }
section&.dig(:loader)&.call(start_date: start_date, end_date: end_date, current_user: user)
end
end
end