fix: Show all packets for tracked callsigns including non-position data
- Modified get_recent_packets to not filter by has_position when tracking a specific callsign - Ensures users can see status updates, messages, and other non-position packets - Fixes issue where callsigns without recent position data wouldn't show any packets - Updated documentation to reflect this behavior 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a0258c0ff0
commit
047cd3a7ce
6 changed files with 238 additions and 8 deletions
|
|
@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- BRIN indexes consideration for time-series data
|
||||
- Connection draining and load balancing for clustered deployments
|
||||
- Monitors CPU usage and connection counts across cluster nodes
|
||||
- Documentation for tracked callsign behavior confirming last packet is always shown regardless of age
|
||||
- Automatically drains connections when node is overloaded (>70% CPU or 2x average connections)
|
||||
- Gracefully reconnects clients to less loaded nodes
|
||||
- Improves overall cluster stability and performance
|
||||
|
|
@ -27,6 +28,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Weather queries now use indexed `has_weather` column
|
||||
- Spatial queries optimized with geography cast index
|
||||
|
||||
### Fixed
|
||||
- Tracked callsigns now show all packets including those without position data
|
||||
- Previously filtered out packets with `has_position == false`
|
||||
- Now shows all packets when tracking a specific callsign via /:callsign URL
|
||||
- Ensures users can see status updates, messages, and other non-position packets
|
||||
|
||||
### Fixed
|
||||
- PostgreSQL notify trigger now sends all required fields for info page updates
|
||||
- Info page real-time updates now display all packet details correctly
|
||||
|
|
|
|||
|
|
@ -48,6 +48,16 @@ This document tracks potential improvements identified during the multi-replica
|
|||
- Created `Aprsme.ShutdownHandler` with configurable drain timeout
|
||||
- Created `Aprsme.SignalHandler` for proper SIGTERM handling
|
||||
- **Silent shutdowns**: Removed all user notifications during graceful shutdown
|
||||
|
||||
### ✅ Tracked Callsign Always Shows Last Packet (2025-07-30)
|
||||
- **Status**: Fixed - Now Shows All Packets Including Non-Position
|
||||
- **Impact**: High - User experience for tracking specific stations
|
||||
- **Implementation**:
|
||||
- Fixed `get_recent_packets` to not filter by `has_position` when tracking callsign
|
||||
- Confirmed `get_latest_packet_for_callsign` fetches without time restrictions
|
||||
- Verified `filter_packets_by_time_and_bounds_with_tracked` always includes tracked packet
|
||||
- Added documentation explaining the behavior
|
||||
- Users can now see status updates, messages, and other non-position packets
|
||||
- Updated health endpoint to return 503 when draining (15s delay)
|
||||
- Added preStop lifecycle hook with 15s sleep
|
||||
- Set terminationGracePeriodSeconds to 60 seconds
|
||||
|
|
|
|||
81
docs/tracked-callsign-behavior.md
Normal file
81
docs/tracked-callsign-behavior.md
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# Tracked Callsign Behavior Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
When viewing a specific callsign at the `/:callsign` route (e.g., `/KE0GB`), the APRS.me application displays the last received packet from that callsign **regardless of age**.
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### 1. Route Handling
|
||||
- Route defined in `router.ex`: `live "/:callsign", MapLive.Index, :index`
|
||||
- The callsign is extracted from the URL and set as `tracked_callsign` in the LiveView socket
|
||||
|
||||
### 2. Latest Packet Retrieval
|
||||
- `Packets.get_latest_packet_for_callsign/1` fetches the most recent packet for a callsign
|
||||
- Uses `QueryBuilder.callsign_history/2` with only a limit parameter (no time filtering)
|
||||
- **No time restrictions** are applied when fetching the latest packet
|
||||
|
||||
### 3. Map Centering
|
||||
- If the tracked callsign has a known position, the map centers on that location at zoom level 12
|
||||
- Handled by `Navigation.handle_callsign_tracking/4` in the MapLive.Navigation module
|
||||
|
||||
### 4. Packet Display Logic
|
||||
The key function ensuring old packets are always displayed is `filter_packets_by_time_and_bounds_with_tracked/5` in MapLive.Index:
|
||||
|
||||
```elixir
|
||||
defp filter_packets_by_time_and_bounds_with_tracked(
|
||||
packets,
|
||||
bounds,
|
||||
time_threshold,
|
||||
tracked_callsign,
|
||||
tracked_latest_packet
|
||||
) do
|
||||
filtered = filter_packets_by_time_and_bounds(packets, bounds, time_threshold)
|
||||
|
||||
# Always include the tracked callsign's latest packet if we have one
|
||||
if tracked_callsign != "" and tracked_latest_packet do
|
||||
key = get_callsign_key(tracked_latest_packet)
|
||||
Map.put(filtered, key, tracked_latest_packet)
|
||||
else
|
||||
filtered
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
This function:
|
||||
1. First filters packets by time threshold and map bounds
|
||||
2. Then **always adds** the tracked callsign's latest packet to the filtered results
|
||||
3. This ensures the tracked callsign is visible even if their last position is older than configured time windows
|
||||
|
||||
### 5. Trail Duration Independence
|
||||
- Even with the shortest trail duration (1 hour), the tracked callsign's latest packet is displayed
|
||||
- The trail duration setting only affects which other packets are shown, not the tracked callsign
|
||||
|
||||
## User Experience
|
||||
|
||||
When a user navigates to `/:callsign`:
|
||||
1. The callsign appears in the search box
|
||||
2. A "Tracking: CALLSIGN" indicator shows below the search
|
||||
3. The map centers on the callsign's last known position (if available)
|
||||
4. The callsign's latest packet is displayed on the map, no matter how old
|
||||
5. Only packets from that callsign are shown (real-time filtering applies)
|
||||
|
||||
## Summary
|
||||
|
||||
The system is correctly implemented to **always show the last received packet** from a tracked callsign, regardless of:
|
||||
- How old the packet is (could be days, weeks, or months old)
|
||||
- The selected trail duration setting
|
||||
- The historical data load setting
|
||||
- Whether the packet is within the current map bounds
|
||||
- **Whether the packet has position data or not** (as of 2025-07-30)
|
||||
|
||||
This ensures users can always find and view the last activity of any callsign they're tracking, including:
|
||||
- Position packets with lat/lon coordinates
|
||||
- Status updates without position
|
||||
- Messages and telemetry data
|
||||
- Weather reports
|
||||
- Any other APRS packet types
|
||||
|
||||
## Fix Applied (2025-07-30)
|
||||
|
||||
The `get_recent_packets` function was modified to not filter by `has_position == true` when a specific callsign is being tracked. This ensures that all packet types are visible when viewing a callsign's activity, not just those with position data.
|
||||
|
|
@ -56,8 +56,6 @@ defmodule Aprsme.EncodingUtils do
|
|||
def to_float(value) when is_integer(value) do
|
||||
if value >= -9.0e15 and value <= 9.0e15 do
|
||||
value * 1.0
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -77,7 +75,6 @@ defmodule Aprsme.EncodingUtils do
|
|||
|
||||
def to_float(_), do: nil
|
||||
|
||||
|
||||
@doc """
|
||||
Converts various types to Decimal for database storage.
|
||||
|
||||
|
|
|
|||
|
|
@ -448,12 +448,21 @@ defmodule Aprsme.Packets do
|
|||
limit = Map.get(opts, :limit, 200)
|
||||
offset = Map.get(opts, :offset, 0)
|
||||
|
||||
# Build base query with time and position filters
|
||||
# Build base query with time filter
|
||||
# Only filter by position if not tracking a specific callsign
|
||||
base_query =
|
||||
from(p in Packet,
|
||||
where: p.has_position == true,
|
||||
where: p.received_at >= ^time_ago
|
||||
)
|
||||
if Map.has_key?(opts, :callsign) and opts[:callsign] != nil and opts[:callsign] != "" do
|
||||
# When tracking a callsign, show all their packets regardless of position
|
||||
from(p in Packet,
|
||||
where: p.received_at >= ^time_ago
|
||||
)
|
||||
else
|
||||
# For general map view, only show packets with positions
|
||||
from(p in Packet,
|
||||
where: p.has_position == true,
|
||||
where: p.received_at >= ^time_ago
|
||||
)
|
||||
end
|
||||
|
||||
# Apply spatial and other filters BEFORE limiting
|
||||
# This ensures we get the most recent packets within the specified bounds
|
||||
|
|
|
|||
|
|
@ -0,0 +1,126 @@
|
|||
defmodule AprsmeWeb.MapLive.TrackedCallsignOldPacketTest do
|
||||
use AprsmeWeb.ConnCase
|
||||
|
||||
import Aprsme.PacketsFixtures
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
describe "tracked callsign shows last packet regardless of age" do
|
||||
test "displays old packet for tracked callsign", %{conn: conn} do
|
||||
# Create a packet that's 30 days old
|
||||
thirty_days_ago = DateTime.add(DateTime.utc_now(), -30 * 24 * 60 * 60, :second)
|
||||
|
||||
old_packet =
|
||||
packet_fixture(%{
|
||||
sender: "TEST-OLD",
|
||||
lat: Decimal.new("35.1234"),
|
||||
lon: Decimal.new("-97.5678"),
|
||||
raw_packet: "TEST-OLD>APRS:!3507.40N/09734.07W>Test old packet",
|
||||
received_at: thirty_days_ago,
|
||||
has_position: true
|
||||
})
|
||||
|
||||
# Navigate to the tracked callsign URL
|
||||
{:ok, view, _html} = live(conn, "/TEST-OLD")
|
||||
|
||||
# Wait for map to be ready
|
||||
send(view.pid, %{
|
||||
event: "map_ready",
|
||||
data: %{
|
||||
center: %{lat: 35.0, lng: -97.0},
|
||||
zoom: 10,
|
||||
bounds: %{
|
||||
north: 36.0,
|
||||
south: 34.0,
|
||||
east: -96.0,
|
||||
west: -98.0
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
# Allow time for the view to process
|
||||
:timer.sleep(100)
|
||||
|
||||
# Check that the tracked callsign is set
|
||||
assert render(view) =~ "TEST-OLD"
|
||||
|
||||
# The packet should be in the view's assigns
|
||||
# Since filter_packets_by_time_and_bounds_with_tracked always includes
|
||||
# the tracked callsign's latest packet, it should be present
|
||||
state = :sys.get_state(view.pid)
|
||||
socket = elem(state, 1).socket
|
||||
|
||||
# Verify the tracked callsign is set
|
||||
assert socket.assigns.tracked_callsign == "TEST-OLD"
|
||||
|
||||
# Verify the tracked latest packet is the old packet
|
||||
assert socket.assigns.tracked_latest_packet.id == old_packet.id
|
||||
assert socket.assigns.tracked_latest_packet.sender == "TEST-OLD"
|
||||
|
||||
# Cleanup handled by test transaction
|
||||
end
|
||||
|
||||
test "includes old packet even with 1-hour trail duration", %{conn: conn} do
|
||||
# Create a packet that's 48 hours old
|
||||
two_days_ago = DateTime.add(DateTime.utc_now(), -48 * 60 * 60, :second)
|
||||
|
||||
old_packet =
|
||||
packet_fixture(%{
|
||||
sender: "OLD-STATION",
|
||||
lat: Decimal.new("40.7128"),
|
||||
lon: Decimal.new("-74.0060"),
|
||||
raw_packet: "OLD-STATION>APRS:!4042.77N/07400.36W>Old position",
|
||||
received_at: two_days_ago,
|
||||
has_position: true
|
||||
})
|
||||
|
||||
# Navigate to the tracked callsign URL
|
||||
{:ok, view, _html} = live(conn, "/OLD-STATION")
|
||||
|
||||
# Set trail duration to 1 hour (shortest option)
|
||||
send(view.pid, {:update_trail_duration, 1})
|
||||
|
||||
# Wait for map to be ready
|
||||
send(view.pid, %{
|
||||
event: "map_ready",
|
||||
data: %{
|
||||
center: %{lat: 40.7, lng: -74.0},
|
||||
zoom: 10,
|
||||
bounds: %{
|
||||
north: 41.0,
|
||||
south: 40.0,
|
||||
east: -73.0,
|
||||
west: -75.0
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
:timer.sleep(100)
|
||||
|
||||
# The old packet should still be tracked despite 1-hour trail setting
|
||||
state = :sys.get_state(view.pid)
|
||||
socket = elem(state, 1).socket
|
||||
|
||||
assert socket.assigns.tracked_callsign == "OLD-STATION"
|
||||
# 1 hour trail duration
|
||||
assert socket.assigns.packet_age_threshold == 1
|
||||
assert socket.assigns.tracked_latest_packet.id == old_packet.id
|
||||
|
||||
# Cleanup handled by test transaction
|
||||
end
|
||||
|
||||
test "shows no packet message when callsign has no packets", %{conn: conn} do
|
||||
# Navigate to a callsign that doesn't exist
|
||||
{:ok, view, _html} = live(conn, "/NOPACKETS-1")
|
||||
|
||||
# Check that the tracked callsign is set
|
||||
assert render(view) =~ "NOPACKETS-1"
|
||||
|
||||
# But there should be no packet data
|
||||
state = :sys.get_state(view.pid)
|
||||
socket = elem(state, 1).socket
|
||||
|
||||
assert socket.assigns.tracked_callsign == "NOPACKETS-1"
|
||||
assert socket.assigns.tracked_latest_packet == nil
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue