diff --git a/lib/bob_web/live/artifacts_live.ex b/lib/bob_web/live/artifacts_live.ex index 656c52d..3e3c1bc 100644 --- a/lib/bob_web/live/artifacts_live.ex +++ b/lib/bob_web/live/artifacts_live.ex @@ -6,13 +6,7 @@ defmodule BobWeb.ArtifactsLive do @impl true def mount(_params, _session, socket) do socket = - socket - |> assign( - query: "", - kind: "", - arch: "", - os: "", - offset: 0, + assign(socket, page: @page, kinds: [], arches: [], @@ -22,35 +16,61 @@ defmodule BobWeb.ArtifactsLive do results: [] ) - socket = if connected?(socket), do: socket |> load_options() |> load(), else: socket - + socket = if connected?(socket), do: load_options(socket), else: socket {:ok, socket} end @impl true - def handle_event("search", params, socket) do - socket = - socket - |> assign( - query: params["query"] || "", - kind: params["kind"] || "", - arch: params["arch"] || "", - os: params["os"] || "", - offset: 0 - ) - |> load() - + def handle_params(params, _uri, socket) do + socket = assign(socket, filter_assigns(params)) + socket = if connected?(socket), do: load(socket), else: socket {:noreply, socket} end + @impl true + def handle_event("search", params, socket) do + filters = Keyword.delete(filter_assigns(params), :offset) + {:noreply, push_patch(socket, to: ~p"/artifacts?#{query(filters, 0)}", replace: true)} + end + def handle_event("page", %{"dir" => dir}, socket) do offset = max(socket.assigns.offset + step(dir), 0) - {:noreply, socket |> assign(offset: offset) |> load()} + + filters = [ + query: socket.assigns.query, + kind: socket.assigns.kind, + arch: socket.assigns.arch, + os: socket.assigns.os + ] + + {:noreply, push_patch(socket, to: ~p"/artifacts?#{query(filters, offset)}")} end defp step("next"), do: @page defp step("prev"), do: -@page + defp filter_assigns(params) do + [ + query: params["query"] || "", + kind: params["kind"] || "", + arch: params["arch"] || "", + os: params["os"] || "", + offset: parse_offset(params) + ] + end + + defp query(filters, offset) do + filters = Enum.reject(filters, fn {_key, value} -> value in [nil, ""] end) + if offset > 0, do: filters ++ [offset: offset], else: filters + end + + defp parse_offset(params) do + case Integer.parse(params["offset"] || "") do + {offset, ""} when offset > 0 -> offset + _ -> 0 + end + end + defp load_options(socket) do assign(socket, kinds: Bob.Artifacts.distinct_kinds(), diff --git a/lib/bob_web/live/jobs_live.ex b/lib/bob_web/live/jobs_live.ex index 50c050b..28c1e18 100644 --- a/lib/bob_web/live/jobs_live.ex +++ b/lib/bob_web/live/jobs_live.ex @@ -31,16 +31,24 @@ defmodule BobWeb.JobsLive do past_more: false ) - socket = - if connected?(socket) do - socket |> assign(modules: load_modules()) |> load() |> schedule_tick() - else - socket - end + socket = if connected?(socket), do: assign(socket, modules: load_modules()), else: socket {:ok, socket} end + @impl true + def handle_params(params, _uri, socket) do + socket = + assign(socket, + selected: parse_modules(params), + queued_offset: parse_offset(params["queued"]), + past_offset: parse_offset(params["past"]) + ) + + socket = if connected?(socket), do: socket |> load() |> schedule_tick(), else: socket + {:noreply, socket} + end + @impl true def handle_info(:jobs_changed, %{assigns: %{refresh_scheduled: true}} = socket) do {:noreply, socket} @@ -67,33 +75,53 @@ defmodule BobWeb.JobsLive do @impl true def handle_event("queued_page", %{"dir" => dir}, socket) do offset = max(socket.assigns.queued_offset + step(dir, @queued_page), 0) - {:noreply, socket |> assign(queued_offset: offset) |> load()} + + {:noreply, + push_patch(socket, + to: ~p"/?#{jobs_query(socket.assigns.selected, offset, socket.assigns.past_offset)}" + )} end def handle_event("past_page", %{"dir" => dir}, socket) do offset = max(socket.assigns.past_offset + step(dir, @past_page), 0) - {:noreply, socket |> assign(past_offset: offset) |> load()} + + {:noreply, + push_patch(socket, + to: ~p"/?#{jobs_query(socket.assigns.selected, socket.assigns.queued_offset, offset)}" + )} end def handle_event("toggle_module", %{"module" => name}, socket) do - selected = socket.assigns.selected - selected = - if MapSet.member?(selected, name) do - MapSet.delete(selected, name) + if MapSet.member?(socket.assigns.selected, name) do + MapSet.delete(socket.assigns.selected, name) else - MapSet.put(selected, name) + MapSet.put(socket.assigns.selected, name) end - {:noreply, - socket - |> assign(selected: selected, queued_offset: 0, past_offset: 0) - |> load()} + {:noreply, push_patch(socket, to: ~p"/?#{jobs_query(selected, 0, 0)}")} end defp step("next", page), do: page defp step("prev", page), do: -page + defp parse_modules(params) do + params |> Map.get("module", []) |> List.wrap() |> MapSet.new() + end + + defp parse_offset(value) do + case Integer.parse(to_string(value || "")) do + {offset, ""} when offset > 0 -> offset + _ -> 0 + end + end + + defp jobs_query(selected, queued_offset, past_offset) do + query = if MapSet.size(selected) > 0, do: [module: MapSet.to_list(selected)], else: [] + query = if queued_offset > 0, do: query ++ [queued: queued_offset], else: query + if past_offset > 0, do: query ++ [past: past_offset], else: query + end + defp load(socket) do selected = socket.assigns.selected filter = Enum.filter(socket.assigns.modules, &MapSet.member?(selected, module_name(&1))) diff --git a/test/bob_web/live/artifacts_live_test.exs b/test/bob_web/live/artifacts_live_test.exs index 6f59a3e..fc5ad0c 100644 --- a/test/bob_web/live/artifacts_live_test.exs +++ b/test/bob_web/live/artifacts_live_test.exs @@ -50,4 +50,18 @@ defmodule BobWeb.ArtifactsLiveTest do assert html =~ "OTP-26.2" refute html =~ "OTP-27.0" end + + test "applies filters from URL params on load", %{conn: conn} do + {:ok, _view, html} = live(conn, ~p"/artifacts?query=27.0") + + assert html =~ "OTP-27.0" + refute html =~ "OTP-26.2" + end + + test "searching patches the URL so filters survive a refresh", %{conn: conn} do + {:ok, view, _html} = live(conn, ~p"/artifacts") + + render_change(view, "search", %{"query" => "27.0", "kind" => "", "arch" => "", "os" => ""}) + assert_patch(view, ~p"/artifacts?query=27.0") + end end diff --git a/test/bob_web/live/jobs_live_test.exs b/test/bob_web/live/jobs_live_test.exs index 20fb626..4ed8765 100644 --- a/test/bob_web/live/jobs_live_test.exs +++ b/test/bob_web/live/jobs_live_test.exs @@ -96,6 +96,26 @@ defmodule BobWeb.JobsLiveTest do assert html =~ ":docker_done" end + test "selecting a module patches the URL so the filter survives a re-mount", %{conn: conn} do + Bob.Queue.add(Bob.Job.OTPChecker, [:otp_queued]) + + {:ok, view, _html} = live(conn, ~p"/") + + view |> element("button[phx-value-module='Bob.Job.OTPChecker']") |> render_click() + assert_patch(view, ~p"/?#{[module: ["Bob.Job.OTPChecker"]]}") + end + + test "applies the module filter from URL params on load", %{conn: conn} do + Bob.Queue.add(Bob.Job.OTPChecker, [:otp_queued]) + Bob.Queue.add(Bob.Job.DockerChecker, [:docker_queued]) + + {:ok, _view, html} = live(conn, ~p"/?#{[module: ["Bob.Job.OTPChecker"]]}") + + assert html =~ ":otp_queued" + refute html =~ ":docker_queued" + assert html =~ "chip--active" + end + test "renders tuple module keys without inspect syntax", %{conn: conn} do Bob.Queue.add({Bob.Job.BuildDockerElixir, "amd64"}, [:tuple_label])