This commit is contained in:
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user