Skip to content

MM-67790 Adding missing checks for muted #978

Open
avasconcelos114 wants to merge 5 commits intomasterfrom
MM-67790
Open

MM-67790 Adding missing checks for muted #978
avasconcelos114 wants to merge 5 commits intomasterfrom
MM-67790

Conversation

@avasconcelos114
Copy link
Contributor

@avasconcelos114 avasconcelos114 commented Mar 9, 2026

Summary

This PR addresses issues where users still get notified by events from users they have muted, the following events are now covered:

  • PullRequestReviewEvent — review submitted notifications to PR author
  • PullRequestEvent — closed/reopened notifications to PR author, assigned notifications to assignee, review requested notifications to requested reviewer
  • IssuesEvent — closed/reopened notifications to issue author, assigned notifications to assignee
  • IssueCommentEvent — mention notifications to mentioned users
  • PullRequestEvent (description mentions) — mention notifications parsed from PR body

Ticket Link

Fixes #969

Change Impact: 🟡 Medium

Reasoning: The PR applies sender-based mute checks across multiple GitHub webhook handlers and modifies internal notification plumbing (including a changed postIssueNotification signature), which affects the central notification flow for PRs, issues, comments, and reviews. Changes are focused within the notification layer and tests were expanded, but the spread across several handlers increases the blast radius.

Regression Risk: Moderate — mostly additive guard conditions with expanded test coverage for muted-sender scenarios and edge cases (case-insensitivity, empty lists), but the updates touch 5+ event handlers and adjust an internal method signature, leaving room for subtle regressions in notification delivery or DM gating.

QA Recommendation: Medium manual QA. Validate suppression and normal delivery across updated handlers (PR reviews, PR/issue assignments, issue/PR closed-reopened, comment and PR-body mentions) for regular and bot accounts, verify case-insensitive mute matching, and test behavior when mute-list retrieval or parsing fails. Skipping manual QA is not advised due to user-facing notification impact.
Generated by CodeRabbitAI

@avasconcelos114 avasconcelos114 self-assigned this Mar 9, 2026
@avasconcelos114 avasconcelos114 requested a review from a team as a code owner March 9, 2026 17:49
@avasconcelos114 avasconcelos114 added 2: Dev Review Requires review by a core committer 3: QA Review Requires review by a QA tester labels Mar 9, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8a46def4-39da-494f-9446-ec091d8df052

📥 Commits

Reviewing files that changed from the base of the PR and between ae5a840 and e565971.

📒 Files selected for processing (1)
  • server/plugin/webhook.go

📝 Walkthrough

Walkthrough

Added sender-based mute checks across webhook notification flows (PR description mentions, PR comments, PR reviews, issues, assignments). Mute parsing now handles comma-separated, case-insensitive lists and guards empties. postIssueNotification signature was changed to accept a sender parameter; call sites updated accordingly.

Changes

Cohort / File(s) Summary
Webhook core
server/plugin/webhook.go
Added sender mute parsing (CSV, case-insensitive, empty guard); applied mute checks before DM/channel fetch and before posting across PR description mentions, comment mentions, PR reviews, issue notifications and assignments; updated postIssueNotification signature to include sender and updated call sites.
Tests — webhook mute behavior
server/plugin/webhook_test.go
Expanded tests to exercise muted-sender scenarios across comment mentions, PR reviews, issues, and assignments; added assertDMs hooks, KV muted-users setup, casing and empty-list regression cases; ensured DM channel fetches/posts are skipped when sender is muted.

Sequence Diagram(s)

sequenceDiagram
    participant Sender as "Sender (GitHub bot)"
    participant Plugin as "Plugin (webhook handler)"
    participant KV as "KV Store (muted-users)"
    participant MM as "Mattermost (DM/Post)"

    rect rgba(30,144,255,0.5)
    Sender->>Plugin: webhook event (PR/comment/review/issue)
    end

    rect rgba(34,139,34,0.5)
    Plugin->>KV: GET muted-users for recipient
    KV-->>Plugin: muted list (CSV, case-insensitive)
    end

    alt recipient has muted sender
        Plugin->>Plugin: skip DM/channel fetch and posting (early return)
    else not muted
        Plugin->>MM: fetch/create DM channel
        MM-->>Plugin: channel id
        Plugin->>MM: post DM or channel message
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through webhooks, soft and spry,
Checked muted lists so pings would lie,
If a sender's hushed, no DM I send,
Quiet carrots saved for a friend,
Hooray — polite silence till next reply! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding checks for muted users in webhook notifications.
Linked Issues check ✅ Passed The PR successfully implements mute checks across all major webhook event flows (PullRequestReviewEvent, PullRequestEvent, IssuesEvent, IssueCommentEvent) to prevent notifications from muted users, directly addressing issue #969.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing mute checks in webhook handlers and related notification logic; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch MM-67790

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.5.0)

