code.gitea.io/gitea@v1.22.3/models/issues/comment_list.go (about)

     1  // Copyright 2018 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package issues
     5  
     6  import (
     7  	"context"
     8  
     9  	"code.gitea.io/gitea/models/db"
    10  	repo_model "code.gitea.io/gitea/models/repo"
    11  	user_model "code.gitea.io/gitea/models/user"
    12  	"code.gitea.io/gitea/modules/container"
    13  	"code.gitea.io/gitea/modules/log"
    14  )
    15  
    16  // CommentList defines a list of comments
    17  type CommentList []*Comment
    18  
    19  func (comments CommentList) getPosterIDs() []int64 {
    20  	return container.FilterSlice(comments, func(c *Comment) (int64, bool) {
    21  		return c.PosterID, c.PosterID > 0
    22  	})
    23  }
    24  
    25  // LoadPosters loads posters
    26  func (comments CommentList) LoadPosters(ctx context.Context) error {
    27  	if len(comments) == 0 {
    28  		return nil
    29  	}
    30  
    31  	posterMaps, err := getPosters(ctx, comments.getPosterIDs())
    32  	if err != nil {
    33  		return err
    34  	}
    35  
    36  	for _, comment := range comments {
    37  		comment.Poster = getPoster(comment.PosterID, posterMaps)
    38  	}
    39  	return nil
    40  }
    41  
    42  func (comments CommentList) getLabelIDs() []int64 {
    43  	return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
    44  		return comment.LabelID, comment.LabelID > 0
    45  	})
    46  }
    47  
    48  func (comments CommentList) loadLabels(ctx context.Context) error {
    49  	if len(comments) == 0 {
    50  		return nil
    51  	}
    52  
    53  	labelIDs := comments.getLabelIDs()
    54  	commentLabels := make(map[int64]*Label, len(labelIDs))
    55  	left := len(labelIDs)
    56  	for left > 0 {
    57  		limit := db.DefaultMaxInSize
    58  		if left < limit {
    59  			limit = left
    60  		}
    61  		rows, err := db.GetEngine(ctx).
    62  			In("id", labelIDs[:limit]).
    63  			Rows(new(Label))
    64  		if err != nil {
    65  			return err
    66  		}
    67  
    68  		for rows.Next() {
    69  			var label Label
    70  			err = rows.Scan(&label)
    71  			if err != nil {
    72  				_ = rows.Close()
    73  				return err
    74  			}
    75  			commentLabels[label.ID] = &label
    76  		}
    77  		_ = rows.Close()
    78  		left -= limit
    79  		labelIDs = labelIDs[limit:]
    80  	}
    81  
    82  	for _, comment := range comments {
    83  		comment.Label = commentLabels[comment.ID]
    84  	}
    85  	return nil
    86  }
    87  
    88  func (comments CommentList) getMilestoneIDs() []int64 {
    89  	return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
    90  		return comment.MilestoneID, comment.MilestoneID > 0
    91  	})
    92  }
    93  
    94  func (comments CommentList) loadMilestones(ctx context.Context) error {
    95  	if len(comments) == 0 {
    96  		return nil
    97  	}
    98  
    99  	milestoneIDs := comments.getMilestoneIDs()
   100  	if len(milestoneIDs) == 0 {
   101  		return nil
   102  	}
   103  
   104  	milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs))
   105  	left := len(milestoneIDs)
   106  	for left > 0 {
   107  		limit := db.DefaultMaxInSize
   108  		if left < limit {
   109  			limit = left
   110  		}
   111  		err := db.GetEngine(ctx).
   112  			In("id", milestoneIDs[:limit]).
   113  			Find(&milestoneMaps)
   114  		if err != nil {
   115  			return err
   116  		}
   117  		left -= limit
   118  		milestoneIDs = milestoneIDs[limit:]
   119  	}
   120  
   121  	for _, issue := range comments {
   122  		issue.Milestone = milestoneMaps[issue.MilestoneID]
   123  	}
   124  	return nil
   125  }
   126  
   127  func (comments CommentList) getOldMilestoneIDs() []int64 {
   128  	return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
   129  		return comment.OldMilestoneID, comment.OldMilestoneID > 0
   130  	})
   131  }
   132  
   133  func (comments CommentList) loadOldMilestones(ctx context.Context) error {
   134  	if len(comments) == 0 {
   135  		return nil
   136  	}
   137  
   138  	milestoneIDs := comments.getOldMilestoneIDs()
   139  	if len(milestoneIDs) == 0 {
   140  		return nil
   141  	}
   142  
   143  	milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs))
   144  	left := len(milestoneIDs)
   145  	for left > 0 {
   146  		limit := db.DefaultMaxInSize
   147  		if left < limit {
   148  			limit = left
   149  		}
   150  		err := db.GetEngine(ctx).
   151  			In("id", milestoneIDs[:limit]).
   152  			Find(&milestoneMaps)
   153  		if err != nil {
   154  			return err
   155  		}
   156  		left -= limit
   157  		milestoneIDs = milestoneIDs[limit:]
   158  	}
   159  
   160  	for _, issue := range comments {
   161  		issue.OldMilestone = milestoneMaps[issue.MilestoneID]
   162  	}
   163  	return nil
   164  }
   165  
   166  func (comments CommentList) getAssigneeIDs() []int64 {
   167  	return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
   168  		return comment.AssigneeID, comment.AssigneeID > 0
   169  	})
   170  }
   171  
   172  func (comments CommentList) loadAssignees(ctx context.Context) error {
   173  	if len(comments) == 0 {
   174  		return nil
   175  	}
   176  
   177  	assigneeIDs := comments.getAssigneeIDs()
   178  	assignees := make(map[int64]*user_model.User, len(assigneeIDs))
   179  	left := len(assigneeIDs)
   180  	for left > 0 {
   181  		limit := db.DefaultMaxInSize
   182  		if left < limit {
   183  			limit = left
   184  		}
   185  		rows, err := db.GetEngine(ctx).
   186  			In("id", assigneeIDs[:limit]).
   187  			Rows(new(user_model.User))
   188  		if err != nil {
   189  			return err
   190  		}
   191  
   192  		for rows.Next() {
   193  			var user user_model.User
   194  			err = rows.Scan(&user)
   195  			if err != nil {
   196  				rows.Close()
   197  				return err
   198  			}
   199  
   200  			assignees[user.ID] = &user
   201  		}
   202  		_ = rows.Close()
   203  
   204  		left -= limit
   205  		assigneeIDs = assigneeIDs[limit:]
   206  	}
   207  
   208  	for _, comment := range comments {
   209  		comment.Assignee = assignees[comment.AssigneeID]
   210  		if comment.Assignee == nil {
   211  			comment.AssigneeID = user_model.GhostUserID
   212  			comment.Assignee = user_model.NewGhostUser()
   213  		}
   214  	}
   215  	return nil
   216  }
   217  
   218  // getIssueIDs returns all the issue ids on this comment list which issue hasn't been loaded
   219  func (comments CommentList) getIssueIDs() []int64 {
   220  	return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
   221  		return comment.IssueID, comment.Issue == nil
   222  	})
   223  }
   224  
   225  // Issues returns all the issues of comments
   226  func (comments CommentList) Issues() IssueList {
   227  	issues := make(map[int64]*Issue, len(comments))
   228  	for _, comment := range comments {
   229  		if comment.Issue != nil {
   230  			if _, ok := issues[comment.Issue.ID]; !ok {
   231  				issues[comment.Issue.ID] = comment.Issue
   232  			}
   233  		}
   234  	}
   235  
   236  	issueList := make([]*Issue, 0, len(issues))
   237  	for _, issue := range issues {
   238  		issueList = append(issueList, issue)
   239  	}
   240  	return issueList
   241  }
   242  
   243  // LoadIssues loads issues of comments
   244  func (comments CommentList) LoadIssues(ctx context.Context) error {
   245  	if len(comments) == 0 {
   246  		return nil
   247  	}
   248  
   249  	issueIDs := comments.getIssueIDs()
   250  	issues := make(map[int64]*Issue, len(issueIDs))
   251  	left := len(issueIDs)
   252  	for left > 0 {
   253  		limit := db.DefaultMaxInSize
   254  		if left < limit {
   255  			limit = left
   256  		}
   257  		rows, err := db.GetEngine(ctx).
   258  			In("id", issueIDs[:limit]).
   259  			Rows(new(Issue))
   260  		if err != nil {
   261  			return err
   262  		}
   263  
   264  		for rows.Next() {
   265  			var issue Issue
   266  			err = rows.Scan(&issue)
   267  			if err != nil {
   268  				rows.Close()
   269  				return err
   270  			}
   271  
   272  			issues[issue.ID] = &issue
   273  		}
   274  		_ = rows.Close()
   275  
   276  		left -= limit
   277  		issueIDs = issueIDs[limit:]
   278  	}
   279  
   280  	for _, comment := range comments {
   281  		if comment.Issue == nil {
   282  			comment.Issue = issues[comment.IssueID]
   283  		}
   284  	}
   285  	return nil
   286  }
   287  
   288  func (comments CommentList) getDependentIssueIDs() []int64 {
   289  	return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
   290  		if comment.DependentIssue != nil {
   291  			return 0, false
   292  		}
   293  		return comment.DependentIssueID, comment.DependentIssueID > 0
   294  	})
   295  }
   296  
   297  func (comments CommentList) loadDependentIssues(ctx context.Context) error {
   298  	if len(comments) == 0 {
   299  		return nil
   300  	}
   301  
   302  	e := db.GetEngine(ctx)
   303  	issueIDs := comments.getDependentIssueIDs()
   304  	issues := make(map[int64]*Issue, len(issueIDs))
   305  	left := len(issueIDs)
   306  	for left > 0 {
   307  		limit := db.DefaultMaxInSize
   308  		if left < limit {
   309  			limit = left
   310  		}
   311  		rows, err := e.
   312  			In("id", issueIDs[:limit]).
   313  			Rows(new(Issue))
   314  		if err != nil {
   315  			return err
   316  		}
   317  
   318  		for rows.Next() {
   319  			var issue Issue
   320  			err = rows.Scan(&issue)
   321  			if err != nil {
   322  				_ = rows.Close()
   323  				return err
   324  			}
   325  
   326  			issues[issue.ID] = &issue
   327  		}
   328  		_ = rows.Close()
   329  
   330  		left -= limit
   331  		issueIDs = issueIDs[limit:]
   332  	}
   333  
   334  	for _, comment := range comments {
   335  		if comment.DependentIssue == nil {
   336  			comment.DependentIssue = issues[comment.DependentIssueID]
   337  			if comment.DependentIssue != nil {
   338  				if err := comment.DependentIssue.LoadRepo(ctx); err != nil {
   339  					return err
   340  				}
   341  			}
   342  		}
   343  	}
   344  	return nil
   345  }
   346  
   347  // getAttachmentCommentIDs only return the comment ids which possibly has attachments
   348  func (comments CommentList) getAttachmentCommentIDs() []int64 {
   349  	return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
   350  		return comment.ID, comment.Type.HasAttachmentSupport()
   351  	})
   352  }
   353  
   354  // LoadAttachmentsByIssue loads attachments by issue id
   355  func (comments CommentList) LoadAttachmentsByIssue(ctx context.Context) error {
   356  	if len(comments) == 0 {
   357  		return nil
   358  	}
   359  
   360  	attachments := make([]*repo_model.Attachment, 0, len(comments)/2)
   361  	if err := db.GetEngine(ctx).Where("issue_id=? AND comment_id>0", comments[0].IssueID).Find(&attachments); err != nil {
   362  		return err
   363  	}
   364  
   365  	commentAttachmentsMap := make(map[int64][]*repo_model.Attachment, len(comments))
   366  	for _, attach := range attachments {
   367  		commentAttachmentsMap[attach.CommentID] = append(commentAttachmentsMap[attach.CommentID], attach)
   368  	}
   369  
   370  	for _, comment := range comments {
   371  		comment.Attachments = commentAttachmentsMap[comment.ID]
   372  	}
   373  	return nil
   374  }
   375  
   376  // LoadAttachments loads attachments
   377  func (comments CommentList) LoadAttachments(ctx context.Context) (err error) {
   378  	if len(comments) == 0 {
   379  		return nil
   380  	}
   381  
   382  	attachments := make(map[int64][]*repo_model.Attachment, len(comments))
   383  	commentsIDs := comments.getAttachmentCommentIDs()
   384  	left := len(commentsIDs)
   385  	for left > 0 {
   386  		limit := db.DefaultMaxInSize
   387  		if left < limit {
   388  			limit = left
   389  		}
   390  		rows, err := db.GetEngine(ctx).
   391  			In("comment_id", commentsIDs[:limit]).
   392  			Rows(new(repo_model.Attachment))
   393  		if err != nil {
   394  			return err
   395  		}
   396  
   397  		for rows.Next() {
   398  			var attachment repo_model.Attachment
   399  			err = rows.Scan(&attachment)
   400  			if err != nil {
   401  				_ = rows.Close()
   402  				return err
   403  			}
   404  			attachments[attachment.CommentID] = append(attachments[attachment.CommentID], &attachment)
   405  		}
   406  
   407  		_ = rows.Close()
   408  		left -= limit
   409  		commentsIDs = commentsIDs[limit:]
   410  	}
   411  
   412  	for _, comment := range comments {
   413  		comment.Attachments = attachments[comment.ID]
   414  	}
   415  	return nil
   416  }
   417  
   418  func (comments CommentList) getReviewIDs() []int64 {
   419  	return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
   420  		return comment.ReviewID, comment.ReviewID > 0
   421  	})
   422  }
   423  
   424  func (comments CommentList) loadReviews(ctx context.Context) error {
   425  	if len(comments) == 0 {
   426  		return nil
   427  	}
   428  
   429  	reviewIDs := comments.getReviewIDs()
   430  	reviews := make(map[int64]*Review, len(reviewIDs))
   431  	if err := db.GetEngine(ctx).In("id", reviewIDs).Find(&reviews); err != nil {
   432  		return err
   433  	}
   434  
   435  	for _, comment := range comments {
   436  		comment.Review = reviews[comment.ReviewID]
   437  		if comment.Review == nil {
   438  			// review request which has been replaced by actual reviews doesn't exist in database anymore, so don't log errors for them.
   439  			if comment.ReviewID > 0 && comment.Type != CommentTypeReviewRequest {
   440  				log.Error("comment with review id [%d] but has no review record", comment.ReviewID)
   441  			}
   442  			continue
   443  		}
   444  
   445  		// If the comment dismisses a review, we need to load the reviewer to show whose review has been dismissed.
   446  		// Otherwise, the reviewer is the poster of the comment, so we don't need to load it.
   447  		if comment.Type == CommentTypeDismissReview {
   448  			if err := comment.Review.LoadReviewer(ctx); err != nil {
   449  				return err
   450  			}
   451  		}
   452  	}
   453  	return nil
   454  }
   455  
   456  // LoadAttributes loads attributes of the comments, except for attachments and
   457  // comments
   458  func (comments CommentList) LoadAttributes(ctx context.Context) (err error) {
   459  	if err = comments.LoadPosters(ctx); err != nil {
   460  		return err
   461  	}
   462  
   463  	if err = comments.loadLabels(ctx); err != nil {
   464  		return err
   465  	}
   466  
   467  	if err = comments.loadMilestones(ctx); err != nil {
   468  		return err
   469  	}
   470  
   471  	if err = comments.loadOldMilestones(ctx); err != nil {
   472  		return err
   473  	}
   474  
   475  	if err = comments.loadAssignees(ctx); err != nil {
   476  		return err
   477  	}
   478  
   479  	if err = comments.LoadAttachments(ctx); err != nil {
   480  		return err
   481  	}
   482  
   483  	if err = comments.loadReviews(ctx); err != nil {
   484  		return err
   485  	}
   486  
   487  	if err = comments.LoadIssues(ctx); err != nil {
   488  		return err
   489  	}
   490  
   491  	return comments.loadDependentIssues(ctx)
   492  }