Merge branch 'areas-meshguide-sync'

This commit is contained in:
dborup
2026-07-28 13:45:47 +02:00
5 changed files with 90 additions and 18 deletions
+1 -1
View File
@@ -447,7 +447,7 @@ func componentSchemas() map[string]interface{} {
"mostEfficientPing": schemaRef("PingScore"),
"relayLeaderboard": map[string]interface{}{"type": "array", "items": schemaRef("PingLeaderboardEntry"), "description": "Top nodes ranked by number of distinct pings they appeared as a relay hop in (deduped per ping first, so one busy ping's many branches can't over-credit a relay)."},
"observerLeaderboard": map[string]interface{}{"type": "array", "items": schemaRef("PingLeaderboardEntry"), "description": "Top observers ranked by number of pings they were the first station to hear."},
"senderLeaderboard": map[string]interface{}{"type": "array", "items": schemaRef("PingLeaderboardEntry"), "description": "Top senders ranked by number of pings sent. Keyed by the sender display name from the channel message itself -- no resolved pubkey, so entries never carry one."},
"senderLeaderboard": map[string]interface{}{"type": "array", "items": schemaRef("PingLeaderboardEntry"), "description": "Top senders ranked by number of pings sent in the last 30 days (unlike the other leaderboards and records, which are all-time). Keyed by the sender display name from the channel message itself -- no resolved pubkey, so entries never carry one."},
},
},
"AreaDensity": map[string]interface{}{
+16 -5
View File
@@ -225,6 +225,15 @@ func (s *Server) computeAllPingScores() *PingScoresSnapshot {
}
relayCounts := map[string]*PingLeaderboardEntry{}
observerCounts := map[string]*PingLeaderboardEntry{}
// SenderLeaderboard is deliberately a rolling 30-day window, NOT
// all-time like the records and the other two leaderboards (dborup,
// after seeing it first ship all-time) -- "who pings the most" is
// more interesting as an ongoing/current thing than a fixed
// leaderboard someone can win once and hold forever. An unparseable
// timestamp is treated as too old (excluded), same fail-toward-stale
// convention as the active/degraded/silent health classification
// elsewhere in this file's package.
senderCutoff := time.Now().AddDate(0, 0, -30)
senderCounts := map[string]*PingLeaderboardEntry{}
for _, trigger := range triggers {
@@ -271,12 +280,14 @@ func (s *Server) computeAllPingScores() *PingScoresSnapshot {
e.Count++
}
if score.Sender != "" {
e := senderCounts[score.Sender]
if e == nil {
e = &PingLeaderboardEntry{Name: score.Sender}
senderCounts[score.Sender] = e
if ts, err := time.Parse(time.RFC3339, score.Timestamp); err == nil && ts.After(senderCutoff) {
e := senderCounts[score.Sender]
if e == nil {
e = &PingLeaderboardEntry{Name: score.Sender}
senderCounts[score.Sender] = e
}
e.Count++
}
e.Count++
}
}
+71 -10
View File
@@ -201,15 +201,19 @@ func TestComputeAllPingScores_Leaderboards(t *testing.T) {
srv, _ := setupPingScoresFixture(t)
// Ping 1: pkrelay1 appears in BOTH branches -- must count once for this ping.
tx1 := seedPingTrigger(t, srv, "lbtest0000001", "#test", "Alice", "2026-01-15T10:00:00Z")
seedPingObservation(t, srv, tx1, "pingobsa", 9.0, `[]`, `[]`, 1736935200)
seedPingObservation(t, srv, tx1, "pingobsb", 6.0, `["aa"]`, `["pkrelay1"]`, 1736935210)
seedPingObservation(t, srv, tx1, "pingobsc", 4.0, `["aa"]`, `["pkrelay1"]`, 1736935220)
// Uses a recent (now-relative) timestamp since SenderLeaderboard is a
// rolling 30-day window -- see TestComputeAllPingScores_SenderLeaderboard30DayCutoff.
ts1 := time.Now().Add(-1 * time.Hour)
tx1 := seedPingTrigger(t, srv, "lbtest0000001", "#test", "Alice", ts1.Format(time.RFC3339))
seedPingObservation(t, srv, tx1, "pingobsa", 9.0, `[]`, `[]`, ts1.Unix())
seedPingObservation(t, srv, tx1, "pingobsb", 6.0, `["aa"]`, `["pkrelay1"]`, ts1.Unix()+10)
seedPingObservation(t, srv, tx1, "pingobsc", 4.0, `["aa"]`, `["pkrelay1"]`, ts1.Unix()+20)
// Ping 2: pkrelay1 appears again -- second distinct ping, should now be 2.
tx2 := seedPingTrigger(t, srv, "lbtest0000002", "#test", "Bob", "2026-01-15T11:00:00Z")
seedPingObservation(t, srv, tx2, "pingobsa", 9.0, `[]`, `[]`, 1736938800)
seedPingObservation(t, srv, tx2, "pingobsb", 6.0, `["aa"]`, `["pkrelay1"]`, 1736938810)
ts2 := time.Now().Add(-30 * time.Minute)
tx2 := seedPingTrigger(t, srv, "lbtest0000002", "#test", "Bob", ts2.Format(time.RFC3339))
seedPingObservation(t, srv, tx2, "pingobsa", 9.0, `[]`, `[]`, ts2.Unix())
seedPingObservation(t, srv, tx2, "pingobsb", 6.0, `["aa"]`, `["pkrelay1"]`, ts2.Unix()+10)
snap := srv.computeAllPingScores()
if snap == nil {
@@ -258,6 +262,62 @@ func TestComputeAllPingScores_Leaderboards(t *testing.T) {
}
}
// TestComputeAllPingScores_SenderLeaderboard30DayCutoff confirms
// SenderLeaderboard only counts pings from the last 30 days, while the
// other leaderboards and the 5 all-time records stay unaffected by a ping's
// age.
func TestComputeAllPingScores_SenderLeaderboard30DayCutoff(t *testing.T) {
srv, _ := setupPingScoresFixture(t)
// Old: 40 days ago -- outside the 30-day window, must NOT appear in
// SenderLeaderboard, but must still count in RelayLeaderboard.
oldTs := time.Now().AddDate(0, 0, -40)
txOld := seedPingTrigger(t, srv, "lbold0000001", "#test", "OldSender", oldTs.Format(time.RFC3339))
seedPingObservation(t, srv, txOld, "pingobsa", 9.0, `[]`, `[]`, oldTs.Unix())
seedPingObservation(t, srv, txOld, "pingobsb", 6.0, `["aa"]`, `["pkrelay1"]`, oldTs.Unix()+10)
// Recent: 5 days ago -- inside the window, must appear in SenderLeaderboard.
recentTs := time.Now().AddDate(0, 0, -5)
txRecent := seedPingTrigger(t, srv, "lbnew0000001", "#test", "RecentSender", recentTs.Format(time.RFC3339))
seedPingObservation(t, srv, txRecent, "pingobsa", 9.0, `[]`, `[]`, recentTs.Unix())
seedPingObservation(t, srv, txRecent, "pingobsb", 6.0, `["aa"]`, `["pkrelay1"]`, recentTs.Unix()+10)
snap := srv.computeAllPingScores()
if snap == nil {
t.Fatal("computeAllPingScores returned nil")
}
for _, e := range snap.SenderLeaderboard {
if e.Name == "OldSender" {
t.Errorf("OldSender (40 days old) must be excluded from SenderLeaderboard, got entry: %+v", e)
}
}
var foundRecent bool
for _, e := range snap.SenderLeaderboard {
if e.Name == "RecentSender" {
foundRecent = true
if e.Count != 1 {
t.Errorf("RecentSender count = %d, want 1", e.Count)
}
}
}
if !foundRecent {
t.Errorf("RecentSender (5 days old) missing from SenderLeaderboard: %+v", snap.SenderLeaderboard)
}
// The 40-day-old ping's relay must still count -- only the sender
// leaderboard is time-windowed, not the underlying data or other boards.
var relay1Count int
for _, e := range snap.RelayLeaderboard {
if e.Pubkey == "pkrelay1" {
relay1Count = e.Count
}
}
if relay1Count != 2 {
t.Errorf("pkrelay1 relay count = %d, want 2 (both old and recent pings count toward RelayLeaderboard)", relay1Count)
}
}
// TestHandlePingScores_EmptyState confirms the endpoint returns a
// well-formed 200 with zero-valued/omitted fields rather than an error
// when no ping has ever been recorded -- an ordinary state, not a failure.
@@ -309,9 +369,10 @@ func TestHandlePingScores_NilCache(t *testing.T) {
// (nested PingScore objects, leaderboards present).
func TestHandlePingScores_Populated(t *testing.T) {
srv, router := setupPingScoresFixture(t)
txID := seedPingTrigger(t, srv, "handlerpop00001", "#test", "Alice", "2026-01-15T10:00:00Z")
seedPingObservation(t, srv, txID, "pingobsa", 9.0, `[]`, `[]`, 1736935200)
seedPingObservation(t, srv, txID, "pingobsb", 6.0, `["aa"]`, `["pkrelay1"]`, 1736935210)
ts := time.Now().Add(-1 * time.Hour)
txID := seedPingTrigger(t, srv, "handlerpop00001", "#test", "Alice", ts.Format(time.RFC3339))
seedPingObservation(t, srv, txID, "pingobsa", 9.0, `[]`, `[]`, ts.Unix())
seedPingObservation(t, srv, txID, "pingobsb", 6.0, `["aa"]`, `["pkrelay1"]`, ts.Unix()+10)
srv.pingScores.Store(srv.computeAllPingScores())
+1 -1
View File
@@ -125,7 +125,7 @@
'<p class="text-muted">Global records and leaderboards from every "ping" sent in any channel (' + data.totalPings + ' total). Not scoped by region. Updated ' + escapeHtml(formatAgo(data.generatedAt)) + '.</p>' +
'<div class="stats-grid ps-records-grid">' + recordsHtml + '</div>' +
'<div class="ps-leaderboards-grid">' +
leaderboardTableHtml('Top Senders', phIcon('megaphone'), data.senderLeaderboard, 'Sender') +
leaderboardTableHtml('Top Senders (30 days)', phIcon('megaphone'), data.senderLeaderboard, 'Sender') +
leaderboardTableHtml('Top Relays', phIcon('repeat'), data.relayLeaderboard) +
leaderboardTableHtml('Top First-Hearers', phIcon('eye'), data.observerLeaderboard) +
'</div>' +
+1 -1
View File
@@ -136,7 +136,7 @@ function makeSandbox(apiImpl) {
assert.ok(container.innerHTML.includes('RelayOne'), 'should show the relay leaderboard entry, got: ' + container.innerHTML);
assert.ok(container.innerHTML.includes('ObsOne'), 'should show the observer leaderboard entry, got: ' + container.innerHTML);
assert.ok(container.innerHTML.includes('PingMaster'), 'should show the sender leaderboard entry, got: ' + container.innerHTML);
assert.ok(container.innerHTML.includes('Top Senders'), 'should show the Top Senders leaderboard heading, got: ' + container.innerHTML);
assert.ok(container.innerHTML.includes('Top Senders (30 days)'), 'should show the Top Senders leaderboard heading with the 30-day window noted, got: ' + container.innerHTML);
passed++;
console.log(' ✅ renders all 5 records and all three leaderboards (including Top Senders) from a populated response');
} catch (e) { failed++; console.log(' ❌ renders all 5 records and all three leaderboards (including Top Senders) from a populated response: ' + e.message); }