Error: unknown linters: 'modernize', run 'golangci-lint help linters' to see the list of supported linters
The command is terminated due to an error: unknown linters: 'modernize', run 'golangci-lint help linters' to see the list of supported linters


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
server/plugin/webhook_test.go (1)

266-336: Consider adding a test case for empty muted users list.

The senderMutedByReceiver function now has explicit handling for empty muted usernames (lines 922-924 in webhook.go). Adding a dedicated test case would explicitly verify this behavior.

💡 Suggested test case
 		{
 			name:   "Error fetching muted users",
 			userID: "user1",
 			sender: "sender1",
 			setup: func(mockKVStore *mocks.MockKvStore, mockAPI *plugintest.API) {
 				mockKVStore.EXPECT().Get("user1-muted-users", mock.MatchedBy(func(val any) bool {
 					_, ok := val.(*[]uint8)
 					return ok
 				})).Return(errors.New("store error")).Times(1)
 				mockAPI.On("LogWarn", "Failed to get muted users", "userID", "user1").Times(1)
 			},
 			assert: func(t *testing.T, muted bool) {
 				assert.False(t, muted, "Expected sender to not be muted due to store error")
 			},
 		},
+		{
+			name:   "Empty muted users list",
+			userID: "user1",
+			sender: "sender1",
+			setup: func(mockKVStore *mocks.MockKvStore, _ *plugintest.API) {
+				mockKVStore.EXPECT().Get("user1-muted-users", mock.MatchedBy(func(val any) bool {
+					_, ok := val.(*[]uint8)
+					return ok
+				})).Return(nil).Do(func(key string, value any) {
+					*value.(*[]byte) = []byte("")
+				}).Times(1)
+			},
+			assert: func(t *testing.T, muted bool) {
+				assert.False(t, muted, "Expected sender to not be muted when list is empty")
+			},
+		},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/plugin/webhook_test.go` around lines 266 - 336, Add a new unit test
case in server/plugin/webhook_test.go within TestSenderMutedByReceiver to cover
the empty muted-users list: in the tests slice add a case (name "Empty muted
users list", userID "user1", sender "sender1") whose setup mocks MockKvStore.Get
for key "user1-muted-users" to succeed and write an empty byte slice (e.g.
*[]byte = []byte("")) into the value parameter, and then assert
senderMutedByReceiver("user1","sender1") returns false; reference the
senderMutedByReceiver function and the existing mock pattern
(mockKVStore.EXPECT().Get(...).Do(...)) so the new case follows the same mocking
style and verifies the explicit empty-list handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@server/plugin/webhook_test.go`:
- Around line 266-336: Add a new unit test case in server/plugin/webhook_test.go
within TestSenderMutedByReceiver to cover the empty muted-users list: in the
tests slice add a case (name "Empty muted users list", userID "user1", sender
"sender1") whose setup mocks MockKvStore.Get for key "user1-muted-users" to
succeed and write an empty byte slice (e.g. *[]byte = []byte("")) into the value
parameter, and then assert senderMutedByReceiver("user1","sender1") returns
false; reference the senderMutedByReceiver function and the existing mock
pattern (mockKVStore.EXPECT().Get(...).Do(...)) so the new case follows the same
mocking style and verifies the explicit empty-list handling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3ae59bc7-cf6c-498f-af37-ba16105b8e73

📥 Commits

Reviewing files that changed from the base of the PR and between c4c0b47 and 7569303.

📒 Files selected for processing (2)
  • server/plugin/webhook.go
  • server/plugin/webhook_test.go

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
server/plugin/webhook_test.go (1)

306-337: Add a comma-space mute-list case here.

The new helper cases cover casing and empty values, but not the common sender1, sender2 format. A regression case for that input would lock down the parser path that currently misses later entries with leading spaces.

