#142: fix stale filter count cutoff, tighten comments (review fixes)

This commit is contained in:
2026-08-21 19:48:24 +07:00
parent e8a3c5f826
commit 134c9307b1
3 changed files with 20 additions and 18 deletions
+1 -2
View File
@@ -19,8 +19,7 @@ type LaneReporter interface {
} }
// ownerWindow is the staleness boundary the Series list's "not checked in // ownerWindow is the staleness boundary the Series list's "not checked in
// 12h" filter compares against, and the same twelve hours the landing page's // 12h" filter compares against. Declared once; later admin tickets read it.
// figures share. Declared once; later admin tickets read it.
const ownerWindow = 12 * time.Hour const ownerWindow = 12 * time.Hour
// adminView is the shared shell data for an administrative page and the roster // adminView is the shared shell data for an administrative page and the roster
+10 -13
View File
@@ -160,7 +160,7 @@ func (h *Handler) seriesListView(r *http.Request) (seriesListView, error) {
for i, a := range data.Rows { for i, a := range data.Rows {
view.Rows = append(view.Rows, seriesRow(a, i, now)) view.Rows = append(view.Rows, seriesRow(a, i, now))
} }
view.Filters, err = h.seriesFilterOptions(filter) view.Filters, err = h.seriesFilterOptions(filter, sf.Cutoff)
if err != nil { if err != nil {
return seriesListView{}, err return seriesListView{}, err
} }
@@ -181,11 +181,12 @@ func (h *Handler) seriesListView(r *http.Request) (seriesListView, error) {
// count, one SeriesShapes pass per filter summed in Go — the shipped surface // count, one SeriesShapes pass per filter summed in Go — the shipped surface
// offers eight grouped passes, not a single stats query (#140). The counts // offers eight grouped passes, not a single stats query (#140). The counts
// are library-wide because the select sits next to the Site narrowing and // are library-wide because the select sits next to the Site narrowing and
// must not shift as the owner narrows the list itself. // must not shift as the owner narrows the list itself. Cutoff travels with
func (h *Handler) seriesFilterOptions(selected string) ([]seriesFilterOption, error) { // the stale filter, or its count would always be zero.
func (h *Handler) seriesFilterOptions(selected string, cutoff int64) ([]seriesFilterOption, error) {
out := make([]seriesFilterOption, 0, len(seriesFilterOrder)) out := make([]seriesFilterOption, 0, len(seriesFilterOrder))
for _, name := range seriesFilterOrder { for _, name := range seriesFilterOrder {
shapes, err := h.store.SeriesShapes(store.SeriesFilter{Name: name}) shapes, err := h.store.SeriesShapes(store.SeriesFilter{Name: name, Cutoff: cutoff})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -203,9 +204,8 @@ func (h *Handler) seriesFilterOptions(selected string) ([]seriesFilterOption, er
return out, nil return out, nil
} }
// seriesRow formats one store row for the template. The note chips are the // seriesRow shapes one store row for the template, capping its chips at two
// row's own hygiene facts, capped at two with a +N tail; attention marks a // plus a +N tail; attention marks a row that carries any.
// row that carries any.
func seriesRow(a store.AdminSeries, i int, now time.Time) seriesRowView { func seriesRow(a store.AdminSeries, i int, now time.Time) seriesRowView {
row := seriesRowView{ row := seriesRowView{
Key: a.Key(), Key: a.Key(),
@@ -273,13 +273,10 @@ func checkedAge(now time.Time, ts int64) string {
} }
} }
// pagerRange is the pager's "1–50 of 120" line. Zero rows reads as "0 of 0": // pagerRange is the pager's "1–50 of 120" line. The template renders the
// the empty state carries the page then, and a window number for a list that // pager only over rows (the empty state replaces it), so it is never asked
// rendered nothing would be a number nothing points at. // to describe an empty list.
func pagerRange(total, rows, page int) string { func pagerRange(total, rows, page int) string {
if rows == 0 {
return "0 of 0"
}
from := (page-1)*seriesPageSize + 1 from := (page-1)*seriesPageSize + 1
return fmt.Sprintf("%d–%d of %d", from, from+rows-1, total) return fmt.Sprintf("%d–%d of %d", from, from+rows-1, total)
} }
+9 -3
View File
@@ -1939,15 +1939,16 @@ func TestSeriesListFilterWiring(t *testing.T) {
t.Fatalf("open %s: %v", dsn, err) t.Fatalf("open %s: %v", dsn, err)
} }
defer db.Close() defer db.Close()
seedSeriesRow(t, st, db, seriesRowSeed{key: "asura:healthy", url: "https://asurascans.com/comics/healthy", cover: "aaa", checkedAt: 9000, latestNum: floatPtr(10), bookmarks: 1}) seedSeriesRow(t, st, db, seriesRowSeed{key: "asura:healthy", url: "https://asurascans.com/comics/healthy", cover: "aaa", checkedAt: time.Now().UnixMilli(), latestNum: floatPtr(10), bookmarks: 1})
seedSeriesRow(t, st, db, seriesRowSeed{key: "asura:nocover", url: "https://asurascans.com/comics/nocover", checkedAt: 9000, latestNum: floatPtr(3), bookmarks: 1}) seedSeriesRow(t, st, db, seriesRowSeed{key: "asura:nocover", url: "https://asurascans.com/comics/nocover", checkedAt: time.Now().UnixMilli(), latestNum: floatPtr(3), bookmarks: 1})
seedSeriesRow(t, st, db, seriesRowSeed{key: "asura:stale", url: "https://asurascans.com/comics/stale", cover: "bbb", checkedAt: time.Now().Add(-24 * time.Hour).UnixMilli(), latestNum: floatPtr(4), bookmarks: 1})
srv := newRouter(st, testConfig(), nil) srv := newRouter(st, testConfig(), nil)
body := adminSeriesPage(t, srv, st, "?filter=no_cover") body := adminSeriesPage(t, srv, st, "?filter=no_cover")
if !strings.Contains(body, "Title of asura:nocover") { if !strings.Contains(body, "Title of asura:nocover") {
t.Errorf("no_cover list misses its row:\n%s", body) t.Errorf("no_cover list misses its row:\n%s", body)
} }
if strings.Contains(body, "Title of asura:healthy") { if strings.Contains(body, "Title of asura:healthy") || strings.Contains(body, "Title of asura:stale") {
t.Errorf("no_cover list renders a covered row:\n%s", body) t.Errorf("no_cover list renders a covered row:\n%s", body)
} }
if !strings.Contains(body, "1 series") || !strings.Contains(body, "No cover") { if !strings.Contains(body, "1 series") || !strings.Contains(body, "No cover") {
@@ -1959,6 +1960,11 @@ func TestSeriesListFilterWiring(t *testing.T) {
if !strings.Contains(body, `<option value="no_cover" selected>`) { if !strings.Contains(body, `<option value="no_cover" selected>`) {
t.Errorf("the no_cover option is not selected:\n%s", body) t.Errorf("the no_cover option is not selected:\n%s", body)
} }
// The stale option's count is cutoff-dependent: the row read passes the
// 12h boundary, and so must the aggregate that numbers the select.
if !strings.Contains(body, "Not checked in 12h (1)") {
t.Errorf("the stale option lacks its cutoff-dependent count:\n%s", body)
}
// An unknown filter value is the absent All case: both rows, no error. // An unknown filter value is the absent All case: both rows, no error.
body = adminSeriesPage(t, srv, st, "?filter=bogus") body = adminSeriesPage(t, srv, st, "?filter=bogus")