solid_queue_monitor 0.1.1 → 0.2.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 (24) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -4
  3. data/app/controllers/solid_queue_monitor/base_controller.rb +194 -0
  4. data/app/controllers/solid_queue_monitor/failed_jobs_controller.rb +62 -0
  5. data/app/controllers/solid_queue_monitor/in_progress_jobs_controller.rb +27 -0
  6. data/app/controllers/solid_queue_monitor/monitor_controller.rb +75 -7
  7. data/app/controllers/solid_queue_monitor/overview_controller.rb +25 -0
  8. data/app/controllers/solid_queue_monitor/queues_controller.rb +11 -0
  9. data/app/controllers/solid_queue_monitor/ready_jobs_controller.rb +14 -0
  10. data/app/controllers/solid_queue_monitor/recurring_jobs_controller.rb +14 -0
  11. data/app/controllers/solid_queue_monitor/scheduled_jobs_controller.rb +24 -0
  12. data/app/presenters/solid_queue_monitor/base_presenter.rb +57 -48
  13. data/app/presenters/solid_queue_monitor/failed_jobs_presenter.rb +257 -21
  14. data/app/presenters/solid_queue_monitor/in_progress_jobs_presenter.rb +71 -0
  15. data/app/presenters/solid_queue_monitor/jobs_presenter.rb +36 -2
  16. data/app/presenters/solid_queue_monitor/scheduled_jobs_presenter.rb +33 -10
  17. data/app/presenters/solid_queue_monitor/stats_presenter.rb +2 -2
  18. data/app/services/solid_queue_monitor/failed_job_service.rb +97 -0
  19. data/app/services/solid_queue_monitor/html_generator.rb +28 -2
  20. data/app/services/solid_queue_monitor/stats_calculator.rb +1 -0
  21. data/app/services/solid_queue_monitor/stylesheet_generator.rb +150 -1
  22. data/config/routes.rb +13 -7
  23. data/lib/solid_queue_monitor/version.rb +2 -2
  24. metadata +12 -2
@@ -0,0 +1,97 @@
1
+ module SolidQueueMonitor
2
+ class FailedJobService
3
+ def retry_job(failed_execution_id)
4
+ failed_execution = SolidQueue::FailedExecution.find_by(id: failed_execution_id)
5
+ return { success: false, message: "Failed job not found" } unless failed_execution
6
+
7
+ job = failed_execution.job
8
+ return { success: false, message: "Associated job not found" } unless job
9
+
10
+ ActiveRecord::Base.transaction do
11
+ SolidQueue::ReadyExecution.create!(
12
+ job_id: job.id,
13
+ queue_name: get_queue_name(failed_execution, job),
14
+ priority: job.priority
15
+ )
16
+
17
+ failed_execution.destroy!
18
+ end
19
+
20
+ { success: true, message: "Job moved to ready queue for retry" }
21
+ end
22
+
23
+ def discard_job(failed_execution_id)
24
+ failed_execution = SolidQueue::FailedExecution.find_by(id: failed_execution_id)
25
+ return { success: false, message: "Failed job not found" } unless failed_execution
26
+
27
+ job = failed_execution.job
28
+ return { success: false, message: "Associated job not found" } unless job
29
+
30
+ ActiveRecord::Base.transaction do
31
+ job.update!(finished_at: Time.current)
32
+
33
+ failed_execution.destroy!
34
+ end
35
+
36
+ { success: true, message: "Job has been discarded" }
37
+ end
38
+
39
+ def retry_all(job_ids)
40
+ return { success: false, message: "No jobs selected" } if job_ids.blank?
41
+
42
+ success_count = 0
43
+ failed_count = 0
44
+
45
+ job_ids.each do |id|
46
+ result = retry_job(id)
47
+ if result[:success]
48
+ success_count += 1
49
+ else
50
+ failed_count += 1
51
+ end
52
+ end
53
+
54
+ if success_count > 0 && failed_count == 0
55
+ { success: true, message: "All selected jobs have been queued for retry" }
56
+ elsif success_count > 0 && failed_count > 0
57
+ { success: true, message: "#{success_count} jobs queued for retry, #{failed_count} failed" }
58
+ else
59
+ { success: false, message: "Failed to retry jobs" }
60
+ end
61
+ end
62
+
63
+ def discard_all(job_ids)
64
+ return { success: false, message: "No jobs selected" } if job_ids.blank?
65
+
66
+ success_count = 0
67
+ failed_count = 0
68
+
69
+ job_ids.each do |id|
70
+ result = discard_job(id)
71
+ if result[:success]
72
+ success_count += 1
73
+ else
74
+ failed_count += 1
75
+ end
76
+ end
77
+
78
+ if success_count > 0 && failed_count == 0
79
+ { success: true, message: "All selected jobs have been discarded" }
80
+ elsif success_count > 0 && failed_count > 0
81
+ { success: true, message: "#{success_count} jobs discarded, #{failed_count} failed" }
82
+ else
83
+ { success: false, message: "Failed to discard jobs" }
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def get_queue_name(failed_execution, job)
90
+ if failed_execution.respond_to?(:queue_name) && failed_execution.queue_name.present?
91
+ failed_execution.queue_name
92
+ else
93
+ job.queue_name
94
+ end
95
+ end
96
+ end
97
+ end
@@ -54,7 +54,32 @@ module SolidQueueMonitor
54
54
  def render_message
