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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Slow job webhook alert — set `alert_slow_job_count_threshold` (integer) to fire a webhook whenever the number of currently-running slow jobs meets or exceeds the configured count; requires `slow_job_threshold` to define what "slow" means; uses the same `alert_webhook_url` and `alert_webhook_cooldown` settings as other alert types; event name `slow_job_threshold_exceeded`
- Stale process webhook alert — set `alert_stale_process_threshold` (integer) to fire a webhook whenever the number of stale workers meets or exceeds the configured count; a process is stale when its heartbeat has not been updated within `SolidQueue.process_alive_threshold`; uses the same `alert_webhook_url` and `alert_webhook_cooldown` settings; event name `stale_process_detected`
- Job wait time column — the Running tab now shows a "Wait Time" column displaying how long each claimed job waited in the queue from enqueue to pickup; also included as `wait_time_seconds` in the CSV export for the claimed status

## [1.3.0] - 2026-05-27

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ SolidQueueWeb surfaces all of this in a browser UI available at any route you ch
- **Dashboard quick actions** — "Retry All Failed" and "Discard All Blocked" cards appear on the dashboard only when the respective count is non-zero; one-click bulk operations with confirm dialogs, keeping the dashboard clean when everything is healthy
- **CSV export** — "Export CSV" button on the jobs, failed jobs, and history pages downloads all records matching the current filters; columns are tailored per view
- **Slow job detection** — when `slow_job_threshold` is configured, claimed jobs running longer than the threshold are flagged with an orange row, a "slow" badge, and a "Running For" duration column on the Running tab; a "Slow Jobs" warning card appears on the dashboard with a link to the Running tab
- **Job wait time** — the Running tab shows a "Wait Time" column with how long each job waited in the queue from enqueue to pickup; also exported as `wait_time_seconds` in the claimed-status CSV
- **Webhook alerts** — set `alert_webhook_url` and `alert_failure_threshold` to receive a POST request whenever the failed job count meets or exceeds the threshold; set `alert_queue_thresholds` for per-queue depth alerts; set `alert_slow_job_count_threshold` (requires `slow_job_threshold`) for slow-job count alerts; set `alert_stale_process_threshold` for stale-worker alerts; all fire asynchronously with a configurable cooldown (default 1 h) to prevent repeated alerts
- **Performance analytics** — per-job-class statistics at `/jobs/performance` showing run count, average, p50, p95, p99, standard deviation, min, and max duration; sorted by p95 descending so the slowest classes surface first; high std dev surfaces inconsistent jobs worth investigating; period filter scopes to 1h / 24h / 7d or all time; each class name links to the filtered History view
- **Failed job trend chart** — a "Failures — Last 12 Hours" bar chart on the dashboard shows failures per hour over the last 12 hours; bars are red, making failure spikes visible before clicking into the failed jobs list
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Pull requests for any of these are welcome. See [Contributing](README.md#contrib
|---|---|
| ~~**Slow job webhook alert**~~ | ✅ Shipped in v1.4 — `alert_slow_job_count_threshold` fires when slow-job count meets or exceeds the threshold. |
| ~~**Process stale webhook alert**~~ | ✅ Shipped in v1.4 — `alert_stale_process_threshold` fires when stale worker count meets or exceeds the threshold. |
| **Job wait time column** | Show time from `enqueued_at` to `created_at` on claimed executions — a direct measure of queue SLA. |
| ~~**Job wait time column**~~ | ✅ Shipped in v1.4 — "Wait Time" column on the Running tab; `wait_time_seconds` in the claimed CSV export. |

---

Expand Down
8 changes: 6 additions & 2 deletions app/controllers/solid_queue_web/jobs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,14 @@ def sort_expression

def jobs_csv(scope)
CSV.generate(headers: true) do |csv|
csv << %w[id class_name queue_name status priority enqueued_at]
headers = %w[id class_name queue_name status priority enqueued_at]
headers << "wait_time_seconds" if @status == "claimed"
csv << headers
scope.each do |execution|
job = execution.job
csv << [job.id, job.class_name, job.queue_name, @status, job.priority, job.created_at.iso8601]
row = [job.id, job.class_name, job.queue_name, @status, job.priority, job.created_at.iso8601]
row << (execution.created_at - job.created_at).to_i if @status == "claimed"
csv << row
end
end
end
Expand Down
4 changes: 4 additions & 0 deletions app/views/solid_queue_web/jobs/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@
<%= sort_header_th("Enqueued At", "created_at", sort_url, current_sort: @sort, current_dir: @direction) %>
<% if @status == "claimed" %>
<th scope="col">Running For</th>
<th scope="col">Wait Time</th>
<% end %>
</tr>
</thead>
Expand Down Expand Up @@ -192,6 +193,9 @@
<td class="sqd-mono<%= slow ? " sqd-slow-duration" : "" %>">
<%= time_ago_in_words(execution.created_at) %>
</td>
<td class="sqd-mono">
<%= format_duration(execution.created_at - job.created_at) %>
</td>
<% end %>
</tr>
<% end %>
Expand Down
50 changes: 50 additions & 0 deletions spec/requests/solid_queue_web/jobs_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,56 @@
end
end

describe "job wait time column (claimed tab)" do
let!(:worker_process) do
SolidQueue::Process.create!(
kind: "Worker", pid: 88_888, hostname: "test-host",
name: "worker-wait-test", last_heartbeat_at: Time.current
)
end

let!(:claimed_job) do
SolidQueue::Job.create!(
queue_name: "default", class_name: "WaitTestJob",
arguments: {}, active_job_id: SecureRandom.uuid
)
end

let!(:claimed_execution) do
execution = SolidQueue::ClaimedExecution.create!(job: claimed_job, process: worker_process)
claimed_job.update_columns(created_at: 5.minutes.ago)
execution.update_columns(created_at: 2.minutes.ago)
execution
end

it "shows the Wait Time column header on the claimed tab" do
get "/jobs/list", params: { status: "claimed" }
expect(response.body).to include("Wait Time")
end

it "does not show the Wait Time column on other tabs" do
get "/jobs/list", params: { status: "ready" }
expect(response.body).not_to include("Wait Time")
end

it "renders a formatted wait time for each claimed job" do
get "/jobs/list", params: { status: "claimed" }
expect(response.body).to include("3m")
end

it "includes wait_time_seconds in the claimed CSV export" do
get "/jobs/list.csv", params: { status: "claimed" }
expect(response.body).to include("wait_time_seconds")
row = response.body.lines.last
expect(row.split(",").last.strip.to_i).to be_within(5).of(180)
end

it "does not include wait_time_seconds in CSV for other statuses" do
get "/jobs/list.csv", params: { status: "ready" }
expect(response.body).not_to include("wait_time_seconds")
end
end

describe "GET /jobs/list?priority= (priority filter)" do
let!(:high_priority_job) do
SolidQueue::Job.create!(
Expand Down