solid_queue_monitor 0.2.0 → 0.3.0

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +24 -10
  3. data/Rakefile +8 -8
  4. data/app/controllers/solid_queue_monitor/application_controller.rb +25 -0
  5. data/app/controllers/solid_queue_monitor/base_controller.rb +70 -67
  6. data/app/controllers/solid_queue_monitor/failed_jobs_controller.rb +8 -7
  7. data/app/controllers/solid_queue_monitor/in_progress_jobs_controller.rb +16 -10
  8. data/app/controllers/solid_queue_monitor/overview_controller.rb +13 -12
  9. data/app/controllers/solid_queue_monitor/queues_controller.rb +4 -2
  10. data/app/controllers/solid_queue_monitor/ready_jobs_controller.rb +8 -7
  11. data/app/controllers/solid_queue_monitor/recurring_jobs_controller.rb +7 -6
  12. data/app/controllers/solid_queue_monitor/scheduled_jobs_controller.rb +8 -7
  13. data/app/presenters/solid_queue_monitor/base_presenter.rb +42 -41
  14. data/app/presenters/solid_queue_monitor/failed_jobs_presenter.rb +51 -41
  15. data/app/presenters/solid_queue_monitor/in_progress_jobs_presenter.rb +13 -2
  16. data/app/presenters/solid_queue_monitor/jobs_presenter.rb +18 -9
  17. data/app/presenters/solid_queue_monitor/queues_presenter.rb +5 -5
  18. data/app/presenters/solid_queue_monitor/ready_jobs_presenter.rb +10 -2
  19. data/app/presenters/solid_queue_monitor/recurring_jobs_presenter.rb +5 -2
  20. data/app/presenters/solid_queue_monitor/scheduled_jobs_presenter.rb +15 -9
  21. data/app/presenters/solid_queue_monitor/stats_presenter.rb +3 -1
  22. data/app/services/solid_queue_monitor/authentication_service.rb +7 -5
  23. data/app/services/solid_queue_monitor/execute_job_service.rb +3 -1
  24. data/app/services/solid_queue_monitor/failed_job_service.rb +38 -36
  25. data/app/services/solid_queue_monitor/html_generator.rb +5 -2
  26. data/app/services/solid_queue_monitor/pagination_service.rb +3 -1
  27. data/app/services/solid_queue_monitor/stats_calculator.rb +3 -1
  28. data/app/services/solid_queue_monitor/status_calculator.rb +4 -1
  29. data/app/services/solid_queue_monitor/stylesheet_generator.rb +23 -21
  30. data/config/initializers/solid_queue_monitor.rb +3 -1
  31. data/config/routes.rb +6 -4
  32. data/lib/generators/solid_queue_monitor/install_generator.rb +7 -5
  33. data/lib/generators/solid_queue_monitor/templates/initializer.rb +3 -1
  34. data/lib/solid_queue_monitor/engine.rb +5 -3
  35. data/lib/solid_queue_monitor/version.rb +2 -2
  36. data/lib/solid_queue_monitor.rb +9 -13
  37. data/lib/tasks/app.rake +42 -40
  38. metadata +9 -148
  39. data/app/controllers/solid_queue_monitor/monitor_controller.rb +0 -318