55
55
  return '' unless @message
56
56
  class_name = @message_type == 'success' ? 'message-success' : 'message-error'
57
- "<div class='message #{class_name}'>#{@message}</div>"
57
+ <<-HTML
58
+ <div id="flash-message" class="message #{class_name}">#{@message}</div>
59
+ <script>
60
+ // Automatically hide the flash message after 5 seconds
61
+ document.addEventListener('DOMContentLoaded', function() {
62
+ var flashMessage = document.getElementById('flash-message');
63
+ if (flashMessage) {
64
+ setTimeout(function() {
65
+ flashMessage.style.opacity = '1';
66
+ // Fade out animation
67
+ var fadeEffect = setInterval(function() {
68
+ if (!flashMessage.style.opacity) {
69
+ flashMessage.style.opacity = 1;
70
+ }
71
+ if (flashMessage.style.opacity > 0) {
72
+ flashMessage.style.opacity -= 0.1;
73
+ } else {
74
+ clearInterval(fadeEffect);
75
+ flashMessage.style.display = 'none';
76
+ }
77
+ }, 50);
78
+ }, 5000); // 5 seconds
79
+ }
80
+ });
81
+ </script>
82
+ HTML
58
83
  end
59
84
 
60
85
  def generate_header
@@ -64,8 +89,9 @@ module SolidQueueMonitor
64
89
  <nav class="navigation">
65
90
  <a href="#{root_path}" class="nav-link">Overview</a>
66
91
  <a href="#{ready_jobs_path}" class="nav-link">Ready Jobs</a>
67
- <a href="#{recurring_jobs_path}" class="nav-link">Recurring Jobs</a>
92
+ <a href="#{in_progress_jobs_path}" class="nav-link">In Progress Jobs</a>
68
93
  <a href="#{scheduled_jobs_path}" class="nav-link">Scheduled Jobs</a>
94
+ <a href="#{recurring_jobs_path}" class="nav-link">Recurring Jobs</a>
69
95
  <a href="#{failed_jobs_path}" class="nav-link">Failed Jobs</a>
70
96
  <a href="#{queues_path}" class="nav-link">Queues</a>
71
97
  </nav>
@@ -7,6 +7,7 @@ module SolidQueueMonitor
7
7
  scheduled: SolidQueue::ScheduledExecution.count,
8
8
  ready: SolidQueue::ReadyExecution.count,
9
9
  failed: SolidQueue::FailedExecution.count,
10
+ in_progress: SolidQueue::ClaimedExecution.count,
10
11
  completed: SolidQueue::Job.where.not(finished_at: nil).count,
11
12
  recurring: SolidQueue::RecurringTask.count
12
13
  }
@@ -200,6 +200,7 @@ module SolidQueueMonitor
200
200
  padding: 1rem;
201
201
  margin-bottom: 1rem;
202
202
  border-radius: 0.375rem;
203
+ transition: opacity 0.5s ease-in-out;
203
204
  }
204
205
 
205
206
  .solid_queue_monitor .message-success {
@@ -318,12 +319,50 @@ module SolidQueueMonitor
318
319
  }
319
320
  }
320
321
 
