Fix Credo issues and telemetry Decimal coercion

- Extract prepare_markers_for_push/2 to reduce nesting depth in
  handle_info
- Split detect_item_or_object into smaller predicate and application
  functions to reduce cyclomatic complexity
- Coerce Decimal/nil values to native numbers in telemetry metrics
  (PostgreSQL sum() returns numeric type mapped to Decimal by Postgrex)
- Align locate button horizontally with zoom controls
This commit is contained in:
Graham McIntire 2026-02-20 09:55:51 -06:00
parent 8dbf8d3c20
commit d09e015857
No known key found for this signature in database
3 changed files with 72 additions and 55 deletions

View file

@ -479,36 +479,44 @@ defmodule Aprsme.PacketConsumer do
# Detect if packet is an item or object and set appropriate fields
defp detect_item_or_object(attrs) do
cond do
# Check for object data type or information field starting with ;
attrs[:data_type] == "object" or attrs["data_type"] == "object" or
(is_binary(attrs[:information_field]) and
String.starts_with?(attrs[:information_field], ";")) ->
# Try data_extended first, then fall back to parsing information_field
object_name =
extract_object_name(attrs) ||
extract_object_name_from_info_field(attrs[:information_field])
attrs
|> Map.put(:object_name, object_name)
|> Map.put(:is_object, true)
|> Map.delete(:itemname)
|> Map.delete("itemname")
# Check for itemname field from parser
Map.has_key?(attrs, :itemname) or Map.has_key?(attrs, "itemname") ->
item_name = Map.get(attrs, :itemname) || Map.get(attrs, "itemname")
attrs
|> Map.put(:item_name, item_name)
|> Map.put(:is_item, true)
|> Map.delete(:itemname)
|> Map.delete("itemname")
true ->
attrs
object_packet?(attrs) -> apply_object_fields(attrs)
item_packet?(attrs) -> apply_item_fields(attrs)
true -> attrs
end
end
defp object_packet?(attrs) do
attrs[:data_type] == "object" or attrs["data_type"] == "object" or
(is_binary(attrs[:information_field]) and
String.starts_with?(attrs[:information_field], ";"))
end
defp item_packet?(attrs) do
Map.has_key?(attrs, :itemname) or Map.has_key?(attrs, "itemname")
end
defp apply_object_fields(attrs) do
object_name =
extract_object_name(attrs) ||
extract_object_name_from_info_field(attrs[:information_field])
attrs
|> Map.put(:object_name, object_name)
|> Map.put(:is_object, true)
|> Map.delete(:itemname)
|> Map.delete("itemname")
end
defp apply_item_fields(attrs) do
item_name = Map.get(attrs, :itemname) || Map.get(attrs, "itemname")
attrs
|> Map.put(:item_name, item_name)
|> Map.put(:is_item, true)
|> Map.delete(:itemname)
|> Map.delete("itemname")
end
defp extract_object_name(attrs) do
case attrs[:data_extended] do
%{name: name} when is_binary(name) -> name

View file

@ -130,7 +130,7 @@ defmodule Aprsme.Telemetry.DatabaseMetrics do
defp collect_database_size do
case Aprsme.Repo.query("SELECT pg_database_size(current_database()) as size") do
{:ok, %{rows: [[size]]}} ->
:telemetry.execute([:aprsme, :postgres, :database], %{size_bytes: size}, %{})
:telemetry.execute([:aprsme, :postgres, :database], %{size_bytes: to_number(size)}, %{})
_ ->
:ok
@ -152,11 +152,11 @@ defmodule Aprsme.Telemetry.DatabaseMetrics do
:telemetry.execute(
[:aprsme, :postgres, :connections],
%{
total: total || 0,
active: active || 0,
idle: idle || 0,
idle_in_transaction: idle_in_tx || 0,
waiting: waiting || 0
total: to_number(total),
active: to_number(active),
idle: to_number(idle),
idle_in_transaction: to_number(idle_in_tx),
waiting: to_number(waiting)
},
%{}
)
@ -202,10 +202,10 @@ defmodule Aprsme.Telemetry.DatabaseMetrics do
:telemetry.execute(
[:aprsme, :postgres, :query_stats],
%{
total_calls: trunc(calls || 0),
total_time_ms: total_time || 0,
avg_time_ms: avg_time || 0,
max_time_ms: max_time || 0
total_calls: to_number(calls, :integer),
total_time_ms: to_number(total_time),
avg_time_ms: to_number(avg_time),
max_time_ms: to_number(max_time)
},
%{}
)
@ -239,15 +239,26 @@ defmodule Aprsme.Telemetry.DatabaseMetrics do
:telemetry.execute(
[:aprsme, :postgres, :packets_table],
%{
live_tuples: live || 0,
dead_tuples: dead || 0,
total_inserts: ins || 0,
total_updates: upd || 0,
total_deletes: del || 0,
table_size_bytes: table_size || 0,
indexes_size_bytes: idx_size || 0
live_tuples: to_number(live),
dead_tuples: to_number(dead),
total_inserts: to_number(ins),
total_updates: to_number(upd),
total_deletes: to_number(del),
table_size_bytes: to_number(table_size),
indexes_size_bytes: to_number(idx_size)
},
%{}
)
end
# Coerce Decimal/nil values to native Erlang numbers for :telemetry.execute
defp to_number(nil), do: 0
defp to_number(%Decimal{} = d), do: Decimal.to_float(d)
defp to_number(n) when is_number(n), do: n
defp to_number(_), do: 0
defp to_number(nil, :integer), do: 0
defp to_number(%Decimal{} = d, :integer), do: d |> Decimal.to_float() |> trunc()
defp to_number(n, :integer) when is_number(n), do: trunc(n)
defp to_number(_, :integer), do: 0
end

View file

@ -885,16 +885,7 @@ defmodule AprsmeWeb.MapLive.Index do
|> Enum.filter(& &1)
|> Enum.uniq()
markers =
Enum.map(reversed, fn data ->
data = Map.delete(data, "convert_from")
if socket.assigns.station_popup_open do
Map.put(data, :openPopup, false)
else
data
end
end)
markers = prepare_markers_for_push(reversed, socket.assigns.station_popup_open)
push_event(socket, "new_packets", %{
packets: markers,
@ -1013,6 +1004,13 @@ defmodule AprsmeWeb.MapLive.Index do
# Private handler functions for each message type
defp prepare_markers_for_push(markers, popup_open?) do
Enum.map(markers, fn data ->
data = Map.delete(data, "convert_from")
if popup_open?, do: Map.put(data, :openPopup, false), else: data
end)
end
defp handle_info_process_bounds_update(map_bounds, socket) do
require Logger
@ -1151,7 +1149,7 @@ defmodule AprsmeWeb.MapLive.Index do
<style>
.locate-button {
position: absolute;
left: 10px;
left: 12px;
top: 100px;
z-index: 1000;
background: white;