You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

review.go 6.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. // Copyright 2018 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package models
  5. import (
  6. "fmt"
  7. "code.gitea.io/gitea/modules/log"
  8. "code.gitea.io/gitea/modules/util"
  9. "github.com/go-xorm/xorm"
  10. "github.com/go-xorm/builder"
  11. )
  12. // ReviewType defines the sort of feedback a review gives
  13. type ReviewType int
  14. // ReviewTypeUnknown unknown review type
  15. const ReviewTypeUnknown ReviewType = -1
  16. const (
  17. // ReviewTypePending is a review which is not published yet
  18. ReviewTypePending ReviewType = iota
  19. // ReviewTypeApprove approves changes
  20. ReviewTypeApprove
  21. // ReviewTypeComment gives general feedback
  22. ReviewTypeComment
  23. // ReviewTypeReject gives feedback blocking merge
  24. ReviewTypeReject
  25. )
  26. // Icon returns the corresponding icon for the review type
  27. func (rt ReviewType) Icon() string {
  28. switch rt {
  29. case ReviewTypeApprove:
  30. return "eye"
  31. case ReviewTypeReject:
  32. return "x"
  33. case ReviewTypeComment, ReviewTypeUnknown:
  34. return "comment"
  35. default:
  36. return "comment"
  37. }
  38. }
  39. // Review represents collection of code comments giving feedback for a PR
  40. type Review struct {
  41. ID int64 `xorm:"pk autoincr"`
  42. Type ReviewType
  43. Reviewer *User `xorm:"-"`
  44. ReviewerID int64 `xorm:"index"`
  45. Issue *Issue `xorm:"-"`
  46. IssueID int64 `xorm:"index"`
  47. Content string
  48. CreatedUnix util.TimeStamp `xorm:"INDEX created"`
  49. UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
  50. // CodeComments are the initial code comments of the review
  51. CodeComments CodeComments `xorm:"-"`
  52. }
  53. func (r *Review) loadCodeComments(e Engine) (err error) {
  54. r.CodeComments, err = fetchCodeCommentsByReview(e, r.Issue, nil, r)
  55. return
  56. }
  57. // LoadCodeComments loads CodeComments
  58. func (r *Review) LoadCodeComments() error {
  59. return r.loadCodeComments(x)
  60. }
  61. func (r *Review) loadIssue(e Engine) (err error) {
  62. r.Issue, err = getIssueByID(e, r.IssueID)
  63. return
  64. }
  65. func (r *Review) loadReviewer(e Engine) (err error) {
  66. if r.ReviewerID == 0 {
  67. return nil
  68. }
  69. r.Reviewer, err = getUserByID(e, r.ReviewerID)
  70. return
  71. }
  72. func (r *Review) loadAttributes(e Engine) (err error) {
  73. if err = r.loadReviewer(e); err != nil {
  74. return
  75. }
  76. if err = r.loadIssue(e); err != nil {
  77. return
  78. }
  79. return
  80. }
  81. // LoadAttributes loads all attributes except CodeComments
  82. func (r *Review) LoadAttributes() error {
  83. return r.loadAttributes(x)
  84. }
  85. // Publish will send notifications / actions to participants for all code comments; parts are concurrent
  86. func (r *Review) Publish() error {
  87. return r.publish(x)
  88. }
  89. func (r *Review) publish(e *xorm.Engine) error {
  90. if r.Type == ReviewTypePending || r.Type == ReviewTypeUnknown {
  91. return fmt.Errorf("review cannot be published if type is pending or unknown")
  92. }
  93. if r.Issue == nil {
  94. if err := r.loadIssue(e); err != nil {
  95. return err
  96. }
  97. }
  98. if err := r.Issue.loadRepo(e); err != nil {
  99. return err
  100. }
  101. if len(r.CodeComments) == 0 {
  102. if err := r.loadCodeComments(e); err != nil {
  103. return err
  104. }
  105. }
  106. for _, lines := range r.CodeComments {
  107. for _, comments := range lines {
  108. for _, comment := range comments {
  109. go func(en *xorm.Engine, review *Review, comm *Comment) {
  110. sess := en.NewSession()
  111. defer sess.Close()
  112. if err := sendCreateCommentAction(sess, &CreateCommentOptions{
  113. Doer: comm.Poster,
  114. Issue: review.Issue,
  115. Repo: review.Issue.Repo,
  116. Type: comm.Type,
  117. Content: comm.Content,
  118. }, comm); err != nil {
  119. log.Warn("sendCreateCommentAction: %v", err)
  120. }
  121. }(e, r, comment)
  122. }
  123. }
  124. }
  125. return nil
  126. }
  127. func getReviewByID(e Engine, id int64) (*Review, error) {
  128. review := new(Review)
  129. if has, err := e.ID(id).Get(review); err != nil {
  130. return nil, err
  131. } else if !has {
  132. return nil, ErrReviewNotExist{ID: id}
  133. } else {
  134. return review, nil
  135. }
  136. }
  137. // GetReviewByID returns the review by the given ID
  138. func GetReviewByID(id int64) (*Review, error) {
  139. return getReviewByID(x, id)
  140. }
  141. // FindReviewOptions represent possible filters to find reviews
  142. type FindReviewOptions struct {
  143. Type ReviewType
  144. IssueID int64
  145. ReviewerID int64
  146. }
  147. func (opts *FindReviewOptions) toCond() builder.Cond {
  148. var cond = builder.NewCond()
  149. if opts.IssueID > 0 {
  150. cond = cond.And(builder.Eq{"issue_id": opts.IssueID})
  151. }
  152. if opts.ReviewerID > 0 {
  153. cond = cond.And(builder.Eq{"reviewer_id": opts.ReviewerID})
  154. }
  155. if opts.Type != ReviewTypeUnknown {
  156. cond = cond.And(builder.Eq{"type": opts.Type})
  157. }
  158. return cond
  159. }
  160. func findReviews(e Engine, opts FindReviewOptions) ([]*Review, error) {
  161. reviews := make([]*Review, 0, 10)
  162. sess := e.Where(opts.toCond())
  163. return reviews, sess.
  164. Asc("created_unix").
  165. Asc("id").
  166. Find(&reviews)
  167. }
  168. // FindReviews returns reviews passing FindReviewOptions
  169. func FindReviews(opts FindReviewOptions) ([]*Review, error) {
  170. return findReviews(x, opts)
  171. }
  172. // CreateReviewOptions represent the options to create a review. Type, Issue and Reviewer are required.
  173. type CreateReviewOptions struct {
  174. Content string
  175. Type ReviewType
  176. Issue *Issue
  177. Reviewer *User
  178. }
  179. func createReview(e Engine, opts CreateReviewOptions) (*Review, error) {
  180. review := &Review{
  181. Type: opts.Type,
  182. Issue: opts.Issue,
  183. IssueID: opts.Issue.ID,
  184. Reviewer: opts.Reviewer,
  185. ReviewerID: opts.Reviewer.ID,
  186. Content: opts.Content,
  187. }
  188. if _, err := e.Insert(review); err != nil {
  189. return nil, err
  190. }
  191. return review, nil
  192. }
  193. // CreateReview creates a new review based on opts
  194. func CreateReview(opts CreateReviewOptions) (*Review, error) {
  195. return createReview(x, opts)
  196. }
  197. func getCurrentReview(e Engine, reviewer *User, issue *Issue) (*Review, error) {
  198. if reviewer == nil {
  199. return nil, nil
  200. }
  201. reviews, err := findReviews(e, FindReviewOptions{
  202. Type: ReviewTypePending,
  203. IssueID: issue.ID,
  204. ReviewerID: reviewer.ID,
  205. })
  206. if err != nil {
  207. return nil, err
  208. }
  209. if len(reviews) == 0 {
  210. return nil, ErrReviewNotExist{}
  211. }
  212. reviews[0].Reviewer = reviewer
  213. reviews[0].Issue = issue
  214. return reviews[0], nil
  215. }
  216. // GetCurrentReview returns the current pending review of reviewer for given issue
  217. func GetCurrentReview(reviewer *User, issue *Issue) (*Review, error) {
  218. return getCurrentReview(x, reviewer, issue)
  219. }
  220. // UpdateReview will update all cols of the given review in db
  221. func UpdateReview(r *Review) error {
  222. if _, err := x.ID(r.ID).AllCols().Update(r); err != nil {
  223. return err
  224. }
  225. return nil
  226. }