Implements spec #137 (spec 4 of 4 from wayfinder map #114). Closes #137. Tickets: #164, #165, #166, #167, #168, #169, #170, #171, #172, #173 — all closed, landed on this branch. ## Summary - #164/#168: sixth outcome word `not_found`; per-Site completed marker predicate. - #165/#169: `poll_failures` row is the failure state; the pass remembers a Site-reported completion. - #166/#171: two new admin filters (`failing`, `unverified`); outbound owner notification + stall condition. - #167/#170: a failure names itself on the Series page; the completion hint reaches the owner and decides nothing. - #172: the other three fault conditions (no-browser-route, sidecar-down, adapter-broken) feeding the notifier. - #173: the landing verdict line shares the same `latest.FaultsFrom` judgement the notifier uses, so the page and the push cannot disagree. `cd backend && go test ./...` green on the merged branch (8 packages). Reviewed-on: #174 Co-authored-by: Sulthan Zaki <sultankiki05@gmail.com> Co-committed-by: Sulthan Zaki <sultankiki05@gmail.com>
This commit was merged in pull request #174.
This commit is contained in:
@@ -2,8 +2,10 @@ package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"bookmarkmanager/backend/internal/latest"
|
||||
"bookmarkmanager/backend/internal/store"
|
||||
)
|
||||
|
||||
@@ -23,10 +25,10 @@ type overviewView struct {
|
||||
// as stale + never_checked: a never-checked Series is already counted on
|
||||
// its own filter, and the verdict wants the inclusive number.
|
||||
Unchecked int
|
||||
// Hygiene is the seven problem filters in the Series list's own render
|
||||
// order plus the finished figure riding last (informational); Library is
|
||||
// the library split plus the roster. Every figure is a door into the list
|
||||
// that counts it, except a zero.
|
||||
// Hygiene is the problem filters in the Series list's own render order
|
||||
// plus the informational tail — finished, then site completed — riding
|
||||
// last; Library is the library split plus the roster. Every figure is a
|
||||
// door into the list that counts it, except a zero.
|
||||
Hygiene []fig
|
||||
Library []fig
|
||||
// Sites is the per-Site library shape table, one row per Site with any
|
||||
@@ -60,10 +62,13 @@ type siteRow struct {
|
||||
}
|
||||
|
||||
// overviewView assembles the landing page from the store's read model: one
|
||||
// SeriesShapes pass per filter summed in Go (the shipped surface offers nine
|
||||
// SeriesShapes pass per filter summed in Go (the shipped surface offers ten
|
||||
// grouped passes, not a stats query — #140), the pass log's latest pass per
|
||||
// Site, and the roster. A failure in any read is a 500 with a logged reason,
|
||||
// never a page of silent zeroes.
|
||||
// Site, and the roster. A failure in the SeriesShapes, pass or roster reads
|
||||
// is a 500 with a logged reason, never a page of silent zeroes. The three
|
||||
// fault-input reads (RefusingSince, SidecarOK, NoChapterShare) fail open:
|
||||
// a failing read logs and contributes no fault, so the landing page still
|
||||
// renders — the same fail-open the poller uses for owner notices.
|
||||
func (h *Handler) overviewView() (overviewView, error) {
|
||||
now := time.Now()
|
||||
cutoff := now.Add(-ownerWindow).UnixMilli()
|
||||
@@ -92,12 +97,36 @@ func (h *Handler) overviewView() (overviewView, error) {
|
||||
|
||||
view := overviewView{Waiting: waiting(passes)}
|
||||
view.Unchecked = totals[store.SeriesFilterStale] + totals[store.SeriesFilterNeverChecked]
|
||||
view.Verdict, view.HasCounts = overviewVerdict(passes, now)
|
||||
var refusingSince map[string]int64
|
||||
if m, err := h.store.RefusingSince(now.UnixMilli()); err != nil {
|
||||
log.Printf("admin overview: refusing since: %v", err)
|
||||
} else {
|
||||
refusingSince = m
|
||||
}
|
||||
var sidecarOK map[string]int64
|
||||
if m, err := h.store.SidecarOK(latest.BrowserBackedSites()); err != nil {
|
||||
log.Printf("admin overview: sidecar ok: %v", err)
|
||||
} else {
|
||||
sidecarOK = m
|
||||
}
|
||||
var noChapterShare map[string]float64
|
||||
if m, err := h.store.NoChapterShare(cutoff); err != nil {
|
||||
log.Printf("admin overview: no-chapter share: %v", err)
|
||||
} else {
|
||||
noChapterShare = m
|
||||
}
|
||||
faults := latest.FaultsFrom(latest.FaultInput{
|
||||
Passes: passes,
|
||||
RefusingSince: refusingSince,
|
||||
SidecarOK: sidecarOK,
|
||||
NoChapterShare: noChapterShare,
|
||||
}, now)
|
||||
view.Verdict, view.HasCounts = overviewVerdict(passes, faults)
|
||||
|
||||
// The hygiene figures, in seriesFilterOrder's tail: the seven problem
|
||||
// filters in permanent-then-fixable order, then the finished figure last —
|
||||
// informational, not a problem, and last because seriesFilterOrder appends
|
||||
// it there. The All filter's count belongs to the Library block, not to a
|
||||
// The hygiene figures, in seriesFilterOrder's tail: the problem filters
|
||||
// in permanent-then-fixable order, then the informational tail — finished
|
||||
// and site completed — riding last because seriesFilterOrder appends them
|
||||
// there. The All filter's count belongs to the Library block, not to a
|
||||
// "hygiene" figure.
|
||||
hygiene := make([]fig, 0, len(seriesFilterOrder)-1)
|
||||
for _, name := range seriesFilterOrder[1:] {
|
||||
@@ -165,28 +194,26 @@ func door(label string, count int, href string) fig {
|
||||
return fig{Label: label, Href: href, Count: count}
|
||||
}
|
||||
|
||||
// overviewVerdict decides the landing page's one line from the latest pass
|
||||
// per Site: no passes at all is "no Lane has reported yet" — never confident
|
||||
// zeroes; otherwise the count of Lanes whose last pass needs the owner, or
|
||||
// "all lanes healthy". The count comes from the same laneState judgement the
|
||||
// Lanes page colours on, so the two pages cannot disagree on what a fault is.
|
||||
func overviewVerdict(passes []store.LanePass, now time.Time) (phrase string, counts bool) {
|
||||
// overviewVerdict decides the landing page's one line: no passes at all is
|
||||
// "no Lane has reported yet" — never confident zeroes; otherwise the count of
|
||||
// faults from the shared FaultsFrom judgement, so the page and the push
|
||||
// cannot disagree. Zero faults is "all lanes healthy". A sidecar-down fault
|
||||
// carries Site == "" and is still one fault. The Lanes page's per-row
|
||||
// laneState is a different question (is this Lane's last pass healthy) from
|
||||
// the notice class, and its known false positives live there deliberately, so
|
||||
// the two now differ.
|
||||
func overviewVerdict(passes []store.LanePass, faults []latest.Fault) (phrase string, counts bool) {
|
||||
if len(passes) == 0 {
|
||||
return "no Lane has reported yet", false
|
||||
}
|
||||
attention := 0
|
||||
for _, p := range passes {
|
||||
if _, _, attn := laneState(p, now); attn {
|
||||
attention++
|
||||
}
|
||||
}
|
||||
if attention == 0 {
|
||||
n := len(faults)
|
||||
if n == 0 {
|
||||
return "all lanes healthy", true
|
||||
}
|
||||
if attention == 1 {
|
||||
if n == 1 {
|
||||
return "1 lane needs a look", true
|
||||
}
|
||||
return fmt.Sprintf("%d lanes need a look", attention), true
|
||||
return fmt.Sprintf("%d lanes need a look", n), true
|
||||
}
|
||||
|
||||
// waiting sums Due over the latest pass per Site: how many Series the Lanes
|
||||
|
||||
Reference in New Issue
Block a user