Documentation
¶
Overview ¶
Package web is the review portal 'jaira web' serves: a page in the browser for a person who wants to see and sort the work without an agent and without the terminal board.
There is no server in the deployment sense. The binary listens on loopback for as long as the command runs, and everything it shows comes from the same files and refs the CLI reads; sync stays git, a fetch in the background and a push after each change. That is also why this package holds no data of its own: a Source hands it the epics and the tickets, and the command that starts it decides where they come from.
The page is a Svelte build under ui/dist, committed and embedded, so a 'go install' without Node still ships it. CI rebuilds it from ui/src and fails when the committed files differ.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrNotFound = errors.New("not found") ErrExists = errors.New("exists already") ErrInvalid = errors.New("invalid") ErrConflict = errors.New("does not apply") )
The errors a Source wraps so the handler answers with what went wrong rather than 500: no such epic or ticket, a name already taken or not usable, and a change that does not apply (a follow-up has no member line to take out).
Functions ¶
func OpenBrowser ¶
OpenBrowser opens url in the default browser and does not wait for it.
It is best effort: a machine with no browser (a container, an ssh session) still gets the address printed by the caller, which is the fallback a person can always act on.
Types ¶
type Epic ¶
type Epic struct {
Name string `json:"name"`
Title string `json:"title"`
Description string `json:"description"`
Done int `json:"done"`
Total int `json:"total"`
Tickets []Ticket `json:"tickets"`
}
Epic is one function of the product with its progress.
type Note ¶
type Note struct {
At time.Time `json:"at"`
Author string `json:"author"`
Text string `json:"text"`
}
Note is one progress note; Text is markdown as its author wrote it.
type Server ¶
type Server struct {
Source Source
// Fetch brings the remote's refs into this clone; Push sends what Assign
// queued. Either may be nil on a board with no remote.
Fetch func() error
Push func() error
// contains filtered or unexported fields
}
Server serves the API and the embedded page.
func (*Server) FetchNow ¶
func (s *Server) FetchNow()
FetchNow runs one fetch and records its outcome. It is what the background loop calls every interval.
func (*Server) Handler ¶
Handler returns the routes: the JSON API under /api/ and the page for everything else.
Every request must name a loopback host. Binding to 127.0.0.1 keeps other machines out, but not a web page in the same browser: through DNS rebinding a foreign site can reach a port on loopback under its own host name. Checking Host refuses that, and checking Origin on writes refuses a cross-site form post.
type Source ¶
type Source interface {
Epics() ([]Epic, error)
Unassigned() ([]Ticket, error)
Ticket(id string) (TicketDetail, error)
CreateEpic(name, title, description string) error
// Assign puts a ticket into epic; with from set it takes it out of that
// epic as well, which is a move.
Assign(epic, from, ticketID string) error
Unassign(epic, ticketID string) error
}
Source is where the page's data comes from and where a change goes.
The writes only record the change locally (and queue its ref); sending it is Push's job, so a remote that is down never makes the change itself fail.
type Sync ¶
type Sync struct {
LastFetch time.Time `json:"last_fetch,omitzero"`
FetchError string `json:"fetch_error,omitzero"`
LastPush time.Time `json:"last_push,omitzero"`
PushError string `json:"push_error,omitzero"`
}
Sync is what the page says about the last round trip to the remote.
type Ticket ¶
type Ticket struct {
ID string `json:"id"`
Handle string `json:"handle"`
Title string `json:"title"`
Lane string `json:"lane"`
Done bool `json:"done"`
// Direct is false for a ticket that belongs to an epic only because it
// follows, or is a child of, one of its members.
Direct bool `json:"direct"`
}
Ticket is one card on the page.
type TicketDetail ¶
type TicketDetail struct {
ID string `json:"id"`
Handle string `json:"handle"`
Title string `json:"title"`
Lane string `json:"lane"`
Goal string `json:"goal"`
Context string `json:"context"`
DoD []Check `json:"dod"`
Notes []Note `json:"notes"`
}
TicketDetail is what the page shows of one ticket when it is opened.