rails_error_dashboard 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,152 @@
1
+ <% content_for :page_title, "Job Health" %>
2
+
3
+ <div class="container-fluid py-4">
4
+ <div class="d-flex justify-content-between align-items-center mb-4">
5
+ <h1 class="h3 mb-0">
6
+ <i class="bi bi-cpu me-2"></i>
7
+ Job Health
8
+ </h1>
9
+
10
+ <div class="btn-group" role="group">
11
+ <%= link_to job_health_summary_errors_path(days: 7), class: "btn btn-sm #{@days == 7 ? 'btn-primary' : 'btn-outline-primary'}" do %>
12
+ 7 Days
13
+ <% end %>
14
+ <%= link_to job_health_summary_errors_path(days: 30), class: "btn btn-sm #{@days == 30 ? 'btn-primary' : 'btn-outline-primary'}" do %>
15
+ 30 Days
16
+ <% end %>
17
+ <%= link_to job_health_summary_errors_path(days: 90), class: "btn btn-sm #{@days == 90 ? 'btn-primary' : 'btn-outline-primary'}" do %>
18
+ 90 Days
19
+ <% end %>
20
+ </div>
21
+ </div>
22
+
23
+ <% if @errors_with_jobs == 0 %>
24
+ <div class="text-center py-5">
25
+ <i class="bi bi-check-circle display-1 text-success mb-3"></i>
26
+ <h4 class="text-muted">No Job Queue Data Found</h4>
27
+ <p class="text-muted">
28
+ No job queue stats were detected in system health snapshots over the last <%= @days %> days.
29
+ </p>
30
+ <div class="card mx-auto" style="max-width: 500px;">
31
+ <div class="card-body text-start">
32
+ <h6>How job health tracking works:</h6>
33
+ <ul class="mb-0">
34
+ <li>System health must be enabled (<code>enable_system_health = true</code>)</li>
35
+ <li>Job queue stats are captured automatically when Sidekiq, SolidQueue, or GoodJob is detected</li>
36
+ <li>Stats are collected per-error at the time of capture</li>
37
+ <li>This page shows job queue health per-error, sorted by failed count</li>
38
+ </ul>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ <% else %>
43
+ <div class="row mb-4">
44
+ <div class="col-md-4">
45
+ <div class="card text-center">
46
+ <div class="card-body">
47
+ <div class="display-6 text-info"><%= @errors_with_jobs %></div>
48
+ <small class="text-muted">Errors with Job Data</small>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ <div class="col-md-4">
53
+ <div class="card text-center">
54
+ <div class="card-body">
55
+ <% failed_color = @total_failed > 0 ? "danger" : "success" %>
56
+ <div class="display-6 text-<%= failed_color %>"><%= @total_failed %></div>
57
+ <small class="text-muted">Total Failed Jobs</small>
58
+ </div>
59
+ </div>
60
+ </div>
61
+ <div class="col-md-4">
62
+ <div class="card text-center">
63
+ <div class="card-body">
64
+ <div class="display-6 text-secondary"><%= @adapters_detected.size %></div>
65
+ <small class="text-muted">Adapters Detected</small>
66
+ </div>
67
+ </div>
68
+ </div>
69
+ </div>
70
+
71
+ <div class="card mb-4">
72
+ <div class="card-header bg-white d-flex justify-content-between align-items-center">
73
+ <h5 class="mb-0">
74
+ <i class="bi bi-cpu text-info me-2"></i>
75
+ Job Queue Health by Error
76
+ <span class="badge bg-info"><%= @errors_with_jobs %></span>
77
+ </h5>
78
+ <small class="text-muted"><%== @pagy.info_tag %></small>
79
+ </div>
80
+ <div class="card-body p-0">
81
+ <div class="table-responsive">
82
+ <table class="table table-hover mb-0">
83
+ <thead class="table-light">
84
+ <tr>
85
+ <th width="100">Error</th>
86
+ <th width="120">Adapter</th>
87
+ <th width="80">Failed</th>
88
+ <th width="120">Queued</th>
89
+ <th>Other Stats</th>
90
+ <th width="140">Last Seen</th>
91
+ </tr>
92
+ </thead>
93
+ <tbody>
94
+ <% @entries.each do |entry| %>
95
+ <tr>
96
+ <td><%= link_to "##{entry[:error_id]}", error_path(entry[:error_id]), class: "text-decoration-none" %></td>
97
+ <td><span class="badge bg-secondary"><%= entry[:adapter] %></span></td>
98
+ <td>
99
+ <% failed = entry[:failed] || entry[:errored] || 0 %>
100
+ <% if failed > 0 %>
101
+ <span class="badge bg-danger"><%= failed %></span>
102
+ <% else %>
103
+ <span class="text-success">0</span>
104
+ <% end %>
105
+ </td>
106
+ <td>
107
+ <% case entry[:adapter] %>
108
+ <% when "sidekiq" %>
109
+ <%= entry[:enqueued] %> enqueued
110
+ <% when "solid_queue" %>
111
+ <%= entry[:ready] %> ready
112
+ <% when "good_job" %>
113
+ <%= entry[:queued] %> queued
114
+ <% end %>
115
+ </td>
116
+ <td>
117
+ <small class="text-muted">
118
+ <% case entry[:adapter] %>
119
+ <% when "sidekiq" %>
120
+ dead: <%= entry[:dead] %>, retry: <%= entry[:retry] %>, workers: <%= entry[:workers] %>
121
+ <% when "solid_queue" %>
122
+ claimed: <%= entry[:claimed] %>, blocked: <%= entry[:blocked] %>, scheduled: <%= entry[:scheduled] %>
123
+ <% when "good_job" %>
124
+ finished: <%= entry[:finished] %>
125
+ <% end %>
126
+ </small>
127
+ </td>
128
+ <td><%= local_time_ago(entry[:occurred_at]) %></td>
129
+ </tr>
130
+ <% end %>
131
+ </tbody>
132
+ </table>
133
+ </div>
134
+ </div>
135
+ <div class="card-footer bg-white border-top d-flex justify-content-between align-items-center">
136
+ <div>
137
+ <small class="text-muted">
138
+ <i class="bi bi-lightbulb text-warning"></i> High failed job counts during errors may indicate job queue saturation or upstream service issues.
139
+ </small>
140
+ <small class="ms-3">
141
+ <a href="https://guides.rubyonrails.org/active_job_basics.html" target="_blank" rel="noopener" class="text-decoration-none">
142
+ <i class="bi bi-book"></i> Active Job Guide <i class="bi bi-box-arrow-up-right" style="font-size: 0.7em;"></i>
143
+ </a>
144
+ </small>
145
+ </div>
146
+ <div>
147
+ <%== @pagy.series_nav(:bootstrap) if @pagy.pages > 1 %>
148
+ </div>
149
+ </div>
150
+ </div>
151
+ <% end %>
152
+ </div>
data/config/routes.rb CHANGED
@@ -25,6 +25,8 @@ RailsErrorDashboard::Engine.routes.draw do
25
25
  get :deprecations
