mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-10 00:14:38 +00:00
The locked-state description advertised the v5 capability set ("PDF
and CSV performance reports plus current-state VM inventory
exports") and never caught up with what the v6 reporting feature
actually delivers behind the gate: AI-narrated executive summary,
fleet outlier detection with named resources, period-over-period
comparison, and Patrol findings rolled into the narrative.
Update both the LockedState (what non-Pro users see on the upsell)
and Guidance (what Pro users see on the enabled surface) copy so
they match what ships. The locked-state copy is honest about the
AI being optional — narration uses Pulse Assistant when configured
and falls back to a deterministic summary otherwise — so users who
haven't set up Assistant don't think the Pro feature is gated
behind a separate AI configuration.
No structural change to the catalog: same fields, same JSON shape,
same downstream consumers. Frontend renders these strings directly
from the catalog endpoint, so the copy update propagates without
any frontend code change.
193 lines
8.3 KiB
Go
193 lines
8.3 KiB
Go
package reporting
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// ReportingFormatDefinition describes one supported output format for an
|
|
// operator-facing reporting surface.
|
|
type ReportingFormatDefinition struct {
|
|
Value ReportFormat `json:"value"`
|
|
Label string `json:"label"`
|
|
}
|
|
|
|
const FilenameDateStyleUTCYYYYMMDD = "utc_yyyymmdd"
|
|
|
|
// ReportingRangeDefinition describes one supported time window for historical
|
|
// performance reporting.
|
|
type ReportingRangeDefinition struct {
|
|
Key string `json:"key"`
|
|
Label string `json:"label"`
|
|
Description string `json:"description"`
|
|
WindowHours int `json:"windowHours"`
|
|
}
|
|
|
|
const FilenameSubjectResourceID = "resource_id"
|
|
|
|
// PerformanceReportDefinition describes the canonical performance reporting
|
|
// surface exposed to operators.
|
|
type PerformanceReportDefinition struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
Description string `json:"description"`
|
|
SingleResourceEndpoint string `json:"singleResourceEndpoint"`
|
|
MultiResourceEndpoint string `json:"multiResourceEndpoint"`
|
|
SingleFilenamePrefix string `json:"singleFilenamePrefix"`
|
|
SingleFilenameSubject string `json:"singleFilenameSubject"`
|
|
MultiFilenamePrefix string `json:"multiFilenamePrefix"`
|
|
FilenameDateStyle string `json:"filenameDateStyle"`
|
|
Formats []ReportingFormatDefinition `json:"formats"`
|
|
DefaultFormat ReportFormat `json:"defaultFormat"`
|
|
Ranges []ReportingRangeDefinition `json:"ranges"`
|
|
DefaultRange string `json:"defaultRange"`
|
|
MultiResourceMax int `json:"multiResourceMax"`
|
|
SupportsMetricFilter bool `json:"supportsMetricFilter"`
|
|
SupportsCustomTitle bool `json:"supportsCustomTitle"`
|
|
}
|
|
|
|
// ReportingCatalog describes the backend-owned admin reporting surface for the
|
|
// Pulse settings UI.
|
|
type ReportingLockedStateDefinition struct {
|
|
Title string `json:"title"`
|
|
Description string `json:"description"`
|
|
}
|
|
|
|
type ReportingGuidanceDefinition struct {
|
|
Title string `json:"title"`
|
|
Description string `json:"description"`
|
|
}
|
|
|
|
type ReportingCatalog struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
Description string `json:"description"`
|
|
LockedState ReportingLockedStateDefinition `json:"lockedState"`
|
|
Guidance ReportingGuidanceDefinition `json:"guidance"`
|
|
PerformanceReport PerformanceReportDefinition `json:"performanceReport"`
|
|
VMInventoryExport VMInventoryExportDefinition `json:"vmInventoryExport"`
|
|
}
|
|
|
|
// SupportsFormat reports whether the performance reporting surface allows the
|
|
// given output format.
|
|
func (d PerformanceReportDefinition) SupportsFormat(format ReportFormat) bool {
|
|
for _, candidate := range d.Formats {
|
|
if candidate.Value == format {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// InvalidFormatError returns the canonical validation message for unsupported
|
|
// performance report formats.
|
|
func (d PerformanceReportDefinition) InvalidFormatError() string {
|
|
allowed := make([]ReportFormat, 0, len(d.Formats))
|
|
for _, candidate := range d.Formats {
|
|
allowed = append(allowed, candidate.Value)
|
|
}
|
|
return invalidFormatErrorMessage(allowed)
|
|
}
|
|
|
|
// SingleAttachmentFilename returns the canonical attachment filename for a
|
|
// single-resource performance report download.
|
|
func (d PerformanceReportDefinition) SingleAttachmentFilename(resourceID string, generatedAt time.Time, format ReportFormat) string {
|
|
return fmt.Sprintf("%s-%s-%s.%s", d.SingleFilenamePrefix, resourceID, reportingDateStamp(generatedAt, d.FilenameDateStyle), format)
|
|
}
|
|
|
|
// MultiAttachmentFilename returns the canonical attachment filename for a
|
|
// multi-resource performance report download.
|
|
func (d PerformanceReportDefinition) MultiAttachmentFilename(generatedAt time.Time, format ReportFormat) string {
|
|
return fmt.Sprintf("%s-%s.%s", d.MultiFilenamePrefix, reportingDateStamp(generatedAt, d.FilenameDateStyle), format)
|
|
}
|
|
|
|
// DefaultRangeDuration returns the canonical fallback time window for the
|
|
// performance reporting surface.
|
|
func (d PerformanceReportDefinition) DefaultRangeDuration() time.Duration {
|
|
for _, candidate := range d.Ranges {
|
|
if candidate.Key == d.DefaultRange {
|
|
return time.Duration(candidate.WindowHours) * time.Hour
|
|
}
|
|
}
|
|
return 24 * time.Hour
|
|
}
|
|
|
|
func invalidFormatErrorMessage(allowed []ReportFormat) string {
|
|
quoted := make([]string, 0, len(allowed))
|
|
for _, format := range allowed {
|
|
if format == "" {
|
|
continue
|
|
}
|
|
quoted = append(quoted, fmt.Sprintf("%q", format))
|
|
}
|
|
if len(quoted) == 0 {
|
|
return "Format is not supported"
|
|
}
|
|
if len(quoted) == 1 {
|
|
return fmt.Sprintf("Format must be %s", quoted[0])
|
|
}
|
|
if len(quoted) == 2 {
|
|
return fmt.Sprintf("Format must be %s or %s", quoted[0], quoted[1])
|
|
}
|
|
return fmt.Sprintf("Format must be %s, or %s", strings.Join(quoted[:len(quoted)-1], ", "), quoted[len(quoted)-1])
|
|
}
|
|
|
|
func reportingDateStamp(generatedAt time.Time, style string) string {
|
|
switch style {
|
|
case "", FilenameDateStyleUTCYYYYMMDD:
|
|
return generatedAt.UTC().Format("20060102")
|
|
default:
|
|
return generatedAt.UTC().Format("20060102")
|
|
}
|
|
}
|
|
|
|
// DescribePerformanceReport returns the canonical definition for Pulse's
|
|
// historical performance reporting surface.
|
|
func DescribePerformanceReport() PerformanceReportDefinition {
|
|
return PerformanceReportDefinition{
|
|
ID: "performance_reports",
|
|
Title: "Performance Reports",
|
|
Description: "Generate PDF summaries or CSV metric exports from historical monitoring data for one or more selected resources.",
|
|
SingleResourceEndpoint: "/api/admin/reports/generate",
|
|
MultiResourceEndpoint: "/api/admin/reports/generate-multi",
|
|
SingleFilenamePrefix: "report",
|
|
SingleFilenameSubject: FilenameSubjectResourceID,
|
|
MultiFilenamePrefix: "fleet-report",
|
|
FilenameDateStyle: FilenameDateStyleUTCYYYYMMDD,
|
|
Formats: []ReportingFormatDefinition{
|
|
{Value: FormatPDF, Label: "PDF Report"},
|
|
{Value: FormatCSV, Label: "CSV Data"},
|
|
},
|
|
DefaultFormat: FormatPDF,
|
|
Ranges: []ReportingRangeDefinition{
|
|
{Key: "24h", Label: "Last 24 Hours", Description: "Current-day operational summary for short-term regressions.", WindowHours: 24},
|
|
{Key: "7d", Label: "Last 7 Days", Description: "Weekly trend window for recent performance changes.", WindowHours: 168},
|
|
{Key: "30d", Label: "Last 30 Days", Description: "Monthly review window for sustained capacity or reliability shifts.", WindowHours: 720},
|
|
},
|
|
DefaultRange: "24h",
|
|
MultiResourceMax: 50,
|
|
SupportsMetricFilter: true,
|
|
SupportsCustomTitle: true,
|
|
}
|
|
}
|
|
|
|
// DescribeReportingCatalog returns the canonical backend-owned settings
|
|
// definition for the advanced reporting feature.
|
|
func DescribeReportingCatalog() ReportingCatalog {
|
|
return ReportingCatalog{
|
|
ID: "advanced_reporting",
|
|
Title: "Detailed Reporting",
|
|
Description: "Generate performance reports and current-state exports across infrastructure and workloads.",
|
|
LockedState: ReportingLockedStateDefinition{
|
|
Title: "Advanced Reporting",
|
|
Description: "AI-narrated performance reports for a single resource or your full fleet, with an executive summary, outlier callouts, and period-over-period changes. PDF and CSV formats, plus current-state VM inventory exports. AI narration uses Pulse Assistant when configured; reports fall back to a deterministic summary otherwise. Available on paid self-hosted and hosted plans.",
|
|
},
|
|
Guidance: ReportingGuidanceDefinition{
|
|
Title: "Advanced Insights",
|
|
Description: "Performance reports include an AI-narrated executive summary, fleet outliers, and period-over-period comparison. They draw from the historical metrics store and Patrol findings within the window. The VM inventory export captures current runtime state for spreadsheet-friendly fleet reviews. Use reports for retrospective trends and the inventory export for current allocation snapshots.",
|
|
},
|
|
PerformanceReport: DescribePerformanceReport(),
|
|
VMInventoryExport: DescribeVMInventoryExport(),
|
|
}
|
|
}
|