neiam
/apollos_crib
It's Apollo's Crib
public · Issues · Pulls 1 · Labels · Forks · Compare · Actions failure · Badges · Packages
⭐
Log in to mark this repository.
Sources: a query made from the map that did not save says so
c7ea72b · Gabe Morell · 2026-11-04 21:44
Message
{commit_body(@commit)}
Files changed
modified
apps/room_sanctum/lib/room_sanctum_web/live/cfg_source_live/show.ex
+70
−58
@@ -1067,79 +1067,79 @@ defmodule RoomSanctumWeb.SourceLive.Show do
| 1067 | 1067 | %{"station_id" => station_id, "name" => name, "type" => type}, |
| 1068 | 1068 | socket |
| 1069 | 1069 | ) do |
| 1070 | − case {socket.assigns.source.type, type} do | |
| 1071 | − {:gbfs, "station"} -> | |
| 1072 | − Configuration.create_query(%{ | |
| 1073 | − user_id: socket.assigns.current_user.id, | |
| 1074 | − source_id: socket.assigns.source.id, | |
| 1075 | − name: "#{name} - Station Query", | |
| 1076 | − query: %{"stop_id" => station_id, "__type__" => "gbfs"}, | |
| 1077 | − public: true | |
| 1078 | − }) | |
| 1079 | − | |
| 1080 | − {:gbfs, "area"} -> | |
| 1081 | − # For free bike area queries, create a radius-based query around the bike location | |
| 1082 | − # We'll use a default 500m radius, but this could be made configurable | |
| 1083 | − bike = Enum.find(socket.assigns.free_bikes, &(&1.bike_id == station_id)) | |
| 1084 | − | |
| 1085 | − if bike do | |
| 1070 | + created = | |
| 1071 | + case {socket.assigns.source.type, type} do | |
| 1072 | + {:gbfs, "station"} -> | |
| 1086 | 1073 | Configuration.create_query(%{ |
| 1087 | 1074 | user_id: socket.assigns.current_user.id, |
| 1088 | 1075 | source_id: socket.assigns.source.id, |
| 1089 | − name: "Free Bikes around #{bike.lat}, #{bike.lon}", | |
| 1090 | − query: %{ | |
| 1091 | − "lat" => bike.lat, | |
| 1092 | − "lng" => bike.lon, | |
| 1093 | − # 500 meter radius | |
| 1094 | − "radius" => 500, | |
| 1095 | − "__type__" => "gbfs" | |
| 1096 | − }, | |
| 1076 | + name: "#{name} - Station Query", | |
| 1077 | + query: %{"stop_id" => station_id, "__type__" => "gbfs"}, | |
| 1097 | 1078 | public: true |
| 1098 | 1079 | }) |
| 1099 | − end | |
| 1100 | − | |
| 1101 | − {:gtfs, "stop"} -> | |
| 1102 | − Configuration.create_query(%{ | |
| 1103 | − user_id: socket.assigns.current_user.id, | |
| 1104 | − source_id: socket.assigns.source.id, | |
| 1105 | − name: "#{name} - Stop Query", | |
| 1106 | − query: %{"stop" => station_id, "__type__" => "gtfs"}, | |
| 1107 | − public: true | |
| 1108 | − }) | |
| 1109 | 1080 | |
| 1110 | − {:gtfs, "vehicle"} -> | |
| 1111 | − # For GTFS vehicle position queries, create a vehicle tracking query | |
| 1112 | − Configuration.create_query(%{ | |
| 1113 | − user_id: socket.assigns.current_user.id, | |
| 1114 | − source_id: socket.assigns.source.id, | |
| 1115 | − name: "#{name}", | |
| 1116 | − query: %{"vehicle_id" => station_id, "__type__" => "gtfs_vehicle_positions"}, | |
| 1117 | − public: true | |
| 1118 | − }) | |
| 1081 | + {:gbfs, "area"} -> | |
| 1082 | + # For free bike area queries, create a radius-based query around the bike location | |
| 1083 | + # We'll use a default 500m radius, but this could be made configurable | |
| 1084 | + bike = Enum.find(socket.assigns.free_bikes, &(&1.bike_id == station_id)) | |
| 1085 | + | |
| 1086 | + if bike do | |
| 1087 | + Configuration.create_query(%{ | |
| 1088 | + user_id: socket.assigns.current_user.id, | |
| 1089 | + source_id: socket.assigns.source.id, | |
| 1090 | + name: "Free Bikes around #{bike.lat}, #{bike.lon}", | |
| 1091 | + query: %{ | |
| 1092 | + "lat" => bike.lat, | |
| 1093 | + "lng" => bike.lon, | |
| 1094 | + # 500 meter radius | |
| 1095 | + "radius" => 500, | |
| 1096 | + "__type__" => "gbfs" | |
| 1097 | + }, | |
| 1098 | + public: true | |
| 1099 | + }) | |
| 1100 | + end | |
| 1119 | 1101 | |
| 1120 | − {_, "query"} -> | |
| 1121 | − # For duplicating existing queries, find the original and copy it | |
| 1122 | − original_query = | |
| 1123 | − Enum.find(socket.assigns.queries, &(&1.id == String.to_integer(station_id))) | |
| 1102 | + {:gtfs, "stop"} -> | |
| 1103 | + Configuration.create_query(%{ | |
| 1104 | + user_id: socket.assigns.current_user.id, | |
| 1105 | + source_id: socket.assigns.source.id, | |
| 1106 | + name: "#{name} - Stop Query", | |
| 1107 | + query: %{"stop" => station_id, "__type__" => "gtfs"}, | |
| 1108 | + public: true | |
| 1109 | + }) | |
| 1124 | 1110 | |
| 1125 | − if original_query do | |
| 1111 | + {:gtfs, "vehicle"} -> | |
| 1112 | + # For GTFS vehicle position queries, create a vehicle tracking query | |
| 1126 | 1113 | Configuration.create_query(%{ |
| 1127 | 1114 | user_id: socket.assigns.current_user.id, |
| 1128 | 1115 | source_id: socket.assigns.source.id, |
| 1129 | − name: "Copy of #{original_query.name}", | |
| 1130 | − query: original_query.query, | |
| 1116 | + name: "#{name}", | |
| 1117 | + query: %{"vehicle_id" => station_id, "__type__" => "gtfs_vehicle_positions"}, | |
| 1131 | 1118 | public: true |
| 1132 | 1119 | }) |
| 1133 | − end | |
| 1134 | 1120 | |
| 1135 | − _ -> | |
| 1136 | − Logger.info( | |
| 1137 | − "Unhandled query creation for type: #{type}, source: #{socket.assigns.source.type}" | |
| 1138 | − ) | |
| 1139 | − end | |
| 1121 | + {_, "query"} -> | |
| 1122 | + # For duplicating existing queries, find the original and copy it | |
| 1123 | + original_query = | |
| 1124 | + Enum.find(socket.assigns.queries, &(&1.id == String.to_integer(station_id))) | |
| 1125 | + | |
| 1126 | + if original_query do | |
| 1127 | + Configuration.create_query(%{ | |
| 1128 | + user_id: socket.assigns.current_user.id, | |
| 1129 | + source_id: socket.assigns.source.id, | |
| 1130 | + name: "Copy of #{original_query.name}", | |
| 1131 | + query: original_query.query, | |
| 1132 | + public: true | |
| 1133 | + }) | |
| 1134 | + end | |
| 1135 | + | |
| 1136 | + _ -> | |
| 1137 | + Logger.info( | |
| 1138 | + "Unhandled query creation for type: #{type}, source: #{socket.assigns.source.type}" | |
| 1139 | + ) | |
| 1140 | + end | |
| 1140 | 1141 | |
| 1141 | − # Show a temporary success message or update | |
| 1142 | − {:noreply, socket |> put_flash(:info, "Query created successfully!")} | |
| 1142 | + {:noreply, created_flash(socket, created, name)} | |
| 1143 | 1143 | end |
| 1144 | 1144 | |
| 1145 | 1145 | @impl true |
@@ -1259,6 +1259,18 @@ defmodule RoomSanctumWeb.SourceLive.Show do
| 1259 | 1259 | end |
| 1260 | 1260 | end |
| 1261 | 1261 | |
| 1262 | + # Only an inserted query is a success. A changeset that failed validation, | |
| 1263 | + # a bike or query that was no longer there to copy, and a type nothing | |
| 1264 | + # handles all used to be reported as created too. | |
| 1265 | + defp created_flash(socket, {:ok, _query}, _name), | |
| 1266 | + do: put_flash(socket, :info, "Query created successfully!") | |
| 1267 | + | |
| 1268 | + defp created_flash(socket, {:error, %Ecto.Changeset{}}, name), | |
| 1269 | + do: put_flash(socket, :error, "Could not create a query for #{name}.") | |
| 1270 | + | |
| 1271 | + defp created_flash(socket, _nothing, name), | |
| 1272 | + do: put_flash(socket, :error, "Nothing to create a query from for #{name}.") | |
| 1273 | + | |
| 1262 | 1274 | defp percent(num, denom) do |
| 1263 | 1275 | (num / denom * 100) |
| 1264 | 1276 | |> Float.floor() |
added
apps/room_sanctum/test/room_sanctum_web/live/source_live_map_query_test.exs
+61
−0
@@ -0,0 +1,61 @@
| 1 | +defmodule RoomSanctumWeb.SourceLiveMapQueryTest do | |
| 2 | + @moduledoc """ | |
| 3 | + Making a query from a marker on the source page's map: the flash says | |
| 4 | + whether one was made. | |
| 5 | + """ | |
| 6 | + use RoomSanctumWeb.ConnCase | |
| 7 | + | |
| 8 | + import Phoenix.LiveViewTest | |
| 9 | + | |
| 10 | + alias RoomSanctum.{Accounts, Configuration} | |
| 11 | + | |
| 12 | + # ConnCase.register_and_log_in_user/1 reaches for RoomSanctum.AccountsFixtures, | |
| 13 | + # which does not exist in this tree, so the user is registered here instead. | |
| 14 | + setup %{conn: conn} do | |
| 15 | + {:ok, user} = | |
| 16 | + Accounts.register_user(%{ | |
| 17 | + email: "mapper#{System.unique_integer([:positive])}@example.com", | |
| 18 | + password: "hello world!hello world!" | |
| 19 | + }) | |
| 20 | + | |
| 21 | + {:ok, source} = | |
| 22 | + Configuration.create_source(%{ | |
| 23 | + name: "Bikes", | |
| 24 | + notes: "", | |
| 25 | + type: :gbfs, | |
| 26 | + enabled: true, | |
| 27 | + user_id: user.id, | |
| 28 | + config: %{"__type__" => "gbfs", "url" => "https://e.test/gbfs.json", "lang" => "en"} | |
| 29 | + }) | |
| 30 | + | |
| 31 | + %{conn: log_in_user(conn, user), source: source} | |
| 32 | + end | |
| 33 | + | |
| 34 | + defp add_from_map(conn, source, station_id) do | |
| 35 | + {:ok, live, _html} = live(conn, Routes.source_show_path(conn, :show, source)) | |
| 36 | + | |
| 37 | + render_hook(live, "add-query-from-map", %{ | |
| 38 | + "station_id" => station_id, | |
| 39 | + "name" => "Dock", | |
| 40 | + "type" => "station" | |
| 41 | + }) | |
| 42 | + | |
| 43 | + live | |
| 44 | + end | |
| 45 | + | |
| 46 | + test "a station query that saves says so", %{conn: conn, source: source} do | |
| 47 | + live = add_from_map(conn, source, "dock-1") | |
| 48 | + | |
| 49 | + assert has_element?(live, ".alert-info", "Query created successfully!") | |
| 50 | + assert [%{name: "Dock - Station Query"}] = Configuration.get_queries(:source, source.id) | |
| 51 | + end | |
| 52 | + | |
| 53 | + test "one that fails validation is not reported as created", %{conn: conn, source: source} do | |
| 54 | + # No station id: the GBFS query changeset requires one. | |
| 55 | + live = add_from_map(conn, source, "") | |
| 56 | + | |
| 57 | + assert has_element?(live, ".alert-error", "Could not create a query") | |
| 58 | + refute has_element?(live, ".alert-info", "Query created successfully!") | |
| 59 | + assert Configuration.get_queries(:source, source.id) == [] | |
| 60 | + end | |
| 61 | +end |
Parents: 8021671