From caee82ba645f2be6d238a05365906bff284fa94e Mon Sep 17 00:00:00 2001 From: Chuck Smith Date: Wed, 20 May 2026 18:40:25 -0400 Subject: [PATCH 1/2] chore: complete Rails 7-action controller refactor for queues Extract QueuesController#pause / #resume into Queues::PausesController #create / #destroy (pause = create a pause record, resume = destroy it). Merge Queues::JobsController#discard_all into #destroy branching on params[:id], matching the pattern already applied to JobsController. Co-Authored-By: Claude Sonnet 4.6 --- .../solid_queue_web/queues/jobs_controller.rb | 34 ++++++++----------- .../queues/pauses_controller.rb | 21 ++++++++++++ .../solid_queue_web/queues_controller.rb | 16 --------- .../solid_queue_web/queues/index.html.erb | 4 +-- config/routes.rb | 7 ++-- spec/requests/solid_queue_web/queues_spec.rb | 8 ++--- 6 files changed, 44 insertions(+), 46 deletions(-) create mode 100644 app/controllers/solid_queue_web/queues/pauses_controller.rb diff --git a/app/controllers/solid_queue_web/queues/jobs_controller.rb b/app/controllers/solid_queue_web/queues/jobs_controller.rb index 90e864d..3f167bc 100644 --- a/app/controllers/solid_queue_web/queues/jobs_controller.rb +++ b/app/controllers/solid_queue_web/queues/jobs_controller.rb @@ -2,7 +2,7 @@ module SolidQueueWeb module Queues class JobsController < ApplicationController before_action :set_queue - before_action :set_status, only: [:destroy, :discard_all] + before_action :set_status, only: [:destroy] def index @status = params[:status].presence_in(Job::STATUSES) || "ready" @@ -15,29 +15,25 @@ def index 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 queue_jobs_path(queue_name: @queue, status: @status), 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 queue_jobs_path(queue_name: @queue, status: @status), notice: "Job discarded." } + end + else + jobs = filtered_scope(model).map(&:job) + model.discard_all_from_jobs(jobs) + redirect_to queue_jobs_path(queue_name: @queue, status: @status), + notice: "#{jobs.size} #{"job".pluralize(jobs.size)} discarded." end rescue ArgumentError => e redirect_to queue_jobs_path(queue_name: @queue, status: @status), alert: e.message rescue => e - redirect_to queue_jobs_path(queue_name: @queue, status: @status), 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 queue_jobs_path(queue_name: @queue, status: @status), - notice: "#{jobs.size} #{"job".pluralize(jobs.size)} discarded." - rescue ArgumentError => e - redirect_to queue_jobs_path(queue_name: @queue, status: @status), alert: e.message - rescue => e - redirect_to queue_jobs_path(queue_name: @queue, status: @status), alert: "Could not discard jobs: #{e.message}" + alert: "Could not discard #{params[:id] ? "job" : "jobs"}: #{e.message}" end private diff --git a/app/controllers/solid_queue_web/queues/pauses_controller.rb b/app/controllers/solid_queue_web/queues/pauses_controller.rb new file mode 100644 index 0000000..216b6b1 --- /dev/null +++ b/app/controllers/solid_queue_web/queues/pauses_controller.rb @@ -0,0 +1,21 @@ +module SolidQueueWeb + module Queues + class PausesController < ApplicationController + def create + queue = SolidQueue::Queue.find_by_name(params[:queue_name]) + queue.pause + redirect_to queues_path, notice: "Queue \"#{queue.name}\" paused." + rescue => e + redirect_to queues_path, alert: "Could not pause queue: #{e.message}" + end + + def destroy + queue = SolidQueue::Queue.find_by_name(params[:queue_name]) + queue.resume + redirect_to queues_path, notice: "Queue \"#{queue.name}\" resumed." + rescue => e + redirect_to queues_path, alert: "Could not resume queue: #{e.message}" + end + end + end +end diff --git a/app/controllers/solid_queue_web/queues_controller.rb b/app/controllers/solid_queue_web/queues_controller.rb index 208232a..f8909ba 100644 --- a/app/controllers/solid_queue_web/queues_controller.rb +++ b/app/controllers/solid_queue_web/queues_controller.rb @@ -8,21 +8,5 @@ def index @oldest_ready = stats.oldest_ready @failure_sparklines = stats.failure_sparklines end - - def pause - queue = SolidQueue::Queue.find_by_name(params[:name]) - queue.pause - redirect_to queues_path, notice: "Queue \"#{queue.name}\" paused." - rescue => e - redirect_to queues_path, alert: "Could not pause queue: #{e.message}" - end - - def resume - queue = SolidQueue::Queue.find_by_name(params[:name]) - queue.resume - redirect_to queues_path, notice: "Queue \"#{queue.name}\" resumed." - rescue => e - redirect_to queues_path, alert: "Could not resume queue: #{e.message}" - end end end diff --git a/app/views/solid_queue_web/queues/index.html.erb b/app/views/solid_queue_web/queues/index.html.erb index da960bf..c2c620b 100644 --- a/app/views/solid_queue_web/queues/index.html.erb +++ b/app/views/solid_queue_web/queues/index.html.erb @@ -60,10 +60,10 @@ <% if queue.paused? %> - <%= button_to "Resume", resume_queue_path(queue.name), method: :post, + <%= button_to "Resume", queue_pause_path(queue.name), method: :delete, class: "sqd-btn sqd-btn--primary sqd-btn--sm" %> <% else %> - <%= button_to "Pause", pause_queue_path(queue.name), method: :post, + <%= button_to "Pause", queue_pause_path(queue.name), method: :post, class: "sqd-btn sqd-btn--muted sqd-btn--sm", data: { confirm: "Pause queue \"#{queue.name}\"?" } %> <% end %> diff --git a/config/routes.rb b/config/routes.rb index fbf7be9..a78c2a1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -8,13 +8,10 @@ resources :recurring_tasks, only: [:index] resources :processes, only: [:index] resources :queues, only: [:index], param: :name do - member do - post :pause - post :resume - end + resource :pause, only: [:create, :destroy], controller: "queues/pauses" resources :jobs, path: "list", only: [:index, :destroy], controller: "queues/jobs" do collection do - post :discard_all + post :discard_all, action: :destroy end end end diff --git a/spec/requests/solid_queue_web/queues_spec.rb b/spec/requests/solid_queue_web/queues_spec.rb index afca098..082e9b1 100644 --- a/spec/requests/solid_queue_web/queues_spec.rb +++ b/spec/requests/solid_queue_web/queues_spec.rb @@ -91,11 +91,11 @@ end end - describe "POST /jobs/queues/:name/resume" do + describe "DELETE /jobs/queues/:name/pause" do before { SolidQueue::Pause.create!(queue_name: "default") } it "resumes the queue and redirects" do - post "/jobs/queues/default/resume" + delete "/jobs/queues/default/pause" expect(response).to redirect_to("/jobs/queues") follow_redirect! expect(response.body).to include("resumed") @@ -103,13 +103,13 @@ it "removes the Pause record" do expect { - post "/jobs/queues/default/resume" + delete "/jobs/queues/default/pause" }.to change(SolidQueue::Pause, :count).by(-1) end it "handles resume failure gracefully" do allow_any_instance_of(SolidQueue::Queue).to receive(:resume).and_raise(RuntimeError, "boom") - post "/jobs/queues/default/resume" + delete "/jobs/queues/default/pause" expect(response).to redirect_to("/jobs/queues") follow_redirect! expect(response.body).to include("Could not resume queue") From acc9b31fa57f36fdd6862b1c44a50765eab9971a Mon Sep 17 00:00:00 2001 From: Chuck Smith Date: Wed, 20 May 2026 18:41:12 -0400 Subject: [PATCH 2/2] docs: update CHANGELOG and README for controller refactor Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 5 +++++ README.md | 3 --- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6188fd6..d56eaae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Queue depth trend — a "Queue Depth — Last 12 Hours" card on the dashboard shows estimated queue depth at 12 hourly snapshots; depth at time T is the count of jobs created before T that had not yet finished by T; bars are purple (distinct from the blue throughput and red failure rate charts); the card header shows current depth; empty state shown when no active jobs exist in the window - Slow job detection — a configurable `slow_job_threshold` setting (default nil = disabled) flags claimed jobs that have been running longer than the threshold; when set, the Running tab gains a "Running For" column showing each job's elapsed time, slow jobs are highlighted with an orange row background and a "slow" badge, and a "Slow Jobs" warning card appears on the dashboard linking to the Running tab +### Changed + +- `QueuesController#pause` / `#resume` extracted into `Queues::PausesController#create` / `#destroy`; pause maps to `POST /queues/:name/pause`, resume to `DELETE /queues/:name/pause` +- `Queues::JobsController#discard_all` merged into `#destroy` branching on `params[:id]`, matching the pattern already used in `JobsController` + ## [0.8.0] - 2026-05-20 ### Added diff --git a/README.md b/README.md index 21a35b0..de8efd9 100644 --- a/README.md +++ b/README.md @@ -123,9 +123,6 @@ Planned features, roughly ordered by priority: - Multi-database support — when Solid Queue runs on a separate database from the host app - Read replica support — route dashboard queries to a replica to avoid impacting the primary -**Code quality** -- Rails controller conventions — complete the 7-action refactor: `QueuesController#pause` / `#resume` → `Queues::PausesController#create` / `#destroy`; `Queues::JobsController#discard_all` → map to `#destroy` matching the pattern already applied to `JobsController` - Pull requests for any of these are welcome. See [Contributing](#contributing) below. ## Contributing