diff --git a/lib/pinchflat/application.ex b/lib/pinchflat/application.ex index 7defa36..49adcf0 100644 --- a/lib/pinchflat/application.ex +++ b/lib/pinchflat/application.ex @@ -24,7 +24,7 @@ defmodule Pinchflat.Application do PinchflatWeb.Endpoint ] - :ok = Oban.Telemetry.attach_default_logger() + attach_oban_telemetry() Logger.add_handlers(:pinchflat) # See https://hexdocs.pm/elixir/Supervisor.html @@ -40,4 +40,11 @@ defmodule Pinchflat.Application do PinchflatWeb.Endpoint.config_change(changed, removed) :ok end + + defp attach_oban_telemetry do + events = [[:oban, :job, :start], [:oban, :job, :stop], [:oban, :job, :exception]] + + :ok = Oban.Telemetry.attach_default_logger() + :telemetry.attach_many("job-telemetry-broadcast", events, &PinchflatWeb.Telemetry.job_state_change_broadcast/4, []) + end end diff --git a/lib/pinchflat_web/controllers/pages/page_controller.ex b/lib/pinchflat_web/controllers/pages/page_controller.ex index bc990b4..a6e36da 100644 --- a/lib/pinchflat_web/controllers/pages/page_controller.ex +++ b/lib/pinchflat_web/controllers/pages/page_controller.ex @@ -20,16 +20,14 @@ defmodule PinchflatWeb.Pages.PageController do end defp render_home_page(conn) do - # TODO: revert conn |> render(:home, - media_profile_count: 1 || Repo.aggregate(MediaProfile, :count, :id), - source_count: 1 || Repo.aggregate(Source, :count, :id), + media_profile_count: Repo.aggregate(MediaProfile, :count, :id), + source_count: Repo.aggregate(Source, :count, :id), media_item_count: - 1 || - MediaQuery.new() - |> MediaQuery.with_media_downloaded_at() - |> Repo.aggregate(:count, :id) + MediaQuery.new() + |> MediaQuery.with_media_downloaded_at() + |> Repo.aggregate(:count, :id) ) end diff --git a/lib/pinchflat_web/controllers/pages/page_html/history_table_live.ex b/lib/pinchflat_web/controllers/pages/page_html/history_table_live.ex index 34147ef..018bcef 100644 --- a/lib/pinchflat_web/controllers/pages/page_html/history_table_live.ex +++ b/lib/pinchflat_web/controllers/pages/page_html/history_table_live.ex @@ -6,7 +6,7 @@ defmodule Pinchflat.Pages.HistoryTableLive do alias Pinchflat.Utils.NumberUtils alias PinchflatWeb.CustomComponents.TextComponents - @limit 10 + @limit 5 def render(%{records: []} = assigns) do ~H""" diff --git a/lib/pinchflat_web/controllers/pages/page_html/home.html.heex b/lib/pinchflat_web/controllers/pages/page_html/home.html.heex index bec4635..c0ecb82 100644 --- a/lib/pinchflat_web/controllers/pages/page_html/home.html.heex +++ b/lib/pinchflat_web/controllers/pages/page_html/home.html.heex @@ -26,16 +26,15 @@
- Active Tasks + Media History
- <%= live_render(@conn, Pinchflat.Pages.JobTableLive) %> + <%= live_render(@conn, Pinchflat.Pages.HistoryTableLive) %>
- Media History -
- <%!-- TODO: revert --%> - <%!-- <%= live_render(@conn, Pinchflat.Pages.HistoryTableLive) %> --%> + Active Tasks +
+ <%= live_render(@conn, Pinchflat.Pages.JobTableLive) %>
diff --git a/lib/pinchflat_web/controllers/pages/page_html/job_table_live.ex b/lib/pinchflat_web/controllers/pages/page_html/job_table_live.ex index 401e0f1..d65f724 100644 --- a/lib/pinchflat_web/controllers/pages/page_html/job_table_live.ex +++ b/lib/pinchflat_web/controllers/pages/page_html/job_table_live.ex @@ -9,51 +9,50 @@ defmodule Pinchflat.Pages.JobTableLive do def render(%{tasks: []} = assigns) do ~H"""
-

Nothing here!

+

Nothing Here!

""" end def render(assigns) do ~H""" -
-
- <.table rows={@tasks} table_class="text-white"> - <:col :let={task} label="Task"> - <%= worker_to_task_name(task.job.worker) %> - - <:col :let={task} label="Subject"> - <.subtle_link href={task_to_link(task)}> - <%= StringUtils.truncate(task_to_record_name(task), 35) %> - - - <:col :let={task} label="Attempt No."> - <%= task.job.attempt %> - - <:col :let={task} label="Started At"> - <%= format_datetime(task.job.attempted_at) %> - - -
+
+ <.table rows={@tasks} table_class="text-white"> + <:col :let={task} label="Task"> + <%= worker_to_task_name(task.job.worker) %> + + <:col :let={task} label="Subject"> + <.subtle_link href={task_to_link(task)}> + <%= StringUtils.truncate(task_to_record_name(task), 35) %> + + + <:col :let={task} label="Attempt No."> + <%= task.job.attempt %> + + <:col :let={task} label="Started At"> + <%= format_datetime(task.job.attempted_at) %> + +
""" end def mount(_params, _session, socket) do - PinchflatWeb.Endpoint.subscribe("tasks:job_table_live") + PinchflatWeb.Endpoint.subscribe("job:state") {:ok, assign(socket, tasks: get_tasks())} end - def handle_info(%{topic: "tasks:job_table_live", event: "reload"}, socket) do + def handle_info(%{topic: "job:state", event: "change"}, socket) do {:noreply, assign(socket, tasks: get_tasks())} end defp get_tasks do TasksQuery.new() |> TasksQuery.join_job() - |> where(^TasksQuery.in_state(["executing", "completed"])) + |> where(^TasksQuery.in_state("executing")) |> where(^TasksQuery.has_tag("show_in_dashboard")) + |> order_by([t, j], desc: j.attempted_at) |> Repo.all() |> Repo.preload([:media_item, :source]) end @@ -68,10 +67,10 @@ defmodule Pinchflat.Pages.JobTableLive do end defp map_worker_to_task_name("FastIndexingWorker"), do: "Fast Indexing Source" - defp map_worker_to_task_name("MediaDownloadWorker"), do: "Download Media" + defp map_worker_to_task_name("MediaDownloadWorker"), do: "Downloading Media" defp map_worker_to_task_name("MediaCollectionIndexingWorker"), do: "Indexing Source" - defp map_worker_to_task_name("MediaQualityUpgradeWorker"), do: "Upgrade Media Quality" - defp map_worker_to_task_name("SourceMetadataStorageWorker"), do: "Fetch Source Metadata" + defp map_worker_to_task_name("MediaQualityUpgradeWorker"), do: "Upgrading Media Quality" + defp map_worker_to_task_name("SourceMetadataStorageWorker"), do: "Fetching Source Metadata" defp map_worker_to_task_name(other), do: other <> " (Report to Devs)" defp task_to_record_name(%Task{} = task) do diff --git a/lib/pinchflat_web/telemetry.ex b/lib/pinchflat_web/telemetry.ex index 172e290..8ce908a 100644 --- a/lib/pinchflat_web/telemetry.ex +++ b/lib/pinchflat_web/telemetry.ex @@ -19,6 +19,11 @@ defmodule PinchflatWeb.Telemetry do Supervisor.init(children, strategy: :one_for_one) end + @doc false + def job_state_change_broadcast(_event, _measure, _meta, _config) do + PinchflatWeb.Endpoint.broadcast("job:state", "change", nil) + end + def metrics do [ # Phoenix Metrics diff --git a/test/pinchflat_web/controllers/pages/job_table_live_test.exs b/test/pinchflat_web/controllers/pages/job_table_live_test.exs new file mode 100644 index 0000000..9e62b4e --- /dev/null +++ b/test/pinchflat_web/controllers/pages/job_table_live_test.exs @@ -0,0 +1,111 @@ +defmodule PinchflatWeb.Pages.JobTableLiveTest do + use PinchflatWeb.ConnCase + + import Ecto.Query, warn: false + import Phoenix.LiveViewTest + import Pinchflat.MediaFixtures + import Pinchflat.SourcesFixtures + + alias Pinchflat.Utils.StringUtils + alias Pinchflat.Pages.JobTableLive + alias Pinchflat.Downloading.MediaDownloadWorker + alias Pinchflat.FastIndexing.FastIndexingWorker + + describe "initial rendering" do + test "shows message when no records", %{conn: conn} do + {:ok, _view, html} = live_isolated(conn, JobTableLive, session: %{}) + + assert html =~ "Nothing Here!" + refute html =~ "Subject" + end + + test "shows records when present", %{conn: conn} do + {_source, _media_item, _task, _job} = create_media_item_job() + {:ok, _view, html} = live_isolated(conn, JobTableLive, session: %{}) + + assert html =~ "Subject" + end + + test "doesn't show records when not in executing state", %{conn: conn} do + {_source, _media_item, _task, _job} = create_media_item_job(:scheduled) + {_source, _media_item, _task, _job} = create_media_item_job(:completed) + {:ok, _view, html} = live_isolated(conn, JobTableLive, session: %{}) + + assert html =~ "Nothing Here!" + refute html =~ "Subject" + end + end + + describe "job rendering" do + test "shows worker name", %{conn: conn} do + {_source, _media_item, _task, _job} = create_media_item_job() + {:ok, _view, html} = live_isolated(conn, JobTableLive, session: %{}) + + assert html =~ "Downloading Media" + end + + test "shows the media item title", %{conn: conn} do + {_source, media_item, _task, _job} = create_media_item_job() + {:ok, _view, html} = live_isolated(conn, JobTableLive, session: %{}) + + assert html =~ StringUtils.truncate(media_item.title, 35) + end + + test "shows a media item link", %{conn: conn} do + {_source, media_item, _task, _job} = create_media_item_job() + {:ok, _view, html} = live_isolated(conn, JobTableLive, session: %{}) + + assert html =~ ~p"/sources/#{media_item.source_id}/media/#{media_item}" + end + + test "shows the source custom name", %{conn: conn} do + {source, _task, _job} = create_source_job() + {:ok, _view, html} = live_isolated(conn, JobTableLive, session: %{}) + + assert html =~ StringUtils.truncate(source.custom_name, 35) + end + + test "shows a source link", %{conn: conn} do + {source, _task, _job} = create_source_job() + {:ok, _view, html} = live_isolated(conn, JobTableLive, session: %{}) + + assert html =~ ~p"/sources/#{source.id}" + end + + test "listens for job:state change events", %{conn: conn} do + {_source, _media_item, _task, _job} = create_media_item_job() + {:ok, _view, _html} = live_isolated(conn, JobTableLive, session: %{}) + + PinchflatWeb.Endpoint.broadcast("job:state", "change", nil) + + assert_receive %Phoenix.Socket.Broadcast{topic: "job:state", event: "change", payload: nil} + end + end + + defp create_media_item_job(job_state \\ :executing) do + source = source_fixture() + media_item = media_item_fixture(source_id: source.id) + {:ok, task} = MediaDownloadWorker.kickoff_with_task(media_item) + + Oban.Job + |> where([j], j.id == ^task.job_id) + |> Repo.update_all(set: [state: to_string(job_state)]) + + job = Repo.get!(Oban.Job, task.job_id) + + {source, media_item, task, job} + end + + defp create_source_job(job_state \\ :executing) do + source = source_fixture() + {:ok, task} = FastIndexingWorker.kickoff_with_task(source) + + Oban.Job + |> where([j], j.id == ^task.job_id) + |> Repo.update_all(set: [state: to_string(job_state)]) + + job = Repo.get!(Oban.Job, task.job_id) + + {source, task, job} + end +end