diff --git a/CHANGELOG.md b/CHANGELOG.md index ddd9d4c..9cc916e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Changelog +## [0.3.2] - 2025-06-12 + +### Added + +- Added reject functionality for scheduled jobs with bulk operations support +- New "Reject Selected" button in scheduled jobs view alongside "Execute Selected" +- Added `RejectJobService` for handling job rejection logic +- Added confirmation dialog for reject operations to prevent accidental job cancellation +- Added `POST /reject_jobs` route for bulk rejection operations + +### Improved + +- Enhanced scheduled jobs UI with dual action buttons (Execute/Reject) +- Improved JavaScript form handling to prevent duplicate job ID submissions +- Added proper error handling and success messaging for reject operations +- Optimized button state management for better user experience + +### Fixed + +- Fixed duplicate job ID issue in form submissions for bulk operations +- Corrected JavaScript form submission logic to prevent parameter duplication + ## [0.3.1] - 2024-03-28 ### Improved diff --git a/Gemfile.lock b/Gemfile.lock index f869342..9baf274 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - solid_queue_monitor (0.2.0) + solid_queue_monitor (0.3.2) rails (>= 7.0) solid_queue (>= 0.1.0) @@ -255,9 +255,6 @@ GEM fugit (~> 1.11.0) railties (>= 7.1) thor (~> 1.3.1) - sqlite3 (2.6.0) - mini_portile2 (~> 2.8.0) - sqlite3 (2.6.0-arm64-darwin) stringio (3.1.5) thor (1.3.2) timeout (0.4.3) @@ -285,7 +282,6 @@ DEPENDENCIES rubocop-rails rubocop-rspec solid_queue_monitor! - sqlite3 BUNDLED WITH 2.6.2 diff --git a/README.md b/README.md index d183bdb..200d62f 100644 --- a/README.md +++ b/README.md @@ -18,12 +18,12 @@ A lightweight, zero-dependency web interface for monitoring Solid Queue backgrou - **Dashboard Overview**: Get a quick snapshot of your queue's health with statistics on all job types - **Ready Jobs**: View jobs that are ready to be executed - **In Progress Jobs**: Monitor jobs currently being processed by workers -- **Scheduled Jobs**: See upcoming jobs scheduled for future execution +- **Scheduled Jobs**: See upcoming jobs scheduled for future execution with ability to execute immediately or reject permanently - **Recurring Jobs**: Manage periodic jobs that run on a schedule - **Failed Jobs**: Track and debug failed jobs, with the ability to retry or discard them - **Queue Management**: View and filter jobs by queue - **Advanced Job Filtering**: Filter jobs by class name, queue, status, and job arguments -- **Quick Actions**: Retry or discard failed jobs directly from any view +- **Quick Actions**: Retry or discard failed jobs, execute or reject scheduled jobs directly from any view - **Performance Optimized**: Designed for high-volume applications with smart pagination - **Optional Authentication**: Secure your dashboard with HTTP Basic Authentication - **Responsive Design**: Works on desktop and mobile devices @@ -44,7 +44,7 @@ A lightweight, zero-dependency web interface for monitoring Solid Queue backgrou Add this line to your application's Gemfile: ```ruby -gem 'solid_queue_monitor', '~> 0.3.1' +gem 'solid_queue_monitor', '~> 0.3.2' ``` Then execute: @@ -103,9 +103,9 @@ The dashboard provides several views: - **Overview**: Shows statistics and recent jobs - **Ready Jobs**: Jobs that are ready to be executed -- **Scheduled Jobs**: Jobs scheduled for future execution +- **Scheduled Jobs**: Jobs scheduled for future execution with execute and reject actions - **Recurring Jobs**: Jobs that run on a recurring schedule -- **Failed Jobs**: Jobs that have failed with error details +- **Failed Jobs**: Jobs that have failed with error details and retry/discard actions - **Queues**: Distribution of jobs across different queues ### API-only Applications @@ -127,7 +127,7 @@ This makes it easy to find specific jobs when debugging issues in your applicati - **Production Monitoring**: Keep an eye on your background job processing in production environments - **Debugging**: Quickly identify and troubleshoot failed jobs -- **Job Management**: Execute scheduled jobs on demand when needed +- **Job Management**: Execute scheduled jobs on demand or reject unwanted jobs permanently - **Performance Analysis**: Track job distribution and identify bottlenecks - **DevOps Integration**: Easily integrate with your monitoring stack diff --git a/app/controllers/solid_queue_monitor/scheduled_jobs_controller.rb b/app/controllers/solid_queue_monitor/scheduled_jobs_controller.rb index b12a181..f7280b8 100644 --- a/app/controllers/solid_queue_monitor/scheduled_jobs_controller.rb +++ b/app/controllers/solid_queue_monitor/scheduled_jobs_controller.rb @@ -21,5 +21,16 @@ def create end redirect_to scheduled_jobs_path end + + def reject_all + result = SolidQueueMonitor::RejectJobService.new.reject_many(params[:job_ids]) + + if result[:success] + set_flash_message(result[:message], 'success') + else + set_flash_message(result[:message], 'error') + end + redirect_to scheduled_jobs_path + end end end diff --git a/app/presenters/solid_queue_monitor/scheduled_jobs_presenter.rb b/app/presenters/solid_queue_monitor/scheduled_jobs_presenter.rb index 6e06fb8..7169247 100644 --- a/app/presenters/solid_queue_monitor/scheduled_jobs_presenter.rb +++ b/app/presenters/solid_queue_monitor/scheduled_jobs_presenter.rb @@ -46,13 +46,14 @@ def generate_filter_form
+
HTML end def generate_table_with_actions <<-HTML -
+ #{generate_table}
HTML @@ -130,7 +159,7 @@ def generate_row(execution) <<-HTML - + #{execution.job.class_name} #{execution.queue_name} diff --git a/app/services/solid_queue_monitor/reject_job_service.rb b/app/services/solid_queue_monitor/reject_job_service.rb new file mode 100644 index 0000000..82d8753 --- /dev/null +++ b/app/services/solid_queue_monitor/reject_job_service.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module SolidQueueMonitor + class RejectJobService + def call(id) + execution = SolidQueue::ScheduledExecution.find(id) + reject_job(execution) + end + + def reject_many(ids) + return { success: false, message: 'No jobs selected' } if ids.blank? + + success_count = 0 + failed_count = 0 + + ids.each do |id| + execution = SolidQueue::ScheduledExecution.find_by(id: id) + if execution + reject_job(execution) + success_count += 1 + else + failed_count += 1 + end + rescue StandardError + failed_count += 1 + end + + if success_count.positive? && failed_count.zero? + { success: true, message: 'All selected jobs have been rejected' } + elsif success_count.positive? && failed_count.positive? + { success: true, message: "#{success_count} jobs rejected, #{failed_count} failed" } + else + { success: false, message: 'Failed to reject jobs' } + end + end + + private + + def reject_job(execution) + ActiveRecord::Base.transaction do + # Mark the associated job as finished to indicate it was rejected + execution.job.update!(finished_at: Time.current) + + # Remove the scheduled execution + execution.destroy + end + end + end +end diff --git a/config/routes.rb b/config/routes.rb index 9e420a3..6eb73b6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,6 +11,7 @@ resources :queues, only: [:index] post 'execute_jobs', to: 'scheduled_jobs#create', as: :execute_jobs + post 'reject_jobs', to: 'scheduled_jobs#reject_all', as: :reject_jobs post 'retry_failed_job/:id', to: 'failed_jobs#retry', as: :retry_failed_job post 'discard_failed_job/:id', to: 'failed_jobs#discard', as: :discard_failed_job diff --git a/lib/solid_queue_monitor/version.rb b/lib/solid_queue_monitor/version.rb index 7879184..aba599d 100644 --- a/lib/solid_queue_monitor/version.rb +++ b/lib/solid_queue_monitor/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module SolidQueueMonitor - VERSION = '0.3.1' + VERSION = '0.3.2' end diff --git a/spec/services/solid_queue_monitor/reject_job_service_spec.rb b/spec/services/solid_queue_monitor/reject_job_service_spec.rb new file mode 100644 index 0000000..44bb7fb --- /dev/null +++ b/spec/services/solid_queue_monitor/reject_job_service_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidQueueMonitor::RejectJobService do + describe '#reject_many' do + subject { described_class.new } + + let!(:scheduled_execution1) { create(:solid_queue_scheduled_execution) } + let!(:scheduled_execution2) { create(:solid_queue_scheduled_execution) } + + it 'rejects scheduled jobs and marks them as finished' do + expect do + result = subject.reject_many([scheduled_execution1.id, scheduled_execution2.id]) + expect(result[:success]).to be true + end.to change(SolidQueue::ScheduledExecution, :count).by(-2) + end + + it 'marks associated jobs as finished when rejecting' do + subject.reject_many([scheduled_execution1.id]) + + job = scheduled_execution1.job.reload + expect(job.finished_at).to be_present + end + + it 'returns success message when all jobs are rejected successfully' do + result = subject.reject_many([scheduled_execution1.id, scheduled_execution2.id]) + + expect(result[:success]).to be true + expect(result[:message]).to eq('All selected jobs have been rejected') + end + + it 'handles non-existent job IDs gracefully' do + result = subject.reject_many([999_999]) + + expect(result[:success]).to be false + expect(result[:message]).to eq('Failed to reject jobs') + end + + it 'handles empty job IDs array gracefully' do + result = subject.reject_many([]) + + expect(result[:success]).to be false + expect(result[:message]).to eq('No jobs selected') + end + + it 'handles mix of valid and invalid job IDs' do + result = subject.reject_many([scheduled_execution1.id, 999_999]) + + expect(result[:success]).to be true + expect(result[:message]).to include('1 jobs rejected, 1 failed') + end + + it 'removes scheduled execution from database' do + subject.reject_many([scheduled_execution1.id]) + + expect(SolidQueue::ScheduledExecution.find_by(id: scheduled_execution1.id)).to be_nil + end + end + + describe '#call' do + subject { described_class.new } + + let!(:scheduled_execution) { create(:solid_queue_scheduled_execution) } + + it 'rejects a single scheduled job' do + expect do + subject.call(scheduled_execution.id) + end.to change(SolidQueue::ScheduledExecution, :count).by(-1) + end + + it 'marks the job as finished' do + subject.call(scheduled_execution.id) + + job = scheduled_execution.job.reload + expect(job.finished_at).to be_present + end + end +end