Resubmit a stored event as a new undelivered event (closes #250)
All checks were successful
check / check (push) Successful in 3m1s

This commit was merged in pull request #251.
This commit is contained in:
2026-08-24 00:53:37 +02:00
parent a83e8fe654
commit 89f3b984d2
11 changed files with 1279 additions and 148 deletions

View File

@@ -821,25 +821,31 @@ func (h *Handlers) HandleSourceLogs() http.HandlerFunc {
totalPages++
}
// The banner a replay POST redirected back with. The
// message comes from a fixed set keyed by the outcome
// code, never from the query string itself.
// The banner a replay or resubmit POST redirected back
// with. The message comes from a fixed set keyed by the
// outcome code, never from the query string itself.
replayMsg, replayOK := replayOutcome(
r.URL.Query().Get(replayOutcomeParam),
)
resubmitMsg, resubmitOK := resubmitOutcome(
r.URL.Query().Get(resubmitOutcomeParam),
)
data := map[string]any{
tmplKeyWebhook: &webhook,
"Events": evts,
"ReplayMessage": replayMsg,
"ReplayQueued": replayOK,
"Page": page,
"TotalPages": totalPages,
"TotalEvents": total,
"HasPrev": page > 1,
"HasNext": page < totalPages,
"PrevPage": page - 1,
"NextPage": page + 1,
tmplKeyWebhook: &webhook,
"Events": evts,
"ReplayMessage": replayMsg,
"ReplayQueued": replayOK,
"ResubmitMessage": resubmitMsg,
"ResubmitQueued": resubmitOK,
"Page": page,
"TotalPages": totalPages,
"TotalEvents": total,
"HasPrev": page > 1,
"HasNext": page < totalPages,
"PrevPage": page - 1,
"NextPage": page + 1,
}
h.renderTemplate(w, r, "source_logs.html", data)
@@ -925,12 +931,10 @@ func (h *Handlers) loadEventsWithDeliveries(
targetMap map[string]eventLogTarget,
page int,
) ([]EventLogView, int64, bool) {
var totalEvents int64
var result []EventLogView
if !h.dbMgr.DBExists(webhook.ID) {
return result, totalEvents, true
return result, 0, true
}
webhookDB, err := h.dbMgr.GetDB(webhook.ID)
@@ -942,29 +946,20 @@ func (h *Handlers) loadEventsWithDeliveries(
return nil, 0, false
}
webhookDB.Model(&database.Event{}).Where(
"webhook_id = ?", webhook.ID,
).Count(&totalEvents)
offset := (page - 1) * paginationPerPage
var rows []eventLogRow
webhookDB.Model(&database.Event{}).Select(
eventLogColumns, maxRenderedBodyBytes,
).Where(
"webhook_id = ?", webhook.ID,
).Order("created_at DESC").Offset(offset).Limit(
paginationPerPage,
).Find(&rows)
rows, totalEvents := loadEventLogRows(
webhookDB, webhook.ID, page,
)
result = make([]EventLogView, len(rows))
eventDeliveries := make([][]database.Delivery, len(rows))
var deliveryIDs []string
eventIDs := make([]string, len(rows))
for i := range rows {
result[i] = rows[i].view()
eventIDs[i] = rows[i].ID
webhookDB.Where(
"event_id = ?", rows[i].ID,
@@ -988,15 +983,86 @@ func (h *Handlers) loadEventsWithDeliveries(
return nil, 0, false
}
resubmits, err := resubmitCounts(webhookDB, eventIDs)
if err != nil {
h.serverError(
w, "failed to count event resubmissions", err,
)
return nil, 0, false
}
for i := range rows {
result[i].Deliveries = newDeliveryViews(
eventDeliveries[i], targetMap, attempts,
)
result[i].ResubmitCount = resubmits[rows[i].ID]
}
return result, totalEvents, true
}
// loadEventLogRows reads one page of the event log projection, newest
// first, and the total number of events the pager counts against.
func loadEventLogRows(
webhookDB *gorm.DB, webhookID string, page int,
) ([]eventLogRow, int64) {
var totalEvents int64
webhookDB.Model(&database.Event{}).Where(
"webhook_id = ?", webhookID,
).Count(&totalEvents)
var rows []eventLogRow
webhookDB.Model(&database.Event{}).Select(
eventLogColumns, maxRenderedBodyBytes,
).Where(
"webhook_id = ?", webhookID,
).Order("created_at DESC").Offset(
(page - 1) * paginationPerPage,
).Limit(paginationPerPage).Find(&rows)
return rows, totalEvents
}
// resubmitCounts reports, for each of the page's events, how many
// events have been resubmitted from it.
//
// One grouped query covers the page rather than one query per event.
// A page holds paginationPerPage ids, far below SQLite's bound
// parameter ceiling, so it needs no chunking as the delivery result
// load does.
func resubmitCounts(
webhookDB *gorm.DB, eventIDs []string,
) (map[string]int, error) {
counts := make(map[string]int, len(eventIDs))
if len(eventIDs) == 0 {
return counts, nil
}
var rows []struct {
ResubmittedFromID string
Total int
}
err := webhookDB.Model(&database.Event{}).
Select("resubmitted_from_id, count(*) AS total").
Where("resubmitted_from_id IN ?", eventIDs).
Group("resubmitted_from_id").
Find(&rows).Error
if err != nil {
return nil, err
}
for _, row := range rows {
counts[row.ResubmittedFromID] = row.Total
}
return counts, nil
}
// deliveryIDChunkSize bounds how many delivery IDs go into one
// IN clause. SQLite refuses a statement carrying more than
// SQLITE_MAX_VARIABLE_NUMBER (32766) bound parameters, and a