From f4ed5b7f4df746c5588762933ce9ac113928f4e2 Mon Sep 17 00:00:00 2001 From: Chuck Smith Date: Wed, 20 May 2026 10:11:20 -0400 Subject: [PATCH 1/5] chore: consolidate retry/discard actions in FailedJobsController MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit destroy and retry now branch on params[:id] — single execution when present, filtered scope when absent. discard_all and retry_all routes map to the same actions via action: option, eliminating two duplicate action methods. Co-Authored-By: Claude Sonnet 4.6 --- .../solid_queue_web/failed_jobs_controller.rb | 40 +++++++++---------- config/routes.rb | 4 +- .../solid_queue_web/failed_jobs_spec.rb | 4 +- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/app/controllers/solid_queue_web/failed_jobs_controller.rb b/app/controllers/solid_queue_web/failed_jobs_controller.rb index 03072a3..e09d6b6 100644 --- a/app/controllers/solid_queue_web/failed_jobs_controller.rb +++ b/app/controllers/solid_queue_web/failed_jobs_controller.rb @@ -1,6 +1,6 @@ module SolidQueueWeb class FailedJobsController < ApplicationController - before_action :set_filter_params, only: [:index, :retry_all, :discard_all] + before_action :set_filter_params, only: [:index, :retry, :destroy] def index respond_to do |format| @@ -14,35 +14,19 @@ def index end def retry - execution = SolidQueue::FailedExecution.find(params[:id]) - execution.retry - redirect_to failed_jobs_path, notice: "Job queued for retry." + executions = params[:id] ? [SolidQueue::FailedExecution.find(params[:id])] : filtered_scope.to_a + perform_retry(executions) rescue => e redirect_to failed_jobs_path, alert: "Could not retry job: #{e.message}" end def destroy - execution = SolidQueue::FailedExecution.find(params[:id]) - execution.discard - redirect_to failed_jobs_path, notice: "Job discarded." + executions = params[:id] ? [SolidQueue::FailedExecution.find(params[:id])] : filtered_scope.to_a + perform_discard(executions) rescue => e redirect_to failed_jobs_path, alert: "Could not discard job: #{e.message}" end - def retry_all - jobs = filtered_scope.map(&:job) - SolidQueue::FailedExecution.retry_all(jobs) - redirect_to failed_jobs_path(queue: @queue, q: @search, period: @period), - notice: "#{jobs.size} #{"job".pluralize(jobs.size)} queued for retry." - end - - def discard_all - jobs = filtered_scope.map(&:job) - SolidQueue::FailedExecution.discard_all_from_jobs(jobs) - redirect_to failed_jobs_path(queue: @queue, q: @search, period: @period), - notice: "#{jobs.size} #{"job".pluralize(jobs.size)} discarded." - end - private def failed_jobs_csv @@ -58,6 +42,20 @@ def failed_jobs_csv end end + def perform_retry(executions) + jobs = executions.map(&:job) + SolidQueue::FailedExecution.retry_all(jobs) + redirect_to failed_jobs_path(queue: @queue, q: @search, period: @period), + notice: "#{jobs.size} #{"job".pluralize(jobs.size)} queued for retry." + end + + def perform_discard(executions) + jobs = executions.map(&:job) + SolidQueue::FailedExecution.discard_all_from_jobs(jobs) + redirect_to failed_jobs_path(queue: @queue, q: @search, period: @period), + notice: "#{jobs.size} #{"job".pluralize(jobs.size)} discarded." + end + def set_filter_params @queue = params[:queue].presence @search = params[:q].presence diff --git a/config/routes.rb b/config/routes.rb index e389559..6130d3d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -33,8 +33,8 @@ controller: "failed_jobs/selections" resources :failed_jobs, only: [:index, :destroy] do collection do - post :retry_all - post :discard_all + post :retry_all, action: :retry + post :discard_all, action: :destroy end member do post :retry diff --git a/spec/requests/solid_queue_web/failed_jobs_spec.rb b/spec/requests/solid_queue_web/failed_jobs_spec.rb index d5cef9c..9dc20d2 100644 --- a/spec/requests/solid_queue_web/failed_jobs_spec.rb +++ b/spec/requests/solid_queue_web/failed_jobs_spec.rb @@ -152,7 +152,7 @@ end it "handles retry failure gracefully" do - allow_any_instance_of(SolidQueue::FailedExecution).to receive(:retry).and_raise(RuntimeError, "boom") + allow(SolidQueue::FailedExecution).to receive(:retry_all).and_raise(RuntimeError, "boom") post "/jobs/failed_jobs/#{execution.id}/retry" expect(response).to redirect_to("/jobs/failed_jobs") follow_redirect! @@ -176,7 +176,7 @@ end it "handles discard failure gracefully" do - allow_any_instance_of(SolidQueue::FailedExecution).to receive(:discard).and_raise(RuntimeError, "boom") + allow(SolidQueue::FailedExecution).to receive(:discard_all_from_jobs).and_raise(RuntimeError, "boom") delete "/jobs/failed_jobs/#{execution.id}" expect(response).to redirect_to("/jobs/failed_jobs") follow_redirect! From b312f193e81a9415b2eb5ed48e0cc3a3c9d6dbb5 Mon Sep 17 00:00:00 2001 From: Chuck Smith Date: Wed, 20 May 2026 10:17:00 -0400 Subject: [PATCH 2/5] chore: extract retry logic into RetryFailedJobsController MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Retry is a creation event — single job and bulk retry_all both route to RetryFailedJobsController#create, branching on params[:id]. FailedJobsController now owns only index and destroy. Co-Authored-By: Claude Sonnet 4.6 --- .../solid_queue_web/failed_jobs_controller.rb | 16 +--------- .../retry_failed_jobs_controller.rb | 31 +++++++++++++++++++ config/routes.rb | 4 +-- .../solid_queue_web/failed_jobs_spec.rb | 4 +-- 4 files changed, 36 insertions(+), 19 deletions(-) create mode 100644 app/controllers/solid_queue_web/retry_failed_jobs_controller.rb diff --git a/app/controllers/solid_queue_web/failed_jobs_controller.rb b/app/controllers/solid_queue_web/failed_jobs_controller.rb index e09d6b6..9a4634f 100644 --- a/app/controllers/solid_queue_web/failed_jobs_controller.rb +++ b/app/controllers/solid_queue_web/failed_jobs_controller.rb @@ -1,6 +1,6 @@ module SolidQueueWeb class FailedJobsController < ApplicationController - before_action :set_filter_params, only: [:index, :retry, :destroy] + before_action :set_filter_params, only: [:index, :destroy] def index respond_to do |format| @@ -13,13 +13,6 @@ def index end end - def retry - executions = params[:id] ? [SolidQueue::FailedExecution.find(params[:id])] : filtered_scope.to_a - perform_retry(executions) - rescue => e - redirect_to failed_jobs_path, alert: "Could not retry job: #{e.message}" - end - def destroy executions = params[:id] ? [SolidQueue::FailedExecution.find(params[:id])] : filtered_scope.to_a perform_discard(executions) @@ -42,13 +35,6 @@ def failed_jobs_csv end end - def perform_retry(executions) - jobs = executions.map(&:job) - SolidQueue::FailedExecution.retry_all(jobs) - redirect_to failed_jobs_path(queue: @queue, q: @search, period: @period), - notice: "#{jobs.size} #{"job".pluralize(jobs.size)} queued for retry." - end - def perform_discard(executions) jobs = executions.map(&:job) SolidQueue::FailedExecution.discard_all_from_jobs(jobs) diff --git a/app/controllers/solid_queue_web/retry_failed_jobs_controller.rb b/app/controllers/solid_queue_web/retry_failed_jobs_controller.rb new file mode 100644 index 0000000..da2ed61 --- /dev/null +++ b/app/controllers/solid_queue_web/retry_failed_jobs_controller.rb @@ -0,0 +1,31 @@ +module SolidQueueWeb + class RetryFailedJobsController < ApplicationController + before_action :set_filter_params + + def create + executions = params[:id] ? [SolidQueue::FailedExecution.find(params[:id])] : filtered_scope.to_a + jobs = executions.map(&:job) + SolidQueue::FailedExecution.retry_all(jobs) + redirect_to failed_jobs_path(queue: @queue, q: @search, period: @period), + notice: "#{jobs.size} #{"job".pluralize(jobs.size)} queued for retry." + rescue => e + redirect_to failed_jobs_path, alert: "Could not retry job: #{e.message}" + end + + private + + def set_filter_params + @queue = params[:queue].presence + @search = params[:q].presence + @period = params[:period].presence_in(PERIOD_DURATIONS.keys) + end + + def filtered_scope + scope = SolidQueue::FailedExecution.includes(:job) + scope = scope.references(:job).where(solid_queue_jobs: { queue_name: @queue }) if @queue.present? + scope = scope.references(:job).where("solid_queue_jobs.class_name LIKE ?", "%#{@search}%") if @search.present? + scope = scope.references(:job).where("solid_queue_jobs.created_at >= ?", PERIOD_DURATIONS[@period].ago) if @period.present? + scope + end + end +end \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 6130d3d..83cffd6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -33,11 +33,11 @@ controller: "failed_jobs/selections" resources :failed_jobs, only: [:index, :destroy] do collection do - post :retry_all, action: :retry + post :retry_all, to: "retry_failed_jobs#create" post :discard_all, action: :destroy end member do - post :retry + post :retry, to: "retry_failed_jobs#create" end end end diff --git a/spec/requests/solid_queue_web/failed_jobs_spec.rb b/spec/requests/solid_queue_web/failed_jobs_spec.rb index 9dc20d2..9f99982 100644 --- a/spec/requests/solid_queue_web/failed_jobs_spec.rb +++ b/spec/requests/solid_queue_web/failed_jobs_spec.rb @@ -137,7 +137,7 @@ end end - describe "POST /jobs/failed_jobs/:id/retry" do + describe "POST /jobs/failed_jobs/:id/retry (RetryFailedJobsController#create)" do it "retries the job and redirects" do post "/jobs/failed_jobs/#{execution.id}/retry" expect(response).to redirect_to("/jobs/failed_jobs") @@ -184,7 +184,7 @@ end end - describe "POST /jobs/failed_jobs/retry_all" do + describe "POST /jobs/failed_jobs/retry_all (RetryFailedJobsController#create)" do it "retries all failed jobs and redirects" do post "/jobs/failed_jobs/retry_all" expect(response).to redirect_to("/jobs/failed_jobs") From d45ce21fb5d2dbd6f802a56215a8268bfffa3303 Mon Sep 17 00:00:00 2001 From: Chuck Smith Date: Wed, 20 May 2026 10:19:52 -0400 Subject: [PATCH 3/5] chore: consolidate destroy and discard_all in JobsController MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit destroy now branches on params[:id] — single execution (with Turbo Stream response) when present, filtered scope bulk discard when absent. discard_all route maps to destroy via action: option. Co-Authored-By: Claude Sonnet 4.6 --- .../solid_queue_web/jobs_controller.rb | 34 ++++++++----------- config/routes.rb | 2 +- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/app/controllers/solid_queue_web/jobs_controller.rb b/app/controllers/solid_queue_web/jobs_controller.rb index 3bf6c48..f78a2e2 100644 --- a/app/controllers/solid_queue_web/jobs_controller.rb +++ b/app/controllers/solid_queue_web/jobs_controller.rb @@ -1,6 +1,6 @@ module SolidQueueWeb class JobsController < ApplicationController - before_action :set_status, only: [:destroy, :discard_all, :discard_selected] + before_action :set_status, only: [:destroy, :discard_selected] def index @status = params[:status].presence_in(Job::STATUSES) || "ready" @@ -30,29 +30,25 @@ def show def destroy model = execution_model_for!(@status) - @execution = model.find(params[:id]) - @execution.discard - @remaining_count = filtered_scope(model).count - respond_to do |format| - format.turbo_stream - format.html { redirect_to jobs_path(status: @status, period: @period), notice: "Job discarded." } + if params[:id] + @execution = model.find(params[:id]) + @execution.discard + @remaining_count = filtered_scope(model).count + respond_to do |format| + format.turbo_stream + format.html { redirect_to jobs_path(status: @status, period: @period), notice: "Job discarded." } + end + else + jobs = filtered_scope(model).map(&:job) + model.discard_all_from_jobs(jobs) + redirect_to jobs_path(status: @status, period: @period), + notice: "#{jobs.size} #{"job".pluralize(jobs.size)} discarded." end rescue ArgumentError => e redirect_to jobs_path(status: @status, period: @period), alert: e.message rescue => e - redirect_to jobs_path(status: @status, period: @period), alert: "Could not discard job: #{e.message}" - end - - def discard_all - model = execution_model_for!(@status) - jobs = filtered_scope(model).map(&:job) - model.discard_all_from_jobs(jobs) redirect_to jobs_path(status: @status, period: @period), - notice: "#{jobs.size} #{"job".pluralize(jobs.size)} discarded." - rescue ArgumentError => e - redirect_to jobs_path(status: @status, period: @period), alert: e.message - rescue => e - redirect_to jobs_path(status: @status, period: @period), alert: "Could not discard jobs: #{e.message}" + alert: "Could not discard #{params[:id] ? "job" : "jobs"}: #{e.message}" end private diff --git a/config/routes.rb b/config/routes.rb index 83cffd6..713b71a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -25,7 +25,7 @@ resource :job_selection, path: "list/selection", only: [:destroy], controller: "jobs/selections" resources :jobs, path: "list", only: [:index, :show, :destroy] do collection do - post :discard_all + post :discard_all, action: :destroy end end From b74f64209102983f32ddc84dd3081408d5b965ed Mon Sep 17 00:00:00 2001 From: Chuck Smith Date: Wed, 20 May 2026 10:22:16 -0400 Subject: [PATCH 4/5] docs: update CHANGELOG for controller action consolidation Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce80c95..df7a07f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Dark mode — a ☽/☀ toggle button in the header switches between light and dark themes; preference persists to `localStorage` and falls back to the OS `prefers-color-scheme` on first visit; implemented via a `[data-theme="dark"]` attribute on `` so all CSS custom properties inherit the new palette automatically; badge and flash hardcoded hex colors get explicit dark-mode overrides in a new `_12_dark_mode.css` partial; powered by a new Stimulus `theme_controller` - Configurable settings via `SolidQueueWeb.configure` — `page_size` (Pagy limit, default 25), `dashboard_refresh_interval` (ms, default 5 000), `default_refresh_interval` (ms, default 10 000 — applies to jobs, processes, and history pages), `search_results_limit` (max results per status in global search, default 25); all settings have safe defaults so zero host-app configuration is required +### Changed + +- `FailedJobsController#destroy` and `#discard_all` consolidated into a single `destroy` action branching on `params[:id]`; `discard_all` route maps to `destroy` via `action:` option +- `FailedJobsController#retry` and `#retry_all` extracted into a new `RetryFailedJobsController#create` action with the same single/bulk branching pattern +- `JobsController#destroy` and `#discard_all` consolidated the same way, preserving the Turbo Stream response for the single-job path + ## [0.7.0] - 2026-05-20 ### Added From 71ff20c64e509fda6f7666cb742ab874c6c00c7b Mon Sep 17 00:00:00 2001 From: Chuck Smith Date: Wed, 20 May 2026 10:23:30 -0400 Subject: [PATCH 5/5] chore: fix trailing newline in RetryFailedJobsController Co-Authored-By: Claude Sonnet 4.6 --- app/controllers/solid_queue_web/retry_failed_jobs_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/solid_queue_web/retry_failed_jobs_controller.rb b/app/controllers/solid_queue_web/retry_failed_jobs_controller.rb index da2ed61..d8eab0c 100644 --- a/app/controllers/solid_queue_web/retry_failed_jobs_controller.rb +++ b/app/controllers/solid_queue_web/retry_failed_jobs_controller.rb @@ -28,4 +28,4 @@ def filtered_scope scope end end -end \ No newline at end of file +end