check / check (push) Waiting to run
Each entrypoint in the webhook page's entrypoint list shows when the last event arrived on its URL (relative, with the full UTC time on hover), or "never" if none ever has, and how many events arrived on it within the webhook's retention period. The last event is kept per entrypoint in a new EntrypointTotals row in the event database, written in the transaction that stores the event, so retention leaves it in place. The count is one query per page, grouped by entrypoint, over a new events index on entrypoint_id, deleted_at, resubmitted_from_id and created_at. Resubmitted copies did not arrive on the URL and count in neither. Pre-1.0: the table and index go into the schema in place. Model: opus-5-5
112 lines
2.8 KiB
Go
112 lines
2.8 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/dustin/go-humanize"
|
|
"gorm.io/gorm"
|
|
"sneak.berlin/go/webhooker/internal/database"
|
|
)
|
|
|
|
// EntrypointView is the display-safe projection of an entrypoint for
|
|
// the UI, in the same way delivery.TargetView is one for a target.
|
|
type EntrypointView struct {
|
|
ID string
|
|
Path string
|
|
Description string
|
|
Active bool
|
|
|
|
// Events is how many events arrived on the entrypoint's URL within
|
|
// the webhook's retention period. LastEvent is when the newest
|
|
// event ever to arrive on it did, relative, and LastEventUTC the
|
|
// full time; both are empty when none ever did.
|
|
Events int64
|
|
LastEvent string
|
|
LastEventUTC string
|
|
}
|
|
|
|
// entrypointEvents is one entrypoint's count read by
|
|
// addEntrypointEvents.
|
|
type entrypointEvents struct {
|
|
EntrypointID string
|
|
Events int64
|
|
}
|
|
|
|
// NewEntrypointViews projects entrypoints for rendering.
|
|
func NewEntrypointViews(
|
|
entrypoints []database.Entrypoint,
|
|
) []EntrypointView {
|
|
views := make([]EntrypointView, 0, len(entrypoints))
|
|
|
|
for i := range entrypoints {
|
|
e := &entrypoints[i]
|
|
|
|
views = append(views, EntrypointView{
|
|
ID: e.ID,
|
|
Path: e.Path,
|
|
Description: e.Description,
|
|
Active: e.Active,
|
|
})
|
|
}
|
|
|
|
return views
|
|
}
|
|
|
|
// addEntrypointEvents fills in each view's event figures from the
|
|
// webhook's event database: when the last event arrived on its URL,
|
|
// from its EntrypointTotals row, and how many events arrived on it
|
|
// since the webhook's retention cutoff, counted in one query over the
|
|
// events' entrypoint_id index. Resubmitted copies did not arrive on
|
|
// the URL and are left out of both.
|
|
func addEntrypointEvents(
|
|
webhookDB *gorm.DB,
|
|
webhook *database.Webhook,
|
|
views []EntrypointView,
|
|
now time.Time,
|
|
) error {
|
|
ids := make([]string, len(views))
|
|
byID := make(map[string]*EntrypointView, len(views))
|
|
|
|
for i := range views {
|
|
ids[i] = views[i].ID
|
|
byID[views[i].ID] = &views[i]
|
|
}
|
|
|
|
var totals []database.EntrypointTotals
|
|
|
|
err := webhookDB.Where("entrypoint_id IN ?", ids).Find(&totals).Error
|
|
if err != nil {
|
|
return fmt.Errorf("reading entrypoint totals: %w", err)
|
|
}
|
|
|
|
query := webhookDB.Model(&database.Event{}).
|
|
Select("entrypoint_id, count(*) AS events").
|
|
Where("entrypoint_id IN ? AND resubmitted_from_id IS NULL", ids)
|
|
|
|
cutoff, finite := webhook.RetentionCutoff(now)
|
|
if finite {
|
|
query = query.Where("created_at >= ?", cutoff)
|
|
}
|
|
|
|
var counts []entrypointEvents
|
|
|
|
err = query.Group("entrypoint_id").Find(&counts).Error
|
|
if err != nil {
|
|
return fmt.Errorf("counting events by entrypoint: %w", err)
|
|
}
|
|
|
|
for _, row := range totals {
|
|
view := byID[row.EntrypointID]
|
|
view.LastEvent = humanize.Time(row.LastEventAt)
|
|
view.LastEventUTC =
|
|
row.LastEventAt.UTC().Format(time.DateTime) + " UTC"
|
|
}
|
|
|
|
for _, row := range counts {
|
|
byID[row.EntrypointID].Events = row.Events
|
|
}
|
|
|
|
return nil
|
|
}
|