mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
fix(reporting): resolve report subjects through the canonical unified view and paginate the metric card grid
Two regressions surfaced by generating a real-mode agent report after a backend restart: - Report subject lookups used Monitor.GetUnifiedResources (the raw resource store), but the raw store's canonical IDs depend on per-boot ingest order for merged-source hosts: after a restart the same host resolved to a different agent-<hash> than the one the UI and /api/state advertise, so reports lost the resource name, availability, and metrics translation entirely. Subject enrichment now reads Monitor.UnifiedResourceSnapshot and MetricsTargetForResource resolves through GetUnifiedReadStateOrSnapshot first (raw store as fallback) - the same re-ingested registry every other read surface uses. - The performance summary card grid positions cards absolutely and never paginated: an agent host reporting 8+ metric families walked off the page bottom, fought fpdf's auto page break, and scattered one orphan element per page (a 7-day delly report rendered 18 pages, ten of them near-blank). The grid now starts a new page before a row that will not fit; the same report renders 9 pages with intact cards. Verified live: real-mode delly report shows name, 288 data points, availability, charts, and correctly paginated cards. The underlying canonical-ID instability (raw store vs re-ingested view, and the resource_changes journal fragmenting across boot eras) is a separate root issue tracked for its own fix.
This commit is contained in:
parent
47392b5f68
commit
9bcf767b1b
4 changed files with 80 additions and 10 deletions
|
|
@ -884,6 +884,9 @@ func (g *PDFGenerator) writeSummarySection(pdf *fpdf.Fpdf, data *ReportData) {
|
|||
startX := 20.0
|
||||
rowStartY := pdf.GetY()
|
||||
|
||||
_, pageHeight := pdf.GetPageSize()
|
||||
cardBottomLimit := pageHeight - 30
|
||||
|
||||
for i, metricType := range metricNames {
|
||||
stats := data.Summary.ByMetric[metricType]
|
||||
unit := GetMetricUnit(metricType)
|
||||
|
|
@ -898,6 +901,16 @@ func (g *PDFGenerator) writeSummarySection(pdf *fpdf.Fpdf, data *ReportData) {
|
|||
pdf.SetY(rowStartY)
|
||||
}
|
||||
|
||||
// The grid positions cards absolutely, so it must paginate
|
||||
// itself: a row that would cross the bottom margin previously
|
||||
// fought fpdf's auto page break and scattered one orphan
|
||||
// element per page for the rest of the section.
|
||||
if col == 0 && rowStartY+cardHeight > cardBottomLimit {
|
||||
pdf.AddPage()
|
||||
rowStartY = pdf.GetY()
|
||||
pdf.SetY(rowStartY)
|
||||
}
|
||||
|
||||
cardX := startX + float64(col)*(cardWidth+cardGap)
|
||||
cardY := rowStartY
|
||||
|
||||
|
|
@ -946,10 +959,9 @@ func (g *PDFGenerator) writeSummarySection(pdf *fpdf.Fpdf, data *ReportData) {
|
|||
writeStat(cardX+46, statsY+6, "Samples", fmt.Sprintf("%d", stats.Count))
|
||||
}
|
||||
|
||||
// Calculate final Y position based on number of rows
|
||||
numRows := (len(metricNames) + 1) / 2 // Round up
|
||||
finalY := rowStartY + float64(numRows)*(cardHeight+cardGap)
|
||||
pdf.SetY(finalY)
|
||||
// rowStartY tracks the current (possibly paginated) row, so the
|
||||
// section ends just below the last row of cards.
|
||||
pdf.SetY(rowStartY + cardHeight + cardGap)
|
||||
}
|
||||
|
||||
// writeChartsSection writes charts for each metric.
|
||||
|
|
|
|||
|
|
@ -467,3 +467,46 @@ func TestGenerateMulti_FlowsResourceBlocksOntoSharedPages(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestSummarySection_PaginatesCardGridForManyMetrics pins the card grid's
|
||||
// self-pagination. Agent hosts report 8+ metric families; the absolutely
|
||||
// positioned grid previously walked off the page bottom and fought fpdf's
|
||||
// auto page break, scattering one orphan element per page for the rest of
|
||||
// the section (observed live as ten near-blank pages).
|
||||
func TestSummarySection_PaginatesCardGridForManyMetrics(t *testing.T) {
|
||||
byMetric := map[string]MetricStats{}
|
||||
for _, m := range []string{"cpu", "memory", "disk", "diskread", "diskwrite", "netin", "netout", "temperature", "iops", "usage"} {
|
||||
byMetric[m] = MetricStats{Min: 1, Max: 9, Avg: 5, Current: 5, Count: 100}
|
||||
}
|
||||
data := &ReportData{
|
||||
Title: "Many metrics",
|
||||
ResourceType: "agent",
|
||||
ResourceID: "agent-1",
|
||||
Resource: &ResourceInfo{Name: "host-01", Status: "online"},
|
||||
Start: time.Now().Add(-7 * 24 * time.Hour),
|
||||
End: time.Now(),
|
||||
GeneratedAt: time.Now(),
|
||||
Summary: MetricSummary{ByMetric: byMetric},
|
||||
TotalPoints: 1000,
|
||||
}
|
||||
|
||||
gen := NewPDFGenerator()
|
||||
out, err := gen.Generate(data)
|
||||
if err != nil {
|
||||
t.Fatalf("Generate: %v", err)
|
||||
}
|
||||
text := extractPDFText(t, out)
|
||||
|
||||
pageTotal := regexp.MustCompile(`Page \d+ of (\d+)`).FindStringSubmatch(text)
|
||||
if pageTotal == nil {
|
||||
t.Fatalf("no page footer found in:\n%s", text)
|
||||
}
|
||||
if pages, _ := strconv.Atoi(pageTotal[1]); pages > 6 {
|
||||
t.Fatalf("10-metric report rendered %d pages; the card grid must paginate compactly", pages)
|
||||
}
|
||||
for _, label := range []string{"Disk Read", "Network Out", "Temperature"} {
|
||||
if !strings.Contains(text, label) {
|
||||
t.Fatalf("metric card %q missing after pagination:\n%s", label, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue