Skip to content

Commit 037ff18

Browse files
committed
feat(cli): enhance analytics status with coverage and last pushed time
Added coverage percentage and last pushed timestamp to repository metrics. Updated display functions to show new data in the output.
1 parent 5fe849a commit 037ff18

2 files changed

Lines changed: 200 additions & 24 deletions

File tree

internal/cli/analytics_status.go

Lines changed: 84 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,17 @@ import (
1616

1717
// repoStatus holds aggregated status for a single repository
1818
type repoStatus struct {
19-
FullName string
20-
Org string
21-
RepoName string
22-
Stars int
23-
Forks int
24-
OpenIssues int
25-
OpenPRs int
26-
TotalAlerts int
27-
LastSyncAt *time.Time
19+
FullName string
20+
Org string
21+
RepoName string
22+
Stars int
23+
Forks int
24+
OpenIssues int
25+
OpenPRs int
26+
TotalAlerts int
27+
LastSyncAt *time.Time
28+
LastPushedAt *time.Time
29+
CoveragePercent *float64
2830
}
2931

3032
// newAnalyticsStatusCmd creates the analytics status command
@@ -106,6 +108,16 @@ func displayAllRepositories(ctx context.Context, analyticsRepo db.AnalyticsRepo)
106108
status.Forks = snapshot.Forks
107109
status.OpenIssues = snapshot.OpenIssues
108110
status.OpenPRs = snapshot.OpenPRs
111+
status.LastPushedAt = snapshot.PushedAt
112+
}
113+
114+
// Get latest CI snapshot for coverage
115+
ciSnap, ciErr := analyticsRepo.GetLatestCISnapshot(ctx, repo.ID)
116+
if ciErr != nil && !errors.Is(ciErr, gorm.ErrRecordNotFound) {
117+
return fmt.Errorf("failed to get CI snapshot for %s: %w", repo.FullName(), ciErr)
118+
}
119+
if ciSnap != nil {
120+
status.CoveragePercent = ciSnap.CoveragePercent
109121
}
110122

