From d09e0158572dc75c3c475473ec70c3b0f874e8f0 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Fri, 20 Feb 2026 09:55:51 -0600 Subject: [PATCH] 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 --- lib/aprsme/packet_consumer.ex | 62 +++++++++++++----------- lib/aprsme/telemetry/database_metrics.ex | 45 ++++++++++------- lib/aprsme_web/live/map_live/index.ex | 20 ++++---- 3 files changed, 72 insertions(+), 55 deletions(-) diff --git a/lib/aprsme/packet_consumer.ex b/lib/aprsme/packet_consumer.ex index b24a789..1b21099 100644 --- a/lib/aprsme/packet_consumer.ex +++ b/lib/aprsme/packet_consumer.ex @@ -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 diff --git a/lib/aprsme/telemetry/database_metrics.ex b/lib/aprsme/telemetry/database_metrics.ex index b712c75..18365d1 100644 --- a/lib/aprsme/telemetry/database_metrics.ex +++ b/lib/aprsme/telemetry/database_metrics.ex @@ -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 diff --git a/lib/aprsme_web/live/map_live/index.ex b/lib/aprsme_web/live/map_live/index.ex index c30936f..c4a5da5 100644 --- a/lib/aprsme_web/live/map_live/index.ex +++ b/lib/aprsme_web/live/map_live/index.ex @@ -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