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.
Credo: the Sanctum web pages in smaller pieces
063bd4c · Gabe Morell · 2026-11-04 21:04
Message
{commit_body(@commit)}
Files changed
modified
apps/room_sanctum/lib/room_sanctum_web/components/query_geospatial_map.ex
+123
−115
@@ -508,40 +508,40 @@ defmodule RoomSanctumWeb.Components.QueryGeospatialMap do
| 508 | 508 | cond do |
| 509 | 509 | query.geom != nil -> |
| 510 | 510 | # Handle PostGIS geometry - you may need to adjust this based on your Geo setup |
| 511 | − case query.geom do | |
| 512 | − %Geo.Point{coordinates: {lng, lat}} -> {lat, lng} | |
| 513 | − # fallback | |
| 514 | − _ -> {0, 0} | |
| 515 | − end | |
| 511 | + point_lat_lng(query.geom) | |
| 516 | 512 | |
| 517 | 513 | query.source && has_point_data?(query.source) -> |
| 518 | − source = query.source | |
| 519 | − geom = Map.get(source, :geom, nil) | |
| 520 | − lat = Map.get(source, :lat, nil) | |
| 521 | − lng = Map.get(source, :lng, nil) | |
| 522 | − latitude = Map.get(source, :latitude, nil) | |
| 523 | − longitude = Map.get(source, :longitude, nil) | |
| 514 | + source_coordinates(query.source) | |
| 524 | 515 | |
| 525 | − cond do | |
| 526 | − geom != nil -> | |
| 527 | − case geom do | |
| 528 | − %Geo.Point{coordinates: {lng, lat}} -> {lat, lng} | |
| 529 | − _ -> {0, 0} | |
| 530 | − end | |
| 516 | + # Handle data from the query's underlying stop/thing being queried | |
| 517 | + query.query && extract_from_query_data(query) -> | |
| 518 | + extract_from_query_data(query) | |
| 531 | 519 | |
| 532 | − lat != nil && lng != nil -> | |
| 533 | − {lat, lng} | |
| 520 | + true -> | |
| 521 | + {0, 0} | |
| 522 | + end | |
| 523 | + end | |
| 534 | 524 | |
| 535 | − latitude != nil && longitude != nil -> | |
| 536 | − {latitude, longitude} | |
| 525 | + defp point_lat_lng(%Geo.Point{coordinates: {lng, lat}}), do: {lat, lng} | |
| 526 | + # fallback | |
| 527 | + defp point_lat_lng(_geom), do: {0, 0} | |
| 537 | 528 | |
| 538 | − true -> | |
| 539 | − {0, 0} | |
| 540 | − end | |
| 529 | + defp source_coordinates(source) do | |
| 530 | + geom = Map.get(source, :geom, nil) | |
| 531 | + lat = Map.get(source, :lat, nil) | |
| 532 | + lng = Map.get(source, :lng, nil) | |
| 533 | + latitude = Map.get(source, :latitude, nil) | |
| 534 | + longitude = Map.get(source, :longitude, nil) | |
| 541 | 535 | |
| 542 | − # Handle data from the query's underlying stop/thing being queried | |
| 543 | − query.query && extract_from_query_data(query) -> | |
| 544 | − extract_from_query_data(query) | |
| 536 | + cond do | |
| 537 | + geom != nil -> | |
| 538 | + point_lat_lng(geom) | |
| 539 | + | |
| 540 | + lat != nil && lng != nil -> | |
| 541 | + {lat, lng} | |
| 542 | + | |
| 543 | + latitude != nil && longitude != nil -> | |
| 544 | + {latitude, longitude} | |
| 545 | 545 | |
| 546 | 546 | true -> |
| 547 | 547 | {0, 0} |
@@ -552,38 +552,10 @@ defmodule RoomSanctumWeb.Components.QueryGeospatialMap do
| 552 | 552 | defp extract_from_query_data(query) do |
| 553 | 553 | case query.source.type do |
| 554 | 554 | :gtfs -> |
| 555 | − # For GTFS, get coordinates from the stop being queried or vehicle positions | |
| 556 | − cond do | |
| 557 | − query.query && Map.has_key?(query.query, :stop) -> | |
| 558 | − get_gtfs_stop_coordinates(query.source.id, query.query.stop) | |
| 559 | − | |
| 560 | − # Check if this is a vehicle position query | |
| 561 | − query.source.config && Map.get(query.source.config, :url_rt_vp) -> | |
| 562 | − get_gtfs_vehicle_coordinates(query.source.id, query) | |
| 563 | − | |
| 564 | − true -> | |
| 565 | − false | |
| 566 | − end | |
| 555 | + gtfs_query_coordinates(query) | |
| 567 | 556 | |
| 568 | 557 | :gbfs -> |
| 569 | − # A station query is placed at its dock; an area query is placed at the | |
| 570 | − # foci it is drawn around, which is also where its radius is centred. | |
| 571 | − # Mode first: an area query still carries the stop_id field, it is just | |
| 572 | − # empty, and asking about the field rather than the mode sent every | |
| 573 | − # area query looking for a station called "". | |
| 574 | − cond do | |
| 575 | − query.query && Map.get(query.query, :mode) == :area -> | |
| 576 | − foci_coordinates(query) | |
| 577 | − | |
| 578 | − query.query && Map.get(query.query, :stop_id) not in [nil, ""] -> | |
| 579 | − get_gbfs_station_coordinates(query.source.id, query.query.stop_id) | |
| 580 | − | |
| 581 | − query.query && (Map.has_key?(query.query, :radius) || Map.has_key?(query.query, :point)) -> | |
| 582 | − get_gbfs_free_bikes_in_area_coordinates(query.source.id, query.query) | |
| 583 | − | |
| 584 | − true -> | |
| 585 | − false | |
| 586 | − end | |
| 558 | + gbfs_query_coordinates(query) | |
| 587 | 559 | |
| 588 | 560 | # Everything else that knows where it is says so with a foci: aqi, |
| 589 | 561 | # weather, tidal, ephem, pollen, drought and icarus area queries all |
@@ -593,6 +565,42 @@ defmodule RoomSanctumWeb.Components.QueryGeospatialMap do
| 593 | 565 | end |
| 594 | 566 | end |
| 595 | 567 | |
| 568 | + # For GTFS, get coordinates from the stop being queried or vehicle positions | |
| 569 | + defp gtfs_query_coordinates(query) do | |
| 570 | + cond do | |
| 571 | + query.query && Map.has_key?(query.query, :stop) -> | |
| 572 | + get_gtfs_stop_coordinates(query.source.id, query.query.stop) | |
| 573 | + | |
| 574 | + # Check if this is a vehicle position query | |
| 575 | + query.source.config && Map.get(query.source.config, :url_rt_vp) -> | |
| 576 | + get_gtfs_vehicle_coordinates(query.source.id, query) | |
| 577 | + | |
| 578 | + true -> | |
| 579 | + false | |
| 580 | + end | |
| 581 | + end | |
| 582 | + | |
| 583 | + # A station query is placed at its dock; an area query is placed at the | |
| 584 | + # foci it is drawn around, which is also where its radius is centred. | |
| 585 | + # Mode first: an area query still carries the stop_id field, it is just | |
| 586 | + # empty, and asking about the field rather than the mode sent every | |
| 587 | + # area query looking for a station called "". | |
| 588 | + defp gbfs_query_coordinates(query) do | |
| 589 | + cond do | |
| 590 | + query.query && Map.get(query.query, :mode) == :area -> | |
| 591 | + foci_coordinates(query) | |
| 592 | + | |
| 593 | + query.query && Map.get(query.query, :stop_id) not in [nil, ""] -> | |
| 594 | + get_gbfs_station_coordinates(query.source.id, query.query.stop_id) | |
| 595 | + | |
| 596 | + query.query && (Map.has_key?(query.query, :radius) || Map.has_key?(query.query, :point)) -> | |
| 597 | + get_gbfs_free_bikes_in_area_coordinates(query.source.id, query.query) | |
| 598 | + | |
| 599 | + true -> | |
| 600 | + false | |
| 601 | + end | |
| 602 | + end | |
| 603 | + | |
| 596 | 604 | defp foci_coordinates(%{query: %{foci_id: foci_id}}) when not is_nil(foci_id) do |
| 597 | 605 | get_foci_coordinates(foci_id) |
| 598 | 606 | end |
@@ -638,23 +646,21 @@ defmodule RoomSanctumWeb.Components.QueryGeospatialMap do
| 638 | 646 | end |
| 639 | 647 | end |
| 640 | 648 | |
| 641 | − # Get coordinates for GBFS free bikes in area | |
| 642 | − defp get_gbfs_free_bikes_in_area_coordinates(source_id, query_params) do | |
| 643 | − # Extract point and radius from query parameters | |
| 644 | − {point, radius} = | |
| 645 | − case query_params do | |
| 646 | − %{point: %Geo.Point{} = geo_point, radius: radius} -> | |
| 647 | − {geo_point, radius} | |
| 649 | + defp area_of(%{point: %Geo.Point{} = geo_point, radius: radius}), do: {geo_point, radius} | |
| 648 | 650 | |
| 649 | − %{lat: lat, lng: lng, radius: radius} when is_number(lat) and is_number(lng) -> | |
| 650 | − {%Geo.Point{coordinates: {lng, lat}}, radius} | |
| 651 | + defp area_of(%{lat: lat, lng: lng, radius: radius}) when is_number(lat) and is_number(lng), | |
| 652 | + do: {%Geo.Point{coordinates: {lng, lat}}, radius} | |
| 651 | 653 | |
| 652 | − %{latitude: lat, longitude: lng, radius: radius} when is_number(lat) and is_number(lng) -> | |
| 653 | − {%Geo.Point{coordinates: {lng, lat}}, radius} | |
| 654 | + defp area_of(%{latitude: lat, longitude: lng, radius: radius}) | |
| 655 | + when is_number(lat) and is_number(lng), | |
| 656 | + do: {%Geo.Point{coordinates: {lng, lat}}, radius} | |
| 654 | 657 | |
| 655 | − _ -> | |
| 656 | − {nil, nil} | |
| 657 | − end | |
| 658 | + defp area_of(_query_params), do: {nil, nil} | |
| 659 | + | |
| 660 | + # Get coordinates for GBFS free bikes in area | |
| 661 | + defp get_gbfs_free_bikes_in_area_coordinates(source_id, query_params) do | |
| 662 | + # Extract point and radius from query parameters | |
| 663 | + {point, radius} = area_of(query_params) | |
| 658 | 664 | |
| 659 | 665 | case {point, radius} do |
| 660 | 666 | {%Geo.Point{} = geo_point, radius} when is_number(radius) -> |
@@ -842,6 +848,19 @@ defmodule RoomSanctumWeb.Components.QueryGeospatialMap do
| 842 | 848 | # with `Map.get`; these two were the exception. |
| 843 | 849 | defp point_of(bike), do: Map.get(bike, :point) || Map.get(bike, :place) |
| 844 | 850 | |
| 851 | + defp bike_position(bike) do | |
| 852 | + cond do | |
| 853 | + point_of(bike) != nil -> | |
| 854 | + point_lat_lng(point_of(bike)) | |
| 855 | + | |
| 856 | + Map.get(bike, :lat) != nil && Map.get(bike, :lon) != nil -> | |
| 857 | + {Map.get(bike, :lat), Map.get(bike, :lon)} | |
| 858 | + | |
| 859 | + true -> | |
| 860 | + {0, 0} | |
| 861 | + end | |
| 862 | + end | |
| 863 | + | |
| 845 | 864 | # Format free bikes for map display |
| 846 | 865 | defp format_free_bikes(free_bikes) do |
| 847 | 866 | # A feed declares how far each of its vehicle types can go -- Bay Wheels |
@@ -854,20 +873,7 @@ defmodule RoomSanctumWeb.Components.QueryGeospatialMap do
| 854 | 873 | point_of(bike) != nil || (Map.get(bike, :lat) != nil && Map.get(bike, :lon) != nil) |
| 855 | 874 | end) |
| 856 | 875 | |> Enum.map(fn bike -> |
| 857 | − {lat, lng} = | |
| 858 | − cond do | |
| 859 | − point_of(bike) != nil -> | |
| 860 | − case point_of(bike) do | |
| 861 | − %Geo.Point{coordinates: {lng, lat}} -> {lat, lng} | |
| 862 | − _ -> {0, 0} | |
| 863 | − end | |
| 864 | − | |
| 865 | − Map.get(bike, :lat) != nil && Map.get(bike, :lon) != nil -> | |
| 866 | − {Map.get(bike, :lat), Map.get(bike, :lon)} | |
| 867 | − | |
| 868 | − true -> | |
| 869 | − {0, 0} | |
| 870 | − end | |
| 876 | + {lat, lng} = bike_position(bike) | |
| 871 | 877 | |
| 872 | 878 | # Extract additional fields with defaults |
| 873 | 879 | is_disabled = Map.get(bike, :is_disabled, false) |
@@ -1091,20 +1097,7 @@ defmodule RoomSanctumWeb.Components.QueryGeospatialMap do
| 1091 | 1097 | station.place != nil || (station.lat != nil && station.lon != nil) |
| 1092 | 1098 | end) |
| 1093 | 1099 | |> Enum.map(fn station -> |
| 1094 | − {lat, lng} = | |
| 1095 | − cond do | |
| 1096 | − station.place != nil -> | |
| 1097 | − case station.place do | |
| 1098 | − %Geo.Point{coordinates: {lng, lat}} -> {lat, lng} | |
| 1099 | − _ -> {0, 0} | |
| 1100 | − end | |
| 1101 | − | |
| 1102 | − station.lat != nil && station.lon != nil -> | |
| 1103 | − {station.lat, station.lon} | |
| 1104 | − | |
| 1105 | − true -> | |
| 1106 | − {0, 0} | |
| 1107 | − end | |
| 1100 | + {lat, lng} = station_position(station) | |
| 1108 | 1101 | |
| 1109 | 1102 | # Get associated station status |
| 1110 | 1103 | status = Map.get(status_map, station.station_id, nil) |
@@ -1135,25 +1128,40 @@ defmodule RoomSanctumWeb.Components.QueryGeospatialMap do
| 1135 | 1128 | } |
| 1136 | 1129 | |
| 1137 | 1130 | # Add station status fields if available |
| 1138 | − if status do | |
| 1139 | − station_data | |
| 1140 | − # What the dock currently holds, for the popup -- which otherwise says | |
| 1141 | − # only where the dock is, next to a button offering to query it. | |
| 1142 | − |> Map.put(:summary, dock_summary(status, station)) | |
| 1143 | − |> Map.put(:num_bikes_available, status.num_bikes_available) | |
| 1144 | − |> Map.put(:num_ebikes_available, status.num_ebikes_available) | |
| 1145 | − |> Map.put(:num_docks_available, status.num_docks_available) | |
| 1146 | − |> Map.put(:is_installed, status.is_installed) | |
| 1147 | − |> Map.put(:is_renting, status.is_renting) | |
| 1148 | − |> Map.put(:is_returning, status.is_returning) | |
| 1149 | − |> Map.put(:station_status, status.station_status) | |
| 1150 | − |> Map.put(:last_reported, status.last_reported) | |
| 1151 | − else | |
| 1152 | − station_data | |
| 1153 | − end | |
| 1131 | + with_status(station_data, status, station) | |
| 1154 | 1132 | end) |
| 1155 | 1133 | end |
| 1156 | 1134 | |
| 1135 | + defp station_position(station) do | |
| 1136 | + cond do | |
| 1137 | + station.place != nil -> | |
| 1138 | + point_lat_lng(station.place) | |
| 1139 | + | |
| 1140 | + station.lat != nil && station.lon != nil -> | |
| 1141 | + {station.lat, station.lon} | |
| 1142 | + | |
| 1143 | + true -> | |
| 1144 | + {0, 0} | |
| 1145 | + end | |
| 1146 | + end | |
| 1147 | + | |
| 1148 | + defp with_status(station_data, nil, _station), do: station_data | |
| 1149 | + | |
| 1150 | + defp with_status(station_data, status, station) do | |
| 1151 | + station_data | |
| 1152 | + # What the dock currently holds, for the popup -- which otherwise says | |
| 1153 | + # only where the dock is, next to a button offering to query it. | |
| 1154 | + |> Map.put(:summary, dock_summary(status, station)) | |
| 1155 | + |> Map.put(:num_bikes_available, status.num_bikes_available) | |
| 1156 | + |> Map.put(:num_ebikes_available, status.num_ebikes_available) | |
| 1157 | + |> Map.put(:num_docks_available, status.num_docks_available) | |
| 1158 | + |> Map.put(:is_installed, status.is_installed) | |
| 1159 | + |> Map.put(:is_renting, status.is_renting) | |
| 1160 | + |> Map.put(:is_returning, status.is_returning) | |
| 1161 | + |> Map.put(:station_status, status.station_status) | |
| 1162 | + |> Map.put(:last_reported, status.last_reported) | |
| 1163 | + end | |
| 1164 | + | |
| 1157 | 1165 | # A dock in the same label/value shape a query summary uses, so one popup |
| 1158 | 1166 | # renderer serves both. |
| 1159 | 1167 | # Laid out as the preview card lays a dock out: standard bikes, electric |
modified
apps/room_sanctum/lib/room_sanctum_web/live/cfg_query_live/show.ex
+50
−107
@@ -105,113 +105,7 @@ defmodule RoomSanctumWeb.QueryLive.Show do
| 105 | 105 | def handle_info(:update, socket) do |
| 106 | 106 | Process.send_after(self(), :update, 5000) |
| 107 | 107 | |
| 108 | − result = | |
| 109 | − case socket.assigns.query.source.type do | |
| 110 | − :gtfs -> | |
| 111 | − RoomGtfs.Worker.query_stop(socket.assigns.query.source.id, socket.assigns.query.query) | |
| 112 | − | |
| 113 | − :gbfs -> | |
| 114 | − RoomGbfs.Worker.query_stop(socket.assigns.query.source.id, socket.assigns.query.query) | |
| 115 | − | |
| 116 | − :tidal -> | |
| 117 | − RoomTidal.Worker.query_tides(socket.assigns.query.source.id, socket.assigns.query.query) | |
| 118 | − | |
| 119 | − :weather -> | |
| 120 | − RoomWeather.Worker.query_weather( | |
| 121 | − socket.assigns.query.source.id, | |
| 122 | − socket.assigns.query.query | |
| 123 | − ) | |
| 124 | − | |
| 125 | − :aqi -> | |
| 126 | − RoomAirQuality.Worker.query_place( | |
| 127 | − socket.assigns.query.source.id, | |
| 128 | − socket.assigns.query.query | |
| 129 | − ) | |
| 130 | − | |
| 131 | − :ephem -> | |
| 132 | − RoomEphem.Worker.query_ephem( | |
| 133 | − socket.assigns.query.source.id, | |
| 134 | − socket.assigns.query.query | |
| 135 | − ) | |
| 136 | − | |
| 137 | − :calendar -> | |
| 138 | − RoomCalendar.Worker.query_calendar( | |
| 139 | − socket.assigns.query.source.id, | |
| 140 | − socket.assigns.query.query | |
| 141 | − ) | |
| 142 | − | |
| 143 | − :cronos -> | |
| 144 | − RoomCronos.Worker.query_cronos( | |
| 145 | − # we want the query's name here | |
| 146 | − socket.assigns.query.id, | |
| 147 | − socket.assigns.query.query | |
| 148 | − ) | |
| 149 | − | |
| 150 | − :gitlab -> | |
| 151 | − RoomGitlab.Worker.read_jobs( | |
| 152 | − socket.assigns.query.source.id, | |
| 153 | − socket.assigns.query.query | |
| 154 | − ) | |
| 155 | − | |
| 156 | − :github -> | |
| 157 | − level = Map.get(socket.assigns.query.query, :level) || "runs" | |
| 158 | − | |
| 159 | − case level do | |
| 160 | − "jobs" -> | |
| 161 | − RoomGithub.Worker.read_jobs( | |
| 162 | − socket.assigns.query.source.id, | |
| 163 | − socket.assigns.query.query | |
| 164 | − ) | |
| 165 | − | |
| 166 | − _ -> | |
| 167 | − RoomGithub.Worker.read_runs( | |
| 168 | − socket.assigns.query.source.id, | |
| 169 | − socket.assigns.query.query | |
| 170 | − ) | |
| 171 | − end | |
| 172 | − | |
| 173 | − :packages -> | |
| 174 | − RoomPackages.Worker.read( | |
| 175 | − socket.assigns.query.source.id, | |
| 176 | − socket.assigns.query.query | |
| 177 | − ) | |
| 178 | − | |
| 179 | − :drought -> | |
| 180 | − RoomDrought.Worker.read( | |
| 181 | − socket.assigns.query.source.id, | |
| 182 | − socket.assigns.query.query | |
| 183 | − ) | |
| 184 | − | |
| 185 | − :pollen -> | |
| 186 | − RoomPollen.Worker.read( | |
| 187 | − socket.assigns.query.source.id, | |
| 188 | − socket.assigns.query.query | |
| 189 | − ) | |
| 190 | − | |
| 191 | − :icarus -> | |
| 192 | − RoomIcarus.Worker.read( | |
| 193 | − socket.assigns.query.source.id, | |
| 194 | − socket.assigns.query.query | |
| 195 | − ) | |
| 196 | − | |
| 197 | − :mailbox -> | |
| 198 | − RoomHermes.Mail.ImapWorker.read( | |
| 199 | − socket.assigns.query.source.id, | |
| 200 | − socket.assigns.query.query | |
| 201 | − ) | |
| 202 | − | |
| 203 | − :treasury -> | |
| 204 | − RoomTreasury.Worker.read( | |
| 205 | − socket.assigns.query.source.id, | |
| 206 | − socket.assigns.query.query | |
| 207 | − ) | |
| 208 | − | |
| 209 | − :bourse -> | |
| 210 | − RoomBourse.Worker.read( | |
| 211 | − socket.assigns.query.source.id, | |
| 212 | − socket.assigns.query.query | |
| 213 | − ) | |
| 214 | − end | |
| 108 | + result = preview_for(socket.assigns.query) | |
| 215 | 109 | |
| 216 | 110 | query = socket.assigns.query |
| 217 | 111 |
@@ -226,6 +120,55 @@ defmodule RoomSanctumWeb.QueryLive.Show do
| 226 | 120 | |> assign(:nearby_stations, nearby_stations(query))} |
| 227 | 121 | end |
| 228 | 122 | |
| 123 | + # Workers answering through read/2. | |
| 124 | + @readers %{ | |
| 125 | + packages: RoomPackages.Worker, | |
| 126 | + drought: RoomDrought.Worker, | |
| 127 | + pollen: RoomPollen.Worker, | |
| 128 | + icarus: RoomIcarus.Worker, | |
| 129 | + mailbox: RoomHermes.Mail.ImapWorker, | |
| 130 | + treasury: RoomTreasury.Worker, | |
| 131 | + bourse: RoomBourse.Worker | |
| 132 | + } | |
| 133 | + | |
| 134 | + defp preview_for(%{source: %{type: :gtfs}} = q), | |
| 135 | + do: RoomGtfs.Worker.query_stop(q.source.id, q.query) | |
| 136 | + | |
| 137 | + defp preview_for(%{source: %{type: :gbfs}} = q), | |
| 138 | + do: RoomGbfs.Worker.query_stop(q.source.id, q.query) | |
| 139 | + | |
| 140 | + defp preview_for(%{source: %{type: :tidal}} = q), | |
| 141 | + do: RoomTidal.Worker.query_tides(q.source.id, q.query) | |
| 142 | + | |
| 143 | + defp preview_for(%{source: %{type: :weather}} = q), | |
| 144 | + do: RoomWeather.Worker.query_weather(q.source.id, q.query) | |
| 145 | + | |
| 146 | + defp preview_for(%{source: %{type: :aqi}} = q), | |
| 147 | + do: RoomAirQuality.Worker.query_place(q.source.id, q.query) | |
| 148 | + | |
| 149 | + defp preview_for(%{source: %{type: :ephem}} = q), | |
| 150 | + do: RoomEphem.Worker.query_ephem(q.source.id, q.query) | |
| 151 | + | |
| 152 | + defp preview_for(%{source: %{type: :calendar}} = q), | |
| 153 | + do: RoomCalendar.Worker.query_calendar(q.source.id, q.query) | |
| 154 | + | |
| 155 | + # we want the query's name here | |
| 156 | + defp preview_for(%{source: %{type: :cronos}} = q), | |
| 157 | + do: RoomCronos.Worker.query_cronos(q.id, q.query) | |
| 158 | + | |
| 159 | + defp preview_for(%{source: %{type: :gitlab}} = q), | |
| 160 | + do: RoomGitlab.Worker.read_jobs(q.source.id, q.query) | |
| 161 | + | |
| 162 | + defp preview_for(%{source: %{type: :github}} = q) do | |
| 163 | + case Map.get(q.query, :level) || "runs" do | |
| 164 | + "jobs" -> RoomGithub.Worker.read_jobs(q.source.id, q.query) | |
| 165 | + _ -> RoomGithub.Worker.read_runs(q.source.id, q.query) | |
| 166 | + end | |
| 167 | + end | |
| 168 | + | |
| 169 | + defp preview_for(%{source: %{type: type}} = q) when is_map_key(@readers, type), | |
| 170 | + do: Map.fetch!(@readers, type).read(q.source.id, q.query) | |
| 171 | + | |
| 229 | 172 | # For an air quality query, the answer is one station -- so the neighbours |
| 230 | 173 | # are the context that makes it readable: whether the number is local or the |
| 231 | 174 | # whole city is like that. Ordered closest first, the query's own station |
modified
apps/room_sanctum/lib/room_sanctum_web/live/cfg_source_live/form_component.ex
+69
−73
@@ -261,20 +261,7 @@ defmodule RoomSanctumWeb.SourceLive.FormComponent do
| 261 | 261 | |
| 262 | 262 | watched = MapSet.union(repo_set, owner_repos) |> MapSet.size() |
| 263 | 263 | |
| 264 | − {watched, source} = | |
| 265 | − cond do | |
| 266 | − watched > 0 -> | |
| 267 | − {watched, if(owners == [], do: :exact, else: :partial)} | |
| 268 | − | |
| 269 | − owners != [] -> | |
| 270 | − {0, :partial} | |
| 271 | − | |
| 272 | − repos == [] and owners == [] and available_repos != [] -> | |
| 273 | − {length(available_repos), :exact} | |
| 274 | − | |
| 275 | − true -> | |
| 276 | − {0, :unknown} | |
| 277 | − end | |
| 264 | + {watched, source} = watched_count(watched, repos, owners, available_repos) | |
| 278 | 265 | |
| 279 | 266 | cycles_per_hour = div(3600, max(poll_seconds, 1)) |
| 280 | 267 | # 1 runs poll per repo per cycle + ~1 jobs poll per repo (one active run avg) |
@@ -286,6 +273,25 @@ defmodule RoomSanctumWeb.SourceLive.FormComponent do
| 286 | 273 | defp github_estimate(_, _, _), |
| 287 | 274 | do: %{watched: 0, calls: 0, source: :unknown, cycles: 0} |
| 288 | 275 | |
| 276 | + # The watched count both estimates report, and how sure of it they are. Named | |
| 277 | + # are the repos or projects listed one by one, groups the owners or | |
| 278 | + # namespaces listed whole. | |
| 279 | + defp watched_count(watched, named, groups, available) do | |
| 280 | + cond do | |
| 281 | + watched > 0 -> | |
| 282 | + {watched, if(groups == [], do: :exact, else: :partial)} | |
| 283 | + | |
| 284 | + groups != [] -> | |
| 285 | + {0, :partial} | |
| 286 | + | |
| 287 | + named == [] and groups == [] and available != [] -> | |
| 288 | + {length(available), :exact} | |
| 289 | + | |
| 290 | + true -> | |
| 291 | + {0, :unknown} | |
| 292 | + end | |
| 293 | + end | |
| 294 | + | |
| 289 | 295 | def github_estimate_class(calls) when calls >= 4500, do: "text-error" |
| 290 | 296 | def github_estimate_class(calls) when calls >= 2500, do: "text-warning" |
| 291 | 297 | def github_estimate_class(_), do: "text-base-content/70" |
@@ -351,20 +357,7 @@ defmodule RoomSanctumWeb.SourceLive.FormComponent do
| 351 | 357 | |
| 352 | 358 | watched = MapSet.union(project_set, namespace_projects) |> MapSet.size() |
| 353 | 359 | |
| 354 | − {watched, source} = | |
| 355 | − cond do | |
| 356 | − watched > 0 -> | |
| 357 | − {watched, if(namespaces == [], do: :exact, else: :partial)} | |
| 358 | − | |
| 359 | − namespaces != [] -> | |
| 360 | − {0, :partial} | |
| 361 | − | |
| 362 | − projects == [] and namespaces == [] and available_projects != [] -> | |
| 363 | − {length(available_projects), :exact} | |
| 364 | − | |
| 365 | − true -> | |
| 366 | − {0, :unknown} | |
| 367 | − end | |
| 360 | + {watched, source} = watched_count(watched, projects, namespaces, available_projects) | |
| 368 | 361 | |
| 369 | 362 | cycles_per_hour = div(3600, max(poll_seconds, 1)) |
| 370 | 363 | # 1 jobs poll per project per cycle |
@@ -507,12 +500,7 @@ defmodule RoomSanctumWeb.SourceLive.FormComponent do
| 507 | 500 | |> then(fn s -> if is_binary(s), do: String.to_integer(s), else: s end) |
| 508 | 501 | |> seconds_to_idx() |
| 509 | 502 | |
| 510 | − poll_secs = | |
| 511 | − case get_in(source_params, ["config", "poll_seconds"]) do | |
| 512 | − s when is_integer(s) -> s | |
| 513 | − s when is_binary(s) -> String.to_integer(s) | |
| 514 | − _ -> nil | |
| 515 | − end | |
| 503 | + poll_secs = poll_secs(get_in(source_params, ["config", "poll_seconds"])) | |
| 516 | 504 | |
| 517 | 505 | type = |
| 518 | 506 | source_params["type"] || |
@@ -526,31 +514,8 @@ defmodule RoomSanctumWeb.SourceLive.FormComponent do
| 526 | 514 | config_for_estimate = |
| 527 | 515 | Ecto.Changeset.get_field(changeset, :config) || socket.assigns.source.config || %{} |
| 528 | 516 | |
| 529 | − {gh_idx, gh_estimate} = | |
| 530 | − if type == "github" do | |
| 531 | − gh_secs = poll_secs || 60 | |
| 532 | − | |
| 533 | − {github_secs_to_idx(gh_secs), | |
| 534 | − github_estimate(config_for_estimate, socket.assigns[:gh_available_repos] || [], gh_secs)} | |
| 535 | − else | |
| 536 | − {socket.assigns[:github_poll_idx] || 2, | |
| 537 | − socket.assigns[:github_estimate] || %{watched: 0, calls: 0, source: :unknown, cycles: 0}} | |
| 538 | − end | |
| 539 | − | |
| 540 | − {gl_idx, gl_estimate} = | |
| 541 | − if type == "gitlab" do | |
| 542 | − gl_secs = poll_secs || 10 | |
| 543 | − | |
| 544 | − {gitlab_secs_to_idx(gl_secs), | |
| 545 | − gitlab_estimate( | |
| 546 | − config_for_estimate, | |
| 547 | − socket.assigns[:gl_available_projects] || [], | |
| 548 | − gl_secs | |
| 549 | − )} | |
| 550 | − else | |
| 551 | − {socket.assigns[:gitlab_poll_idx] || 1, | |
| 552 | − socket.assigns[:gitlab_estimate] || %{watched: 0, calls: 0, source: :unknown, cycles: 0}} | |
| 553 | − end | |
| 517 | + {gh_idx, gh_estimate} = github_poll(type, poll_secs, config_for_estimate, socket.assigns) | |
| 518 | + {gl_idx, gl_estimate} = gitlab_poll(type, poll_secs, config_for_estimate, socket.assigns) | |
| 554 | 519 | |
| 555 | 520 | # Read back from the converted params rather than the changeset: a slider |
| 556 | 521 | # the user has just dragged is in the params, and its label has to move with |
@@ -559,20 +524,7 @@ defmodule RoomSanctumWeb.SourceLive.FormComponent do
| 559 | 524 | |
| 560 | 525 | rt_idxs = |
| 561 | 526 | Map.new(rt_groups, fn {_url, kinds} -> |
| 562 | − # From the converted params, not the changeset: a slider just dragged is | |
| 563 | − # in the params, and its label has to move with it. | |
| 564 | − seconds = | |
| 565 | − kinds | |
| 566 | − |> Enum.map(&get_in(source_params, ["config", "rt_period_#{&1}"])) | |
| 567 | − |> Enum.reject(&is_nil/1) | |
| 568 | − | |
| 569 | − idx = | |
| 570 | − case seconds do | |
| 571 | − [] -> rt_group_idx(config_for_estimate, kinds) | |
| 572 | − values -> values |> Enum.map(&to_seconds/1) |> Enum.min() |> rt_secs_to_idx() | |
| 573 | − end | |
| 574 | − | |
| 575 | − {kinds, idx} | |
| 527 | + {kinds, rt_idx(kinds, source_params, config_for_estimate)} | |
| 576 | 528 | end) |
| 577 | 529 | |
| 578 | 530 | {:noreply, |
@@ -600,6 +552,50 @@ defmodule RoomSanctumWeb.SourceLive.FormComponent do
| 600 | 552 | save_source(socket, socket.assigns.action, source_params) |
| 601 | 553 | end |
| 602 | 554 | |
| 555 | + defp poll_secs(s) when is_integer(s), do: s | |
| 556 | + defp poll_secs(s) when is_binary(s), do: String.to_integer(s) | |
| 557 | + defp poll_secs(_), do: nil | |
| 558 | + | |
| 559 | + # The slider position and call estimate for a GitHub source; any other type | |
| 560 | + # keeps what it had. | |
| 561 | + defp github_poll("github", poll_secs, config, assigns) do | |
| 562 | + gh_secs = poll_secs || 60 | |
| 563 | + | |
| 564 | + {github_secs_to_idx(gh_secs), | |
| 565 | + github_estimate(config, assigns[:gh_available_repos] || [], gh_secs)} | |
| 566 | + end | |
| 567 | + | |
| 568 | + defp github_poll(_type, _poll_secs, _config, assigns) do | |
| 569 | + {assigns[:github_poll_idx] || 2, | |
| 570 | + assigns[:github_estimate] || %{watched: 0, calls: 0, source: :unknown, cycles: 0}} | |
| 571 | + end | |
| 572 | + | |
| 573 | + defp gitlab_poll("gitlab", poll_secs, config, assigns) do | |
| 574 | + gl_secs = poll_secs || 10 | |
| 575 | + | |
| 576 | + {gitlab_secs_to_idx(gl_secs), | |
| 577 | + gitlab_estimate(config, assigns[:gl_available_projects] || [], gl_secs)} | |
| 578 | + end | |
| 579 | + | |
| 580 | + defp gitlab_poll(_type, _poll_secs, _config, assigns) do | |
| 581 | + {assigns[:gitlab_poll_idx] || 1, | |
| 582 | + assigns[:gitlab_estimate] || %{watched: 0, calls: 0, source: :unknown, cycles: 0}} | |
| 583 | + end | |
| 584 | + | |
| 585 | + # From the converted params, not the changeset: a slider just dragged is | |
| 586 | + # in the params, and its label has to move with it. | |
| 587 | + defp rt_idx(kinds, source_params, config) do | |
| 588 | + seconds = | |
| 589 | + kinds | |
| 590 | + |> Enum.map(&get_in(source_params, ["config", "rt_period_#{&1}"])) | |
| 591 | + |> Enum.reject(&is_nil/1) | |
| 592 | + | |
| 593 | + case seconds do | |
| 594 | + [] -> rt_group_idx(config, kinds) | |
| 595 | + values -> values |> Enum.map(&to_seconds/1) |> Enum.min() |> rt_secs_to_idx() | |
| 596 | + end | |
| 597 | + end | |
| 598 | + | |
| 603 | 599 | defp save_source(socket, :edit, source_params) do |
| 604 | 600 | case Configuration.update_source(socket.assigns.source, source_params) do |
| 605 | 601 | {:ok, source} -> |
modified
apps/room_sanctum/lib/room_sanctum_web/live/live_preview.ex
+20
−34
@@ -5,20 +5,19 @@ defmodule RoomSanctumWeb.LivePreview do
| 5 | 5 | use PhoenixHTMLHelpers |
| 6 | 6 | import Phoenix.LiveView.Helpers |
| 7 | 7 | |
| 8 | − defp gtfs_icon(route_str) do | |
| 9 | − case route_str do | |
| 10 | − "LightRail" -> "fa-train-tram" | |
| 11 | − "Subway" -> "fa-train-subway" | |
| 12 | − "Rail" -> "fa-train" | |
| 13 | − "Bus" -> "fa-bus" | |
| 14 | − "Ferry" -> "fa-ferry" | |
| 15 | − "CableCar" -> "fa-cable-car" | |
| 16 | − "Funicular" -> "fa-mountain" | |
| 17 | − "Trolleybus" -> "fa-bus-simple" | |
| 18 | − "Monorail" -> "fa-magic" | |
| 19 | − _otherwise -> "fa-train-tram" | |
| 20 | − end | |
| 21 | − end | |
| 8 | + @gtfs_icons %{ | |
| 9 | + "LightRail" => "fa-train-tram", | |
| 10 | + "Subway" => "fa-train-subway", | |
| 11 | + "Rail" => "fa-train", | |
| 12 | + "Bus" => "fa-bus", | |
| 13 | + "Ferry" => "fa-ferry", | |
| 14 | + "CableCar" => "fa-cable-car", | |
| 15 | + "Funicular" => "fa-mountain", | |
| 16 | + "Trolleybus" => "fa-bus-simple", | |
| 17 | + "Monorail" => "fa-magic" | |
| 18 | + } | |
| 19 | + | |
| 20 | + defp gtfs_icon(route_str), do: Map.get(@gtfs_icons, route_str, "fa-train-tram") | |
| 22 | 21 | |
| 23 | 22 | defp weather_icon(status) do |
| 24 | 23 | case status do |
@@ -68,30 +67,17 @@ defmodule RoomSanctumWeb.LivePreview do
| 68 | 67 | def tsls(%Time{} = t), do: tsl(t |> Time.to_string()) |
| 69 | 68 | def tsls(st), do: st |> String.slice(0, 8) |
| 70 | 69 | |
| 70 | + # Sixteen points, with N again at the end for the bearings that round up to 360. | |
| 71 | + @compass {"N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", | |
| 72 | + "NW", "NNW", "N"} | |
| 73 | + | |
| 71 | 74 | defp to_l(direction) do |
| 72 | 75 | idx = (direction / 22.5) |> Kernel.round() |
| 73 | − | |
| 74 | − case idx do | |
| 75 | − 0 -> "N" | |
| 76 | − 1 -> "NNE" | |
| 77 | − 2 -> "NE" | |
| 78 | − 3 -> "ENE" | |
| 79 | − 4 -> "E" | |
| 80 | − 5 -> "ESE" | |
| 81 | − 6 -> "SE" | |
| 82 | − 7 -> "SSE" | |
| 83 | − 8 -> "S" | |
| 84 | − 9 -> "SSW" | |
| 85 | − 10 -> "SW" | |
| 86 | − 11 -> "WSW" | |
| 87 | − 12 -> "W" | |
| 88 | − 13 -> "WNW" | |
| 89 | − 14 -> "NW" | |
| 90 | − 15 -> "NNW" | |
| 91 | − 16 -> "N" | |
| 92 | − end | |
| 76 | + compass_point(idx) | |
| 93 | 77 | end |
| 94 | 78 | |
| 79 | + defp compass_point(idx) when idx in 0..16, do: elem(@compass, idx) | |
| 80 | + | |
| 95 | 81 | @doc """ |
| 96 | 82 | A route drawn as the agency draws it. |
| 97 | 83 |
Parents: ca307c7