improve rollup reporting
Some checks failed
continuous-integration/drone/push Build was killed

This commit is contained in:
2026-01-14 10:03:04 +11:00
parent 7400e08c54
commit ca8b39ba0e
4 changed files with 365 additions and 144 deletions

View File

@@ -6,6 +6,7 @@ import (
"database/sql"
"fmt"
"log/slog"
"strconv"
"strings"
"time"
"vctp/db"
@@ -187,6 +188,63 @@ ORDER BY snapshot_time DESC, table_name DESC
return records, rows.Err()
}
func ListSnapshotsByRange(ctx context.Context, database db.Database, snapshotType string, start time.Time, end time.Time) ([]SnapshotRecord, error) {
dbConn := database.DB()
driver := strings.ToLower(dbConn.DriverName())
startUnix := start.Unix()
endUnix := end.Unix()
var rows *sqlx.Rows
var err error
switch driver {
case "sqlite":
rows, err = dbConn.QueryxContext(ctx, `
SELECT table_name, snapshot_time, snapshot_type
FROM snapshot_registry
WHERE snapshot_type = ?
AND snapshot_time >= ?
AND snapshot_time < ?
ORDER BY snapshot_time ASC, table_name ASC
`, snapshotType, startUnix, endUnix)
case "pgx", "postgres":
rows, err = dbConn.QueryxContext(ctx, `
SELECT table_name, snapshot_time, snapshot_type
FROM snapshot_registry
WHERE snapshot_type = $1
AND snapshot_time >= $2
AND snapshot_time < $3
ORDER BY snapshot_time ASC, table_name ASC
`, snapshotType, startUnix, endUnix)
default:
return nil, fmt.Errorf("unsupported driver for listing snapshots: %s", driver)
}
if err != nil {
return nil, err
}
defer rows.Close()
records := make([]SnapshotRecord, 0)
for rows.Next() {
var (
tableName string
snapshotTime int64
recordType string
)
if err := rows.Scan(&tableName, &snapshotTime, &recordType); err != nil {
return nil, err
}
records = append(records, SnapshotRecord{
TableName: tableName,
SnapshotTime: time.Unix(snapshotTime, 0),
SnapshotType: recordType,
})
}
return records, rows.Err()
}
func FormatSnapshotLabel(snapshotType string, snapshotTime time.Time, tableName string) string {
switch snapshotType {
case "hourly":
@@ -214,6 +272,23 @@ func CreateTableReport(logger *slog.Logger, Database db.Database, ctx context.Co
return nil, fmt.Errorf("no columns found for table %s", tableName)
}
humanizeTimes := strings.HasPrefix(tableName, "inventory_daily_summary_") || strings.HasPrefix(tableName, "inventory_monthly_summary_")
type columnSpec struct {
Name string
SourceIndex int
Humanize bool
}
specs := make([]columnSpec, 0, len(columns)+2)
for i, columnName := range columns {
specs = append(specs, columnSpec{Name: columnName, SourceIndex: i})
if humanizeTimes && columnName == "CreationTime" {
specs = append(specs, columnSpec{Name: "CreationTimeReadable", SourceIndex: i, Humanize: true})
}
if humanizeTimes && columnName == "DeletionTime" {
specs = append(specs, columnSpec{Name: "DeletionTimeReadable", SourceIndex: i, Humanize: true})
}
}
query := fmt.Sprintf(`SELECT * FROM %s`, tableName)
orderBy := snapshotOrderBy(columns)
if orderBy != "" {
@@ -240,12 +315,12 @@ func CreateTableReport(logger *slog.Logger, Database db.Database, ctx context.Co
logger.Error("Error setting document properties", "error", err, "sheet_name", sheetName)
}
for i, columnName := range columns {
for i, spec := range specs {
cell := fmt.Sprintf("%s1", string(rune('A'+i)))
xlsx.SetCellValue(sheetName, cell, columnName)
xlsx.SetCellValue(sheetName, cell, spec.Name)
}
if endCell, err := excelize.CoordinatesToCellName(len(columns), 1); err == nil {
if endCell, err := excelize.CoordinatesToCellName(len(specs), 1); err == nil {
filterRange := "A1:" + endCell
if err := xlsx.AutoFilter(sheetName, filterRange, nil); err != nil {
logger.Error("Error setting autofilter", "error", err)
@@ -269,9 +344,14 @@ func CreateTableReport(logger *slog.Logger, Database db.Database, ctx context.Co
if err != nil {
return nil, err
}
for colIndex, value := range values {
for colIndex, spec := range specs {
cell := fmt.Sprintf("%s%d", string(rune('A'+colIndex)), rowIndex)
xlsx.SetCellValue(sheetName, cell, normalizeCellValue(value))
value := values[spec.SourceIndex]
if spec.Humanize {
xlsx.SetCellValue(sheetName, cell, formatEpochHuman(value))
} else {
xlsx.SetCellValue(sheetName, cell, normalizeCellValue(value))
}
}
rowIndex++
}
@@ -410,3 +490,37 @@ func normalizeCellValue(value interface{}) interface{} {
return v
}
}
func formatEpochHuman(value interface{}) string {
var epoch int64
switch v := value.(type) {
case nil:
return ""
case int64:
epoch = v
case int32:
epoch = int64(v)
case int:
epoch = int64(v)
case float64:
epoch = int64(v)
case []byte:
parsed, err := strconv.ParseInt(string(v), 10, 64)
if err != nil {
return ""
}
epoch = parsed
case string:
parsed, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return ""
}
epoch = parsed
default:
return ""
}
if epoch <= 0 {
return ""
}
return time.Unix(epoch, 0).Local().Format("Mon 02 Jan 2006 15:04:05 MST")
}