aboutsummaryrefslogtreecommitdiffstats
path: root/modules/indexer/issues/indexer.go
diff options
context:
space:
mode:
Diffstat (limited to 'modules/indexer/issues/indexer.go')
-rw-r--r--modules/indexer/issues/indexer.go222
1 files changed, 115 insertions, 107 deletions
diff --git a/modules/indexer/issues/indexer.go b/modules/indexer/issues/indexer.go
index fe5c5d8f26..42279cbddb 100644
--- a/modules/indexer/issues/indexer.go
+++ b/modules/indexer/issues/indexer.go
@@ -11,7 +11,6 @@ import (
"time"
db_model "code.gitea.io/gitea/models/db"
- issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/indexer/issues/bleve"
@@ -26,9 +25,24 @@ import (
"code.gitea.io/gitea/modules/util"
)
+// IndexerMetadata is used to send data to the queue, so it contains only the ids.
+// It may look weired, because it has to be compatible with the old queue data format.
+// If the IsDelete flag is true, the IDs specify the issues to delete from the index without querying the database.
+// If the IsDelete flag is false, the ID specify the issue to index, so Indexer will query the database to get the issue data.
+// It should be noted that if the id is not existing in the database, it's index will be deleted too even if IsDelete is false.
+// Valid values:
+// - IsDelete = true, IDs = [1, 2, 3], and ID will be ignored
+// - IsDelete = false, ID = 1, and IDs will be ignored
+type IndexerMetadata struct {
+ ID int64 `json:"id"`
+
+ IsDelete bool `json:"is_delete"`
+ IDs []int64 `json:"ids"`
+}
+
var (
// issueIndexerQueue queue of issue ids to be updated
- issueIndexerQueue *queue.WorkerPoolQueue[*internal.IndexerData]
+ issueIndexerQueue *queue.WorkerPoolQueue[*IndexerMetadata]
// globalIndexer is the global indexer, it cannot be nil.
// When the real indexer is not ready, it will be a dummy indexer which will return error to explain it's not ready.
// So it's always safe use it as *globalIndexer.Load() and call its methods.
@@ -50,37 +64,7 @@ func InitIssueIndexer(syncReindex bool) {
indexerInitWaitChannel := make(chan time.Duration, 1)
// Create the Queue
- switch setting.Indexer.IssueType {
- case "bleve", "elasticsearch", "meilisearch":
- handler := func(items ...*internal.IndexerData) (unhandled []*internal.IndexerData) {
- indexer := *globalIndexer.Load()
- toIndex := make([]*internal.IndexerData, 0, len(items))
- for _, indexerData := range items {
- log.Trace("IndexerData Process: %d %v %t", indexerData.ID, indexerData.IDs, indexerData.IsDelete)
- if indexerData.IsDelete {
- if err := indexer.Delete(ctx, indexerData.IDs...); err != nil {
- log.Error("Issue indexer handler: failed to from index: %v Error: %v", indexerData.IDs, err)
- unhandled = append(unhandled, indexerData)
- }
- continue
- }
- toIndex = append(toIndex, indexerData)
- }
- if err := indexer.Index(ctx, toIndex); err != nil {
- log.Error("Error whilst indexing: %v Error: %v", toIndex, err)
- unhandled = append(unhandled, toIndex...)
- }
- return unhandled
- }
-
- issueIndexerQueue = queue.CreateSimpleQueue(ctx, "issue_indexer", handler)
-
- if issueIndexerQueue == nil {
- log.Fatal("Unable to create issue indexer queue")
- }
- default:
- issueIndexerQueue = queue.CreateSimpleQueue[*internal.IndexerData](ctx, "issue_indexer", nil)
- }
+ issueIndexerQueue = queue.CreateUniqueQueue(ctx, "issue_indexer", getIssueIndexerQueueHandler(ctx))
graceful.GetManager().RunAtTerminate(finished)
@@ -176,6 +160,44 @@ func InitIssueIndexer(syncReindex bool) {
}
}
+func getIssueIndexerQueueHandler(ctx context.Context) func(items ...*IndexerMetadata) []*IndexerMetadata {
+ return func(items ...*IndexerMetadata) []*IndexerMetadata {
+ var unhandled []*IndexerMetadata
+
+ indexer := *globalIndexer.Load()
+ for _, item := range items {
+ log.Trace("IndexerMetadata Process: %d %v %t", item.ID, item.IDs, item.IsDelete)
+ if item.IsDelete {
+ if err := indexer.Delete(ctx, item.IDs...); err != nil {
+ log.Error("Issue indexer handler: failed to from index: %v Error: %v", item.IDs, err)
+ unhandled = append(unhandled, item)
+ }
+ continue
+ }
+ data, existed, err := getIssueIndexerData(ctx, item.ID)
+ if err != nil {
+ log.Error("Issue indexer handler: failed to get issue data of %d: %v", item.ID, err)
+ unhandled = append(unhandled, item)
+ continue
+ }
+ if !existed {
+ if err := indexer.Delete(ctx, item.ID); err != nil {
+ log.Error("Issue indexer handler: failed to delete issue %d from index: %v", item.ID, err)
+ unhandled = append(unhandled, item)
+ }
+ continue
+ }
+ if err := indexer.Index(ctx, data); err != nil {
+ log.Error("Issue indexer handler: failed to index issue %d: %v", item.ID, err)
+ unhandled = append(unhandled, item)
+ continue
+ }
+ }
+
+ return unhandled
+ }
+}
+
// populateIssueIndexer populate the issue indexer with issue data
func populateIssueIndexer(ctx context.Context) {
ctx, _, finished := process.GetManager().AddTypedContext(ctx, "Service: PopulateIssueIndexer", process.SystemProcessType, true)
@@ -203,101 +225,87 @@ func populateIssueIndexer(ctx context.Context) {
}
for _, repo := range repos {
- select {
- case <-ctx.Done():
- log.Info("Issue Indexer population shutdown before completion")
- return
- default:
+ for {
+ select {
+ case <-ctx.Done():
+ log.Info("Issue Indexer population shutdown before completion")
+ return
+ default:
+ }
+ if err := updateRepoIndexer(ctx, repo.ID); err != nil {
+ log.Warn("Retry to populate issue indexer for repo %d: %v", repo.ID, err)
+ continue
+ }
+ break
}
- UpdateRepoIndexer(ctx, repo)
}
}
}
// UpdateRepoIndexer add/update all issues of the repositories
-func UpdateRepoIndexer(ctx context.Context, repo *repo_model.Repository) {
- is, err := issues_model.Issues(ctx, &issues_model.IssuesOptions{
- RepoIDs: []int64{repo.ID},
- IsClosed: util.OptionalBoolNone,
- IsPull: util.OptionalBoolNone,
- })
- if err != nil {
- log.Error("Issues: %v", err)
- return
- }
- if err = issues_model.IssueList(is).LoadDiscussComments(ctx); err != nil {
- log.Error("LoadDiscussComments: %v", err)
- return
- }
- for _, issue := range is {
- UpdateIssueIndexer(issue)
+func UpdateRepoIndexer(ctx context.Context, repoID int64) {
+ if err := updateRepoIndexer(ctx, repoID); err != nil {
+ log.Error("Unable to push repo %d to issue indexer: %v", repoID, err)
}
}
// UpdateIssueIndexer add/update an issue to the issue indexer
-func UpdateIssueIndexer(issue *issues_model.Issue) {
- var comments []string
- for _, comment := range issue.Comments {
- if comment.Type == issues_model.CommentTypeComment {
- comments = append(comments, comment.Content)
- }
- }
- issueType := "issue"
- if issue.IsPull {
- issueType = "pull"
- }
- indexerData := &internal.IndexerData{
- ID: issue.ID,
- RepoID: issue.RepoID,
- State: string(issue.State()),
- IssueType: issueType,
- Title: issue.Title,
- Content: issue.Content,
- Comments: comments,
- }
- log.Debug("Adding to channel: %v", indexerData)
- if err := issueIndexerQueue.Push(indexerData); err != nil {
- log.Error("Unable to push to issue indexer: %v: Error: %v", indexerData, err)
+func UpdateIssueIndexer(issueID int64) {
+ if err := updateIssueIndexer(issueID); err != nil {
+ log.Error("Unable to push issue %d to issue indexer: %v", issueID, err)
}
}
// DeleteRepoIssueIndexer deletes repo's all issues indexes
-func DeleteRepoIssueIndexer(ctx context.Context, repo *repo_model.Repository) {
- var ids []int64
- ids, err := issues_model.GetIssueIDsByRepoID(ctx, repo.ID)
- if err != nil {
- log.Error("GetIssueIDsByRepoID failed: %v", err)
- return
+func DeleteRepoIssueIndexer(ctx context.Context, repoID int64) {
+ if err := deleteRepoIssueIndexer(ctx, repoID); err != nil {
+ log.Error("Unable to push deleted repo %d to issue indexer: %v", repoID, err)
}
+}
- if len(ids) == 0 {
- return
- }
- indexerData := &internal.IndexerData{
- IDs: ids,
- IsDelete: true,
- }
- if err := issueIndexerQueue.Push(indexerData); err != nil {
- log.Error("Unable to push to issue indexer: %v: Error: %v", indexerData, err)
- }
+// IsAvailable checks if issue indexer is available
+func IsAvailable(ctx context.Context) bool {
+ return (*globalIndexer.Load()).Ping(ctx) == nil
}
-// SearchIssuesByKeyword search issue ids by keywords and repo id
-// WARNNING: You have to ensure user have permission to visit repoIDs' issues
-func SearchIssuesByKeyword(ctx context.Context, repoIDs []int64, keyword, state string) ([]int64, error) {
- var issueIDs []int64
+// SearchOptions indicates the options for searching issues
+type SearchOptions internal.SearchOptions
+
+const (
+ SortByCreatedDesc = internal.SortByCreatedDesc
+ SortByUpdatedDesc = internal.SortByUpdatedDesc
+ SortByCommentsDesc = internal.SortByCommentsDesc
+ SortByDeadlineDesc = internal.SortByDeadlineDesc
+ SortByCreatedAsc = internal.SortByCreatedAsc
+ SortByUpdatedAsc = internal.SortByUpdatedAsc
+ SortByCommentsAsc = internal.SortByCommentsAsc
+ SortByDeadlineAsc = internal.SortByDeadlineAsc
+)
+
+// SearchIssues search issues by options.
+// It returns issue ids and a bool value indicates if the result is imprecise.
+func SearchIssues(ctx context.Context, opts *SearchOptions) ([]int64, int64, error) {
indexer := *globalIndexer.Load()
- res, err := indexer.Search(ctx, keyword, repoIDs, 50, 0, state)
+
+ if opts.Keyword == "" {
+ // This is a conservative shortcut.
+ // If the keyword is empty, db has better (at least not worse) performance to filter issues.
+ // When the keyword is empty, it tends to listing rather than searching issues.
+ // So if the user creates an issue and list issues immediately, the issue may not be listed because the indexer needs time to index the issue.
+ // Even worse, the external indexer like elastic search may not be available for a while,
+ // and the user may not be able to list issues completely until it is available again.
+ indexer = db.NewIndexer()
+ }
+
+ result, err := indexer.Search(ctx, (*internal.SearchOptions)(opts))
if err != nil {
- return nil, err
+ return nil, 0, err
}
- for _, r := range res.Hits {
- issueIDs = append(issueIDs, r.ID)
+
+ ret := make([]int64, 0, len(result.Hits))
+ for _, hit := range result.Hits {
+ ret = append(ret, hit.ID)
}
- return issueIDs, nil
-}
-// IsAvailable checks if issue indexer is available
-func IsAvailable(ctx context.Context) bool {
- return (*globalIndexer.Load()).Ping(ctx) == nil
+ return ret, result.Total, nil
}