Documentation
¶
Overview ¶
Package sprintdash is the sprint dashboard's server (docs/SPEC-SPRINT-DASHBOARD.md): one page, embedded in the binary, /api/sprint, a cached copy of the sprint as `nova-sprint where --json --cards` prints it, /events, each new copy pushed as it is read, and the pull routes (pull.go), a worker's own view of the same copy. The terminal table stays the canonical view; this is a second view of the same JSON.
The server is a function of its requests and its clock: Read is how it reads the sprint and Now is its clock, so a test drives it with no socket and no real time. The server reads the sprint once per Every whoever is looking (Run, on a ticker the caller hands it: one poller, its reads back to back with Every as their floor), and while Run polls a request only ever answers from the copy. A read that fails holds the last good copy, marked ok false with a short reason (never the reader's own error text, which goes to Log only), and a read that runs past ReadTimeout is marked so. One freshness check (fresh.go) raises an alarm when the served data stays old; a puller (From) reads another dashboard's copy instead.
Index ¶
- Constants
- type LaneRow
- type MergeFacts
- type MergeRow
- type PullCard
- type PullJudgment
- type PullRow
- type PullView
- type ReadError
- type Server
- func (s *Server) Build() string
- func (s *Server) Pull() http.Handler
- func (s *Server) Refresh()
- func (s *Server) Run(ctx context.Context, tick <-chan time.Time)
- func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (s *Server) Snapshot() []byte
- func (s *Server) SnapshotOf(release string) []byte
- func (s *Server) Stale() string
- func (s *Server) Tick()
- type SprintLine
- type TeamCard
- type TeamFriend
- type TeamView
- type Upstream
Constants ¶
const ( // Window is how far back the throughput's samples reach. Window = time.Hour // MinSpan is the samples throughput needs before it shows: until then it is null. MinSpan = 10 * time.Minute // LogEvery is the time between the read-time summary lines on Log. LogEvery = time.Minute // ReadTimeoutDefault is how long a read is waited on before it is marked failed. ReadTimeoutDefault = time.Minute )
const ( // StaleAfterDefault is the age of the served data past which it is stale. StaleAfterDefault = 2 * time.Second // StaleForDefault is how long the data stays stale before the alarm is raised. StaleForDefault = 30 * time.Second // UpstreamTimeout bounds a puller's read of the other dashboard. UpstreamTimeout = 5 * time.Second )
const ( GateRed = "red" GateGreen = "green" GateUnknown = "" )
The base gate's states: red names its failing test; "" is not known, drawn "-".
const ( KindFriend = "friend" KindMachine = "machine" )
The kinds of view, and the table and the member each reads.
const AllReleases = "all"
AllReleases is the release that is every stream, labelled or not.
const KeepaliveDefault = 15 * time.Second
KeepaliveDefault is the time between two keepalive comments on an idle stream.
const MergeWindow = 30 * time.Minute
MergeWindow is the span landed_per_30m counts.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type LaneRow ¶
type LaneRow struct {
Kind string `json:"kind"`
Machine string `json:"machine"`
Width int `json:"width"`
Held []string `json:"held"`
Waiting []string `json:"waiting"`
}
LaneRow is one machine's lanes of a kind as where --json --cards prints them (docs/SPEC-SPRINT-DASHBOARD.md, "Lanes"; docs/SPEC-SPRINT.md section 5, The fleet): the machine's width, the holders and the waiters, in the order the sprint records them.
type MergeFacts ¶
type MergeFacts struct {
Now time.Time
Merging, Review int64 // the work table's merging and review counts
Landed []time.Time // landing stamps (the where record's)
// MergingRead says the merging cards were read (where --json --cards or --rows), and
// MergingSince is their accepted stamps, a zero one unreadable. Unread, the oldest is
// not known.
MergingRead bool
MergingSince []time.Time
// BaseRed is the open base-red judgments' texts (the base-gate rule's stop and the
// drift alarm's whole-tree gate); LanderGreen says the lander's tree gate passed at a
// stream's last landing (the merge table's ci).
BaseRed []string
LanderGreen bool
// BaseLacks and DevLacks are the drift the last dev sync measured; LastSync and
// LastPromotion are zero when none is recorded.
BaseLacks, DevLacks int
LastSync, LastPromotion time.Time
}
MergeFacts is what where reads for the merge row.
type MergeRow ¶
type MergeRow struct {
Merging int64 `json:"merging"`
Review int64 `json:"review"`
LandedPer30m int64 `json:"landed_per_30m"`
OldestMergingMin *int `json:"oldest_merging_min"`
BaseGate string `json:"base_gate"`
FailingTest string `json:"failing_test"`
BaseLacks int `json:"base_lacks"`
DevLacks int `json:"dev_lacks"`
SyncMinutes *int `json:"sync_minutes"`
PromotionMinutes *int `json:"promotion_minutes"`
}
MergeRow is where --json's merge_row: the cards in merging and in review, the landings of the last 30 minutes, the oldest merging card's age, the base's gate and its failing test, the commits the base lacks of the development branch and dev lacks of the base, and the minutes since the last dev sync and the last promotion. A nil minute is not known.
func MergeRowOf ¶
func MergeRowOf(f MergeFacts) MergeRow
MergeRowOf is the merge row of the facts. The gate is red on any open base-red judgment, green when there is none and the lander's gate passed at its last landing, and not known otherwise: never green by default.
type PullCard ¶
type PullCard struct {
ID string `json:"id"`
Primary string `json:"primary,omitempty"`
Stream string `json:"stream"`
Member string `json:"member,omitempty"`
State string `json:"state"`
Since time.Time `json:"since,omitzero"`
Deadline time.Time `json:"deadline,omitzero"`
Branch string `json:"branch"`
}
PullCard is a work card dealt to a row and not finished, as where --json --cards prints it.
type PullJudgment ¶
type PullJudgment struct {
ID string `json:"id"`
Kind string `json:"kind"`
Card string `json:"card"`
}
PullJudgment is an open judgment naming a card's primary: its note id and its kind.
type PullRow ¶
type PullRow struct {
Status string `json:"status"`
Ready string `json:"ready"`
Working string `json:"working"`
Width string `json:"width"`
Done string `json:"done"`
OKPct string `json:"okpct"`
Tokens string `json:"tokens,omitempty"`
Load string `json:"load,omitempty"`
}
PullRow is a row of the friends table or the fleet table as where prints its cells.
type PullView ¶
type PullView struct {
At time.Time `json:"at"`
Kind string `json:"kind"` // friend or machine
Name string `json:"name"`
Sprint SprintLine `json:"sprint"`
Row PullRow `json:"row"`
Cards []PullCard `json:"cards"`
Judgments []PullJudgment `json:"judgments"`
}
PullView is one friend's or one machine's view: the sprint line, the row, the cards dealt to it and not finished, and the open judgments naming them.
type ReadError ¶
type ReadError struct{ Why, Detail string }
ReadError is a failed read in two parts: Why is served in /api/sprint's error, and Detail (what the reader itself printed) goes to Log only, so nothing a read printed reaches the page.
type Server ¶
type Server struct {
// Read reads the sprint once: the bytes `where --json` prints. It is bounded by its
// own transport (the sprint server's client, or the store's connection).
Read func() ([]byte, error)
// Now is the server's clock.
Now func() time.Time
// Every is the least time between two reads' starts.
Every time.Duration
// Logo is the image file served as the logo and the favicon; "" is none, and
// the page's logo slot renders nothing.
Logo string
// Version is the binary's version, part of the build number.
Version string
// Log takes a line per new read failure and a read-time summary a minute.
Log io.Writer
// Keepalive is the time between two keepalive comments on an idle /events stream;
// zero is KeepaliveDefault.
Keepalive time.Duration
// From is the dashboard a puller reads its copy from in place of Read; nil reads the
// sprint.
From *Upstream
// StaleAfter and StaleFor are the freshness check's: the served data older than
// StaleAfter for StaleFor raises the alarm; zero is StaleAfterDefault, StaleForDefault.
StaleAfter, StaleFor time.Duration
// ReadTimeout is how long a read is waited on before it is marked failed; the next
// read still waits for it to end. Zero is ReadTimeoutDefault.
ReadTimeout time.Duration
// contains filtered or unexported fields
}
Server serves the page and the sprint's cached copy. Read, Now and Every are required; the rest may be left zero.
func (*Server) Build ¶
Build is the build number: it changes with the page's files, the version, and the logo file (its name, size and time), and an open page reloads itself when it does.
func (*Server) Pull ¶
Pull is the handler of the pull routes, served on listeners of their own (the page's proxy never fronts them): /api/sprint, /api/friend/<name>, /friend/<name>, /api/machine/<name>, /machine/<name>, /team, /api/team, the event streams /events, /events/friend/<name> and /events/machine/<name>, and /healthz. Each answers from the cached copy, read at most once per Every however many ask; every answer is no-store and (but a stream's) carries the copy's time in Sprint-At; nothing is written.
func (*Server) Refresh ¶
func (s *Server) Refresh()
Refresh reads the sprint when Every has passed since the last read began and no read is running; otherwise the cached copy stands. While Run polls it reads nothing once a read has begun: the poller is the one reader, and a request answers from the copy (the verb makes the first read before it listens, so a page never reads).
func (*Server) Run ¶
Run reads the sprint on each tick, whoever is looking, so the copy is never older than a tick and each new one is pushed to the /events clients as it is read, then checks the copy's freshness (Tick); it returns when ctx is done. While it runs it is the one reader: a request (Refresh) answers from the copy and never reads. tick is the clock's ticker (time.Ticker's channel, every Every); a test hands it a channel of its own.
func (*Server) ServeHTTP ¶
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP answers every path the page uses; every answer is no-store.
func (*Server) Snapshot ¶
Snapshot is /api/sprint's body now, with the build number, as the default release shows it.
func (*Server) SnapshotOf ¶
SnapshotOf is /api/sprint's body now as release shows it (release.go): "" is the default, all is every stream.
type SprintLine ¶
type SprintLine struct {
Landed int64 `json:"landed"`
All int64 `json:"all"`
Held int64 `json:"held"`
ETA string `json:"eta"`
Machine string `json:"machine"`
}
SprintLine is the sprint in one line: landed of all, held, the ETA and the machine.
type TeamCard ¶
type TeamCard struct {
ID string `json:"id"`
Stream string `json:"stream"`
State string `json:"state"`
Since time.Time `json:"since,omitzero"`
}
TeamCard is a card a friend holds, as the team sees it.
type TeamFriend ¶
type TeamFriend struct {
Name string `json:"name"`
Row PullRow `json:"row"`
Cards []TeamCard `json:"cards"`
}
TeamFriend is one friend's block: her row and her cards.
type TeamView ¶
type TeamView struct {
At time.Time `json:"at"`
Sprint SprintLine `json:"sprint"`
Friends []TeamFriend `json:"friends"`
}
TeamView is every friend's block (the owner, 2026-10-03 11:56 AM: "we need to get the friends working together."): the sprint line, then each friend's row and the cards she holds, so each friend sees what every other is on.
type Upstream ¶
type Upstream struct {
URL string
// Client reads the upstream; nil is a client bounded by UpstreamTimeout.
Client *http.Client
}
Upstream is the dashboard a puller reads its copy from (nova-sprint dashboard --pull <url>): URL is that dashboard's /api/sprint. The puller serves the data, its time and its throughput as the upstream read them, so the two pages agree, and the puller's freshness check sees the upstream's age.