Store a target's number of delivery attempts, at least one (closes #540)
check / check (push) Canceled after 0s

Target.MaxRetries (max_retries) becomes Target.MaxAttempts
(max_attempts), and the form field, parser and ceiling are renamed to
match. 1 is a single attempt with no circuit breaker; above 1 retries
behind the breaker up to that many attempts. Both target forms refuse
0 and less and default a blank field to 1. The target page shows the
plain number, and the README's two paragraphs on 0 against 1 become
one sentence.

Model: opus-5-5
This commit is contained in:
2026-10-15 10:58:23 +00:00
parent c0f7cc29fd
commit 2fcf3b12c6
37 changed files with 844 additions and 716 deletions
+13 -13
View File
@@ -21,24 +21,24 @@ func TestHandleTargetCreate_EveryType(t *testing.T) {
// fields is the rest of each submission, as a query string.
cases := []struct {
targetType database.TargetType
fields string
wantConfig string
wantRetries int
targetType database.TargetType
fields string
wantConfig string
wantAttempts int
}{
{
database.TargetTypeHTTP, "timeout=12&max_retries=3",
database.TargetTypeHTTP, "timeout=12&max_attempts=3",
`{"url":"` + editOriginalURL + `","timeout":12}`, 3,
},
{
database.TargetTypeSlack, "max_retries=4",
database.TargetTypeSlack, "max_attempts=4",
`{"webhookUrl":"` + editOriginalURL + `"}`, 4,
},
{
database.TargetTypeDatabase, "expiry=720h",
`{"expiry":"720h"}`, 0,
`{"expiry":"720h"}`, 1,
},
{database.TargetTypeLog, "", "", 0},
{database.TargetTypeLog, "", "", 1},
}
for _, tc := range cases {
@@ -62,7 +62,7 @@ func TestHandleTargetCreate_EveryType(t *testing.T) {
targets := targetsForWebhook(t, env.db, webhook.ID)
require.Len(t, targets, 1)
assert.Equal(t, tc.targetType, targets[0].Type)
assert.Equal(t, tc.wantRetries, targets[0].MaxRetries)
assert.Equal(t, tc.wantAttempts, targets[0].MaxAttempts)
if tc.wantConfig == "" {
assert.Empty(t, targets[0].Config)
@@ -90,11 +90,11 @@ func TestHandleTargetCreate_RefusedFormComesBack(t *testing.T) {
{
database.TargetTypeHTTP,
"name=private&url=" + editBlockedURL +
"&timeout=12&max_retries=3",
"&timeout=12&max_attempts=3",
"Invalid target URL",
},
{
database.TargetTypeSlack, "name=no-url&max_retries=4",
database.TargetTypeSlack, "name=no-url&max_attempts=4",
"Webhook URL is required for Slack targets",
},
{
@@ -133,8 +133,8 @@ func TestHandleTargetCreate_RefusedFormComesBack(t *testing.T) {
assert.Contains(t, page, html.EscapeString(tc.reason))
// Each value comes back in a data attribute of the targets
// section named after its field (max_retries as
// data-max-retries), except url, which comes back in
// section named after its field (max_attempts as
// data-max-attempts), except url, which comes back in
// data-destination; templates/source_detail.html says why.
for field := range typed {
attr := "data-" + strings.ReplaceAll(field, "_", "-")