Skip to content

Commit 5a2be1e

Browse files
authored
buttons to retry, cancel, delete, etc (#5)
1 parent d4aaf10 commit 5a2be1e

File tree

4 files changed

+144
-23
lines changed

4 files changed

+144
-23
lines changed

lib/obanalyze/dashboard.ex

+56-20
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ defmodule Obanalyze.Dashboard do
44
use Phoenix.LiveDashboard.PageBuilder, refresher?: true
55

66
import Phoenix.LiveDashboard.Helpers, only: [format_value: 2]
7+
import Obanalyze.Helpers
78

89
alias Obanalyze.ObanJobs
910
alias Obanalyze.NavItem
@@ -43,24 +44,40 @@ defmodule Obanalyze.Dashboard do
4344
</.live_nav_bar>
4445
4546
<.live_modal :if={@job != nil} id="job-modal" title={"Job - #{@job.id}"} return_to={live_dashboard_path(@socket, @page, params: %{})}>
46-
<.label_value_list>
47-
<:elem label="ID"><%= @job.id %></:elem>
48-
<:elem label="State"><%= @job.state %></:elem>
49-
<:elem label="Queue"><%= @job.queue %></:elem>
50-
<:elem label="Worker"><%= @job.worker %></:elem>
51-
<:elem label="Args"><%= format_value(@job.args, nil) %></:elem>
52-
<:elem :if={@job.meta != %{}} label="Meta"><%= format_value(@job.meta, nil) %></:elem>
53-
<:elem :if={@job.tags != []} label="Tags"><%= format_value(@job.tags, nil) %></:elem>
54-
<:elem :if={@job.errors != []} label="Errors"><%= format_errors(@job.errors) %></:elem>
55-
<:elem label="Attempts"><%= @job.attempt %>/<%= @job.max_attempts %></:elem>
56-
<:elem label="Priority"><%= @job.priority %></:elem>
57-
<:elem label="Attempted at"><%= format_value(@job.attempted_at) %></:elem>
58-
<:elem :if={@job.cancelled_at} label="Cancelled at"><%= format_value(@job.cancelled_at) %></:elem>
59-
<:elem :if={@job.completed_at} label="Completed at"><%= format_value(@job.completed_at) %></:elem>
60-
<:elem :if={@job.discarded_at} label="Discarded at"><%= format_value(@job.discarded_at) %></:elem>
61-
<:elem label="Inserted at"><%= format_value(@job.inserted_at) %></:elem>
62-
<:elem label="Scheduled at"><%= format_value(@job.scheduled_at) %></:elem>
63-
</.label_value_list>
47+
<div class="mb-4 btn-toolbar" role="toolbar" aria-label="Oban Job actions">
48+
<div :if={can_cancel_job?(@job)} class="btn-group" role="group">
49+
<button type="button" class="btn btn-primary btn-sm mr-2" phx-click="cancel_job" phx-value-job={@job.id} data-disable-with="Cancelling...">Cancel</button>
50+
</div>
51+
<div :if={can_run_job?(@job)} class="btn-group" role="group">
52+
<button type="button" class="btn btn-primary btn-sm mr-2" phx-click="retry_job" phx-value-job={@job.id} data-disable-with="Running...">Run now</button>
53+
</div>
54+
<div :if={can_retry_job?(@job)} class="btn-group" role="group">
55+
<button type="button" class="btn btn-primary btn-sm mr-2" phx-click="retry_job" phx-value-job={@job.id} data-disable-with="Retrying...">Retry</button>
56+
</div>
57+
<div :if={can_delete_job?(@job)} class="btn-group" role="group">
58+
<button type="button" class="btn btn-primary btn-sm mr-2" phx-click="delete_job" phx-value-job={@job.id} data-disable-with="Deleting..." data-confirm="Are you sure you want to delete this job?">Delete</button>
59+
</div>
60+
</div>
61+
<div class="tabular-info">
62+
<.label_value_list>
63+
<:elem label="ID"><%= @job.id %></:elem>
64+
<:elem label="State"><%= @job.state %></:elem>
65+
<:elem label="Queue"><%= @job.queue %></:elem>
66+
<:elem label="Worker"><%= @job.worker %></:elem>
67+
<:elem label="Args"><%= format_value(@job.args, nil) %></:elem>
68+
<:elem :if={@job.meta != %{}} label="Meta"><%= format_value(@job.meta, nil) %></:elem>
69+
<:elem :if={@job.tags != []} label="Tags"><%= format_value(@job.tags, nil) %></:elem>
70+
<:elem :if={@job.errors != []} label="Errors"><%= format_errors(@job.errors) %></:elem>
71+
<:elem label="Attempts"><%= @job.attempt %>/<%= @job.max_attempts %></:elem>
72+
<:elem label="Priority"><%= @job.priority %></:elem>
73+
<:elem label="Attempted at"><%= format_value(@job.attempted_at) %></:elem>
74+
<:elem :if={@job.cancelled_at} label="Cancelled at"><%= format_value(@job.cancelled_at) %></:elem>
75+
<:elem :if={@job.completed_at} label="Completed at"><%= format_value(@job.completed_at) %></:elem>
76+
<:elem :if={@job.discarded_at} label="Discarded at"><%= format_value(@job.discarded_at) %></:elem>
77+
<:elem label="Inserted at"><%= format_value(@job.inserted_at) %></:elem>
78+
<:elem label="Scheduled at"><%= format_value(@job.scheduled_at) %></:elem>
79+
</.label_value_list>
80+
</div>
6481
</.live_modal>
6582
"""
6683
end
@@ -92,6 +109,25 @@ defmodule Obanalyze.Dashboard do
92109
{:noreply, push_patch(socket, to: to)}
93110
end
94111

112+
def handle_event("cancel_job", %{"job" => job_id}, socket) do
113+
with {:ok, job} <- ObanJobs.cancel_oban_job(job_id) do
114+
{:noreply, assign(socket, job: job)}
115+
end
116+
end
117+
118+
def handle_event("retry_job", %{"job" => job_id}, socket) do
119+
with {:ok, job} <- ObanJobs.retry_oban_job(job_id) do
120+
{:noreply, assign(socket, job: job)}
121+
end
122+
end
123+
124+
def handle_event("delete_job", %{"job" => job_id}, socket) do
125+
with :ok <- ObanJobs.delete_oban_job(job_id) do
126+
to = live_dashboard_path(socket, socket.assigns.page, params: %{})
127+
{:noreply, push_patch(socket, to: to)}
128+
end
129+
end
130+
95131
@impl true
96132
def handle_refresh(socket) do
97133
socket =
@@ -107,8 +143,8 @@ defmodule Obanalyze.Dashboard do
107143

108144
defp assign_job(socket, job_id) do
109145
if job_id do
110-
case ObanJobs.get_oban_job(job_id) do
111-
%Oban.Job{} = job ->
146+
case ObanJobs.fetch_oban_job(job_id) do
147+
{:ok, job} ->
112148
assign(socket, job: job)
113149

114150
_ ->

lib/obanalyze/helpers.ex

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
defmodule Obanalyze.Helpers do
2+
alias Oban.Job
3+
4+
def can_cancel_job?(%Job{} = job) do
5+
job.state in ["available", "executing", "inserted", "retryable", "scheduled"]
6+
end
7+
8+
def can_delete_job?(%Job{} = job) do
9+
job.state not in ["executing"]
10+
end
11+
12+
def can_retry_job?(%Job{} = job) do
13+
job.state in ["cancelled", "completed", "discarded", "retryable"]
14+
end
15+
16+
def can_run_job?(%Job{} = job) do
17+
job.state in ["inserted", "scheduled"]
18+
end
19+
end

lib/obanalyze/oban_jobs.ex

+33-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,38 @@
11
defmodule Obanalyze.ObanJobs do
2-
import Ecto.Query, only: [group_by: 3, order_by: 2, order_by: 3, select: 3, limit: 2, where: 3]
2+
import Ecto.Query,
3+
only: [from: 2, group_by: 3, order_by: 2, order_by: 3, select: 3, limit: 2, where: 3]
34

4-
def get_oban_job(id) do
5-
Oban.Repo.get(Oban.config(), Oban.Job, id)
5+
def get_oban_job(job_id) do
6+
Oban.Repo.get(Oban.config(), Oban.Job, job_id)
7+
end
8+
9+
def fetch_oban_job(job_id) do
10+
case get_oban_job(job_id) do
11+
%Oban.Job{} = job -> {:ok, job}
12+
_ -> {:error, :not_found}
13+
end
14+
end
15+
16+
def delete_oban_job(job_id) do
17+
query = from(oj in Oban.Job, where: oj.id in [^job_id])
18+
Oban.Repo.delete_all(Oban.config(), query)
19+
:ok
20+
end
21+
22+
def retry_oban_job(job_id) do
23+
with {:ok, job} <- fetch_oban_job(job_id),
24+
:ok <- Oban.Engine.retry_job(Oban.config(), job),
25+
{:ok, job} <- fetch_oban_job(job_id) do
26+
{:ok, job}
27+
end
28+
end
29+
30+
def cancel_oban_job(job_id) do
31+
with {:ok, job} <- fetch_oban_job(job_id),
32+
:ok <- Oban.Engine.cancel_job(Oban.config(), job),
33+
{:ok, job} <- fetch_oban_job(job_id) do
34+
{:ok, job}
35+
end
636
end
737

838
def list_jobs_with_total(params, job_state) do

test/obanalyze/dashboard_test.exs

+36
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,42 @@ defmodule Obanalyze.DashboardTest do
7575
|> length() == 0
7676
end
7777

78+
test "run now job" do
79+
job = job_fixture(%{foo: "bar"}, schedule_in: 1000)
80+
{:ok, live, _rendered} = live(build_conn(), "/dashboard/obanalyze?params[job]=#{job.id}")
81+
82+
assert has_element?(live, "pre", "scheduled")
83+
element(live, "button", "Run now") |> render_click()
84+
assert has_element?(live, "pre", "available")
85+
end
86+
87+
test "retry job" do
88+
job = job_fixture(%{foo: "bar"}, state: "cancelled")
89+
{:ok, live, _rendered} = live(build_conn(), "/dashboard/obanalyze?params[job]=#{job.id}")
90+
91+
assert has_element?(live, "pre", "cancelled")
92+
element(live, "button", "Retry") |> render_click()
93+
assert has_element?(live, "pre", "available")
94+
end
95+
96+
test "cancel job" do
97+
job = job_fixture(%{foo: "bar"}, schedule_in: 1000)
98+
{:ok, live, _rendered} = live(build_conn(), "/dashboard/obanalyze?params[job]=#{job.id}")
99+
100+
assert has_element?(live, "pre", "scheduled")
101+
element(live, "button", "Cancel") |> render_click()
102+
assert has_element?(live, "pre", "cancelled")
103+
end
104+
105+
test "delete job" do
106+
job = job_fixture(%{foo: "bar"}, schedule_in: 1000)
107+
{:ok, live, _rendered} = live(build_conn(), "/dashboard/obanalyze?params[job]=#{job.id}")
108+
109+
assert has_element?(live, "pre", "scheduled")
110+
element(live, "button", "Delete") |> render_click()
111+
assert_patched(live, "/dashboard/obanalyze?")
112+
end
113+
78114
defp job_fixture(args, opts) do
79115
opts = Keyword.put_new(opts, :worker, "FakeWorker")
80116
{:ok, job} = Oban.Job.new(args, opts) |> Oban.insert()

0 commit comments

Comments
 (0)