code.gitea.io/gitea@v1.22.3/modules/indexer/issues/util.go (about) 1 // Copyright 2023 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package issues 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 11 "code.gitea.io/gitea/models/db" 12 issue_model "code.gitea.io/gitea/models/issues" 13 "code.gitea.io/gitea/modules/container" 14 "code.gitea.io/gitea/modules/indexer/issues/internal" 15 "code.gitea.io/gitea/modules/log" 16 "code.gitea.io/gitea/modules/queue" 17 ) 18 19 // getIssueIndexerData returns the indexer data of an issue and a bool value indicating whether the issue exists. 20 func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerData, bool, error) { 21 issue, err := issue_model.GetIssueByID(ctx, issueID) 22 if err != nil { 23 if issue_model.IsErrIssueNotExist(err) { 24 return nil, false, nil 25 } 26 return nil, false, err 27 } 28 29 // FIXME: what if users want to search for a review comment of a pull request? 30 // The comment type is CommentTypeCode or CommentTypeReview. 31 // But LoadDiscussComments only loads CommentTypeComment. 32 if err := issue.LoadDiscussComments(ctx); err != nil { 33 return nil, false, err 34 } 35 36 comments := make([]string, 0, len(issue.Comments)) 37 for _, comment := range issue.Comments { 38 if comment.Content != "" { 39 // what ever the comment type is, index the content if it is not empty. 40 comments = append(comments, comment.Content) 41 } 42 } 43 44 if err := issue.LoadAttributes(ctx); err != nil { 45 return nil, false, err 46 } 47 48 labels := make([]int64, 0, len(issue.Labels)) 49 for _, label := range issue.Labels { 50 labels = append(labels, label.ID) 51 } 52 53 mentionIDs, err := issue_model.GetIssueMentionIDs(ctx, issueID) 54 if err != nil { 55 return nil, false, err 56 } 57 58 var ( 59 reviewedIDs []int64 60 reviewRequestedIDs []int64 61 ) 62 { 63 reviews, err := issue_model.FindReviews(ctx, issue_model.FindReviewOptions{ 64 ListOptions: db.ListOptionsAll, 65 IssueID: issueID, 66 OfficialOnly: false, 67 }) 68 if err != nil { 69 return nil, false, err 70 } 71 72 reviewedIDsSet := make(container.Set[int64], len(reviews)) 73 reviewRequestedIDsSet := make(container.Set[int64], len(reviews)) 74 for _, review := range reviews { 75 if review.Type == issue_model.ReviewTypeRequest { 76 reviewRequestedIDsSet.Add(review.ReviewerID) 77 } else { 78 reviewedIDsSet.Add(review.ReviewerID) 79 } 80 } 81 reviewedIDs = reviewedIDsSet.Values() 82 reviewRequestedIDs = reviewRequestedIDsSet.Values() 83 } 84 85 subscriberIDs, err := issue_model.GetIssueWatchersIDs(ctx, issue.ID, true) 86 if err != nil { 87 return nil, false, err 88 } 89 90 var projectID int64 91 if issue.Project != nil { 92 projectID = issue.Project.ID 93 } 94 95 return &internal.IndexerData{ 96 ID: issue.ID, 97 RepoID: issue.RepoID, 98 IsPublic: !issue.Repo.IsPrivate, 99 Title: issue.Title, 100 Content: issue.Content, 101 Comments: comments, 102 IsPull: issue.IsPull, 103 IsClosed: issue.IsClosed, 104 LabelIDs: labels, 105 NoLabel: len(labels) == 0, 106 MilestoneID: issue.MilestoneID, 107 ProjectID: projectID, 108 ProjectBoardID: issue.ProjectBoardID(ctx), 109 PosterID: issue.PosterID, 110 AssigneeID: issue.AssigneeID, 111 MentionIDs: mentionIDs, 112 ReviewedIDs: reviewedIDs, 113 ReviewRequestedIDs: reviewRequestedIDs, 114 SubscriberIDs: subscriberIDs, 115 UpdatedUnix: issue.UpdatedUnix, 116 CreatedUnix: issue.CreatedUnix, 117 DeadlineUnix: issue.DeadlineUnix, 118 CommentCount: int64(len(issue.Comments)), 119 }, true, nil 120 } 121 122 func updateRepoIndexer(ctx context.Context, repoID int64) error { 123 ids, err := issue_model.GetIssueIDsByRepoID(ctx, repoID) 124 if err != nil { 125 return fmt.Errorf("issue_model.GetIssueIDsByRepoID: %w", err) 126 } 127 for _, id := range ids { 128 if err := updateIssueIndexer(ctx, id); err != nil { 129 return err 130 } 131 } 132 return nil 133 } 134 135 func updateIssueIndexer(ctx context.Context, issueID int64) error { 136 return pushIssueIndexerQueue(ctx, &IndexerMetadata{ID: issueID}) 137 } 138 139 func deleteRepoIssueIndexer(ctx context.Context, repoID int64) error { 140 var ids []int64 141 ids, err := issue_model.GetIssueIDsByRepoID(ctx, repoID) 142 if err != nil { 143 return fmt.Errorf("issue_model.GetIssueIDsByRepoID: %w", err) 144 } 145 146 if len(ids) == 0 { 147 return nil 148 } 149 return pushIssueIndexerQueue(ctx, &IndexerMetadata{ 150 IDs: ids, 151 IsDelete: true, 152 }) 153 } 154 155 type keepRetryKey struct{} 156 157 // contextWithKeepRetry returns a context with a key indicating that the indexer should keep retrying. 158 // Please note that it's for background tasks only, and it should not be used for user requests, or it may cause blocking. 159 func contextWithKeepRetry(ctx context.Context) context.Context { 160 return context.WithValue(ctx, keepRetryKey{}, true) 161 } 162 163 func pushIssueIndexerQueue(ctx context.Context, data *IndexerMetadata) error { 164 if issueIndexerQueue == nil { 165 // Some unit tests will trigger indexing, but the queue is not initialized. 166 // It's OK to ignore it, but log a warning message in case it's not a unit test. 167 log.Warn("Trying to push %+v to issue indexer queue, but the queue is not initialized, it's OK if it's a unit test", data) 168 return nil 169 } 170 171 for { 172 select { 173 case <-ctx.Done(): 174 return ctx.Err() 175 default: 176 } 177 err := issueIndexerQueue.Push(data) 178 if errors.Is(err, queue.ErrAlreadyInQueue) { 179 return nil 180 } 181 if errors.Is(err, context.DeadlineExceeded) { // the queue is full 182 log.Warn("It seems that issue indexer is slow and the queue is full. Please check the issue indexer or increase the queue size.") 183 if ctx.Value(keepRetryKey{}) == nil { 184 return err 185 } 186 // It will be better to increase the queue size instead of retrying, but users may ignore the previous warning message. 187 // However, even it retries, it may still cause index loss when there's a deadline in the context. 188 log.Debug("Retry to push %+v to issue indexer queue", data) 189 continue 190 } 191 return err 192 } 193 }