Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 15 additions & 19 deletions app/controllers/solid_queue_web/queues/jobs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down
21 changes: 21 additions & 0 deletions app/controllers/solid_queue_web/queues/pauses_controller.rb
Original file line number Diff line number Diff line change
@@ -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
16 changes: 0 additions & 16 deletions app/controllers/solid_queue_web/queues_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions app/views/solid_queue_web/queues/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@
</td>
<td class="sqd-row-actions">
<% 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 %>
Expand Down
7 changes: 2 additions & 5 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions spec/requests/solid_queue_web/queues_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,25 +91,25 @@
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")
end

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")
Expand Down
Loading