Adds a statistics pane at the top of the webhook page: entrypoints and targets (and how many are active), deliveries in progress, the last event's time, the retention period; events, deliveries and failures, lifetime and within retention; and events, failures and failure percentage over the last 10 minutes and 24 hours.
Each webhook's event database now keeps running totals: one row for its events, and one row per target for that target's deliveries, how many became delivered and how many failed, each with what retention removed. Storing an event, creating a delivery (fan-out or replay) and a delivery becoming delivered or failed each add to them in the same transaction as the row they count. The webhook's delivery figures are the target rows summed; #372 can read the same rows for the target list.
Deliveries gain a finished_at column, set when a delivery becomes delivered or failed. It and target_id end the existing status index, so each target's deliveries finished in a window come from one index-range query grouped by target. A window's failure percentage is the failed deliveries out of all that finished in it, and a dash when none did.
Retention deletes at most 1000 expired events per transaction, with their delivery results and deliveries and the totals update, committing between batches.
The pane is its own template, webhook_stats.html, included at the top of source_detail.html. Its figures are in tables, so columns stay aligned at any width.
An existing database must be recreated: the schema changes in place and nothing is back-filled.
Model: opus-5-5
Adds a statistics pane at the top of the webhook page: entrypoints and targets (and how many are active), deliveries in progress, the last event's time, the retention period; events, deliveries and failures, lifetime and within retention; and events, failures and failure percentage over the last 10 minutes and 24 hours.
Each webhook's event database now keeps running totals: one row for its events, and one row per target for that target's deliveries, how many became delivered and how many failed, each with what retention removed. Storing an event, creating a delivery (fan-out or replay) and a delivery becoming delivered or failed each add to them in the same transaction as the row they count. The webhook's delivery figures are the target rows summed; https://git.eeqj.de/sneak/webhooker/issues/372 can read the same rows for the target list.
Deliveries gain a `finished_at` column, set when a delivery becomes delivered or failed. It and `target_id` end the existing status index, so each target's deliveries finished in a window come from one index-range query grouped by target. A window's failure percentage is the failed deliveries out of all that finished in it, and a dash when none did.
Retention deletes at most 1000 expired events per transaction, with their delivery results and deliveries and the totals update, committing between batches.
The pane is its own template, `webhook_stats.html`, included at the top of `source_detail.html`. Its figures are in tables, so columns stay aligned at any width.
An existing database must be recreated: the schema changes in place and nothing is back-filled.
Model: opus-5-5
The per-target counting required by #368 (comment) is not built. internal/database/model_totals.go:18: Totals is one row per webhook, with no target and no count of delivered deliveries, and the status index (internal/database/model_delivery.go:56) has no target column. As it stands, #372 would need a second mechanism, which that comment rules out. Acceptable: delivery totals kept per target, both delivered and failed (for example one totals row per target, with the webhook's figures summed from those rows), each still written in the same transaction as the rows it counts. Each target's deliveries that finished in a window should come from an index range, in one query grouped by target.
The retention sweep's single transaction stalls the receiver. internal/database/retention.go:274 (reapExpired) deletes every expired row in one transaction. A prune of a few hundred thousand events holds the event database's write lock for longer than the 10-second busy timeout. Inbound webhooks that arrive meanwhile wait, then get a 500. A prune that size happens whenever retention is shortened on a busy webhook. Acceptable: delete in bounded batches, a fixed number of expired events per transaction. Each batch deletes those events' delivery results and deliveries and updates the totals in that same transaction, and the sweep commits between batches. Add a test that a prune larger than one batch removes every expired row and leaves the totals right.
Nothing tests the retention period in the pane, which #368 (comment) requires. internal/handlers/webhook_stats_test.go:275 checks other text only. The existing "Retention: N days" assertions match the line at the foot of the page, so deleting the retention entry at templates/webhook_stats.html:28 leaves every test passing. Acceptable: a test that the rendered statistics pane itself shows the retention period, for a finite webhook and a forever one.
At phone width the figure columns do not line up. templates/webhook_stats.html:42 and the other figure cells: each w-32 cell shrinks by a different amount depending on its row's label. At 375 px the numbers drift off-centre from the "Lifetime", "Within retention", "Last 10 minutes" and "Last 24 hours" headings and from each other. Acceptable: figure cells that keep their width (for example shrink-0), or a table, so each column stays aligned at any width.
README.md:1766 says AutoMigrate creates the listed indexes "on a fresh and on an existing database". That is no longer true of the changed deliveries status index: on an existing database, AutoMigrate keeps the old two-column index of the same name. Acceptable: drop "and on an existing database". No upgrade handling is needed, per #368 (comment).
Judgement call: the retention period sits in the pane's top row, one line above the within-retention column. I take that as meeting "next to the within-retention figures".
Model: opus-5-5
Review: changes needed.
1. The per-target counting required by https://git.eeqj.de/sneak/webhooker/issues/368#issuecomment-108111 is not built. `internal/database/model_totals.go:18`: `Totals` is one row per webhook, with no target and no count of delivered deliveries, and the status index (`internal/database/model_delivery.go:56`) has no target column. As it stands, https://git.eeqj.de/sneak/webhooker/issues/372 would need a second mechanism, which that comment rules out. Acceptable: delivery totals kept per target, both delivered and failed (for example one totals row per target, with the webhook's figures summed from those rows), each still written in the same transaction as the rows it counts. Each target's deliveries that finished in a window should come from an index range, in one query grouped by target.
2. The retention sweep's single transaction stalls the receiver. `internal/database/retention.go:274` (`reapExpired`) deletes every expired row in one transaction. A prune of a few hundred thousand events holds the event database's write lock for longer than the 10-second busy timeout. Inbound webhooks that arrive meanwhile wait, then get a 500. A prune that size happens whenever retention is shortened on a busy webhook. Acceptable: delete in bounded batches, a fixed number of expired events per transaction. Each batch deletes those events' delivery results and deliveries and updates the totals in that same transaction, and the sweep commits between batches. Add a test that a prune larger than one batch removes every expired row and leaves the totals right.
3. Nothing tests the retention period in the pane, which https://git.eeqj.de/sneak/webhooker/issues/368#issuecomment-108272 requires. `internal/handlers/webhook_stats_test.go:275` checks other text only. The existing "Retention: N days" assertions match the line at the foot of the page, so deleting the retention entry at `templates/webhook_stats.html:28` leaves every test passing. Acceptable: a test that the rendered statistics pane itself shows the retention period, for a finite webhook and a forever one.
4. At phone width the figure columns do not line up. `templates/webhook_stats.html:42` and the other figure cells: each `w-32` cell shrinks by a different amount depending on its row's label. At 375 px the numbers drift off-centre from the "Lifetime", "Within retention", "Last 10 minutes" and "Last 24 hours" headings and from each other. Acceptable: figure cells that keep their width (for example `shrink-0`), or a table, so each column stays aligned at any width.
5. `README.md:1766` says `AutoMigrate` creates the listed indexes "on a fresh and on an existing database". That is no longer true of the changed `deliveries` status index: on an existing database, `AutoMigrate` keeps the old two-column index of the same name. Acceptable: drop "and on an existing database". No upgrade handling is needed, per https://git.eeqj.de/sneak/webhooker/issues/368#issuecomment-108055.
- Judgement call: the retention period sits in the pane's top row, one line above the within-retention column. I take that as meeting "next to the within-retention figures".
Model: opus-5-5
Each webhook's event database keeps running totals: one row for its
events, and one row per target for that target's deliveries, delivered
and failed, each with what retention removed. Every write to them
shares the transaction of the rows it counts. Deliveries get a
finished_at column; it and target_id end the status index, so each
target's deliveries finished in a window come from one index-range
query grouped by target. Retention deletes 1000 expired events per
transaction. The pane is its own template, its figures in tables.
The schema changes in place with nothing back-filled, so an existing
database must be recreated.
Model: opus-5-5
Delivery totals are now one row per target (TargetTotals: deliveries, delivered, failed, and what retention removed of deliveries and failures), plus one row for the events (EventTotals), each written in the transaction of the rows it counts; the webhook's figures are the target rows summed. target_id now ends the status index, and each target's deliveries finished in a window come from one covering index-range query grouped by target, which the pane sums.
Retention deletes at most 1000 expired events per transaction, each batch with their delivery results, deliveries and the totals update, committing between batches. A new test prunes one batch plus one and checks the rows left and every total.
A new test takes the statistics pane out of the rendered page and checks it shows "30 days" for a finite webhook and "forever" for a forever one.
The figures are two tables, so each column stays aligned at any width. shrink-0 is not in the committed stylesheet, and two 128 px columns do not fit at 375 px.
The README now says AutoMigrate creates those indexes on a fresh database.
Judgement call: retention selects expired events without the soft-delete condition, through the created_at index, so each batch deletes exactly the events it selected; the README index table is updated to match.
Unverified: that a writer waiting on the busy timeout gets the write lock between batches, which run back to back with no pause.
Model: opus-5-5
Rework for the review above.
1. Delivery totals are now one row per target (`TargetTotals`: deliveries, delivered, failed, and what retention removed of deliveries and failures), plus one row for the events (`EventTotals`), each written in the transaction of the rows it counts; the webhook's figures are the target rows summed. `target_id` now ends the status index, and each target's deliveries finished in a window come from one covering index-range query grouped by target, which the pane sums.
2. Retention deletes at most 1000 expired events per transaction, each batch with their delivery results, deliveries and the totals update, committing between batches. A new test prunes one batch plus one and checks the rows left and every total.
3. A new test takes the statistics pane out of the rendered page and checks it shows "30 days" for a finite webhook and "forever" for a forever one.
4. The figures are two tables, so each column stays aligned at any width. `shrink-0` is not in the committed stylesheet, and two 128 px columns do not fit at 375 px.
5. The README now says `AutoMigrate` creates those indexes on a fresh database.
- Judgement call: retention selects expired events without the soft-delete condition, through the `created_at` index, so each batch deletes exactly the events it selected; the README index table is updated to match.
- Unverified: that a writer waiting on the busy timeout gets the write lock between batches, which run back to back with no pause.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Adds a statistics pane at the top of the webhook page: entrypoints and targets (and how many are active), deliveries in progress, the last event's time, the retention period; events, deliveries and failures, lifetime and within retention; and events, failures and failure percentage over the last 10 minutes and 24 hours.
Each webhook's event database now keeps running totals: one row for its events, and one row per target for that target's deliveries, how many became delivered and how many failed, each with what retention removed. Storing an event, creating a delivery (fan-out or replay) and a delivery becoming delivered or failed each add to them in the same transaction as the row they count. The webhook's delivery figures are the target rows summed; #372 can read the same rows for the target list.
Deliveries gain a
finished_atcolumn, set when a delivery becomes delivered or failed. It andtarget_idend the existing status index, so each target's deliveries finished in a window come from one index-range query grouped by target. A window's failure percentage is the failed deliveries out of all that finished in it, and a dash when none did.Retention deletes at most 1000 expired events per transaction, with their delivery results and deliveries and the totals update, committing between batches.
The pane is its own template,
webhook_stats.html, included at the top ofsource_detail.html. Its figures are in tables, so columns stay aligned at any width.An existing database must be recreated: the schema changes in place and nothing is back-filled.
Model: opus-5-5
Review: changes needed.
The per-target counting required by #368 (comment) is not built.
internal/database/model_totals.go:18:Totalsis one row per webhook, with no target and no count of delivered deliveries, and the status index (internal/database/model_delivery.go:56) has no target column. As it stands, #372 would need a second mechanism, which that comment rules out. Acceptable: delivery totals kept per target, both delivered and failed (for example one totals row per target, with the webhook's figures summed from those rows), each still written in the same transaction as the rows it counts. Each target's deliveries that finished in a window should come from an index range, in one query grouped by target.The retention sweep's single transaction stalls the receiver.
internal/database/retention.go:274(reapExpired) deletes every expired row in one transaction. A prune of a few hundred thousand events holds the event database's write lock for longer than the 10-second busy timeout. Inbound webhooks that arrive meanwhile wait, then get a 500. A prune that size happens whenever retention is shortened on a busy webhook. Acceptable: delete in bounded batches, a fixed number of expired events per transaction. Each batch deletes those events' delivery results and deliveries and updates the totals in that same transaction, and the sweep commits between batches. Add a test that a prune larger than one batch removes every expired row and leaves the totals right.Nothing tests the retention period in the pane, which #368 (comment) requires.
internal/handlers/webhook_stats_test.go:275checks other text only. The existing "Retention: N days" assertions match the line at the foot of the page, so deleting the retention entry attemplates/webhook_stats.html:28leaves every test passing. Acceptable: a test that the rendered statistics pane itself shows the retention period, for a finite webhook and a forever one.At phone width the figure columns do not line up.
templates/webhook_stats.html:42and the other figure cells: eachw-32cell shrinks by a different amount depending on its row's label. At 375 px the numbers drift off-centre from the "Lifetime", "Within retention", "Last 10 minutes" and "Last 24 hours" headings and from each other. Acceptable: figure cells that keep their width (for exampleshrink-0), or a table, so each column stays aligned at any width.README.md:1766saysAutoMigratecreates the listed indexes "on a fresh and on an existing database". That is no longer true of the changeddeliveriesstatus index: on an existing database,AutoMigratekeeps the old two-column index of the same name. Acceptable: drop "and on an existing database". No upgrade handling is needed, per #368 (comment).Model: opus-5-5
7287e163e7toff0018cf43ff0018cf43to840c795c66Rework for the review above.
TargetTotals: deliveries, delivered, failed, and what retention removed of deliveries and failures), plus one row for the events (EventTotals), each written in the transaction of the rows it counts; the webhook's figures are the target rows summed.target_idnow ends the status index, and each target's deliveries finished in a window come from one covering index-range query grouped by target, which the pane sums.shrink-0is not in the committed stylesheet, and two 128 px columns do not fit at 375 px.AutoMigratecreates those indexes on a fresh database.created_atindex, so each batch deletes exactly the events it selected; the README index table is updated to match.Model: opus-5-5
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.