feat(me): add 30-day mention age cutoff to filter stale @-mentions

Previously, query_mentioned_in returned mentions from any time in the
entity's history as long as the entity was still open (or recently closed).
This caused noise: a mention from 6 months ago on a still-open issue would
appear in the dashboard indefinitely.

Now the SQL filters notes by created_at > mention_cutoff_ms, defaulting to
30 days. The recency_cutoff (7 days) still governs closed/merged entity
visibility — this new cutoff governs mention note age on open entities.

Signature change: query_mentioned_in gains a mention_cutoff_ms parameter.
All existing test call sites updated. Two new tests verify the boundary:
- mentioned_in_excludes_old_mention_on_open_issue (45-day mention filtered)
- mentioned_in_includes_recent_mention_on_open_issue (5-day mention kept)
This commit is contained in:
teernisse
2026-03-12 10:07:07 -04:00
parent 7e5ffe35d3
commit 9c909df6b2
3 changed files with 67 additions and 16 deletions

View File

@@ -946,7 +946,7 @@ fn mentioned_in_finds_mention_on_unassigned_issue() {
);
let recency_cutoff = now_ms() - 7 * 24 * 3600 * 1000;
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff).unwrap();
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff, 0).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].entity_type, "issue");
assert_eq!(results[0].iid, 42);
@@ -964,7 +964,7 @@ fn mentioned_in_excludes_assigned_issue() {
insert_note_at(&conn, 200, disc_id, 1, "bob", false, "hey @alice", t);
let recency_cutoff = now_ms() - 7 * 24 * 3600 * 1000;
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff).unwrap();
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff, 0).unwrap();
assert!(results.is_empty(), "should exclude assigned issues");
}
@@ -979,7 +979,7 @@ fn mentioned_in_excludes_authored_issue() {
insert_note_at(&conn, 200, disc_id, 1, "bob", false, "hey @alice", t);
let recency_cutoff = now_ms() - 7 * 24 * 3600 * 1000;
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff).unwrap();
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff, 0).unwrap();
assert!(results.is_empty(), "should exclude authored issues");
}
@@ -995,7 +995,7 @@ fn mentioned_in_finds_mention_on_non_authored_mr() {
insert_note_at(&conn, 200, disc_id, 1, "bob", false, "cc @alice", t);
let recency_cutoff = now_ms() - 7 * 24 * 3600 * 1000;
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff).unwrap();
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff, 0).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].entity_type, "mr");
assert_eq!(results[0].iid, 99);
@@ -1012,7 +1012,7 @@ fn mentioned_in_excludes_authored_mr() {
insert_note_at(&conn, 200, disc_id, 1, "bob", false, "@alice thoughts?", t);
let recency_cutoff = now_ms() - 7 * 24 * 3600 * 1000;
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff).unwrap();
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff, 0).unwrap();
assert!(results.is_empty(), "should exclude authored MRs");
}
@@ -1028,7 +1028,7 @@ fn mentioned_in_excludes_reviewer_mr() {
insert_note_at(&conn, 200, disc_id, 1, "charlie", false, "@alice fyi", t);
let recency_cutoff = now_ms() - 7 * 24 * 3600 * 1000;
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff).unwrap();
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff, 0).unwrap();
assert!(
results.is_empty(),
"should exclude MRs where user is reviewer"
@@ -1052,7 +1052,7 @@ fn mentioned_in_includes_recently_closed_issue() {
insert_note_at(&conn, 200, disc_id, 1, "bob", false, "hey @alice", t);
let recency_cutoff = now_ms() - 7 * 24 * 3600 * 1000;
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff).unwrap();
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff, 0).unwrap();
assert_eq!(results.len(), 1, "recently closed issue should be included");
assert_eq!(results[0].state, "closed");
}
@@ -1074,7 +1074,7 @@ fn mentioned_in_excludes_old_closed_issue() {
insert_note_at(&conn, 200, disc_id, 1, "bob", false, "hey @alice", t);
let recency_cutoff = now_ms() - 7 * 24 * 3600 * 1000;
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff).unwrap();
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff, 0).unwrap();
assert!(results.is_empty(), "old closed issue should be excluded");
}
@@ -1099,7 +1099,7 @@ fn mentioned_in_attention_needs_attention_when_unreplied() {
// alice has NOT replied
let recency_cutoff = now_ms() - 7 * 24 * 3600 * 1000;
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff).unwrap();
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff, 0).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].attention_state, AttentionState::NeedsAttention);
}
@@ -1126,7 +1126,7 @@ fn mentioned_in_attention_awaiting_when_replied() {
insert_note_at(&conn, 201, disc_id, 1, "alice", false, "looks good", t2);
let recency_cutoff = now_ms() - 7 * 24 * 3600 * 1000;
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff).unwrap();
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff, 0).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].attention_state, AttentionState::AwaitingResponse);
}
@@ -1147,7 +1147,7 @@ fn mentioned_in_project_filter() {
insert_note_at(&conn, 201, disc_b, 2, "bob", false, "@alice", t);
let recency_cutoff = now_ms() - 7 * 24 * 3600 * 1000;
let results = query_mentioned_in(&conn, "alice", &[1], recency_cutoff).unwrap();
let results = query_mentioned_in(&conn, "alice", &[1], recency_cutoff, 0).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].project_path, "group/repo-a");
}
@@ -1166,7 +1166,7 @@ fn mentioned_in_deduplicates_multiple_mentions_same_entity() {
insert_note_at(&conn, 201, disc_id, 1, "charlie", false, "@alice +1", t2);
let recency_cutoff = now_ms() - 7 * 24 * 3600 * 1000;
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff).unwrap();
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff, 0).unwrap();
assert_eq!(results.len(), 1, "should deduplicate to one entity");
}
@@ -1190,10 +1190,47 @@ fn mentioned_in_rejects_false_positive_email() {
);
let recency_cutoff = now_ms() - 7 * 24 * 3600 * 1000;
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff).unwrap();
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff, 0).unwrap();
assert!(results.is_empty(), "email-like text should not match");
}
#[test]
fn mentioned_in_excludes_old_mention_on_open_issue() {
let conn = setup_test_db();
insert_project(&conn, 1, "group/repo");
insert_issue(&conn, 10, 1, 42, "someone");
let disc_id = 100;
insert_discussion(&conn, disc_id, 1, None, Some(10));
// Mention from 45 days ago — outside 30-day mention window
let t = now_ms() - 45 * 24 * 3600 * 1000;
insert_note_at(&conn, 200, disc_id, 1, "bob", false, "hey @alice", t);
let recency_cutoff = now_ms() - 7 * 24 * 3600 * 1000;
let mention_cutoff = now_ms() - 30 * 24 * 3600 * 1000;
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff, mention_cutoff).unwrap();
assert!(
results.is_empty(),
"mentions older than 30 days should be excluded"
);
}
#[test]
fn mentioned_in_includes_recent_mention_on_open_issue() {
let conn = setup_test_db();
insert_project(&conn, 1, "group/repo");
insert_issue(&conn, 10, 1, 42, "someone");
let disc_id = 100;
insert_discussion(&conn, disc_id, 1, None, Some(10));
// Mention from 5 days ago — within 30-day window
let t = now_ms() - 5 * 24 * 3600 * 1000;
insert_note_at(&conn, 200, disc_id, 1, "bob", false, "hey @alice", t);
let recency_cutoff = now_ms() - 7 * 24 * 3600 * 1000;
let mention_cutoff = now_ms() - 30 * 24 * 3600 * 1000;
let results = query_mentioned_in(&conn, "alice", &[], recency_cutoff, mention_cutoff).unwrap();
assert_eq!(results.len(), 1, "recent mentions should be included");
}
// ─── Helper Tests ──────────────────────────────────────────────────────────
#[test]