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 }