322
+ .solid_queue_monitor .filter-and-actions-container {
323
+ display: flex;
324
+ justify-content: space-between;
325
+ align-items: flex-start;
326
+ gap: 1rem;
327
+ margin-bottom: 1rem;
328
+ }
329
+
321
330
  .solid_queue_monitor .filter-form-container {
322
331
  background: white;
323
332
  padding: 1rem;
324
333
  border-radius: 0.5rem;
325
- margin-bottom: 1rem;
326
334
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
335
+ flex: 3;
336
+ }
337
+
338
+ .solid_queue_monitor .bulk-actions-container {
339
+ display: flex;
340
+ flex-direction: row;
341
+ gap: 0.75rem;
342
+ padding: 1rem;
343
+ background: white;
344
+ border-radius: 0.5rem;
345
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
346
+ flex: 2;
347
+ align-items: center;
348
+ justify-content: center;
349
+ }
350
+
351
+ .solid_queue_monitor .large-button {
352
+ padding: 0.75rem 1.25rem;
353
+ font-size: 0.9rem;
354
+ text-align: center;
355
+ flex: 1;
356
+ }
357
+
358
+ @media (max-width: 992px) {
359
+ .solid_queue_monitor .filter-and-actions-container {
360
+ flex-direction: column;
361
+ }
362
+
363
+ .solid_queue_monitor .bulk-actions-container {
364
+ width: 100%;
365
+ }
327
366
  }
328
367
 