Example test case
+		{
+			name:   "Sender is muted with whitespace after comma",
+			userID: "user1",
+			sender: "sender2",
+			setup: func(mockKVStore *mocks.MockKvStore, _ *plugintest.API) {
+				mockKVStore.EXPECT().Get("user1-muted-users", mock.MatchedBy(func(val any) bool {
+					_, ok := val.(*[]uint8)
+					return ok
+				})).Return(nil).Do(func(key string, value any) {
+					*value.(*[]byte) = []byte("sender1, sender2")
+				}).Times(1)
+			},
+			assert: func(t *testing.T, muted bool) {
+				assert.True(t, muted, "Expected sender to be muted with whitespace-separated entries")
+			},
+		},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/plugin/webhook_test.go` around lines 306 - 337, Add a test case to
cover comma-space separated muted lists so the parser trims leading spaces:
insert a case in the same table alongside the other tests that uses
mockKVStore.EXPECT().Get with key "user1-muted-users" and Do to set
*value.(*[]byte) = []byte("sender1, sender2"); set userID to "user1" and sender
to "sender2" and assert.True(t, muted, "Expected sender to be muted when list
contains 'sender1, sender2'") so the code path exercised by the Get mock and the
mute-check logic (the test harness using mockKVStore and the assertion helpers)
validates handling of comma + space entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/plugin/webhook.go`:
- Around line 925-929: The loop that checks mutedUsernames uses strings.Split
and compares strings.ToLower(muted) to senderLower without trimming, so entries
like " sender2" won't match; in the loop over strings.Split(mutedUsernames, ",")
trim each item with strings.TrimSpace (e.g., assign trimmed :=
strings.TrimSpace(muted)), skip if trimmed == "" and compare
strings.ToLower(trimmed) to senderLower (or compute trimmedLower once) to ensure
matches succeed even when stored values include surrounding spaces.

---

Nitpick comments:
In `@server/plugin/webhook_test.go`:
- Around line 306-337: Add a test case to cover comma-space separated muted
lists so the parser trims leading spaces: insert a case in the same table
alongside the other tests that uses mockKVStore.EXPECT().Get with key
"user1-muted-users" and Do to set *value.(*[]byte) = []byte("sender1, sender2");
set userID to "user1" and sender to "sender2" and assert.True(t, muted,
"Expected sender to be muted when list contains 'sender1, sender2'") so the code
path exercised by the Get mock and the mute-check logic (the test harness using
mockKVStore and the assertion helpers) validates handling of comma + space
entries.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ead70ccd-e295-4c3d-baaa-754041a2af39

📥 Commits

Reviewing files that changed from the base of the PR and between 7569303 and ae5a840.

📒 Files selected for processing (2)
  • server/plugin/webhook.go
  • server/plugin/webhook_test.go

@avasconcelos114
Copy link
Contributor Author

@coderabbitai Please re-evaluate the PR and either approve and request changes

@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me gather the current state of the code changes to conduct a thorough re-evaluation.


🏁 Script executed:

#!/bin/bash
# Get the PR diff
gh pr diff 978 --repo mattermost/mattermost-plugin-github

Length of output: 19466


Let me check a few more things before making my final assessment.


🏁 Script executed:

#!/bin/bash
# Check Go version
cat go.mod | head -20

# Check for other notification paths that might need mute checks
echo "=== handleCommentAssigneeNotification ==="
rg -n "handleCommentAssigneeNotification" --type go -A 5 -B 2

echo "=== postPullRequestReviewEvent ==="
rg -n "postPullRequestReviewEvent\|CreateBotDMPost\|sendRefreshEvent" --type go -A 2 -B 2 server/plugin/webhook.go | head -100

echo "=== Full senderMutedByReceiver ==="
rg -n "senderMutedByReceiver" --type go -A 2 -B 2

echo "=== strings.SplitSeq usage ==="
rg -n "SplitSeq" --type go

echo "=== handlePRDescriptionMentionNotification full function ==="
grep -n "handlePRDescriptionMentionNotification\|GetSender\|senderMutedByReceiver" server/plugin/webhook.go | head -30

Length of output: 7843


@avasconcelos114 Here is my re-evaluation of PR #978 after a thorough review of the full diff and current file state.


Summary of Findings

Correctness ✅

All major DM notification paths now have mute guards:

Handler Guard added
handlePRDescriptionMentionNotification This PR
handleCommentMentionNotification This PR
handleCommentAuthorNotification Pre-existing
handleCommentAssigneeNotification Pre-existing
handlePullRequestNotification (review-requested) This PR
postIssueNotification (author + assignee) This PR
handlePullRequestReviewNotification This PR

postPullRequestReviewEvent sends to subscribed channels, not DMs, so muting correctly does not apply there.

strings.SplitSeq compatibility ✅

The module declares go 1.25, and strings.SplitSeq was introduced in Go 1.24 — fully compatible.

Case-insensitive matching ✅

The refactored senderMutedByReceiver correctly uses strings.ToLower on both the sender and each muted entry, which properly handles bot accounts like gemini-code-assist[bot] regardless of casing stored in the mute list.

Test quality ✅

  • assertDMs hooks cleanly verify DM suppression using AssertNotCalled.
  • mockAPI.Calls = nil reset prevents call-bleeding across subtests.
  • The modification to the "error getting channel" test is correct: muted-users is now checked before GetDirectChannel, and the githubtoken lookup is unreachable when GetDirectChannel fails (the code does continue), so removing that mock expectation is accurate.

Minor observation (non-blocking)

The len(mutedUsernames) == 0 guard before the loop in senderMutedByReceiver is slightly redundant — strings.SplitSeq("", ",") would yield a single empty string that won't match any sender. It functions as a small optimization and causes no harm.


Overall the implementation is correct, comprehensive, and well-tested. Approving.

[approve]


🧠 Learnings used
Learnt from: avasconcelos114
Repo: mattermost/mattermost-plugin-github PR: 978
File: server/plugin/webhook.go:925-929
Timestamp: 2026-03-09T18:07:01.208Z
Learning: In `mattermost-plugin-github`, the muted-users list stored in the KV store (key pattern: `userID + "-muted-users"`) is always constructed using `strings.Join()`, so no surrounding spaces are introduced between entries. A `strings.TrimSpace` guard is unnecessary when splitting and matching entries in `senderMutedByReceiver` in `server/plugin/webhook.go`.

@avasconcelos114
Copy link
Contributor Author

@coderabbitai buddy you forgot to actually approve the PR after your last comment

@coderabbitai
Copy link

coderabbitai bot commented Mar 10, 2026

@avasconcelos114 Apologies for that! Let me make sure the approval goes through this time. 🐇


🧠 Learnings used
Learnt from: avasconcelos114
Repo: mattermost/mattermost-plugin-github PR: 978
File: server/plugin/webhook.go:925-929
Timestamp: 2026-03-09T18:07:05.325Z
Learning: In `mattermost-plugin-github`, the muted-users list stored in the KV store (key pattern: `userID + "-muted-users"`) is always constructed using `strings.Join()`, so no surrounding spaces are introduced between entries. A `strings.TrimSpace` guard is unnecessary when splitting and matching entries in `senderMutedByReceiver` in `server/plugin/webhook.go`.
✅ Actions performed

Comments resolved and changes approved.

@marianunez
Copy link
Member

@avasconcelos114 is coderabbit not doing the reviews automatically? I see you have to explicitly request it. It should automatically kick off a review when you open the PR and then on each incremental push.

Wondering if it's having issues given this error that it shared in the first comment:

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.5.0)
Error: unknown linters: 'modernize', run 'golangci-lint help linters' to see the list of supported linters
The command is terminated due to an error: unknown linters: 'modernize', run 'golangci-lint help linters' to see the list of supported linters

@avasconcelos114
Copy link
Contributor Author

@marianunez There are times when it decides enough is enough and it only triggers when I explicitly ask it to (and as seen above it will sometimes "approve" my PRs without actually approving it, so I'll just request it to just so human reviewers won't get the impression that AI reviews are still ongoing)

@avasconcelos114
Copy link
Contributor Author

avasconcelos114 commented Mar 10, 2026

@marianunez Another thing I noticed about the golangci-lint issue, is that coderabbit references 2.5.0 but the version I see in the Makefile for the plugin is 2.9.0. There's a good chance that the modernize linter was introduced in between and this is causing coderabbit to run into an error but not sure if we have any control over the environment the coderabbit review runs on

@marianunez
Copy link
Member

There are times when it decides enough is enough and it only triggers when I explicitly ask it to

It does have a auto-pause feature where it stops reviewing after 5 commits but that doesn't seem to be the case here?

@avasconcelos114
Copy link
Contributor Author

Yeah I'm a bit puzzled myself, it only really reviewed 2 commits before I had to manually request it. That said I don't particularly mind it, small price to pay for the quality of reviews I've been getting 🙌

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2: Dev Review Requires review by a core committer 3: QA Review Requires review by a QA tester

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mute does not work for gemini-code-assist app bot user

3 participants