Documentation
¶
Overview ¶
Package dashboard holds the domain model both forge clients produce and the aggregator that merges them into one snapshot. Neither forge client depends on the other; both only depend on this package's types.
Index ¶
- Constants
- type Aggregator
- type CIStatus
- type Forge
- type ForgeClient
- type ForgeHealth
- type GenericSource
- type Issue
- type Label
- type Manager
- func (m *Manager) Ensure(ctx context.Context, userID []byte, sources []Source)
- func (m *Manager) Get(userID []byte) Snapshot
- func (m *Manager) RefreshNow(ctx context.Context, userID []byte) bool
- func (m *Manager) Remove(userID []byte)
- func (m *Manager) Running(userID []byte) bool
- func (m *Manager) Stop()
- func (m *Manager) Subscribe(userID []byte) (<-chan Snapshot, func(), bool)
- type PullRequest
- type RepoRef
- type Result
- type Snapshot
- type Source
Constants ¶
const DefaultMaxConcurrency = 8
DefaultMaxConcurrency bounds how many repositories a GenericSource fetches at once. Generous enough to make quick work of a ~100-repo account without opening that many sockets at once for no benefit.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Aggregator ¶
type Aggregator struct {
// contains filtered or unexported fields
}
Aggregator holds the most recently fetched Snapshot and refreshes it in the background. Get never blocks on a forge: it always returns whatever was last successfully assembled, even if a refresh is running right now or every source just failed.
func NewAggregator ¶
func NewAggregator(sources []Source) *Aggregator
NewAggregator returns an Aggregator whose Get answers an empty snapshot until the first Refresh (or Run) completes.
func (*Aggregator) Get ¶
func (a *Aggregator) Get() Snapshot
Get returns the current snapshot. Safe to call concurrently with Refresh.
func (*Aggregator) Refresh ¶
func (a *Aggregator) Refresh(ctx context.Context)
Refresh fetches every source concurrently and replaces the snapshot with the merged result. A source's own Fetch never returns an error — a forge it can't reach at all shows up as an unreachable ForgeHealth entry instead, so one broken forge never drops the other's data.
func (*Aggregator) Run ¶
func (a *Aggregator) Run(ctx context.Context, interval time.Duration)
Run refreshes immediately, then again every interval, until ctx is canceled. Meant to run in its own goroutine for the lifetime of the process.
func (*Aggregator) Subscribe ¶ added in v0.23.0
func (a *Aggregator) Subscribe() (<-chan Snapshot, func())
Subscribe returns a channel that receives the new Snapshot after every completed Refresh, and a function to stop receiving them. The channel is buffered by exactly one — a subscriber that falls behind (an SSE client whose write is blocked on a slow network) never makes Refresh itself block; it just misses an intermediate update and catches up on the next one. Call the returned function when done, typically via defer: it closes the channel, so a range over it ends cleanly.
type CIStatus ¶
type CIStatus string
CIStatus is the combined result across every check reported against a pull request's head commit. Matches components.schemas.CIStatus.
type Forge ¶
type Forge string
Forge names one of the two forges this service talks to. Matches components.schemas.Forge in api/openapi.yaml.
type ForgeClient ¶
type ForgeClient interface {
ListRepos(ctx context.Context) ([]RepoRef, error)
ListOpenPullRequests(ctx context.Context, owner, name, repo string) ([]PullRequest, error)
ListOpenIssues(ctx context.Context, owner, name, repo string) ([]Issue, error)
}
ForgeClient is what a forge-specific client (internal/github, internal/forgejo) provides for GenericSource to drive. Both clients' method sets already match this shape, so neither needs an adapter.
type ForgeHealth ¶
type ForgeHealth struct {
Forge Forge `json:"forge"`
Reachable bool `json:"reachable"`
Error string `json:"error,omitempty"`
RepoCount int `json:"repoCount"`
}
ForgeHealth matches components.schemas.ForgeHealth.
type GenericSource ¶
type GenericSource struct {
// contains filtered or unexported fields
}
GenericSource drives a ForgeClient the same way regardless of which forge it talks to: list repositories, then fetch each one's open pull requests and issues concurrently, bounded, skipping a single repo's failure rather than failing the whole forge.
func NewGenericSource ¶
func NewGenericSource(forge Forge, client ForgeClient, maxConcurrency int) *GenericSource
NewGenericSource returns a Source reporting as forge, driving client with at most maxConcurrency repositories fetched at once.
type Issue ¶
type Issue struct {
Forge Forge `json:"forge"`
Repo string `json:"repo"`
Number int `json:"number"`
Title string `json:"title"`
URL string `json:"url"`
Author string `json:"author"`
Labels []Label `json:"labels"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
Issue matches components.schemas.Issue in api/openapi.yaml.
type Label ¶ added in v0.22.0
Label matches components.schemas.Label — a label's name and its real colour from the forge, not just the name.
type Manager ¶ added in v0.8.0
type Manager struct {
// contains filtered or unexported fields
}
Manager owns one Aggregator per user, each refreshing itself in the background on its own schedule — the per-user equivalent of a single global Aggregator, now that each user's dashboard reflects their own saved forge credentials rather than one shared environment-configured set.
func NewManager ¶ added in v0.8.0
NewManager returns a Manager whose per-user Aggregators refresh every refreshInterval.
func (*Manager) Ensure ¶ added in v0.8.0
Ensure (re)builds userID's Aggregator from sources and starts its background refresh, stopping whatever was running for that user before. Call it once after login (using their currently saved credentials) and again every time those credentials are saved — there's no way to add or remove a Source from a running Aggregator, so a changed configuration means a new one.
func (*Manager) Get ¶ added in v0.8.0
Get returns userID's current snapshot — empty, not nil arrays, if Ensure was never called for them (no credentials saved yet).
func (*Manager) RefreshNow ¶ added in v0.23.0
RefreshNow fetches userID's sources immediately, out of band from their regular refreshInterval ticker — the webhook handler's entry point for turning a forge event into an up-to-date snapshot within seconds rather than waiting for the next scheduled refresh. Reports false if userID has no running Aggregator (Ensure was never called for them), rather than silently doing nothing.
func (*Manager) Remove ¶ added in v0.9.0
Remove stops userID's refresh loop and evicts their snapshot — a no-op if they had none running. Used when an admin revokes or deletes an account, so a locked-out user's background refresh doesn't keep polling either forge for no one.
func (*Manager) Running ¶ added in v0.27.1
Running reports whether userID currently has an Aggregator refreshing in the background. False right after a process restart for every user — the Manager holds no state across one — until something re-Ensures them; a caller can use this to tell "nothing saved in Settings" apart from "just needs rewarming from what's already saved."
type PullRequest ¶
type PullRequest struct {
Forge Forge `json:"forge"`
Repo string `json:"repo"`
Number int `json:"number"`
Title string `json:"title"`
URL string `json:"url"`
Author string `json:"author"`
Draft bool `json:"draft"`
Labels []Label `json:"labels"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
CI CIStatus `json:"ci"`
}
PullRequest matches components.schemas.PullRequest in api/openapi.yaml.
type RepoRef ¶
RepoRef names one repository a ForgeClient is configured to track — every repo the configured credential has write access to, or, with no credential at all, every public repo a configured username owns.
type Result ¶
type Result struct {
Health ForgeHealth
PullRequests []PullRequest
Issues []Issue
}
Result is what one forge contributes to a Snapshot. Health.Reachable is false only for a fetch that failed outright (couldn't list repositories at all); a single repository's own fetch failing is logged and skipped by the Source, not surfaced here — a good forge with one broken repo still reports Reachable: true.
type Snapshot ¶
type Snapshot struct {
GeneratedAt time.Time `json:"generatedAt"`
Forges []ForgeHealth `json:"forges"`
PullRequests []PullRequest `json:"pullRequests"`
Issues []Issue `json:"issues"`
}
Snapshot matches components.schemas.Dashboard — the whole body GET /api/dashboard answers with.