| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- // Copyright 2023 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package issues
-
- import (
- "context"
- "slices"
- "sort"
-
- "code.gitea.io/gitea/models/db"
- organization_model "code.gitea.io/gitea/models/organization"
- user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/container"
- "code.gitea.io/gitea/modules/optional"
-
- "xorm.io/builder"
- )
-
- type ReviewList []*Review
-
- // LoadReviewers loads reviewers
- func (reviews ReviewList) LoadReviewers(ctx context.Context) error {
- reviewerIDs := make([]int64, len(reviews))
- for i := range reviews {
- reviewerIDs[i] = reviews[i].ReviewerID
- }
- reviewers, err := user_model.GetPossibleUserByIDs(ctx, reviewerIDs)
- if err != nil {
- return err
- }
-
- userMap := make(map[int64]*user_model.User, len(reviewers))
- for _, reviewer := range reviewers {
- userMap[reviewer.ID] = reviewer
- }
- for _, review := range reviews {
- review.Reviewer = userMap[review.ReviewerID]
- }
- return nil
- }
-
- // LoadReviewersTeams loads reviewers teams
- func (reviews ReviewList) LoadReviewersTeams(ctx context.Context) error {
- reviewersTeamsIDs := make([]int64, 0)
- for _, review := range reviews {
- if review.ReviewerTeamID != 0 {
- reviewersTeamsIDs = append(reviewersTeamsIDs, review.ReviewerTeamID)
- }
- }
-
- teamsMap, err := organization_model.GetTeamsByIDs(ctx, reviewersTeamsIDs)
- if err != nil {
- return err
- }
-
- for _, review := range reviews {
- if review.ReviewerTeamID != 0 {
- review.ReviewerTeam = teamsMap[review.ReviewerTeamID]
- }
- }
-
- return nil
- }
-
- func (reviews ReviewList) LoadIssues(ctx context.Context) error {
- issueIDs := container.FilterSlice(reviews, func(review *Review) (int64, bool) {
- return review.IssueID, true
- })
-
- issues, err := GetIssuesByIDs(ctx, issueIDs)
- if err != nil {
- return err
- }
- if _, err := issues.LoadRepositories(ctx); err != nil {
- return err
- }
- issueMap := make(map[int64]*Issue, len(issues))
- for _, issue := range issues {
- issueMap[issue.ID] = issue
- }
-
- for _, review := range reviews {
- review.Issue = issueMap[review.IssueID]
- }
- return nil
- }
-
- // FindReviewOptions represent possible filters to find reviews
- type FindReviewOptions struct {
- db.ListOptions
- Types []ReviewType
- IssueID int64
- ReviewerID int64
- OfficialOnly bool
- Dismissed optional.Option[bool]
- }
-
- func (opts *FindReviewOptions) toCond() builder.Cond {
- cond := builder.NewCond()
- if opts.IssueID > 0 {
- cond = cond.And(builder.Eq{"issue_id": opts.IssueID})
- }
- if opts.ReviewerID > 0 {
- cond = cond.And(builder.Eq{"reviewer_id": opts.ReviewerID})
- }
- if len(opts.Types) > 0 {
- cond = cond.And(builder.In("type", opts.Types))
- }
- if opts.OfficialOnly {
- cond = cond.And(builder.Eq{"official": true})
- }
- if opts.Dismissed.Has() {
- cond = cond.And(builder.Eq{"dismissed": opts.Dismissed.Value()})
- }
- return cond
- }
-
- // FindReviews returns reviews passing FindReviewOptions
- func FindReviews(ctx context.Context, opts FindReviewOptions) (ReviewList, error) {
- reviews := make([]*Review, 0, 10)
- sess := db.GetEngine(ctx).Where(opts.toCond())
- if opts.Page > 0 && !opts.IsListAll() {
- sess = db.SetSessionPagination(sess, &opts)
- }
- return reviews, sess.
- Asc("created_unix").
- Asc("id").
- Find(&reviews)
- }
-
- // FindLatestReviews returns only latest reviews per user, passing FindReviewOptions
- func FindLatestReviews(ctx context.Context, opts FindReviewOptions) (ReviewList, error) {
- reviews := make([]*Review, 0, 10)
- cond := opts.toCond()
- sess := db.GetEngine(ctx).Where(cond)
- if opts.Page > 0 {
- sess = db.SetSessionPagination(sess, &opts)
- }
-
- sess.In("id", builder.
- Select("max(id)").
- From("review").
- Where(cond).
- GroupBy("reviewer_id"))
-
- return reviews, sess.
- Asc("created_unix").
- Asc("id").
- Find(&reviews)
- }
-
- // CountReviews returns count of reviews passing FindReviewOptions
- func CountReviews(ctx context.Context, opts FindReviewOptions) (int64, error) {
- return db.GetEngine(ctx).Where(opts.toCond()).Count(&Review{})
- }
-
- // GetReviewsByIssueID gets the latest review of each reviewer for a pull request
- // The first returned parameter is the latest review of each individual reviewer or team
- // The second returned parameter is the latest review of each original author which is migrated from other systems
- // The reviews are sorted by updated time
- func GetReviewsByIssueID(ctx context.Context, issueID int64) (latestReviews, migratedOriginalReviews ReviewList, err error) {
- reviews := make([]*Review, 0, 10)
-
- // Get all reviews for the issue id
- if err := db.GetEngine(ctx).Where("issue_id=?", issueID).OrderBy("updated_unix ASC").Find(&reviews); err != nil {
- return nil, nil, err
- }
-
- // filter them in memory to get the latest review of each reviewer
- // Since the reviews should not be too many for one issue, less than 100 commonly, it's acceptable to do this in memory
- // And since there are too less indexes in review table, it will be very slow to filter in the database
- reviewersMap := make(map[int64][]*Review) // key is reviewer id
- originalReviewersMap := make(map[int64][]*Review) // key is original author id
- reviewTeamsMap := make(map[int64][]*Review) // key is reviewer team id
- countedReivewTypes := []ReviewType{ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest}
- for _, review := range reviews {
- if review.ReviewerTeamID == 0 && slices.Contains(countedReivewTypes, review.Type) && !review.Dismissed {
- if review.OriginalAuthorID != 0 {
- originalReviewersMap[review.OriginalAuthorID] = append(originalReviewersMap[review.OriginalAuthorID], review)
- } else {
- reviewersMap[review.ReviewerID] = append(reviewersMap[review.ReviewerID], review)
- }
- } else if review.ReviewerTeamID != 0 && review.OriginalAuthorID == 0 {
- reviewTeamsMap[review.ReviewerTeamID] = append(reviewTeamsMap[review.ReviewerTeamID], review)
- }
- }
-
- individualReviews := make([]*Review, 0, 10)
- for _, reviews := range reviewersMap {
- individualReviews = append(individualReviews, reviews[len(reviews)-1])
- }
- sort.Slice(individualReviews, func(i, j int) bool {
- return individualReviews[i].UpdatedUnix < individualReviews[j].UpdatedUnix
- })
-
- originalReviews := make([]*Review, 0, 10)
- for _, reviews := range originalReviewersMap {
- originalReviews = append(originalReviews, reviews[len(reviews)-1])
- }
- sort.Slice(originalReviews, func(i, j int) bool {
- return originalReviews[i].UpdatedUnix < originalReviews[j].UpdatedUnix
- })
-
- teamReviewRequests := make([]*Review, 0, 5)
- for _, reviews := range reviewTeamsMap {
- teamReviewRequests = append(teamReviewRequests, reviews[len(reviews)-1])
- }
- sort.Slice(teamReviewRequests, func(i, j int) bool {
- return teamReviewRequests[i].UpdatedUnix < teamReviewRequests[j].UpdatedUnix
- })
-
- return append(individualReviews, teamReviewRequests...), originalReviews, nil
- }
|