sprintdash

package
v1.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 9, 2026 License: MIT Imports: 24 Imported by: 0

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

View Source
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
)
View Source
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
)
View Source
const (
	GateRed     = "red"
	GateGreen   = "green"
	GateUnknown = ""
)

The base gate's states: red names its failing test; "" is not known, drawn "-".

View Source
const (
	KindFriend  = "friend"
	KindMachine = "machine"
)

The kinds of view, and the table and the member each reads.

View Source
const AllReleases = "all"

AllReleases is the release that is every stream, labelled or not.

View Source
const KeepaliveDefault = 15 * time.Second

KeepaliveDefault is the time between two keepalive comments on an idle stream.

View Source
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.

func (MergeRow) Line

func (m MergeRow) Line() string

Line is the row in one line, as the page reads it left to right.

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.

func (PullView) Text

func (v PullView) Text() string

Text is the view as plain text, one line an item and no markup: the sprint line, the row, a line per card (id, stream, state, how long in it, the time to its deadline or past it, the branch), then a line per judgment. Times are from the copy's at.

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.

func (*ReadError) Error

func (e *ReadError) Error() string

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
	// 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

func (s *Server) Build() string

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

func (s *Server) Pull() http.Handler

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

func (s *Server) Run(ctx context.Context, tick <-chan time.Time)

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

func (s *Server) Snapshot() []byte

Snapshot is /api/sprint's body now, with the build number, as the default release shows it.

func (*Server) SnapshotOf

func (s *Server) SnapshotOf(release string) []byte

SnapshotOf is /api/sprint's body now as release shows it (release.go): "" is the default, all is every stream.

func (*Server) Stale

func (s *Server) Stale() string

Stale is why the served data is not to be trusted now, "" while the alarm is down.

func (*Server) Tick

func (s *Server) Tick()

Tick is one tick of the clock's ticker: a read of the sprint when no read is running, then the freshness check. Run calls it each tick; a test calls it by hand.

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.

func (TeamView) Text

func (t TeamView) Text() string

Text is the team as plain text: the sprint line, then for each friend a line of her row and an indented line per card she holds (id, stream, state, how long in it).

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL