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
+14 -16
View File
@@ -348,21 +348,19 @@ func TestEntrypointCopyButtonIsProgressiveEnhancement(t *testing.T) {
)
}
// maxRetriesHelp is the wording both target forms must carry. The
// delivery core makes max_retries attempts in total, not that many
// maxAttemptsHelp is the wording both target forms must carry. The
// delivery core makes max_attempts attempts in total, not that many
// retries on top of a first try (a fresh delivery starts at attempt 1
// and target_http gives up once the attempt number reaches
// max_retries), and 0 is special-cased to a single fire-and-forget
// attempt with no circuit breaker.
const maxRetriesHelp = "How many times each delivery is attempted in " +
"all, the first attempt included. 0 means a single attempt with no " +
"retries and no circuit breaker."
// max_attempts).
const maxAttemptsHelp = "How many times each delivery is attempted in " +
"all, the first attempt included."
// TestTargetFormMaxRetriesCopyMatchesBehaviour pins the max_retries
// TestTargetFormMaxAttemptsCopyMatchesBehaviour pins the max_attempts
// label, "Delivery attempts", and help text on both the create form
// (the add-target form on the webhook detail page) and the edit form,
// so the copy cannot drift back to calling the number a retry count.
func TestTargetFormMaxRetriesCopyMatchesBehaviour(t *testing.T) {
func TestTargetFormMaxAttemptsCopyMatchesBehaviour(t *testing.T) {
t.Parallel()
var h *handlers.Handlers
@@ -394,11 +392,11 @@ func TestTargetFormMaxRetriesCopyMatchesBehaviour(t *testing.T) {
assert.Contains(t, createBody, "Delivery attempts:</label>")
assert.Contains(
t, createBody, maxRetriesHelp,
"the add-target form must explain max_retries as total attempts",
t, createBody, maxAttemptsHelp,
"the add-target form must explain max_attempts as total attempts",
)
// A slack target exercises the same max_retries field while needing
// A slack target exercises the same max_attempts field while needing
// only a URL from the edit template, so the test data stays
// minimal. The Target and TargetForm keys mirror the field names
// the template reads off the handler's values.
@@ -412,8 +410,8 @@ func TestTargetFormMaxRetriesCopyMatchesBehaviour(t *testing.T) {
"Active": true,
},
"TargetForm": map[string]any{
"URL": "https://hooks.slack.com/services/x",
"MaxRetries": "3",
"URL": "https://hooks.slack.com/services/x",
"MaxAttempts": "3",
},
dataKeyError: "",
},
@@ -421,8 +419,8 @@ func TestTargetFormMaxRetriesCopyMatchesBehaviour(t *testing.T) {
assert.Contains(t, editBody, `class="label">Delivery attempts</label>`)
assert.Contains(
t, editBody, maxRetriesHelp,
"the target edit form must explain max_retries as total attempts",
t, editBody, maxAttemptsHelp,
"the target edit form must explain max_attempts as total attempts",
)
}