Sources: a query made from the map that did not save says so

c7ea72b · Gabe Morell · 2026-11-04 21:44

11 files +131 -58
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