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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<html>` 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
Expand Down
36 changes: 10 additions & 26 deletions app/controllers/solid_queue_web/failed_jobs_controller.rb
Original file line number Diff line number Diff line change
@@ -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, :destroy]

def index
respond_to do |format|
Expand All @@ -13,36 +13,13 @@ def index
end
end

def retry
execution = SolidQueue::FailedExecution.find(params[:id])
execution.retry
redirect_to failed_jobs_path, notice: "Job queued for retry."
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
Expand All @@ -58,6 +35,13 @@ def failed_jobs_csv
end
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
Expand Down
34 changes: 15 additions & 19 deletions app/controllers/solid_queue_web/jobs_controller.rb
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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
Expand Down
31 changes: 31 additions & 0 deletions app/controllers/solid_queue_web/retry_failed_jobs_controller.rb
Original file line number Diff line number Diff line change
@@ -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
8 changes: 4 additions & 4 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,19 @@
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

resource :failed_job_selection, path: "failed_jobs/selection", only: [:create, :destroy],
controller: "failed_jobs/selections"
resources :failed_jobs, only: [:index, :destroy] do
collection do
post :retry_all
post :discard_all
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
8 changes: 4 additions & 4 deletions spec/requests/solid_queue_web/failed_jobs_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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!
Expand All @@ -176,15 +176,15 @@
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!
expect(response.body).to include("Could not discard job")
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")
Expand Down
Loading