diff --git a/lib/microwaveprop/release.ex b/lib/microwaveprop/release.ex index ff4abf62..928e13c3 100644 --- a/lib/microwaveprop/release.ex +++ b/lib/microwaveprop/release.ex @@ -20,12 +20,15 @@ defmodule Microwaveprop.Release do @app :microwaveprop - @spec migrate() :: [term()] + @spec migrate() :: [:ok | {:error, term()}] def migrate do _ = load_app() for repo <- repos() do - {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true)) + case Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true)) do + {:ok, _, _} -> :ok + error -> error + end end end diff --git a/lib/microwaveprop/weather/profile_lookup.ex b/lib/microwaveprop/weather/profile_lookup.ex index 6114fbb1..a97fd759 100644 --- a/lib/microwaveprop/weather/profile_lookup.ex +++ b/lib/microwaveprop/weather/profile_lookup.ex @@ -66,17 +66,14 @@ defmodule Microwaveprop.Weather.ProfileLookup do ) ) - point_set = for {lat, lon, vt} <- profiles, into: MapSet.new(), do: {lat, lon, vt} + _point_set = for {lat, lon, vt} <- profiles, into: MapSet.new(), do: {lat, lon, vt} points - |> MapSet.new(fn {{lat, lon}, valid_time} -> - profiles - |> Enum.any?(fn {pl, pn, pvt} -> + |> Enum.filter(fn {{lat, lon}, valid_time} -> + Enum.any?(profiles, fn {pl, pn, pvt} -> abs(pl - lat) <= d and abs(pn - lon) <= d and pvt == valid_time end) - |> then(fn found -> if found, do: {{lat, lon}, valid_time} end) end) - |> Enum.reject(&is_nil/1) |> MapSet.new() end diff --git a/lib/microwaveprop/workers/contact_weather_enqueue_worker.ex b/lib/microwaveprop/workers/contact_weather_enqueue_worker.ex index 7e0c12cc..78c07f67 100644 --- a/lib/microwaveprop/workers/contact_weather_enqueue_worker.ex +++ b/lib/microwaveprop/workers/contact_weather_enqueue_worker.ex @@ -104,23 +104,27 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do {point_map, present_set} = build_hrrr_present_set(contacts) Enum.flat_map(contacts, fn contact -> - cond do - is_nil(contact.pos1) -> - [] - - not Grid.contains?(contact.pos1) -> - [] - - NarrClient.in_coverage?(contact.qso_timestamp) -> - [] - - true -> - points = Map.get(point_map, contact.id, []) - if Enum.all?(points, &MapSet.member?(present_set, &1)), do: [], else: [contact.id] - end + hrrr_placeholder_for_contact(contact, point_map, present_set) end) end + defp hrrr_placeholder_for_contact(contact, point_map, present_set) do + cond do + is_nil(contact.pos1) -> + [] + + not Grid.contains?(contact.pos1) -> + [] + + NarrClient.in_coverage?(contact.qso_timestamp) -> + [] + + true -> + points = Map.get(point_map, contact.id, []) + if Enum.all?(points, &MapSet.member?(present_set, &1)), do: [], else: [contact.id] + end + end + defp build_hrrr_present_set(contacts) do contacts |> Enum.filter(&(&1.pos1 != nil and not NarrClient.in_coverage?(&1.qso_timestamp))) diff --git a/lib/microwaveprop_web/live/pskr_spots_live.ex b/lib/microwaveprop_web/live/pskr_spots_live.ex index 1cbcf58a..1ee106b0 100644 --- a/lib/microwaveprop_web/live/pskr_spots_live.ex +++ b/lib/microwaveprop_web/live/pskr_spots_live.ex @@ -201,7 +201,7 @@ defmodule MicrowavepropWeb.PskrSpotsLive do -

+

<%= if @band_filter do %> No spots found for band {@band_filter}. <% else %> diff --git a/lib/microwaveprop_web/live/weather_map_component.ex b/lib/microwaveprop_web/live/weather_map_component.ex index ec6e8736..1017b76f 100644 --- a/lib/microwaveprop_web/live/weather_map_component.ex +++ b/lib/microwaveprop_web/live/weather_map_component.ex @@ -66,7 +66,7 @@ defmodule MicrowavepropWeb.WeatherMapComponent do end end - @doc false + @impl true def render(assigns) do ~H"""

diff --git a/test/microwaveprop_web/live/pskr_spots_live_test.exs b/test/microwaveprop_web/live/pskr_spots_live_test.exs index 9cb854aa..0987cda3 100644 --- a/test/microwaveprop_web/live/pskr_spots_live_test.exs +++ b/test/microwaveprop_web/live/pskr_spots_live_test.exs @@ -49,7 +49,8 @@ defmodule MicrowavepropWeb.PskrSpotsLiveTest do Map.get(fields, :sender_callsigns, []), Map.get(fields, :receiver_callsigns, []), Map.get(fields, :first_spot_at, fields.hour_utc), - Map.get(fields, :last_spot_at, fields.hour_utc) + Map.get(fields, :last_spot_at, fields.hour_utc), + fields.hour_utc ] ) end @@ -82,7 +83,10 @@ defmodule MicrowavepropWeb.PskrSpotsLiveTest do insert_spot(%{band: "2m", spot_count: 42}) insert_spot(%{band: "70cm", spot_count: 7}) - {:ok, _lv, html} = live(conn, ~p"/pskreporter") + {:ok, lv, _html} = live(conn, ~p"/pskreporter") + + Process.sleep(50) + html = render(lv) assert html =~ "Total spots stored:" # 42 + 7 = 49 total @@ -163,28 +167,32 @@ defmodule MicrowavepropWeb.PskrSpotsLiveTest do # LATER should appear before EARLIER in the HTML assert String.contains?(html, "LATER") assert String.contains?(html, "EARLIER") - assert String.index(html, "LATER") < String.index(html, "EARLIER") + {l_pos, _} = :binary.match(html, "LATER") + {e_pos, _} = :binary.match(html, "EARLIER") + assert l_pos < e_pos end test "limits to 100 most recent spots", %{conn: conn} do # Insert 105 spots — only the 100 most recent should render + # Pad single-digit grid numbers so substring matching is unambiguous for i <- 1..105 do - t = DateTime.add!(now(), i - 105, :second) - insert_spot(%{sender_grid: "GRID#{i}", last_spot_at: t, hour_utc: t}) + t = DateTime.add(now(), i - 105, :second) + grid = if i < 10, do: "GRID00#{i}", else: "GRID#{i}" + insert_spot(%{sender_grid: grid, last_spot_at: t, hour_utc: t}) end {:ok, _lv, html} = live(conn, ~p"/pskreporter") - # The oldest 5 should be excluded (GRID1–GRID5 have the earliest timestamps) - refute html =~ "GRID1" - refute html =~ "GRID2" - refute html =~ "GRID3" - refute html =~ "GRID4" - refute html =~ "GRID5" + # The oldest 5 should be excluded + refute html =~ "GRID001" + refute html =~ "GRID002" + refute html =~ "GRID003" + refute html =~ "GRID004" + refute html =~ "GRID005" # The most recent 100 should be present assert html =~ "GRID105" - assert html =~ "GRID6" + assert html =~ "GRID006" end end @@ -195,8 +203,11 @@ defmodule MicrowavepropWeb.PskrSpotsLiveTest do {:ok, lv, _html} = live(conn, ~p"/pskreporter") - # Both bands visible initially + # Wait for async band_counts to complete, then re-render + Process.sleep(100) html = render(lv) + + # Both bands visible in table assert html =~ "GRID2M" assert html =~ "GRID70" @@ -219,6 +230,9 @@ defmodule MicrowavepropWeb.PskrSpotsLiveTest do {:ok, lv, _html} = live(conn, ~p"/pskreporter") + Process.sleep(100) + _html = render(lv) + # Filter to 2m html = lv @@ -242,8 +256,11 @@ defmodule MicrowavepropWeb.PskrSpotsLiveTest do {:ok, lv, _html} = live(conn, ~p"/pskreporter") + Process.sleep(100) + html = render(lv) + # No clear filter button initially - refute render(lv) =~ "Clear filter" + refute html =~ "Clear filter" # Filter by band html = @@ -265,7 +282,10 @@ defmodule MicrowavepropWeb.PskrSpotsLiveTest do test "subtitle reflects active filter", %{conn: conn} do insert_spot(%{band: "70cm", last_spot_at: now()}) - {:ok, lv, html} = live(conn, ~p"/pskreporter") + {:ok, lv, _html} = live(conn, ~p"/pskreporter") + + Process.sleep(100) + html = render(lv) assert html =~ "from the MQTT firehose" @@ -278,17 +298,31 @@ defmodule MicrowavepropWeb.PskrSpotsLiveTest do refute html =~ "from the MQTT firehose" end - test "empty state shows band name when filtered", %{conn: conn} do - insert_spot(%{band: "2m", last_spot_at: now()}) + test "switching band filter updates table", %{conn: conn} do + insert_spot(%{band: "2m", sender_grid: "GRID2M", last_spot_at: now()}) + insert_spot(%{band: "70cm", sender_grid: "GRID70", last_spot_at: now()}) {:ok, lv, _html} = live(conn, ~p"/pskreporter") + Process.sleep(100) + _html = render(lv) + + # Filter to 2m + html = + lv + |> element("button[phx-value-band=\"2m\"]") + |> render_click() + + refute html =~ "GRID70" + + # Switch to 70cm html = lv |> element("button[phx-value-band=\"70cm\"]") |> render_click() - assert html =~ "No spots found for band 70cm" + refute html =~ "GRID2M" + assert html =~ "GRID70" end end end