111123
// Get alert counts
@@ -143,34 +155,63 @@ func displayAllRepositories(ctx context.Context, analyticsRepo db.AnalyticsRepo)
143155
for _, org := range orgOrder {
144156
group := orgGroups[org]
145157
output.Plain("")
158+
output.Plain("")
146159
repoWord := "repositories"
147160
if len(group) == 1 {
148161
repoWord = "repository"
149162
}
150163
output.Info(fmt.Sprintf("%s (%d %s)", org, len(group), repoWord))
151-
output.Plain(strings.Repeat("─", 80))
152-
output.Plain(fmt.Sprintf("%-35s %6s %6s %7s %5s %7s %s",
153-
"Repository", "Stars", "Forks", "Issues", "PRs", "Alerts", "Last Sync"))
154-
output.Plain(strings.Repeat("─", 80))
164+
output.Plain(strings.Repeat("─", 115))
165+
output.Plain(fmt.Sprintf("%-35s %6s %6s %7s %5s %7s %8s %-16s %s",
166+
"Repository", "Stars", "Forks", "Issues", "PRs", "Alerts", "Coverage", "Last Push", "Last Sync"))
167+
output.Plain(strings.Repeat("─", 115))
155168

156169
for _, status := range group {
157170
lastSync := "Never"
158171
if status.LastSyncAt != nil {
159172
lastSync = formatTimeAgo(status.LastSyncAt)
160173
}
161174

162-
output.Plain(fmt.Sprintf("%-35s %6d %6d %7d %5d %7d %s",
175+
output.Plain(fmt.Sprintf("%-35s %6d %6d %7d %5d %7d %8s %-16s %s",
163176
truncate(status.RepoName, 35),
164177
status.Stars,
165178
status.Forks,
166179
status.OpenIssues,
167180
status.OpenPRs,
168181
status.TotalAlerts,
182+
formatCoverage(status.CoveragePercent),
183+
formatTimeAgoShort(status.LastPushedAt),
169184
lastSync))
170185
}
186+
187+
// Summary row
188+
var sumStars, sumForks, sumIssues, sumPRs, sumAlerts int
189+
var sumCoverage float64
190+
var covCount int
191+
for _, s := range group {
192+
sumStars += s.Stars
193+
sumForks += s.Forks
194+
sumIssues += s.OpenIssues
195+
sumPRs += s.OpenPRs
196+
sumAlerts += s.TotalAlerts
197+
if s.CoveragePercent != nil {
198+
sumCoverage += *s.CoveragePercent
199+
covCount++
200+
}
201+
}
202+
avgCoverage := "-"
203+
if covCount > 0 {
204+
avg := sumCoverage / float64(covCount)
205+
avgCoverage = fmt.Sprintf("%.1f%%", avg)
206+
}
207+
output.Plain(strings.Repeat("─", 115))
208+
output.Plain(fmt.Sprintf("%-35s %6d %6d %7d %5d %7d %8s %-16s %s",
209+
"Total", sumStars, sumForks, sumIssues, sumPRs, sumAlerts, avgCoverage, "", ""))
171210
}
172211

173212
// Display summary
213+
output.Plain(strings.Repeat("─", 115))
214+
174215
output.Plain("")
175216
if reposWithAlerts > 0 {
176217
output.Info(fmt.Sprintf("Total: %d repositories, %d with %d open alerts",
@@ -189,7 +230,7 @@ func displayAllRepositories(ctx context.Context, analyticsRepo db.AnalyticsRepo)
189230
output.Info(fmt.Sprintf("Last sync: %s (%s)", formatTimeAgo(lastSync.CompletedAt), duration))
190231
}
191232

192-
output.Plain(strings.Repeat("─", 80))
233+
output.Plain(strings.Repeat("─", 115))
193234
output.Plain("")
194235

195236
return nil
@@ -212,6 +253,12 @@ func displaySingleRepository(ctx context.Context, analyticsRepo db.AnalyticsRepo
212253
return fmt.Errorf("failed to get snapshot: %w", err)
213254
}
214255

256+
// Get latest CI snapshot for coverage
257+
ciSnap, err := analyticsRepo.GetLatestCISnapshot(ctx, repo.ID)
258+
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
259+
return fmt.Errorf("failed to get CI snapshot: %w", err)
260+
}
261+
215262
// Get alert counts by type
216263
alertsByType, err := analyticsRepo.GetAlertCountsByType(ctx, repo.ID)
217264
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
@@ -268,6 +315,12 @@ func displaySingleRepository(ctx context.Context, analyticsRepo db.AnalyticsRepo
268315
if snapshot.BranchCount > 0 {
269316
output.Plain(fmt.Sprintf(" Branches: %d", snapshot.BranchCount))
270317
}
318+
if ciSnap != nil && ciSnap.CoveragePercent != nil {
319+
output.Plain(fmt.Sprintf(" Coverage: %s", formatCoverage(ciSnap.CoveragePercent)))
320+
}
321+
if snapshot.PushedAt != nil {
322+
output.Plain(fmt.Sprintf(" Last Push: %s", formatTimeAgo(snapshot.PushedAt)))
323+
}
271324
} else {
272325
output.Warn("No metrics available yet. Run 'analytics sync' to collect data.")
273326
}
@@ -381,6 +434,22 @@ func formatTimeAgo(t *time.Time) string {
381434
}
382435
}
383436

437+
// formatTimeAgoShort is like formatTimeAgo but returns "never" instead of "unknown" for nil.
438+
func formatTimeAgoShort(t *time.Time) string {
439+
if t == nil {
440+
return "never"
441+
}
442+
return formatTimeAgo(t)
443+
}
444+
445+
// formatCoverage formats a nullable coverage percentage for display.
446+
func formatCoverage(p *float64) string {
447+
if p == nil {
448+
return "-"
449+
}
450+
return fmt.Sprintf("%.1f%%", *p)
451+
}
452+
384453
// formatDuration converts milliseconds to a human-readable duration format
385454
func formatDuration(ms int64) string {
386455
if ms == 0 {

0 commit comments

Comments
 (0)