329
368
  .solid_queue_monitor .filter-form {
@@ -390,6 +429,116 @@ module SolidQueueMonitor
390
429
  .solid_queue_monitor .reset-button:hover {
391
430
  background: #e5e7eb;
392
431
  }
432
+
433
+ /* Action buttons for retry/discard */
434
+ .solid_queue_monitor .action-button {
435
+ padding: 0.5rem 1rem;
436
+ border-radius: 0.375rem;
437
+ font-size: 0.75rem;
438
+ font-weight: 500;
439
+ cursor: pointer;
440
+ transition: background-color 0.2s;
441
+ border: none;
442
+ text-decoration: none;
443
+ }
444
+
445
+ .solid_queue_monitor .retry-button {
446
+ background: #3b82f6;
447
+ color: white;
448
+ }
449
+
450
+ .solid_queue_monitor .retry-button:hover {
451
+ background: #2563eb;
452
+ }
453
+
454
+ .solid_queue_monitor .discard-button {
455
+ background: #ef4444;
456
+ color: white;
457
+ }
458
+
459
+ .solid_queue_monitor .discard-button:hover {
460
+ background: #dc2626;
461
+ }
462
+
463
+ .solid_queue_monitor .action-button:disabled {
464
+ opacity: 0.5;
465
+ cursor: not-allowed;
466
+ }
467
+
468
+ .solid_queue_monitor .inline-form {
469
+ display: inline-block;
470
+ margin-right: 0.5rem;
471
+ }
472
+
473
+ .solid_queue_monitor .actions-cell {
474
+ white-space: nowrap;
475
+ }
476
+
477
+ .solid_queue_monitor .bulk-actions {
478
+ display: flex;
479
+ gap: 0.5rem;
480
+ }
481
+
482
+ .solid_queue_monitor .error-message {
483
+ color: #dc2626;
484
+ font-weight: 500;
485
+ margin-bottom: 0.25rem;
486
+ }
487
+
488
+ .solid_queue_monitor .error-backtrace {
489
+ font-size: 0.75rem;
490
+ white-space: pre-wrap;
491
+ max-height: 200px;
492
+ overflow-y: auto;
493
+ background: #f3f4f6;
494
+ padding: 0.5rem;
495
+ border-radius: 0.25rem;
496
+ margin-top: 0.5rem;
497
+ }
498
+
499
+ .solid_queue_monitor details {
500
+ margin-top: 0.25rem;
501
+ }
502
+
503
+ .solid_queue_monitor summary {
504
+ cursor: pointer;
505
+ color: #6b7280;
506
+ font-size: 0.75rem;
507
+ }
508
+
509
+ .solid_queue_monitor summary:hover {
510
+ color: #4b5563;
511
+ }
512
+
513
+ .solid_queue_monitor .job-checkbox,
514
+ .solid_queue_monitor .select-all-checkbox {
515
+ width: 1rem;
516
+ height: 1rem;
517
+ }
518
+
519
+ .solid_queue_monitor .bulk-actions-bar {
520
+ display: flex;
521
+ gap: 0.75rem;
522
+ margin: 1rem 0;
523
+ background: white;
524
+ padding: 0.75rem;
525
+ border-radius: 0.5rem;
526
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
527
+ }
528
+
529
+ .solid_queue_monitor .bulk-actions-bar .action-button {
530
+ padding: 0.6rem 1rem;
531
+ font-size: 0.875rem;
532
+ }
533
+
534
+ .solid_queue_monitor .execute-button {
535
+ background: var(--primary-color);
536
+ color: white;
537
+ }
538
+
539
+ .solid_queue_monitor .execute-button:hover {
540
+ background: #2563eb;
541
+ }
393
542
  CSS
394
543
  end
395
544
  end
data/config/routes.rb CHANGED
@@ -1,11 +1,17 @@
1
1
  SolidQueueMonitor::Engine.routes.draw do
2
- root to: 'monitor#index'
2
+ root to: 'overview#index', as: :root
3
3
 
4
- get 'ready_jobs', to: 'monitor#ready_jobs'
5
- get 'scheduled_jobs', to: 'monitor#scheduled_jobs'
6
- get 'failed_jobs', to: 'monitor#failed_jobs'
7
- get 'recurring_jobs', to: 'monitor#recurring_jobs'
8
- get 'queues', to: 'monitor#queues'
4
+ resources :ready_jobs, only: [:index]
5
+ resources :scheduled_jobs, only: [:index]
6
+ resources :recurring_jobs, only: [:index]
7
+ resources :failed_jobs, only: [:index]
8
+ resources :in_progress_jobs, only: [:index]
9
+ resources :queues, only: [:index]
9
10
 
10
- post 'execute_jobs', to: 'monitor#execute_jobs'
11
+ post 'execute_jobs', to: 'scheduled_jobs#create', as: :execute_jobs
12
+
13
+ post 'retry_failed_job/:id', to: 'failed_jobs#retry', as: :retry_failed_job
14
+ post 'discard_failed_job/:id', to: 'failed_jobs#discard', as: :discard_failed_job
15
+ post 'retry_failed_jobs', to: 'failed_jobs#retry_all', as: :retry_failed_jobs
16
+ post 'discard_failed_jobs', to: 'failed_jobs#discard_all', as: :discard_failed_jobs
11
17
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidQueueMonitor
4
- VERSION = "0.1.1"
5
- end
4
+ VERSION = "0.2.0"
5
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_queue_monitor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vishal Sadriya
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-03-15 00:00:00.000000000 Z
10
+ date: 2025-03-28 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -187,9 +187,18 @@ extra_rdoc_files: []
187
187
  files:
188
188
  - README.md
189
189
  - Rakefile
190
+ - app/controllers/solid_queue_monitor/base_controller.rb
191
+ - app/controllers/solid_queue_monitor/failed_jobs_controller.rb
192
+ - app/controllers/solid_queue_monitor/in_progress_jobs_controller.rb
190
193
  - app/controllers/solid_queue_monitor/monitor_controller.rb
194
+ - app/controllers/solid_queue_monitor/overview_controller.rb
195
+ - app/controllers/solid_queue_monitor/queues_controller.rb
196
+ - app/controllers/solid_queue_monitor/ready_jobs_controller.rb
197
+ - app/controllers/solid_queue_monitor/recurring_jobs_controller.rb
198
+ - app/controllers/solid_queue_monitor/scheduled_jobs_controller.rb
191
199
  - app/presenters/solid_queue_monitor/base_presenter.rb
192
200
  - app/presenters/solid_queue_monitor/failed_jobs_presenter.rb
201
+ - app/presenters/solid_queue_monitor/in_progress_jobs_presenter.rb
193
202
  - app/presenters/solid_queue_monitor/jobs_presenter.rb
194
203
  - app/presenters/solid_queue_monitor/queues_presenter.rb
195
204
  - app/presenters/solid_queue_monitor/ready_jobs_presenter.rb
@@ -198,6 +207,7 @@ files:
198
207
  - app/presenters/solid_queue_monitor/stats_presenter.rb
199
208
  - app/services/solid_queue_monitor/authentication_service.rb
200
209
  - app/services/solid_queue_monitor/execute_job_service.rb
210
+ - app/services/solid_queue_monitor/failed_job_service.rb
201
211
  - app/services/solid_queue_monitor/html_generator.rb
202
212
  - app/services/solid_queue_monitor/pagination_service.rb
203
213
  - app/services/solid_queue_monitor/stats_calculator.rb