fix(me): show activity on closed/merged items in dashboard

The activity feed and since-last-check inbox previously filtered to
only open items via state = 'opened' checks in the SQL subqueries.
This meant comments on merged MRs (post-merge follow-ups, questions)
and closed issues were silently dropped from the feed.

Remove the state filter from the association checks in both
query_activity() and query_since_last_check(). The user-association
checks (assigned, authored, reviewing) remain — activity still only
appears for items the user is connected to, regardless of state.

The simplified subqueries also eliminate unnecessary JOINs to the
issues/merge_requests tables that were only needed for the state
check, resulting in slightly more efficient index-only scans on
issue_assignees and mr_reviewers.

Add 4 tests covering: merged MR (authored), closed MR (reviewer),
closed issue (assignee), and merged MR in the since-last-check inbox.
This commit is contained in:
teernisse
2026-03-10 11:06:53 -04:00
parent 06852e90a6
commit 62fbd7275e
2 changed files with 122 additions and 15 deletions

View File

@@ -362,19 +362,18 @@ pub fn query_activity(
let project_clause = build_project_clause_at("p.id", project_ids, 3);
// Build the "my items" subquery fragments for issue/MR association checks.
// These ensure we only see activity on items CURRENTLY associated with the user
// AND currently open (AC-3.6). Without the state filter, activity would include
// events on closed/merged items that don't appear in the dashboard lists.
// These ensure we only see activity on items associated with the user,
// regardless of state (open, closed, or merged). Comments on merged MRs
// and closed issues are still relevant (follow-up discussions, post-merge
// questions, etc.).
let my_issue_check = "EXISTS (
SELECT 1 FROM issue_assignees ia
JOIN issues i2 ON ia.issue_id = i2.id
WHERE ia.issue_id = {entity_issue_id} AND ia.username = ?1 AND i2.state = 'opened'
WHERE ia.issue_id = {entity_issue_id} AND ia.username = ?1
)";
let my_mr_check = "(
EXISTS (SELECT 1 FROM merge_requests mr2 WHERE mr2.id = {entity_mr_id} AND mr2.author_username = ?1 AND mr2.state = 'opened')
EXISTS (SELECT 1 FROM merge_requests mr2 WHERE mr2.id = {entity_mr_id} AND mr2.author_username = ?1)
OR EXISTS (SELECT 1 FROM mr_reviewers rv
JOIN merge_requests mr3 ON rv.merge_request_id = mr3.id
WHERE rv.merge_request_id = {entity_mr_id} AND rv.username = ?1 AND mr3.state = 'opened')
WHERE rv.merge_request_id = {entity_mr_id} AND rv.username = ?1)
)";
// Source 1: Human comments on my items
@@ -574,7 +573,7 @@ struct RawSinceCheckRow {
/// Query actionable events from others since `cursor_ms`.
/// Returns events from three sources:
/// 1. Others' comments on my open items
/// 1. Others' comments on my items (any state)
/// 2. @mentions on any item (not restricted to my items)
/// 3. Assignment/review-request system notes mentioning me
pub fn query_since_last_check(
@@ -583,19 +582,18 @@ pub fn query_since_last_check(
cursor_ms: i64,
) -> Result<Vec<SinceCheckGroup>> {
// Build the "my items" subquery fragments (reused from activity).
// No state filter: comments on closed/merged items are still actionable.
let my_issue_check = "EXISTS (
SELECT 1 FROM issue_assignees ia
JOIN issues i2 ON ia.issue_id = i2.id
WHERE ia.issue_id = {entity_issue_id} AND ia.username = ?1 AND i2.state = 'opened'
WHERE ia.issue_id = {entity_issue_id} AND ia.username = ?1
)";
let my_mr_check = "(
EXISTS (SELECT 1 FROM merge_requests mr2 WHERE mr2.id = {entity_mr_id} AND mr2.author_username = ?1 AND mr2.state = 'opened')
EXISTS (SELECT 1 FROM merge_requests mr2 WHERE mr2.id = {entity_mr_id} AND mr2.author_username = ?1)
OR EXISTS (SELECT 1 FROM mr_reviewers rv
JOIN merge_requests mr3 ON rv.merge_request_id = mr3.id
WHERE rv.merge_request_id = {entity_mr_id} AND rv.username = ?1 AND mr3.state = 'opened')
WHERE rv.merge_request_id = {entity_mr_id} AND rv.username = ?1)
)";
// Source 1: Others' comments on my open items
// Source 1: Others' comments on my items (any state)
let source1 = format!(
"SELECT n.created_at, 'note',
CASE WHEN d.issue_id IS NOT NULL THEN 'issue' ELSE 'mr' END,