From d5cac0bb21048f8669bd5b22b57d87fb603f49e4 Mon Sep 17 00:00:00 2001 From: Marty Zalega Date: Sun, 10 Nov 2024 19:22:37 +1000 Subject: [PATCH 1/2] Add retry and cancel job actions to modal --- lib/oban/live_dashboard.ex | 91 ++++++++++++++++++++++--------- test/oban/live_dashboard_test.exs | 22 +++++++- 2 files changed, 86 insertions(+), 27 deletions(-) diff --git a/lib/oban/live_dashboard.ex b/lib/oban/live_dashboard.ex index e4bffbb..e6d4ed4 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 @@ -91,6 +101,33 @@ defmodule Oban.LiveDashboard do {: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 +135,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 +144,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 From 1d78255ffbb6944ec59c2554d67faa3748bd59a1 Mon Sep 17 00:00:00 2001 From: Marty Zalega Date: Sun, 10 Nov 2024 19:23:10 +1000 Subject: [PATCH 2/2] Refresh the current job periodically --- lib/oban/live_dashboard.ex | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/oban/live_dashboard.ex b/lib/oban/live_dashboard.ex index e6d4ed4..7e148eb 100644 --- a/lib/oban/live_dashboard.ex +++ b/lib/oban/live_dashboard.ex @@ -95,6 +95,15 @@ 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)