diff --git a/lib/oban/live_dashboard.ex b/lib/oban/live_dashboard.ex
index e4bffbb..7e148eb 100644
--- a/lib/oban/live_dashboard.ex
+++ b/lib/oban/live_dashboard.ex
@@ -41,30 +41,40 @@ defmodule Oban.LiveDashboard do
title="Job"
return_to={live_dashboard_path(@socket, @page, params: %{})}
>
- <.label_value_list>
- <:elem label="ID"><%= @job.id %>
- <:elem label="State"><%= @job.state %>
- <:elem label="Queue"><%= @job.queue %>
- <:elem label="Worker"><%= @job.worker %>
- <:elem label="Args"><%= format_value(@job.args, nil) %>
- <:elem :if={@job.meta != %{}} label="Meta"><%= format_value(@job.meta, nil) %>
- <:elem :if={@job.tags != []} label="Tags"><%= format_value(@job.tags, nil) %>
- <:elem :if={@job.errors != []} label="Errors"><%= format_errors(@job.errors) %>
- <:elem label="Attempts"><%= @job.attempt %>/<%= @job.max_attempts %>
- <:elem label="Priority"><%= @job.priority %>
- <:elem label="Attempted at"><%= format_value(@job.attempted_at) %>
- <:elem :if={@job.cancelled_at} label="Cancelled at">
- <%= format_value(@job.cancelled_at) %>
-
- <:elem :if={@job.completed_at} label="Completed at">
- <%= format_value(@job.completed_at) %>
-
- <:elem :if={@job.discarded_at} label="Discarded at">
- <%= format_value(@job.discarded_at) %>
-
- <:elem label="Inserted at"><%= format_value(@job.inserted_at) %>
- <:elem label="Scheduled at"><%= format_value(@job.scheduled_at) %>
-
+
+
+ <.label_value_list>
+ <:elem label="ID"><%= @job.id %>
+ <:elem label="State"><%= @job.state %>
+ <:elem label="Queue"><%= @job.queue %>
+ <:elem label="Worker"><%= @job.worker %>
+ <:elem label="Args"><%= format_value(@job.args, nil) %>
+ <:elem :if={@job.meta != %{}} label="Meta"><%= format_value(@job.meta, nil) %>
+ <:elem :if={@job.tags != []} label="Tags"><%= format_value(@job.tags, nil) %>
+ <:elem :if={@job.errors != []} label="Errors"><%= format_errors(@job.errors) %>
+ <:elem label="Attempts"><%= @job.attempt %>/<%= @job.max_attempts %>
+ <:elem label="Priority"><%= @job.priority %>
+ <:elem label="Attempted at"><%= format_value(@job.attempted_at) %>
+ <:elem :if={@job.cancelled_at} label="Cancelled at">
+ <%= format_value(@job.cancelled_at) %>
+
+ <:elem :if={@job.completed_at} label="Completed at">
+ <%= format_value(@job.completed_at) %>
+
+ <:elem :if={@job.discarded_at} label="Discarded at">
+ <%= format_value(@job.discarded_at) %>
+
+ <:elem label="Inserted at"><%= format_value(@job.inserted_at) %>
+ <:elem label="Scheduled at"><%= format_value(@job.scheduled_at) %>
+
+
"""
end
@@ -85,12 +95,48 @@ defmodule Oban.LiveDashboard do
{:noreply, assign(socket, job: nil)}
end
+ @impl true
+ def handle_refresh(socket) do
+ {:noreply,
+ Phoenix.Component.update(socket, :job, fn
+ nil -> nil
+ %{id: job_id} -> get_job(job_id)
+ end)}
+ end
+
@impl true
def handle_event("show_job", params, socket) do
to = live_dashboard_path(socket, socket.assigns.page, params: params)
{:noreply, push_patch(socket, to: to)}
end
+ def handle_event("run_job", %{"job" => job_id}, socket) do
+ with {:ok, job} <- fetch_job(job_id),
+ :ok <- Oban.Engine.retry_job(Oban.config(), job),
+ # Refresh job
+ {:ok, job} <- fetch_job(job.id) do
+ {:noreply, assign(socket, :job, job)}
+ else
+ _ ->
+ {:noreply, socket}
+ end
+ end
+
+ def handle_event("cancel_job", %{"job" => job_id}, socket) do
+ with {:ok, job} <- fetch_job(job_id),
+ :ok <- Oban.Engine.cancel_job(Oban.config(), job) do
+ to = live_dashboard_path(socket, socket.assigns.page, params: %{})
+ {:noreply, push_patch(socket, to: to)}
+ else
+ _ ->
+ {:noreply, socket}
+ end
+ end
+
+ defp get_job(id) do
+ Oban.Repo.get(Oban.config(), Oban.Job, id)
+ end
+
defp fetch_jobs(params, _node) do
total_jobs = Oban.Repo.aggregate(Oban.config(), Oban.Job, :count)
jobs = Oban.Repo.all(Oban.config(), jobs_query(params)) |> Enum.map(&Map.from_struct/1)
@@ -98,7 +144,7 @@ defmodule Oban.LiveDashboard do
end
defp fetch_job(id) do
- case Oban.Repo.get(Oban.config(), Oban.Job, id) do
+ case get_job(id) do
nil ->
:error
@@ -107,6 +153,10 @@ defmodule Oban.LiveDashboard do
end
end
+ defp can_retry_job?(%Oban.Job{state: state}), do: state not in ["available", "executing"]
+
+ defp can_cancel_job?(%Oban.Job{state: state}), do: state != "cancelled"
+
defp jobs_query(%{sort_by: sort_by, sort_dir: sort_dir, limit: l}) do
Oban.Job
|> limit(^l)
diff --git a/test/oban/live_dashboard_test.exs b/test/oban/live_dashboard_test.exs
index e87f878..8aaafda 100644
--- a/test/oban/live_dashboard_test.exs
+++ b/test/oban/live_dashboard_test.exs
@@ -29,8 +29,26 @@ defmodule Oban.LiveDashboardTest do
refute live |> element("#modal-close") |> render_click() =~ "modal"
end
- defp job_fixture(args \\ %{}) do
- {:ok, job} = Oban.Job.new(args, worker: "FakeWorker") |> Oban.insert()
+ test "retry job from modal" do
+ job = job_fixture(%{something: "foobar"}, schedule_in: 1000)
+ {:ok, live, _rendered} = live(build_conn(), "/dashboard/oban?params[job]=#{job.id}")
+
+ assert has_element?(live, "pre", "scheduled")
+ element(live, "button", "Retry Job") |> render_click()
+ assert has_element?(live, "pre", "available")
+ end
+
+ test "cancel job from modal" do
+ job = job_fixture(%{something: "foobar"})
+ {:ok, live, _rendered} = live(build_conn(), "/dashboard/oban?params[job]=#{job.id}")
+
+ element(live, "button", "Cancel Job") |> render_click()
+ assert_patched(live, "/dashboard/oban?")
+ end
+
+ defp job_fixture(args \\ %{}, opts \\ []) do
+ opts = Keyword.put_new(opts, :worker, "FakeWorker")
+ {:ok, job} = Oban.Job.new(args, opts) |> Oban.insert()
job
end
end