Compare commits

1 Commits
Author SHA1 Message Date
clawbot 1adb625a7a Show each entrypoint's last event and event count on the webhook page (closes #393)
check / check (push) Successful in 3m8s
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
2026-10-02 20:52:16 +00:00
+29 -30
View File
@@ -1892,19 +1892,18 @@ that arrives on its URL:
| `entrypoint_id` | UUID | The entrypoint (primary key) |
| `last_event_at` | timestamp | When the newest event arrived on its URL; a resubmitted event leaves it as it is, and so does retention |
Each count changes in the transaction that writes or deletes the rows it
counts, and each `last_event_at` in the transaction that stores the event. The pane's lifetime events are `events`, and its lifetime
deliveries and failures are `deliveries` and `failed` summed over the
targets; each figure within retention is the same less what retention
removed, so neither needs the rows themselves. Its last event is
`last_event_at` in `EventTotals`, so it still shows once retention has
removed every event; each entrypoint's last event, from `EntrypointTotals`,
does too. Its last-10-minutes and
last-24-hours figures are counted from the `events` and `deliveries`
indexes over just that window, the deliveries in one query grouped by
target. Its failure percentage for a window is the deliveries that became
`failed` in it out of all that became `delivered` or `failed` in it, and
a dash when none did.
Each count changes in the transaction that writes or deletes the rows it counts,
and each `last_event_at` in the transaction that stores the event. The pane's
lifetime events are `events`, and its lifetime deliveries and failures are
`deliveries` and `failed` summed over the targets; each figure within retention
is the same less what retention removed, so neither needs the rows themselves.
Its last event is `last_event_at` in `EventTotals`, so it still shows once
retention has removed every event; each entrypoint's last event, from
`EntrypointTotals`, does too. Its last-10-minutes and last-24-hours figures are
counted from the `events` and `deliveries` indexes over just that window, the
deliveries in one query grouped by target. Its failure percentage for a window
is the deliveries that became `failed` in it out of all that became `delivered`
or `failed` in it, and a dash when none did.
The webhook list at `/hooks` shows three of the pane's figures for each
webhook: its events within retention and its last event, both from
@@ -1929,27 +1928,28 @@ tags, so `AutoMigrate` creates them on a fresh database:
| `events` | `entrypoint_id`, `deleted_at`, `resubmitted_from_id`, `created_at` | The webhook page's entrypoint list, which counts the events that arrived on each entrypoint's URL within the retention period |
| `events` | `created_at` | Retention, which selects expired events by age |
GORM's soft delete adds `deleted_at IS NULL` to these queries; retention
leaves it out. SQLite keeps no statistics on these tables, and without them it
rates the `deleted_at` index, which every live row matches, above an index on
a column matched against several values or compared with a range. So every
index but the last also covers `deleted_at`. It comes second in the `event_id`
and `delivery_id` indexes, so that retention can use them without it. The event
GORM's soft delete adds `deleted_at IS NULL` to these queries; retention leaves
it out. SQLite keeps no statistics on these tables, and without them it rates
the `deleted_at` index, which every live row matches, above an index on a column
matched against several values or compared with a range. So every index but the
last also covers `deleted_at`. It comes second in the `event_id` and
`delivery_id` indexes, so that retention can use them without it. The event
log's count, the one query on the `resubmitted_from_id` index, always carries
`deleted_at IS NULL` and uses both columns. The entrypoint list's count, the one
query on the `entrypoint_id` index, uses all four, `resubmitted_from_id IS NULL`
leaving out resubmitted copies and `created_at` last because it compares it with
a range (`>=`). In the statistics' `events` index
`deleted_at` comes first, because they compare `created_at` with a range (`>=`)
and SQLite narrows by a range only on the last column it uses.
a range (`>=`). In the statistics' `events` index `deleted_at` comes first,
because they compare `created_at` with a range (`>=`) and SQLite narrows by a
range only on the last column it uses.
#### Common Fields
Every entity except `Setting`, `EventTotals`, `TargetTotals` and
`EntrypointTotals` includes these fields from `BaseModel`. `Setting` is a
bare key-value row with no `id`, no timestamps and no soft delete, and the
three totals tables hold counts and `last_event_at`, keyed by a numeric
`id`, by `target_id` and by `entrypoint_id`:
`EntrypointTotals` includes these fields from `BaseModel`. `Setting` is a bare
key-value row with no `id`, no timestamps and no soft delete. Of the three
totals tables, `event_totals` holds counts and `last_event_at`, keyed by a
numeric `id`; `target_totals` holds counts, keyed by `target_id`; and
`entrypoint_totals` holds `last_event_at`, keyed by `entrypoint_id`:
| Field | Type | Description |
| ------------ | --------- | ----------- |
@@ -3334,10 +3334,9 @@ check, see [The login endpoint](#the-login-endpoint).
`ENTRYPOINT` script, which sets the data directory's owner and mode
before the app starts; the image's health check; and `docker exec`,
unless given `--user`
- GORM soft deletes on every entity that carries `BaseModel`, which is
all of them but `Setting`, `EventTotals`, `TargetTotals` and
`EntrypointTotals` (data
preserved for audit)
- GORM soft deletes on every entity that carries `BaseModel`, which is all of
them but `Setting`, `EventTotals`, `TargetTotals` and `EntrypointTotals`
(data preserved for audit)
### Shutdown