mirror of
https://github.com/lukaszraczylo/ghtracker.git
synced 2026-10-12 00:45:38 +00:00
feat(ui): add a progress loading screen and lazy project list
The loading screen shows live scan progress from the server, with a pulsing outline of the dashboard. The server now reports how many repositories the running scan has finished. The project list renders 20 rows first and adds more as the end scrolls into view. Groups on very wide screens fill two balanced columns, the fact figures sit in a row under the health strip, and the headline counts match the rows listed below it.
This commit is contained in:
@@ -33,6 +33,9 @@ type View struct {
|
||||
Warn int
|
||||
Loaded bool
|
||||
Refreshing bool
|
||||
// ScanDone and ScanTotal count the repos of the running (or last) scan.
|
||||
ScanDone int
|
||||
ScanTotal int
|
||||
}
|
||||
|
||||
// Stats are monotonic counters and timings of the refresh loop.
|
||||
@@ -56,6 +59,8 @@ type Collector struct {
|
||||
th config.Thresholds
|
||||
interval time.Duration
|
||||
refreshes atomic.Uint64
|
||||
scanDone atomic.Int64
|
||||
scanTotal atomic.Int64
|
||||
repoErrs atomic.Uint64
|
||||
lastDur time.Duration
|
||||
concurrency int
|
||||
@@ -118,6 +123,8 @@ func (c *Collector) Refresh(ctx context.Context) {
|
||||
|
||||
start := c.now()
|
||||
c.limited.Store(false)
|
||||
c.scanDone.Store(0)
|
||||
c.scanTotal.Store(int64(len(c.names)))
|
||||
results := make([]model.Repo, len(c.names))
|
||||
sem := make(chan struct{}, c.concurrency)
|
||||
var wg sync.WaitGroup
|
||||
@@ -128,6 +135,7 @@ func (c *Collector) Refresh(ctx context.Context) {
|
||||
defer wg.Done()
|
||||
defer func() { <-sem }()
|
||||
results[i] = c.refreshOne(ctx, name, start)
|
||||
c.scanDone.Add(1)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
@@ -193,6 +201,7 @@ func (c *Collector) View() View {
|
||||
v := View{
|
||||
Now: now, LastRefresh: c.lastRefresh, Interval: c.interval, Thresholds: c.th,
|
||||
Loaded: c.loaded, Refreshing: c.refreshing.Load(),
|
||||
ScanDone: int(c.scanDone.Load()), ScanTotal: int(c.scanTotal.Load()),
|
||||
}
|
||||
v.Repos = make([]model.Repo, 0, len(c.names))
|
||||
for _, n := range c.names {
|
||||
|
||||
@@ -312,3 +312,28 @@ func TestSummarizeWarningsMergesSharedReason(t *testing.T) {
|
||||
t.Fatalf("got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanProgressAdvancesAsReposFinish(t *testing.T) {
|
||||
release := make(chan struct{})
|
||||
started := make(chan struct{}, 4)
|
||||
f := &fakeFetcher{fn: func(name string) (model.Repo, error) {
|
||||
started <- struct{}{}
|
||||
<-release
|
||||
return model.Repo{FullName: name}, nil
|
||||
}}
|
||||
c := newCollector(f, "o/a", "o/b", "o/c")
|
||||
if v := c.View(); v.ScanDone != 0 || v.ScanTotal != 0 {
|
||||
t.Fatalf("before any scan: %+v", v)
|
||||
}
|
||||
done := make(chan struct{})
|
||||
go func() { c.Refresh(context.Background()); close(done) }()
|
||||
<-started
|
||||
if v := c.View(); v.ScanTotal != 3 || v.ScanDone != 0 || !v.Refreshing {
|
||||
t.Fatalf("during scan: done=%d total=%d refreshing=%v", v.ScanDone, v.ScanTotal, v.Refreshing)
|
||||
}
|
||||
close(release)
|
||||
<-done
|
||||
if v := c.View(); v.ScanDone != 3 || v.ScanTotal != 3 {
|
||||
t.Fatalf("after scan: done=%d total=%d", v.ScanDone, v.ScanTotal)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user