@@ -1,318 +0,0 @@
1
- module SolidQueueMonitor
2
- class MonitorController < ActionController::Base
3
- include ActionController::HttpAuthentication::Basic::ControllerMethods
4
- include ActionController::Flash
5
-
6
- before_action :authenticate, if: -> { SolidQueueMonitor::AuthenticationService.authentication_required? }
7
- layout false
8
- skip_before_action :verify_authenticity_token, only: [:execute_jobs, :retry_failed_job, :discard_failed_job, :retry_failed_jobs, :discard_failed_jobs]
9
-
10
- # Define a helper method for setting flash messages
11
- def set_flash_message(message, type)
12
- session[:flash_message] = message
13
- session[:flash_type] = type
14
- end
15
-
16
- def index
17
- @stats = SolidQueueMonitor::StatsCalculator.calculate
18
-
19
- # Get all jobs with pagination
20
- @recent_jobs = paginate(filter_jobs(SolidQueue::Job.order(created_at: :desc)))
21
-
22
- # Preload failed job information
23
- preload_job_statuses(@recent_jobs[:records])
24
-
25
- render_page('Overview', generate_overview_content)
26
- end
27
-
28
- def ready_jobs
29
- base_query = SolidQueue::ReadyExecution.includes(:job).order(created_at: :desc)
30
- @ready_jobs = paginate(filter_ready_jobs(base_query))
31
- render_page('Ready Jobs', SolidQueueMonitor::ReadyJobsPresenter.new(@ready_jobs[:records],
32
- current_page: @ready_jobs[:current_page],
33
- total_pages: @ready_jobs[:total_pages],
34
- filters: filter_params
35
- ).render)
36
- end
37
-
38
- def scheduled_jobs
39
- base_query = SolidQueue::ScheduledExecution.includes(:job).order(scheduled_at: :asc)
40
- @scheduled_jobs = paginate(filter_scheduled_jobs(base_query))
41
- render_page('Scheduled Jobs', SolidQueueMonitor::ScheduledJobsPresenter.new(@scheduled_jobs[:records],
42
- current_page: @scheduled_jobs[:current_page],
43
- total_pages: @scheduled_jobs[:total_pages],
44
- filters: filter_params
45
- ).render)
46
- end
47
-
48
- def recurring_jobs
49
- base_query = filter_recurring_jobs(SolidQueue::RecurringTask.order(:key))
50
- @recurring_jobs = paginate(base_query)
51
- render_page('Recurring Jobs', SolidQueueMonitor::RecurringJobsPresenter.new(@recurring_jobs[:records],
52
- current_page: @recurring_jobs[:current_page],
53
- total_pages: @recurring_jobs[:total_pages],
54
- filters: filter_params
55
- ).render)
56
- end
57
-
58
- def failed_jobs
59
- base_query = SolidQueue::FailedExecution.includes(:job).order(created_at: :desc)
60
- @failed_jobs = paginate(filter_failed_jobs(base_query))
61
- render_page('Failed Jobs', SolidQueueMonitor::FailedJobsPresenter.new(@failed_jobs[:records],
62
- current_page: @failed_jobs[:current_page],
63
- total_pages: @failed_jobs[:total_pages],
64
- filters: filter_params
65
- ).render)
66
- end
67
-
68
- def queues
69
- @queues = SolidQueue::Job.group(:queue_name)
70
- .select('queue_name, COUNT(*) as job_count')
71
- .order('job_count DESC')
72
- render_page('Queues', SolidQueueMonitor::QueuesPresenter.new(@queues).render)
73
- end
74
-
75
- def execute_jobs
76
- if params[:job_ids].present?
77
- SolidQueueMonitor::ExecuteJobService.new.execute_many(params[:job_ids])
78
- set_flash_message('Selected jobs moved to ready queue', 'success')
79
- else
80
- set_flash_message('No jobs selected', 'error')
81
- end
82
- redirect_to scheduled_jobs_path
83
- end
84
-
85
- def retry_failed_job
86
- id = params[:id]
87
- service = SolidQueueMonitor::FailedJobService.new
88
-
89
- if service.retry_job(id)
90
- set_flash_message("Job #{id} has been queued for retry.", 'success')
91
- else
92
- set_flash_message("Failed to retry job #{id}.", 'error')
93
- end
94
- redirect_to failed_jobs_path
95
- end
96
-
97
- def discard_failed_job
98
- id = params[:id]
99
- service = SolidQueueMonitor::FailedJobService.new
100
-
101
- if service.discard_job(id)
102
- set_flash_message("Job #{id} has been discarded.", 'success')
103
- else
104
- set_flash_message("Failed to discard job #{id}.", 'error')
105
- end
106
- redirect_to failed_jobs_path
107
- end
108
-
109
- def retry_failed_jobs
110
- result = SolidQueueMonitor::FailedJobService.new.retry_all(params[:job_ids])
111
-
112
- if result[:success]
113
- set_flash_message(result[:message], 'success')
114
- else
115
- set_flash_message(result[:message], 'error')
116
- end
117
- redirect_to failed_jobs_path
118
- end
119
-
120
- def discard_failed_jobs
121
- result = SolidQueueMonitor::FailedJobService.new.discard_all(params[:job_ids])
122
-
123
- if result[:success]
124
- set_flash_message(result[:message], 'success')
125
- else
126
- set_flash_message(result[:message], 'error')
127
- end
128
- redirect_to failed_jobs_path
129
- end
130
-
131
- private
132
-
133
- def authenticate
134
- authenticate_or_request_with_http_basic do |username, password|
135
- SolidQueueMonitor::AuthenticationService.authenticate(username, password)
136
- end
137
- end
138
-
139
- def paginate(relation)
140
- PaginationService.new(relation, current_page, per_page).paginate
141
- end
142
-
143
- def render_page(title, content)
144
- # Get flash message from session
145
- message = session[:flash_message]
146
- message_type = session[:flash_type]
147
-
148
- # Clear the flash message from session after using it
149
- session.delete(:flash_message)
150
- session.delete(:flash_type)
151
-
152
- html = SolidQueueMonitor::HtmlGenerator.new(
153
- title: title,
154
- content: content,
155
- message: message,
156
- message_type: message_type
157
- ).generate
158
-
159
- render html: html.html_safe
160
- end
161
-
162
- def generate_overview_content
163
- SolidQueueMonitor::StatsPresenter.new(@stats).render +
164
- SolidQueueMonitor::JobsPresenter.new(@recent_jobs[:records],
165
- current_page: @recent_jobs[:current_page],
166
- total_pages: @recent_jobs[:total_pages],
167
- filters: filter_params
168
- ).render
169
- end
170
-
171
- def current_page
172
- (params[:page] || 1).to_i
173
- end
174
-
175
- def per_page
176
- SolidQueueMonitor.jobs_per_page
177
- end
178
-
179
- # Preload job statuses to avoid N+1 queries
180
- def preload_job_statuses(jobs)
181
- return if jobs.empty?
182
-
183
- # Get all job IDs
184
- job_ids = jobs.map(&:id)
185
-
186
- # Find all failed jobs in a single query
187
- failed_job_ids = SolidQueue::FailedExecution.where(job_id: job_ids).pluck(:job_id)
188
-
189
- # Find all scheduled jobs in a single query
190
- scheduled_job_ids = SolidQueue::ScheduledExecution.where(job_id: job_ids).pluck(:job_id)
191
-
192
- # Attach the status information to each job
193
- jobs.each do |job|
194
- job.instance_variable_set(:@failed, failed_job_ids.include?(job.id))
195
- job.instance_variable_set(:@scheduled, scheduled_job_ids.include?(job.id))
196
- end
197
-
198
- # Define the method to check if a job is failed
199
- SolidQueue::Job.class_eval do
200
- def failed?
201
- if instance_variable_defined?(:@failed)
202
- @failed
203
- else
204
- SolidQueue::FailedExecution.exists?(job_id: id)
205
- end
206
- end
207
-
208
- def scheduled?
209
- if instance_variable_defined?(:@scheduled)
210
- @scheduled
211
- else
212
- SolidQueue::ScheduledExecution.exists?(job_id: id)
213
- end
214
- end
215
- end
216
- end
217
-
218
- def filter_jobs(relation)
219
- relation = relation.where("class_name LIKE ?", "%#{params[:class_name]}%") if params[:class_name].present?
220
- relation = relation.where("queue_name LIKE ?", "%#{params[:queue_name]}%") if params[:queue_name].present?
221
-
222
- if params[:status].present?
223
- case params[:status]
224
- when 'completed'
225
- relation = relation.where.not(finished_at: nil)
226
- when 'failed'
227
- failed_job_ids = SolidQueue::FailedExecution.pluck(:job_id)
228
- relation = relation.where(id: failed_job_ids)
229
- when 'scheduled'
230
- scheduled_job_ids = SolidQueue::ScheduledExecution.pluck(:job_id)
231
- relation = relation.where(id: scheduled_job_ids)
232
- when 'pending'
233
- # Pending jobs are those that are not completed, failed, or scheduled
234
- failed_job_ids = SolidQueue::FailedExecution.pluck(:job_id)
235
- scheduled_job_ids = SolidQueue::ScheduledExecution.pluck(:job_id)
236
- relation = relation.where(finished_at: nil)
237
- .where.not(id: failed_job_ids + scheduled_job_ids)
238
- end
239
- end
240
-
241
- relation
242
- end
243
-
244
- def filter_ready_jobs(relation)
245
- return relation unless params[:class_name].present? || params[:queue_name].present?
246
-
247
- if params[:class_name].present?
248
- job_ids = SolidQueue::Job.where("class_name LIKE ?", "%#{params[:class_name]}%").pluck(:id)
249
- relation = relation.where(job_id: job_ids)
250
- end
251
-
252
- if params[:queue_name].present?
253
- relation = relation.where("queue_name LIKE ?", "%#{params[:queue_name]}%")
254
- end
255
-
256
- relation
257
- end
258
-
259
- def filter_scheduled_jobs(relation)
260
- return relation unless params[:class_name].present? || params[:queue_name].present?
261
-
262
- if params[:class_name].present?
263
- job_ids = SolidQueue::Job.where("class_name LIKE ?", "%#{params[:class_name]}%").pluck(:id)
264
- relation = relation.where(job_id: job_ids)
265
- end
266
-
267
- if params[:queue_name].present?
268
- relation = relation.where("queue_name LIKE ?", "%#{params[:queue_name]}%")
269
- end
270
-
271
- relation
272
- end
273
-
274
- def filter_recurring_jobs(relation)
275
- return relation unless params[:class_name].present? || params[:queue_name].present?
276
-
277
- if params[:class_name].present?
278
- relation = relation.where("class_name LIKE ?", "%#{params[:class_name]}%")
279
- end
280
-
281
- if params[:queue_name].present?
282
- relation = relation.where("queue_name LIKE ?", "%#{params[:queue_name]}%")
283
- end
284
-
285
- relation
286
- end
287
-
288
- def filter_failed_jobs(relation)
289
- return relation unless params[:class_name].present? || params[:queue_name].present?
290
-
291
- if params[:class_name].present?
292
- job_ids = SolidQueue::Job.where("class_name LIKE ?", "%#{params[:class_name]}%").pluck(:id)
293
- relation = relation.where(job_id: job_ids)
294
- end
295
-
296
- if params[:queue_name].present?
297
- # Check if FailedExecution has queue_name column
298
- if relation.column_names.include?('queue_name')
299
- relation = relation.where("queue_name LIKE ?", "%#{params[:queue_name]}%")
300
- else
301
- # If not, filter by job's queue_name
302
- job_ids = SolidQueue::Job.where("queue_name LIKE ?", "%#{params[:queue_name]}%").pluck(:id)
303
- relation = relation.where(job_id: job_ids)
304
- end
305
- end
306
-
307
- relation
308
- end
309
-
310
- def filter_params
311
- {
312
- class_name: params[:class_name],
313
- queue_name: params[:queue_name],
314
- status: params[:status]
315
- }
316
- end
317
- end
318
- end