Add PostgreSQL checkpoint functionality and update related database operations
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -279,6 +279,8 @@ func SetColAutoWidth(xlsx *excelize.File, sheetName string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
const minColWidth = 10
|
||||
const maxColWidth = 80
|
||||
for idx, col := range cols {
|
||||
largestWidth := 0
|
||||
for _, rowCell := range col {
|
||||
@@ -287,12 +289,22 @@ func SetColAutoWidth(xlsx *excelize.File, sheetName string) error {
|
||||
largestWidth = cellWidth
|
||||
}
|
||||
}
|
||||
// Keep a sane minimum so sheets that rely on computed content
|
||||
// (for example pivot output populated by Excel) don't collapse to width 0.
|
||||
if largestWidth < minColWidth {
|
||||
largestWidth = minColWidth
|
||||
}
|
||||
if largestWidth > maxColWidth {
|
||||
largestWidth = maxColWidth
|
||||
}
|
||||
//fmt.Printf("SetColAutoWidth calculated largest width for column index '%d' is '%d'\n", idx, largestWidth)
|
||||
name, err := excelize.ColumnNumberToName(idx + 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
xlsx.SetColWidth(sheetName, name, name, float64(largestWidth))
|
||||
if err := xlsx.SetColWidth(sheetName, name, name, float64(largestWidth)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// No errors at this point
|
||||
return nil
|
||||
|
||||
@@ -928,7 +928,9 @@ func addSummaryPivotSheet(logger *slog.Logger, xlsx *excelize.File, dataSheet st
|
||||
logger.Warn("summary worksheet skipped due to invalid data range", "table", tableName, "error", err)
|
||||
return
|
||||
}
|
||||
dataRange := fmt.Sprintf("%s!A1:%s", quoteSheetName(dataSheet), endCell)
|
||||
// excelize AddPivotTable expects unquoted sheet references like:
|
||||
// "Snapshot Report!A1:Z999". Quoted sheet names cause pivot creation to fail.
|
||||
dataRange := fmt.Sprintf("%s!A1:%s", dataSheet, endCell)
|
||||
lowerToHeader := make(map[string]string, len(headers))
|
||||
for _, header := range headers {
|
||||
lowerToHeader[strings.ToLower(strings.TrimSpace(header))] = header
|
||||
@@ -943,7 +945,7 @@ func addSummaryPivotSheet(logger *slog.Logger, xlsx *excelize.File, dataSheet st
|
||||
Title: "Sum of Avg vCPUs",
|
||||
TitleCell: "A1",
|
||||
PivotName: "PivotAvgVcpu",
|
||||
PivotRange: fmt.Sprintf("%s!A3:H1000", quoteSheetName(summarySheet)),
|
||||
PivotRange: fmt.Sprintf("%s!A3:H22", summarySheet),
|
||||
RowFields: []string{"Datacenter", "ResourcePool"},
|
||||
DataField: "AvgVcpuCount",
|
||||
DataName: "Sum of Avg vCPUs",
|
||||
@@ -953,7 +955,7 @@ func addSummaryPivotSheet(logger *slog.Logger, xlsx *excelize.File, dataSheet st
|
||||
Title: "Sum of Avg RAM",
|
||||
TitleCell: "J1",
|
||||
PivotName: "PivotAvgRam",
|
||||
PivotRange: fmt.Sprintf("%s!J3:P1000", quoteSheetName(summarySheet)),
|
||||
PivotRange: fmt.Sprintf("%s!J3:P22", summarySheet),
|
||||
RowFields: []string{"Datacenter"},
|
||||
DataField: "AvgRamGB",
|
||||
DataName: "Sum of Avg RAM",
|
||||
@@ -961,9 +963,9 @@ func addSummaryPivotSheet(logger *slog.Logger, xlsx *excelize.File, dataSheet st
|
||||
},
|
||||
{
|
||||
Title: "Sum of prorated VM count",
|
||||
TitleCell: "A1003",
|
||||
TitleCell: "A23",
|
||||
PivotName: "PivotProratedVmCount",
|
||||
PivotRange: fmt.Sprintf("%s!A1005:H2002", quoteSheetName(summarySheet)),
|
||||
PivotRange: fmt.Sprintf("%s!A25:H44", summarySheet),
|
||||
RowFields: []string{"Datacenter"},
|
||||
DataField: "AvgIsPresent",
|
||||
DataName: "Sum of prorated VM count",
|
||||
@@ -971,9 +973,9 @@ func addSummaryPivotSheet(logger *slog.Logger, xlsx *excelize.File, dataSheet st
|
||||
},
|
||||
{
|
||||
Title: "Count of VM Name",
|
||||
TitleCell: "J1003",
|
||||
TitleCell: "J23",
|
||||
PivotName: "PivotVmNameCount",
|
||||
PivotRange: fmt.Sprintf("%s!J1005:P2002", quoteSheetName(summarySheet)),
|
||||
PivotRange: fmt.Sprintf("%s!J25:P44", summarySheet),
|
||||
RowFields: []string{"Datacenter"},
|
||||
DataField: "Name",
|
||||
DataName: "Count of VM Name",
|
||||
|
||||
62
internal/report/snapshots_pivot_test.go
Normal file
62
internal/report/snapshots_pivot_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
func TestAddSummaryPivotSheetCreatesPivotTables(t *testing.T) {
|
||||
xlsx := excelize.NewFile()
|
||||
const dataSheet = "Snapshot Report"
|
||||
if err := xlsx.SetSheetName("Sheet1", dataSheet); err != nil {
|
||||
t.Fatalf("SetSheetName failed: %v", err)
|
||||
}
|
||||
|
||||
headers := []string{"Name", "Datacenter", "ResourcePool", "AvgVcpuCount", "AvgRamGB", "AvgIsPresent"}
|
||||
if err := xlsx.SetSheetRow(dataSheet, "A1", &headers); err != nil {
|
||||
t.Fatalf("SetSheetRow header failed: %v", err)
|
||||
}
|
||||
|
||||
row1 := []interface{}{"vm-1", "dc-1", "pool-1", 4.0, 16.0, 1.0}
|
||||
if err := xlsx.SetSheetRow(dataSheet, "A2", &row1); err != nil {
|
||||
t.Fatalf("SetSheetRow data failed: %v", err)
|
||||
}
|
||||
|
||||
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||
addSummaryPivotSheet(logger, xlsx, dataSheet, headers, 1, "inventory_daily_summary_20260215")
|
||||
|
||||
pivots, err := xlsx.GetPivotTables("Summary")
|
||||
if err != nil {
|
||||
t.Fatalf("GetPivotTables failed: %v", err)
|
||||
}
|
||||
if len(pivots) != 4 {
|
||||
t.Fatalf("expected 4 pivot tables, got %d", len(pivots))
|
||||
}
|
||||
|
||||
expectedNames := map[string]bool{
|
||||
"PivotAvgVcpu": false,
|
||||
"PivotAvgRam": false,
|
||||
"PivotProratedVmCount": false,
|
||||
"PivotVmNameCount": false,
|
||||
}
|
||||
for _, pivot := range pivots {
|
||||
if _, ok := expectedNames[pivot.Name]; ok {
|
||||
expectedNames[pivot.Name] = true
|
||||
}
|
||||
if strings.Contains(pivot.DataRange, "'") {
|
||||
t.Fatalf("pivot %q has quoted DataRange %q; expected unquoted sheet reference", pivot.Name, pivot.DataRange)
|
||||
}
|
||||
if strings.Contains(pivot.PivotTableRange, "'") {
|
||||
t.Fatalf("pivot %q has quoted PivotTableRange %q; expected unquoted sheet reference", pivot.Name, pivot.PivotTableRange)
|
||||
}
|
||||
}
|
||||
for name, seen := range expectedNames {
|
||||
if !seen {
|
||||
t.Fatalf("missing expected pivot table %q", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,11 +202,13 @@ func (c *CronTask) aggregateDailySummary(ctx context.Context, targetTime time.Ti
|
||||
}
|
||||
c.Logger.Debug("Generated daily report", "table", summaryTable, "duration", time.Since(reportStart))
|
||||
checkpointStart := time.Now()
|
||||
c.Logger.Debug("Checkpointing sqlite after daily aggregation", "table", summaryTable)
|
||||
if err := db.CheckpointSQLite(ctx, dbConn); err != nil {
|
||||
c.Logger.Warn("failed to checkpoint sqlite after daily aggregation", "error", err)
|
||||
driver := strings.ToLower(dbConn.DriverName())
|
||||
c.Logger.Debug("Running database checkpoint after daily aggregation", "table", summaryTable, "driver", driver)
|
||||
action, err := db.CheckpointDatabase(ctx, dbConn)
|
||||
if err != nil {
|
||||
c.Logger.Warn("failed to run database checkpoint after daily aggregation", "driver", driver, "action", action, "error", err)
|
||||
} else {
|
||||
c.Logger.Debug("Checkpointed sqlite after daily aggregation", "table", summaryTable, "duration", time.Since(checkpointStart))
|
||||
c.Logger.Debug("Completed database checkpoint after daily aggregation", "table", summaryTable, "driver", driver, "action", action, "duration", time.Since(checkpointStart))
|
||||
}
|
||||
|
||||
c.Logger.Debug("Finished daily inventory aggregation", "summary_table", summaryTable)
|
||||
@@ -520,11 +522,13 @@ LIMIT 1
|
||||
}
|
||||
c.Logger.Debug("Generated daily report", "table", summaryTable, "duration", time.Since(reportStart))
|
||||
checkpointStart := time.Now()
|
||||
c.Logger.Debug("Checkpointing sqlite after daily aggregation", "table", summaryTable)
|
||||
if err := db.CheckpointSQLite(ctx, dbConn); err != nil {
|
||||
c.Logger.Warn("failed to checkpoint sqlite after daily aggregation (Go path)", "error", err)
|
||||
driver := strings.ToLower(dbConn.DriverName())
|
||||
c.Logger.Debug("Running database checkpoint after daily aggregation", "table", summaryTable, "driver", driver)
|
||||
action, err := db.CheckpointDatabase(ctx, dbConn)
|
||||
if err != nil {
|
||||
c.Logger.Warn("failed to run database checkpoint after daily aggregation (Go path)", "driver", driver, "action", action, "error", err)
|
||||
} else {
|
||||
c.Logger.Debug("Checkpointed sqlite after daily aggregation", "table", summaryTable, "duration", time.Since(checkpointStart))
|
||||
c.Logger.Debug("Completed database checkpoint after daily aggregation", "table", summaryTable, "driver", driver, "action", action, "duration", time.Since(checkpointStart))
|
||||
}
|
||||
|
||||
c.Logger.Debug("Finished daily inventory aggregation (Go path)",
|
||||
|
||||
Reference in New Issue
Block a user