loading up the forgejo repo on tangled to test page performance
1// Copyright 2022 The Gitea Authors. All rights reserved.
2// SPDX-License-Identifier: MIT
3
4package issues
5
6import (
7 "context"
8
9 "forgejo.org/models/db"
10 user_model "forgejo.org/models/user"
11 "forgejo.org/modules/markup"
12 "forgejo.org/modules/markup/markdown"
13
14 "xorm.io/builder"
15)
16
17// CodeConversation contains the comment of a given review
18type CodeConversation []*Comment
19
20// CodeConversationsAtLine contains the conversations for a given line
21type CodeConversationsAtLine map[int64][]CodeConversation
22
23// CodeConversationsAtLineAndTreePath contains the conversations for a given TreePath and line
24type CodeConversationsAtLineAndTreePath map[string]CodeConversationsAtLine
25
26func newCodeConversationsAtLineAndTreePath(comments []*Comment) CodeConversationsAtLineAndTreePath {
27 tree := make(CodeConversationsAtLineAndTreePath)
28 for _, comment := range comments {
29 tree.insertComment(comment)
30 }
31 return tree
32}
33
34func (tree CodeConversationsAtLineAndTreePath) insertComment(comment *Comment) {
35 // attempt to append comment to existing conversations (i.e. list of comments belonging to the same review)
36 for i, conversation := range tree[comment.TreePath][comment.Line] {
37 if conversation[0].ReviewID == comment.ReviewID {
38 tree[comment.TreePath][comment.Line][i] = append(conversation, comment)
39 return
40 }
41 }
42
43 // no previous conversation was found at this line, create it
44 if tree[comment.TreePath] == nil {
45 tree[comment.TreePath] = make(map[int64][]CodeConversation)
46 }
47
48 tree[comment.TreePath][comment.Line] = append(tree[comment.TreePath][comment.Line], CodeConversation{comment})
49}
50
51// FetchCodeConversations will return a 2d-map: ["Path"]["Line"] = List of CodeConversation (one per review) for this line
52func FetchCodeConversations(ctx context.Context, issue *Issue, doer *user_model.User, showOutdatedComments bool) (CodeConversationsAtLineAndTreePath, error) {
53 opts := FindCommentsOptions{
54 Type: CommentTypeCode,
55 IssueID: issue.ID,
56 }
57 comments, err := findCodeComments(ctx, opts, issue, doer, nil, showOutdatedComments)
58 if err != nil {
59 return nil, err
60 }
61
62 return newCodeConversationsAtLineAndTreePath(comments), nil
63}
64
65// CodeComments represents comments on code by using this structure: FILENAME -> LINE (+ == proposed; - == previous) -> COMMENTS
66type CodeComments map[string]map[int64][]*Comment
67
68func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, doer *user_model.User, review *Review, showOutdatedComments bool) (CodeComments, error) {
69 pathToLineToComment := make(CodeComments)
70 if review == nil {
71 review = &Review{ID: 0}
72 }
73 opts := FindCommentsOptions{
74 Type: CommentTypeCode,
75 IssueID: issue.ID,
76 ReviewID: review.ID,
77 }
78
79 comments, err := findCodeComments(ctx, opts, issue, doer, review, showOutdatedComments)
80 if err != nil {
81 return nil, err
82 }
83
84 for _, comment := range comments {
85 if pathToLineToComment[comment.TreePath] == nil {
86 pathToLineToComment[comment.TreePath] = make(map[int64][]*Comment)
87 }
88 pathToLineToComment[comment.TreePath][comment.Line] = append(pathToLineToComment[comment.TreePath][comment.Line], comment)
89 }
90 return pathToLineToComment, nil
91}
92
93func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issue, doer *user_model.User, review *Review, showOutdatedComments bool) (CommentList, error) {
94 var comments CommentList
95 if review == nil {
96 review = &Review{ID: 0}
97 }
98 conds := opts.ToConds()
99
100 if !showOutdatedComments && review.ID == 0 {
101 conds = conds.And(builder.Eq{"invalidated": false})
102 }
103
104 e := db.GetEngine(ctx)
105 if err := e.Where(conds).
106 Asc("comment.created_unix").
107 Asc("comment.id").
108 Find(&comments); err != nil {
109 return nil, err
110 }
111
112 if err := issue.LoadRepo(ctx); err != nil {
113 return nil, err
114 }
115
116 if err := comments.LoadPosters(ctx); err != nil {
117 return nil, err
118 }
119
120 if err := comments.LoadAttachments(ctx); err != nil {
121 return nil, err
122 }
123
124 // Find all reviews by ReviewID
125 reviews := make(map[int64]*Review)
126 ids := make([]int64, 0, len(comments))
127 for _, comment := range comments {
128 if comment.ReviewID != 0 {
129 ids = append(ids, comment.ReviewID)
130 }
131 }
132 if err := e.In("id", ids).Find(&reviews); err != nil {
133 return nil, err
134 }
135
136 n := 0
137 for _, comment := range comments {
138 if re, ok := reviews[comment.ReviewID]; ok && re != nil {
139 // If the review is pending only the author can see the comments (except if the review is set)
140 if review.ID == 0 && re.Type == ReviewTypePending &&
141 (doer == nil || doer.ID != re.ReviewerID) {
142 continue
143 }
144 comment.Review = re
145 }
146 comments[n] = comment
147 n++
148
149 if err := comment.LoadResolveDoer(ctx); err != nil {
150 return nil, err
151 }
152
153 if err := comment.LoadReactions(ctx, issue.Repo); err != nil {
154 return nil, err
155 }
156
157 var err error
158 if comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
159 Ctx: ctx,
160 Links: markup.Links{
161 Base: issue.Repo.Link(),
162 },
163 Metas: issue.Repo.ComposeMetas(ctx),
164 }, comment.Content); err != nil {
165 return nil, err
166 }
167 }
168 return comments[:n], nil
169}
170
171// FetchCodeConversation fetches the code conversation of a given comment (same review, treePath and line number)
172func FetchCodeConversation(ctx context.Context, comment *Comment, doer *user_model.User) (CommentList, error) {
173 opts := FindCommentsOptions{
174 Type: CommentTypeCode,
175 IssueID: comment.IssueID,
176 ReviewID: comment.ReviewID,
177 TreePath: comment.TreePath,
178 Line: comment.Line,
179 }
180 return findCodeComments(ctx, opts, comment.Issue, doer, nil, true)
181}