Add a statistics pane to the webhook page (closes #368)
check / check (push) Failing after 1m15s

Each webhook's event database keeps one row of running totals: events,
deliveries and failures, and how many of each retention removed.
Storing an event, creating a delivery, a delivery becoming failed and
the retention sweep each update it in the transaction that writes or
deletes the rows it counts. Deliveries get a finished_at column, the
last column of the status index, so the last-10-minutes and
last-24-hours figures are index-range counts. The pane is its own
template, included at the top of the page.

The schema changes in place with nothing back-filled, so an existing
database must be recreated.

Model: opus-5-5
This commit is contained in:
2026-10-01 19:31:49 +00:00
parent b79e4649a1
commit 2036d0fc80
21 changed files with 1200 additions and 50 deletions
+30 -2
View File
@@ -1554,8 +1554,9 @@ func (e *Engine) updateDeliveryStatus(
targetType database.TargetType,
status database.DeliveryStatus,
) error {
err := webhookDB.Model(d).
Update("status", status).Error
err := webhookDB.Transaction(func(tx *gorm.DB) error {
return writeDeliveryStatus(tx, d, status)
})
if err != nil {
return fmt.Errorf(
"updating delivery %s to status %s: %w",
@@ -1574,6 +1575,33 @@ func (e *Engine) updateDeliveryStatus(
return nil
}
// writeDeliveryStatus writes a delivery's new status. A delivery that
// becomes delivered or failed also gets the time it finished, and a
// failed one is added to the webhook's running failure total. The
// failure is counted only if the row was still there to update:
// retention may have deleted it while the engine was working on it.
func writeDeliveryStatus(
tx *gorm.DB,
d *database.Delivery,
status database.DeliveryStatus,
) error {
columns := map[string]any{"status": status}
if status.Terminal() {
columns["finished_at"] = time.Now()
}
res := tx.Model(d).Updates(columns)
if res.Error != nil {
return res.Error
}
if status == database.DeliveryStatusFailed && res.RowsAffected > 0 {
return database.AddTotals(tx, database.Totals{Failures: 1})
}
return nil
}
// settleStatus moves a delivery to its outcome status and reports a
// failed write through bookkeepingFailed, which leaves the row
// recoverable. It exists so the target call sites read as one