26
26
  get :n_plus_one_summary
27
27
  get :cache_health_summary
28
+ get :job_health_summary
29
+ get :database_health_summary
28
30
  post :batch_action
29
31
  end
30
32
  end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsErrorDashboard
4
+ module Queries
5
+ # Query: Aggregate connection pool health stats from system_health across all errors
6
+ # Scans error_logs system_health JSON, extracts connection_pool data per error
7
+ class DatabaseHealthSummary
8
+ def self.call(days = 30, application_id: nil)
9
+ new(days, application_id: application_id).call
10
+ end
11
+
12
+ def initialize(days = 30, application_id: nil)
13
+ @days = days
14
+ @application_id = application_id
15
+ @start_date = days.days.ago
16
+ end
17
+
18
+ def call
19
+ {
20
+ entries: aggregated_entries
21
+ }
22
+ end
23
+
24
+ private
25
+
26
+ def base_query
27
+ scope = ErrorLog.where("occurred_at >= ?", @start_date)
28
+ .where.not(system_health: nil)
29
+ scope = scope.where(application_id: @application_id) if @application_id.present?
30
+ scope
31
+ end
32
+
33
+ def aggregated_entries
34
+ results = []
35
+
36
+ base_query.select(:id, :error_type, :system_health, :occurred_at).find_each(batch_size: 500) do |error_log|
37
+ health = parse_system_health(error_log.system_health)
38
+ next if health.blank?
39
+
40
+ pool = health["connection_pool"]
41
+ next if pool.blank?
42
+
43
+ results << build_entry(error_log, pool)
44
+ end
45
+
46
+ # Sort by stress score descending (worst first)
47
+ results.sort_by { |r| -(r[:busy] + r[:dead] + r[:waiting]) }
48
+ rescue => e
49
+ Rails.logger.error("[RailsErrorDashboard] DatabaseHealthSummary query failed: #{e.class}: #{e.message}")
50
+ []
51
+ end
52
+
53
+ def build_entry(error_log, pool)
54
+ size = pool["size"].to_i
55
+ busy = pool["busy"].to_i
56
+ dead = pool["dead"].to_i
57
+ idle = pool["idle"].to_i
58
+ waiting = pool["waiting"].to_i
59
+ utilization = size > 0 ? (busy.to_f / size * 100).round(1) : 0.0
60
+
61
+ {
62
+ error_id: error_log.id,
63
+ error_type: error_log.error_type,
64
+ size: size,
65
+ busy: busy,
66
+ dead: dead,
67
+ idle: idle,
68
+ waiting: waiting,
69
+ utilization: utilization,
70
+ occurred_at: error_log.occurred_at
71
+ }
72
+ end
73
+
74
+ def parse_system_health(raw)
75
+ return nil if raw.blank?
76
+ JSON.parse(raw)
77
+ rescue JSON::ParserError
78
+ nil
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsErrorDashboard
4
+ module Queries
5
+ # Query: Aggregate job queue health stats from system_health across all errors
6
+ # Scans error_logs system_health JSON, extracts job_queue data per error
7
+ class JobHealthSummary
8
+ def self.call(days = 30, application_id: nil)
9
+ new(days, application_id: application_id).call
10
+ end
11
+
12
+ def initialize(days = 30, application_id: nil)
13
+ @days = days
14
+ @application_id = application_id
15
+ @start_date = days.days.ago
16
+ end
17
+
18
+ def call
19
+ {
20
+ entries: aggregated_entries
21
+ }
22
+ end
23
+
24
+ private
25
+
26
+ def base_query
27
+ scope = ErrorLog.where("occurred_at >= ?", @start_date)
28
+ .where.not(system_health: nil)
29
+ scope = scope.where(application_id: @application_id) if @application_id.present?
30
+ scope
31
+ end
32
+
33
+ def aggregated_entries
34
+ results = []
35
+
36
+ base_query.select(:id, :system_health, :occurred_at).find_each(batch_size: 500) do |error_log|
37
+ health = parse_system_health(error_log.system_health)
38
+ next if health.blank?
39
+
40
+ job_queue = health["job_queue"]
41
+ next if job_queue.blank?
42
+
43
+ adapter = job_queue["adapter"]
44
+ next if adapter.blank?
45
+
46
+ results << build_entry(error_log, job_queue, adapter)
47
+ end
48
+
49
+ # Sort by failed count descending (worst first)
50
+ results.sort_by { |r| -(r[:failed] || 0) }
51
+ rescue => e
52
+ Rails.logger.error("[RailsErrorDashboard] JobHealthSummary query failed: #{e.class}: #{e.message}")
53
+ []
54
+ end
55
+
56
+ def build_entry(error_log, job_queue, adapter)
57
+ entry = {
58
+ error_id: error_log.id,
59
+ adapter: adapter,
60
+ occurred_at: error_log.occurred_at
61
+ }
62
+
63
+ case adapter
64
+ when "sidekiq"
65
+ entry.merge!(
66
+ enqueued: job_queue["enqueued"],
67
+ processed: job_queue["processed"],
68
+ failed: job_queue["failed"],
69
+ dead: job_queue["dead"],
70
+ scheduled: job_queue["scheduled"],
71
+ retry: job_queue["retry"],
72
+ workers: job_queue["workers"]
73
+ )
74
+ when "solid_queue"
75
+ entry.merge!(
76
+ ready: job_queue["ready"],
77
+ scheduled: job_queue["scheduled"],
78
+ claimed: job_queue["claimed"],
79
+ failed: job_queue["failed"],
80
+ blocked: job_queue["blocked"]
81
+ )
82
+ when "good_job"
83
+ entry.merge!(
84
+ queued: job_queue["queued"],
85
+ errored: job_queue["errored"],
86
+ finished: job_queue["finished"]
87
+ )
88
+ end
89
+
90
+ entry
91
+ end
92
+
93
+ def parse_system_health(raw)
94
+ return nil if raw.blank?
95
+ JSON.parse(raw)
96
+ rescue JSON::ParserError
97
+ nil
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsErrorDashboard
4
+ module Services
5
+ # Display-time only service: queries PostgreSQL system tables when the
6
+ # DB Health page loads. NOT used in the capture path.
7
+ #
8
+ # Feature-detects PostgreSQL — returns nil for tables/indexes/activity
9
+ # on SQLite/MySQL. Connection pool stats work on all adapters.
10
+ # Every method individually rescue-wrapped (returns nil).
11
+ class DatabaseHealthInspector
12
+ def self.call
13
+ new.call
14
+ rescue => e
15
+ Rails.logger.error("[RailsErrorDashboard] DatabaseHealthInspector failed: #{e.class}: #{e.message}")
16
+ { adapter: nil, postgresql: false, connection_pool: nil, tables: nil,
17
+ indexes: nil, unused_indexes: nil, activity: nil }
18
+ end
19
+
20
+ def call
21
+ {
22
+ adapter: adapter_name,
23
+ postgresql: postgresql?,
24
+ connection_pool: connection_pool_stats,
25
+ tables: postgresql? ? table_stats : nil,
26
+ indexes: postgresql? ? index_stats : nil,
27
+ unused_indexes: postgresql? ? unused_index_stats : nil,
28
+ activity: postgresql? ? activity_stats : nil
29
+ }
30
+ end
31
+
32
+ private
33
+
34
+ def connection
35
+ ActiveRecord::Base.connection
36
+ end
37
+
38
+ def adapter_name
39
+ connection.adapter_name
40
+ rescue => e
41
+ nil
42
+ end
43
+
44
+ def postgresql?
45
+ adapter_name == "PostgreSQL"
46
+ end
47
+
48
+ def connection_pool_stats
49
+ pool = ActiveRecord::Base.connection_pool
50
+ stat = pool.stat
51
+ { size: stat[:size], busy: stat[:busy], dead: stat[:dead],
52
+ idle: stat[:idle], waiting: stat[:waiting] }
53
+ rescue => e
54
+ nil
55
+ end
56
+
57
+ def table_stats
58
+ rows = connection.select_all(<<~SQL)
59
+ SELECT
60
+ schemaname,
61
+ relname AS name,
62
+ n_live_tup AS estimated_rows,
63
+ pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(relname)) AS total_bytes,
64
+ seq_scan,
65
+ idx_scan,
66
+ n_dead_tup AS dead_tuples,
67
+ last_vacuum,
68
+ last_autovacuum,
69
+ last_analyze
70
+ FROM pg_stat_user_tables
71
+ ORDER BY pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(relname)) DESC
72
+ SQL
73
+
74
+ rows.map do |row|
75
+ {
76
+ name: row["name"],
77
+ estimated_rows: row["estimated_rows"].to_i,
78
+ total_bytes: row["total_bytes"].to_i,
79
+ seq_scan: row["seq_scan"].to_i,
80
+ idx_scan: row["idx_scan"].to_i,
81
+ dead_tuples: row["dead_tuples"].to_i,
82
+ last_vacuum: row["last_vacuum"],
83
+ last_autovacuum: row["last_autovacuum"],
84
+ last_analyze: row["last_analyze"],
85
+ gem_table: row["name"].to_s.start_with?("rails_error_dashboard_")
86
+ }
87
+ end
88
+ rescue => e
89
+ nil
90
+ end
91
+
92
+ def index_stats
93
+ rows = connection.select_all(<<~SQL)
94
+ SELECT
95
+ sui.indexrelname AS name,
96
+ sui.relname AS table_name,
97
+ pg_relation_size(sui.indexrelid) AS size_bytes,
98
+ sui.idx_scan AS scans,
99
+ sui.idx_tup_read AS tuples_read,
100
+ sui.idx_tup_fetch AS tuples_fetched
101
+ FROM pg_stat_user_indexes sui
102
+ ORDER BY pg_relation_size(sui.indexrelid) DESC
103
+ SQL
104
+
105
+ rows.map do |row|
106
+ {
107
+ name: row["name"],
108
+ table_name: row["table_name"],
109
+ size_bytes: row["size_bytes"].to_i,
110
+ scans: row["scans"].to_i,
111
+ tuples_read: row["tuples_read"].to_i,
112
+ tuples_fetched: row["tuples_fetched"].to_i
113
+ }
114
+ end
115
+ rescue => e
116
+ nil
117
+ end
118
+
119
+ def unused_index_stats
120
+ rows = connection.select_all(<<~SQL)
121
+ SELECT
122
+ sui.indexrelname AS name,
123
+ sui.relname AS table_name,
124
+ pg_relation_size(sui.indexrelid) AS size_bytes
125
+ FROM pg_stat_user_indexes sui
126
+ WHERE sui.idx_scan = 0
127
+ AND pg_relation_size(sui.indexrelid) > 0
128
+ ORDER BY pg_relation_size(sui.indexrelid) DESC
129
+ SQL
130
+
131
+ rows.map do |row|
132
+ {
133
+ name: row["name"],
134
+ table_name: row["table_name"],
135
+ size_bytes: row["size_bytes"].to_i
136
+ }
137
+ end
138
+ rescue => e
139
+ nil
140
+ end
141
+
142
+ def activity_stats
143
+ rows = connection.select_all(<<~SQL)
144
+ SELECT
145
+ COALESCE(state, 'unknown') AS state,
146
+ COUNT(*) AS count,
147
+ COUNT(*) FILTER (WHERE wait_event_type IS NOT NULL) AS waiting
148
+ FROM pg_stat_activity
149
+ WHERE datname = current_database()
150
+ GROUP BY state
151
+ ORDER BY count DESC
152
+ SQL
153
+
154
+ by_state = rows.map do |row|
155
+ { state: row["state"], count: row["count"].to_i, waiting: row["waiting"].to_i }
156
+ end
157
+
158
+ {
159
+ by_state: by_state,
160
+ total: by_state.sum { |r| r[:count] },
161
+ total_waiting: by_state.sum { |r| r[:waiting] }
162
+ }
163
+ rescue => e
164
+ nil
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsErrorDashboard
4
+ module Services
5
+ # Pure algorithm: Assemble an RSpec request spec from an error log's request data
6
+ #
7
+ # Operates on data already stored in ErrorLog — zero runtime cost.
8
+ # Called at display time only.
9
+ #
10
+ # @example
11
+ # RailsErrorDashboard::Services::RspecGenerator.call(error)
12
+ # # => "RSpec.describe 'POST /users', type: :request do\n it 'reproduces the error' do\n ..."
13
+ class RspecGenerator
14
+ BODY_METHODS = %w[ POST PUT PATCH DELETE ].freeze
15
+
16
+ # @param error [ErrorLog] An error log record
17
+ # @return [String] RSpec request spec string, or "" if insufficient data
18
+ def self.call(error)
19
+ new(error).generate
20
+ rescue => e
21
+ ""
22
+ end
23
+
24
+ def initialize(error)
25
+ @error = error
26
+ end
27
+
28
+ # @return [String]
29
+ def generate
30
+ path = request_path
31
+ return "" if path.blank?
32
+
33
+ method = http_method
34
+ lines = []
35
+ lines << header_lines(method, path)
36
+ lines << ""
37
+ lines << " it \"reproduces the error\" do"
38
+ lines << request_line(method, path)
39
+ lines << ""
40
+ lines << " # Original error: #{error_type}"
41
+ lines << " # Expect the response to indicate the error"
42
+ lines << " expect(response).to have_http_status(:internal_server_error)"
43
+ lines << " end"
44
+ lines << "end"
45
+
46
+ lines.join("\n")
47
+ rescue => e
48
+ ""
49
+ end
50
+
51
+ private
52
+
53
+ def header_lines(method, path)
54
+ "RSpec.describe \"#{method} #{path}\", type: :request do"
55
+ end
56
+
57
+ def request_line(method, path)
58
+ verb = method.downcase
59
+ parts = []
60
+
61
+ if BODY_METHODS.include?(method) && parsed_params.present?
62
+ params_str = format_params(parsed_params)
63
+ headers_str = format_headers
64
+
65
+ args = [ "\"#{path}\"" ]
66
+ args << "params: #{params_str}"
67
+ args << "headers: #{headers_str}" if headers_str
68
+
69
+ parts << " #{verb} #{args.join(', ')}"
70
+ else
71
+ query_params = extract_query_params(path)
72
+ clean_path = path.split("?").first
73
+
74
+ if query_params.present?
75
+ params_str = format_params(query_params)
76
+ parts << " #{verb} \"#{clean_path}\", params: #{params_str}"
77
+ else
78
+ parts << " #{verb} \"#{path}\""
79
+ end
80
+ end
81
+
82
+ parts.join("\n")
83
+ end
84
+
85
+ def http_method
86
+ method = @error.respond_to?(:http_method) && @error.http_method.presence
87
+ (method || "GET").upcase
88
+ end
89
+
90
+ def request_path
91
+ url = @error.respond_to?(:request_url) && @error.request_url.presence
92
+ return nil if url.blank?
93
+
94
+ # Strip scheme + host to get just the path
95
+ if url.start_with?("http://", "https://")
96
+ URI.parse(url).request_uri
97
+ else
98
+ url
99
+ end
100
+ rescue URI::InvalidURIError
101
+ url
102
+ end
103
+
104
+ def error_type
105
+ @error.respond_to?(:error_type) && @error.error_type.presence || "Unknown"
106
+ end
107
+
108
+ def parsed_params
109
+ raw = @error.respond_to?(:request_params) && @error.request_params.presence
110
+ return nil if raw.blank?
111
+
112
+ JSON.parse(raw)
113
+ rescue JSON::ParserError
114
+ nil
115
+ end
116
+
117
+ def extract_query_params(path)
118
+ query = path.split("?", 2).last
119
+ return nil if query == path || query.blank?
120
+
121
+ Rack::Utils.parse_query(query)
122
+ rescue => e
123
+ nil
124
+ end
125
+
126
+ def format_params(hash)
127
+ return nil if hash.blank?
128
+
129
+ pairs = hash.map { |k, v| "#{format_key(k)} => #{v.inspect}" }
130
+ "{ #{pairs.join(', ')} }"
131
+ end
132
+
133
+ def format_headers
134
+ content_type = @error.respond_to?(:content_type) && @error.content_type.presence
135
+ return nil if content_type.blank?
136
+
137
+ "{ \"Content-Type\" => #{content_type.inspect} }"
138
+ end
139
+
140
+ def format_key(key)
141
+ key.to_s.inspect
142
+ end
143
+ end
144
+ end
145
+ end
@@ -1,3 +1,3 @@
1
1
  module RailsErrorDashboard
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -54,6 +54,8 @@ require "rails_error_dashboard/services/notification_throttler"
54
54
  require "rails_error_dashboard/services/breadcrumb_collector"
55
55
  require "rails_error_dashboard/services/n_plus_one_detector"
56
56
  require "rails_error_dashboard/services/curl_generator"
57
+ require "rails_error_dashboard/services/rspec_generator"
58
+ require "rails_error_dashboard/services/database_health_inspector"
57
59
  require "rails_error_dashboard/services/cache_analyzer"
58
60
  require "rails_error_dashboard/subscribers/breadcrumb_subscriber"
59
61
  require "rails_error_dashboard/queries/co_occurring_errors"
@@ -89,6 +91,8 @@ require "rails_error_dashboard/queries/critical_alerts"
89
91
  require "rails_error_dashboard/queries/deprecation_warnings"
90
92
  require "rails_error_dashboard/queries/n_plus_one_summary"
91
93
  require "rails_error_dashboard/queries/cache_health_summary"
94
+ require "rails_error_dashboard/queries/job_health_summary"
95
+ require "rails_error_dashboard/queries/database_health_summary"
92
96
  require "rails_error_dashboard/error_reporter"
93
97
  require "rails_error_dashboard/middleware/error_catcher"
94
98
  require "rails_error_dashboard/middleware/rate_limiter"