Commit b5b27ddb48

b5b27ddb48f97f23fc9f4b58fa71e8c45096e4a7

parent: 815ee2641c

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-19 01:48 UTC

store: MRCommentCounts folds conversation and diff-thread comments

Ref #230
internal/store/mrs.go +49
@@ -487,6 +487,55 @@ func (s *Store) ListMRComments(mrID int64) ([]IssueComment, error) {
487487 return out, rows.Err()
488488}
489489
490// MRCommentCounts totals, per MR, conversation comments plus diff-thread
491// roots — what the list page shows as one comment count. System comments,
492// diff-thread replies, and pending (unpublished) diff comments do not
493// count. The list handler asks for every row on a page in one call rather
494// than one query per MR.
495func (s *Store) MRCommentCounts(repoID int64, mrIDs []int64) (map[int64]int, error) {
496 out := map[int64]int{}
497 if len(mrIDs) == 0 {
498 return out, nil
499 }
500 ph := "?" + strings.Repeat(",?", len(mrIDs)-1)
501 args := make([]any, 0, len(mrIDs)+1)
502 args = append(args, repoID)
503 for _, id := range mrIDs {
504 args = append(args, id)
505 }
506 add := func(query string) error {
507 rows, err := s.DB.Query(query, args...)
508 if err != nil {
509 return err
510 }
511 defer rows.Close()
512 for rows.Next() {
513 var mrID int64
514 var n int
515 if err := rows.Scan(&mrID, &n); err != nil {
516 return err
517 }
518 out[mrID] += n
519 }
520 return rows.Err()
521 }
522 if err := add(`
523 SELECT c.mr_id, COUNT(*) FROM mr_comments c
524 JOIN merge_requests m ON m.id = c.mr_id
525 WHERE m.repo_id = ? AND c.kind <> 'system' AND c.mr_id IN (` + ph + `)
526 GROUP BY c.mr_id`); err != nil {
527 return nil, err
528 }
529 if err := add(`
530 SELECT c.mr_id, COUNT(*) FROM mr_diff_comments c
531 JOIN merge_requests m ON m.id = c.mr_id
532 WHERE m.repo_id = ? AND c.reply_to IS NULL AND c.pending = 0 AND c.mr_id IN (` + ph + `)
533 GROUP BY c.mr_id`); err != nil {
534 return nil, err
535 }
536 return out, nil
537}
538
490539func (s *Store) AddMRReview(mrID, reviewerID int64, verdict, headSHA string) error {
491540 _, err := s.DB.Exec(
492541 "INSERT INTO mr_reviews (mr_id, reviewer_id, verdict, head_sha) VALUES (?, ?, ?, ?)",
internal/store/mrs_test.go +50
@@ -184,3 +184,53 @@ func TestPreferredVerifiedEmailDeterministicTiebreak(t *testing.T) {
184184 t.Fatalf("tiebreak should be alphabetical: %q, %v", addr, err)
185185 }
186186}
187
188// MRCommentCounts folds conversation comments and diff-thread roots into
189// one count per MR, for the list page. System comments, diff-thread
190// replies, and pending (unpublished) diff comments do not count.
191func TestMRCommentCounts(t *testing.T) {
192 s, repoID, uid := mrFixture(t)
193 mr1, err := s.MRByNumber(repoID, 1)
194 if err != nil {
195 t.Fatal(err)
196 }
197 if _, err := s.CreateMR(repoID, uid, repoID, "feature2", "main", "t2", "", "def456", "md", false); err != nil {
198 t.Fatal(err)
199 }
200 mr2, err := s.MRByNumber(repoID, 2)
201 if err != nil {
202 t.Fatal(err)
203 }
204
205 if err := s.AddMRComment(mr1.ID, uid, "hi", "md"); err != nil {
206 t.Fatal(err)
207 }
208 if err := s.AddMRSystemComment(mr1.ID, uid, "merged"); err != nil {
209 t.Fatal(err)
210 }
211 rootID, err := s.AddDiffComment(mr1.ID, uid, "abc123", "file.txt", "new", 1, "root", 0, false)
212 if err != nil {
213 t.Fatal(err)
214 }
215 if _, err := s.AddDiffComment(mr1.ID, uid, "abc123", "file.txt", "new", 1, "reply", rootID, false); err != nil {
216 t.Fatal(err)
217 }
218 if _, err := s.AddDiffComment(mr1.ID, uid, "abc123", "file.txt", "new", 2, "pending root", 0, true); err != nil {
219 t.Fatal(err)
220 }
221
222 counts, err := s.MRCommentCounts(repoID, []int64{mr1.ID, mr2.ID})
223 if err != nil {
224 t.Fatal(err)
225 }
226 if counts[mr1.ID] != 2 {
227 t.Fatalf("mr1 count = %d, want 2 (1 comment + 1 diff root)", counts[mr1.ID])
228 }
229 if counts[mr2.ID] != 0 {
230 t.Fatalf("mr2 count = %d, want 0", counts[mr2.ID])
231 }
232
233 if empty, err := s.MRCommentCounts(repoID, nil); err != nil || len(empty) != 0 {
234 t.Fatalf("MRCommentCounts(nil) = %v, %v", empty, err)
235 }
236}