gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package issues
  4. import (
  5. "context"
  6. "slices"
  7. "sort"
  8. "code.gitea.io/gitea/models/db"
  9. organization_model "code.gitea.io/gitea/models/organization"
  10. user_model "code.gitea.io/gitea/models/user"
  11. "code.gitea.io/gitea/modules/container"
  12. "code.gitea.io/gitea/modules/optional"
  13. "xorm.io/builder"
  14. )
  15. type ReviewList []*Review
  16. // LoadReviewers loads reviewers
  17. func (reviews ReviewList) LoadReviewers(ctx context.Context) error {
  18. reviewerIDs := make([]int64, len(reviews))
  19. for i := range reviews {
  20. reviewerIDs[i] = reviews[i].ReviewerID
  21. }
  22. reviewers, err := user_model.GetPossibleUserByIDs(ctx, reviewerIDs)
  23. if err != nil {
  24. return err
  25. }
  26. userMap := make(map[int64]*user_model.User, len(reviewers))
  27. for _, reviewer := range reviewers {
  28. userMap[reviewer.ID] = reviewer
  29. }
  30. for _, review := range reviews {
  31. review.Reviewer = userMap[review.ReviewerID]
  32. }
  33. return nil
  34. }
  35. // LoadReviewersTeams loads reviewers teams
  36. func (reviews ReviewList) LoadReviewersTeams(ctx context.Context) error {
  37. reviewersTeamsIDs := make([]int64, 0)
  38. for _, review := range reviews {
  39. if review.ReviewerTeamID != 0 {
  40. reviewersTeamsIDs = append(reviewersTeamsIDs, review.ReviewerTeamID)
  41. }
  42. }
  43. teamsMap, err := organization_model.GetTeamsByIDs(ctx, reviewersTeamsIDs)
  44. if err != nil {
  45. return err
  46. }
  47. for _, review := range reviews {
  48. if review.ReviewerTeamID != 0 {
  49. review.ReviewerTeam = teamsMap[review.ReviewerTeamID]
  50. }
  51. }
  52. return nil
  53. }
  54. func (reviews ReviewList) LoadIssues(ctx context.Context) error {
  55. issueIDs := container.FilterSlice(reviews, func(review *Review) (int64, bool) {
  56. return review.IssueID, true
  57. })
  58. issues, err := GetIssuesByIDs(ctx, issueIDs)
  59. if err != nil {
  60. return err
  61. }
  62. if _, err := issues.LoadRepositories(ctx); err != nil {
  63. return err
  64. }
  65. issueMap := make(map[int64]*Issue, len(issues))
  66. for _, issue := range issues {
  67. issueMap[issue.ID] = issue
  68. }
  69. for _, review := range reviews {
  70. review.Issue = issueMap[review.IssueID]
  71. }
  72. return nil
  73. }
  74. // FindReviewOptions represent possible filters to find reviews
  75. type FindReviewOptions struct {
  76. db.ListOptions
  77. Types []ReviewType
  78. IssueID int64
  79. ReviewerID int64
  80. OfficialOnly bool
  81. Dismissed optional.Option[bool]
  82. }
  83. func (opts *FindReviewOptions) toCond() builder.Cond {
  84. cond := builder.NewCond()
  85. if opts.IssueID > 0 {
  86. cond = cond.And(builder.Eq{"issue_id": opts.IssueID})
  87. }
  88. if opts.ReviewerID > 0 {
  89. cond = cond.And(builder.Eq{"reviewer_id": opts.ReviewerID})
  90. }
  91. if len(opts.Types) > 0 {
  92. cond = cond.And(builder.In("type", opts.Types))
  93. }
  94. if opts.OfficialOnly {
  95. cond = cond.And(builder.Eq{"official": true})
  96. }
  97. if opts.Dismissed.Has() {
  98. cond = cond.And(builder.Eq{"dismissed": opts.Dismissed.Value()})
  99. }
  100. return cond
  101. }
  102. // FindReviews returns reviews passing FindReviewOptions
  103. func FindReviews(ctx context.Context, opts FindReviewOptions) (ReviewList, error) {
  104. reviews := make([]*Review, 0, 10)
  105. sess := db.GetEngine(ctx).Where(opts.toCond())
  106. if opts.Page > 0 && !opts.IsListAll() {
  107. sess = db.SetSessionPagination(sess, &opts)
  108. }
  109. return reviews, sess.
  110. Asc("created_unix").
  111. Asc("id").
  112. Find(&reviews)
  113. }
  114. // FindLatestReviews returns only latest reviews per user, passing FindReviewOptions
  115. func FindLatestReviews(ctx context.Context, opts FindReviewOptions) (ReviewList, error) {
  116. reviews := make([]*Review, 0, 10)
  117. cond := opts.toCond()
  118. sess := db.GetEngine(ctx).Where(cond)
  119. if opts.Page > 0 {
  120. sess = db.SetSessionPagination(sess, &opts)
  121. }
  122. sess.In("id", builder.
  123. Select("max(id)").
  124. From("review").
  125. Where(cond).
  126. GroupBy("reviewer_id"))
  127. return reviews, sess.
  128. Asc("created_unix").
  129. Asc("id").
  130. Find(&reviews)
  131. }
  132. // CountReviews returns count of reviews passing FindReviewOptions
  133. func CountReviews(ctx context.Context, opts FindReviewOptions) (int64, error) {
  134. return db.GetEngine(ctx).Where(opts.toCond()).Count(&Review{})
  135. }
  136. // GetReviewsByIssueID gets the latest review of each reviewer for a pull request
  137. // The first returned parameter is the latest review of each individual reviewer or team
  138. // The second returned parameter is the latest review of each original author which is migrated from other systems
  139. // The reviews are sorted by updated time
  140. func GetReviewsByIssueID(ctx context.Context, issueID int64) (latestReviews, migratedOriginalReviews ReviewList, err error) {
  141. reviews := make([]*Review, 0, 10)
  142. // Get all reviews for the issue id
  143. if err := db.GetEngine(ctx).Where("issue_id=?", issueID).OrderBy("updated_unix ASC").Find(&reviews); err != nil {
  144. return nil, nil, err
  145. }
  146. // filter them in memory to get the latest review of each reviewer
  147. // Since the reviews should not be too many for one issue, less than 100 commonly, it's acceptable to do this in memory
  148. // And since there are too less indexes in review table, it will be very slow to filter in the database
  149. reviewersMap := make(map[int64][]*Review) // key is reviewer id
  150. originalReviewersMap := make(map[int64][]*Review) // key is original author id
  151. reviewTeamsMap := make(map[int64][]*Review) // key is reviewer team id
  152. countedReivewTypes := []ReviewType{ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest}
  153. for _, review := range reviews {
  154. if review.ReviewerTeamID == 0 && slices.Contains(countedReivewTypes, review.Type) && !review.Dismissed {
  155. if review.OriginalAuthorID != 0 {
  156. originalReviewersMap[review.OriginalAuthorID] = append(originalReviewersMap[review.OriginalAuthorID], review)
  157. } else {
  158. reviewersMap[review.ReviewerID] = append(reviewersMap[review.ReviewerID], review)
  159. }
  160. } else if review.ReviewerTeamID != 0 && review.OriginalAuthorID == 0 {
  161. reviewTeamsMap[review.ReviewerTeamID] = append(reviewTeamsMap[review.ReviewerTeamID], review)
  162. }
  163. }
  164. individualReviews := make([]*Review, 0, 10)
  165. for _, reviews := range reviewersMap {
  166. individualReviews = append(individualReviews, reviews[len(reviews)-1])
  167. }
  168. sort.Slice(individualReviews, func(i, j int) bool {
  169. return individualReviews[i].UpdatedUnix < individualReviews[j].UpdatedUnix
  170. })
  171. originalReviews := make([]*Review, 0, 10)
  172. for _, reviews := range originalReviewersMap {
  173. originalReviews = append(originalReviews, reviews[len(reviews)-1])
  174. }
  175. sort.Slice(originalReviews, func(i, j int) bool {
  176. return originalReviews[i].UpdatedUnix < originalReviews[j].UpdatedUnix
  177. })
  178. teamReviewRequests := make([]*Review, 0, 5)
  179. for _, reviews := range reviewTeamsMap {
  180. teamReviewRequests = append(teamReviewRequests, reviews[len(reviews)-1])
  181. }
  182. sort.Slice(teamReviewRequests, func(i, j int) bool {
  183. return teamReviewRequests[i].UpdatedUnix < teamReviewRequests[j].UpdatedUnix
  184. })
  185. return append(individualReviews, teamReviewRequests...), originalReviews, nil
  186. }