feat: add Slack target type for incoming webhook notifications #47
Reference in New Issue
Block a user
Delete Branch "feature/slack-target-type"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds a new
slacktarget type that sends webhook events as formatted messages to any Slack-compatible incoming webhook URL (Slack, Mattermost, and other compatible services).closes #44
What it does
When a webhook event is received, the Slack target:
{"text": "..."}JSON payload to the configured webhook URLChanges
internal/database/model_target.go— AddTargetTypeSlackconstantinternal/delivery/engine.go— AddSlackTargetConfigstruct,deliverSlackmethod,FormatSlackMessagefunction (exported),parseSlackConfighelper. Route slack targets inprocessDeliveryswitch.internal/handlers/source_management.go— Handleslacktype inHandleTargetCreate, buildingwebhook_urlconfig from the URL form fieldtemplates/source_detail.html— Add "Slack" option to target type dropdown with URL field and helper textREADME.md— Document the new target type, update roadmapTests
TestParseSlackConfig_Valid/_Empty/_MissingWebhookURL— Config parsingTestFormatSlackMessage_JSONBody/_NonJSONBody/_EmptyBody/_LargeJSONTruncated— Message formattingTestDeliverSlack_Success/_Failure/_InvalidConfig— End-to-end deliveryTestProcessDelivery_RoutesToSlack— Routing from processDelivery switchAll existing tests continue to pass.
docker build .(which runsmake check) passes clean.Code Review: PR #47 — Slack target type
Policy Divergences
1. FAIL — Duplicate
name="url"form fields break Slack target creation (templates/source_detail.html, lines 101 and 108)The "Add target" form contains TWO
<input>elements withname="url":x-show="targetType === 'http'"div): HTTP URL fieldx-show="targetType === 'slack'"div): Slack webhook URL fieldAlpine.js
x-showonly toggles CSSdisplay: none— both inputs remain in the DOM and are submitted with the form. When a user selects "Slack" and fills in the Slack URL, the empty HTTP URL field (first in DOM order) is also submitted. Go'sr.FormValue("url")returns the first value — the empty string from the hidden HTTP input. The handler then hitsif url == "" { http.Error(w, "Webhook URL is required for Slack targets", ...) }and rejects the request.Result: Creating Slack targets through the web UI is impossible. The core feature cannot be used.
2. FAIL — Message formatting uses Markdown instead of Slack mrkdwn (
internal/delivery/engine.go,FormatSlackMessage)The function uses standard Markdown formatting:
**Webhook Event Received**— Slack mrkdwn uses*bold*, not**bold**```json— Slack doesn't support language hints in code blocksWhen sent to actual Slack, the message will display literal
**characters instead of bold text. Issue #44 explicitly requires a "slack format notification message" — the format must render correctly in Slack. This works in Mattermost (which supports standard Markdown) but not in the primary target system named in the feature.3. FAIL — README line exceeds 80-column hard wrap (
README.md)The added line
Slack targets, database targets (local operations), and log targets (stdout) do not useis 83 characters. REPO_POLICIES.md requiresproseWrap: alwaysfor Markdown (hard-wrap at 80 columns).Requirements Checklist (issue #44)
SlackTargetConfigwithwebhook_url{"text": "..."}payload**bold**(renders as literal asterisks in Slack) instead of*bold*name="url"fields prevent creationBuild Result
docker build .passes. All tests pass. No linter/CI/test config changes detected.Verdict: FAIL
Two blocking issues:
Plus a minor README wrapping violation.
Rework Summary for PR #47
Fixed all three review issues from the code review:
1. Duplicate
name="url"form fields (FIXED)Added Alpine.js
:disabledbindings to both URL input fields intemplates/source_detail.html. The HTTP URL input is now disabled whentargetType !== 'http', and the Slack URL input is disabled whentargetType !== 'slack'. Disabled inputs are excluded from form submission, sor.FormValue("url")now correctly returns only the active input's value. Slack target creation through the web UI now works.2. Markdown formatting → Slack mrkdwn (FIXED)
Updated
FormatSlackMessageininternal/delivery/engine.go:**bold**(Markdown) to*bold*(Slack mrkdwn) for all bold text```json→```(Slack doesn't support language-hinted code blocks)Updated test assertions in
internal/delivery/engine_test.goto match the corrected formatting (both unit tests and integration testTestDeliverSlack_Success).3. README line wrapping (FIXED)
Rewrapped the 83-character line in
README.mdto stay within the 80-column hard wrap requirement.Build
docker build .passes. All tests pass.Code Review: PR #47 — Slack target type (post-rework)
Policy Divergences
No policy violations found.
@sha256:✅.golangci.yml,Makefile, CI config, or test assertions ✅Previous Review Fixes Verified
Duplicate
name="url"form fields — FIXED. Both URL inputs now have:disabledAlpine.js bindings (:disabled="targetType !== 'http'"and:disabled="targetType !== 'slack'"). Disabled inputs are excluded from form submission, sor.FormValue("url")returns only the active input's value.Markdown formatting → Slack mrkdwn — FIXED.
FormatSlackMessageuses*bold*(single asterisks) for Slack mrkdwn. Code blocks use plain```without language hints. Tests explicitly assertNotContains "**Webhook Event Received**"andNotContains "```json".README line exceeding 80-column wrap — FIXED. All added/modified lines verified to be within 80 columns.
Requirements Checklist (issue #44)
SlackTargetConfigwithwebhook_urlfield{"text": "..."}JSON payloadjson.Indentwith 2-space indentation*bold*mrkdwn,_italic_for empty body:disabledbindings prevent form conflictsTest Coverage
SlackTargetConfig(exported type): tested viaTestParseSlackConfig_Valid,TestDeliverSlack_SuccessFormatSlackMessage(exported function): tested with JSON body, non-JSON body, empty body, large JSON truncationdeliverSlack(unexported): tested viaTestDeliverSlack_Success,_Failure,_InvalidConfigparseSlackConfig(unexported): tested viaTestParseSlackConfig_Valid,_Empty,_MissingWebhookURLprocessDeliveryrouting: tested viaTestProcessDelivery_RoutesToSlackBuild Result
docker build .passes (includesmake check: fmt-check, lint, test, build).Code Quality Notes
io.LimitReaderfor safetyVerdict: PASS
All three previous review issues are properly fixed. The implementation is correct, well-tested, follows all repo policies, and fully addresses issue #44. Branch is already up-to-date with
main; no rebase needed.