From ce2287ff673f9a6f38358c06a923cc1ab4a0d36b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 8 Feb 2025 22:49:40 -0800 Subject: [PATCH 1/9] Make index matched issues in the front of other keyword matched issues --- models/issues/issue.go | 23 +++++++ routers/web/repo/issue_suggestions.go | 99 +++++++++++++++++++-------- 2 files changed, 94 insertions(+), 28 deletions(-) diff --git a/models/issues/issue.go b/models/issues/issue.go index 564a9fb8359bb..8faf04d96d10c 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -501,6 +501,29 @@ func GetIssueByIndex(ctx context.Context, repoID, index int64) (*Issue, error) { return issue, nil } +func FindIssuesWithIndexPrefix(ctx context.Context, repoID, index int64, pageSize int) ([]*Issue, error) { + issues := make([]*Issue, 0, pageSize) + var cond string + switch { + case setting.Database.Type.IsSQLite3(): + cond = "CAST(`index` AS TEXT) LIKE ?" + case setting.Database.Type.IsMySQL(): + cond = "CAST(`index` AS CHAR) LIKE ?" + case setting.Database.Type.IsPostgreSQL(): + cond = "index::TEXT LIKE ?" + case setting.Database.Type.IsMSSQL(): + cond = "CAST([index] AS VARCHAR) LIKE ?" + } + + err := db.GetEngine(ctx).Where("repo_id = ?", repoID). + And("`index` <> ?", index). + And(cond, fmt.Sprintf("%d%%", index)). + OrderBy("`index` ASC"). + Limit(pageSize). + Find(&issues) + return issues, err +} + // GetIssueWithAttrsByIndex returns issue by index in a repository. func GetIssueWithAttrsByIndex(ctx context.Context, repoID, index int64) (*Issue, error) { issue, err := GetIssueByIndex(ctx, repoID, index) diff --git a/routers/web/repo/issue_suggestions.go b/routers/web/repo/issue_suggestions.go index 46e9f339a5a53..1c87267a8e2ed 100644 --- a/routers/web/repo/issue_suggestions.go +++ b/routers/web/repo/issue_suggestions.go @@ -5,6 +5,8 @@ package repo import ( "net/http" + "sort" + "strconv" "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" @@ -29,31 +31,78 @@ func IssueSuggestions(ctx *context.Context) { isPull = optional.Some(false) } - searchOpt := &issue_indexer.SearchOptions{ - Paginator: &db.ListOptions{ - Page: 0, - PageSize: 5, - }, - Keyword: keyword, - RepoIDs: []int64{ctx.Repo.Repository.ID}, - IsPull: isPull, - IsClosed: nil, - SortBy: issue_indexer.SortByUpdatedDesc, + indexKeyword, _ := strconv.ParseInt(keyword, 10, 64) + pageSize := 5 + issues := make(issues_model.IssueList, 0, pageSize) + if indexKeyword > 0 { + issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, indexKeyword) + if err != nil && !issues_model.IsErrIssueNotExist(err) { + ctx.ServerError("GetIssueByIndex", err) + return + } + if issue != nil { + pageSize-- + } + + issues, err = issues_model.FindIssuesWithIndexPrefix(ctx, ctx.Repo.Repository.ID, indexKeyword, pageSize) + if err != nil { + ctx.ServerError("FindIssuesWithIndexPrefix", err) + return + } + pageSize -= len(issues) + if issue != nil { + issues = append([]*issues_model.Issue{issue}, issues...) + } } - ids, _, err := issue_indexer.SearchIssues(ctx, searchOpt) - if err != nil { - ctx.ServerError("SearchIssues", err) - return + if pageSize > 0 { + searchOpt := &issue_indexer.SearchOptions{ + Paginator: &db.ListOptions{ + Page: 0, + PageSize: pageSize, + }, + Keyword: keyword, + RepoIDs: []int64{ctx.Repo.Repository.ID}, + IsPull: isPull, + IsClosed: nil, + SortBy: issue_indexer.SortByUpdatedDesc, + } + + ids, _, err := issue_indexer.SearchIssues(ctx, searchOpt) + if err != nil { + ctx.ServerError("SearchIssues", err) + return + } + + for i := 0; i < len(ids); i++ { + for _, issue := range issues { + if ids[i] == issue.ID { + ids = append(ids[:i], ids[i+1:]...) + i-- + break + } + } + } + + if len(ids) > 0 { + newIssues, err := issues_model.GetIssuesByIDs(ctx, ids, true) + if err != nil { + ctx.ServerError("FindIssuesByIDs", err) + return + } + sort.Slice(newIssues, func(i, j int) bool { + return newIssues[i].Index > newIssues[j].Index + }) + issues = append(issues, newIssues...) + } } - issues, err := issues_model.GetIssuesByIDs(ctx, ids, true) - if err != nil { - ctx.ServerError("FindIssuesByIDs", err) + + if err := issues.LoadPullRequests(ctx); err != nil { + ctx.ServerError("LoadPullRequests", err) return } suggestions := make([]*structs.Issue, 0, len(issues)) - for _, issue := range issues { suggestion := &structs.Issue{ ID: issue.ID, @@ -62,16 +111,10 @@ func IssueSuggestions(ctx *context.Context) { State: issue.State(), } - if issue.IsPull { - if err := issue.LoadPullRequest(ctx); err != nil { - ctx.ServerError("LoadPullRequest", err) - return - } - if issue.PullRequest != nil { - suggestion.PullRequest = &structs.PullRequestMeta{ - HasMerged: issue.PullRequest.HasMerged, - IsWorkInProgress: issue.PullRequest.IsWorkInProgress(ctx), - } + if issue.IsPull && issue.PullRequest != nil { + suggestion.PullRequest = &structs.PullRequestMeta{ + HasMerged: issue.PullRequest.HasMerged, + IsWorkInProgress: issue.PullRequest.IsWorkInProgress(ctx), } } From 684d744f8e20eeee329e38986a75cf768f88b9a2 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 9 Feb 2025 12:51:29 -0800 Subject: [PATCH 2/9] Add logic if keyword is empty --- models/issues/issue.go | 35 +++++++++- routers/web/repo/issue_suggestions.go | 97 ++------------------------- services/issue/suggestion.go | 67 ++++++++++++++++++ 3 files changed, 103 insertions(+), 96 deletions(-) create mode 100644 services/issue/suggestion.go diff --git a/models/issues/issue.go b/models/issues/issue.go index 8faf04d96d10c..14a4ae36ad253 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -17,6 +17,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" @@ -501,8 +502,35 @@ func GetIssueByIndex(ctx context.Context, repoID, index int64) (*Issue, error) { return issue, nil } -func FindIssuesWithIndexPrefix(ctx context.Context, repoID, index int64, pageSize int) ([]*Issue, error) { +func isPullToCond(isPull optional.Option[bool]) builder.Cond { + if isPull.Has() { + return builder.Eq{"is_pull": isPull.Value()} + } + return builder.NewCond() +} + +func FindLatestIssues(ctx context.Context, repoID int64, isPull optional.Option[bool], pageSize int) (IssueList, error) { issues := make([]*Issue, 0, pageSize) + err := db.GetEngine(ctx).Where("repo_id = ?", repoID). + And(isPullToCond(isPull)). + OrderBy("created_unix DESC"). + Limit(pageSize). + Find(&issues) + return issues, err +} + +func FindIssuesTitleKeywords(ctx context.Context, repoID int64, keyword string, isPull optional.Option[bool], pageSize int) (IssueList, error) { + issues := make([]*Issue, 0, pageSize) + err := db.GetEngine(ctx).Where("repo_id = ?", repoID). + And(isPullToCond(isPull)). + And("name LIKE ?", "%"+keyword+"%"). + OrderBy("created_unix DESC"). + Limit(pageSize). + Find(&issues) + return issues, err +} + +func FindIssuesWithIndexPrefix(ctx context.Context, repoID, index int64, isPull optional.Option[bool], pageSize int) (IssueList, error) { var cond string switch { case setting.Database.Type.IsSQLite3(): @@ -515,9 +543,10 @@ func FindIssuesWithIndexPrefix(ctx context.Context, repoID, index int64, pageSiz cond = "CAST([index] AS VARCHAR) LIKE ?" } + issues := make([]*Issue, 0, pageSize) err := db.GetEngine(ctx).Where("repo_id = ?", repoID). - And("`index` <> ?", index). - And(cond, fmt.Sprintf("%d%%", index)). + And(isPullToCond(isPull)). + Where(cond, fmt.Sprintf("%d%%", index)). OrderBy("`index` ASC"). Limit(pageSize). Find(&issues) diff --git a/routers/web/repo/issue_suggestions.go b/routers/web/repo/issue_suggestions.go index 1c87267a8e2ed..9ef39425041f8 100644 --- a/routers/web/repo/issue_suggestions.go +++ b/routers/web/repo/issue_suggestions.go @@ -5,16 +5,11 @@ package repo import ( "net/http" - "sort" - "strconv" - "code.gitea.io/gitea/models/db" - issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/unit" - issue_indexer "code.gitea.io/gitea/modules/indexer/issues" "code.gitea.io/gitea/modules/optional" - "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/services/context" + issue_service "code.gitea.io/gitea/services/issue" ) // IssueSuggestions returns a list of issue suggestions @@ -31,95 +26,11 @@ func IssueSuggestions(ctx *context.Context) { isPull = optional.Some(false) } - indexKeyword, _ := strconv.ParseInt(keyword, 10, 64) - pageSize := 5 - issues := make(issues_model.IssueList, 0, pageSize) - if indexKeyword > 0 { - issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, indexKeyword) - if err != nil && !issues_model.IsErrIssueNotExist(err) { - ctx.ServerError("GetIssueByIndex", err) - return - } - if issue != nil { - pageSize-- - } - - issues, err = issues_model.FindIssuesWithIndexPrefix(ctx, ctx.Repo.Repository.ID, indexKeyword, pageSize) - if err != nil { - ctx.ServerError("FindIssuesWithIndexPrefix", err) - return - } - pageSize -= len(issues) - if issue != nil { - issues = append([]*issues_model.Issue{issue}, issues...) - } - } - - if pageSize > 0 { - searchOpt := &issue_indexer.SearchOptions{ - Paginator: &db.ListOptions{ - Page: 0, - PageSize: pageSize, - }, - Keyword: keyword, - RepoIDs: []int64{ctx.Repo.Repository.ID}, - IsPull: isPull, - IsClosed: nil, - SortBy: issue_indexer.SortByUpdatedDesc, - } - - ids, _, err := issue_indexer.SearchIssues(ctx, searchOpt) - if err != nil { - ctx.ServerError("SearchIssues", err) - return - } - - for i := 0; i < len(ids); i++ { - for _, issue := range issues { - if ids[i] == issue.ID { - ids = append(ids[:i], ids[i+1:]...) - i-- - break - } - } - } - - if len(ids) > 0 { - newIssues, err := issues_model.GetIssuesByIDs(ctx, ids, true) - if err != nil { - ctx.ServerError("FindIssuesByIDs", err) - return - } - sort.Slice(newIssues, func(i, j int) bool { - return newIssues[i].Index > newIssues[j].Index - }) - issues = append(issues, newIssues...) - } - } - - if err := issues.LoadPullRequests(ctx); err != nil { - ctx.ServerError("LoadPullRequests", err) + suggestions, err := issue_service.GetSuggestion(ctx, ctx.Repo.Repository, isPull, keyword) + if err != nil { + ctx.ServerError("GetSuggestion", err) return } - suggestions := make([]*structs.Issue, 0, len(issues)) - for _, issue := range issues { - suggestion := &structs.Issue{ - ID: issue.ID, - Index: issue.Index, - Title: issue.Title, - State: issue.State(), - } - - if issue.IsPull && issue.PullRequest != nil { - suggestion.PullRequest = &structs.PullRequestMeta{ - HasMerged: issue.PullRequest.HasMerged, - IsWorkInProgress: issue.PullRequest.IsWorkInProgress(ctx), - } - } - - suggestions = append(suggestions, suggestion) - } - ctx.JSON(http.StatusOK, suggestions) } diff --git a/services/issue/suggestion.go b/services/issue/suggestion.go new file mode 100644 index 0000000000000..9862c10dbff0c --- /dev/null +++ b/services/issue/suggestion.go @@ -0,0 +1,67 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package issue + +import ( + "context" + "strconv" + + issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/optional" + "code.gitea.io/gitea/modules/structs" +) + +func GetSuggestion(ctx context.Context, repo *repo_model.Repository, isPull optional.Option[bool], keyword string) ([]*structs.Issue, error) { + var issues issues_model.IssueList + var err error + pageSize := 5 + if keyword == "" { + issues, err = issues_model.FindLatestIssues(ctx, repo.ID, isPull, pageSize) + if err != nil { + return nil, err + } + } else { + indexKeyword, _ := strconv.ParseInt(keyword, 10, 64) + if indexKeyword > 0 { + issues, err = issues_model.FindIssuesWithIndexPrefix(ctx, repo.ID, indexKeyword, isPull, pageSize) + if err != nil { + return nil, err + } + pageSize -= len(issues) + } + + if pageSize > 0 { + newIssues, err := issues_model.FindIssuesTitleKeywords(ctx, repo.ID, keyword, isPull, pageSize) + if err != nil { + return nil, err + } + issues = append(issues, newIssues...) + } + } + + if err := issues.LoadPullRequests(ctx); err != nil { + return nil, err + } + + suggestions := make([]*structs.Issue, 0, len(issues)) + for _, issue := range issues { + suggestion := &structs.Issue{ + ID: issue.ID, + Index: issue.Index, + Title: issue.Title, + State: issue.State(), + } + + if issue.IsPull && issue.PullRequest != nil { + suggestion.PullRequest = &structs.PullRequestMeta{ + HasMerged: issue.PullRequest.HasMerged, + IsWorkInProgress: issue.PullRequest.IsWorkInProgress(ctx), + } + } + suggestions = append(suggestions, suggestion) + } + + return suggestions, nil +} From a2822a0837054f3ed0cac2933776a7352c2d6d63 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 9 Feb 2025 13:09:01 -0800 Subject: [PATCH 3/9] Add test and fix bug --- models/issues/issue.go | 11 +++--- services/issue/suggestion.go | 6 +++- services/issue/suggestion_test.go | 57 +++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 services/issue/suggestion_test.go diff --git a/models/issues/issue.go b/models/issues/issue.go index 14a4ae36ad253..c6f9b6a52168b 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -519,11 +519,14 @@ func FindLatestIssues(ctx context.Context, repoID int64, isPull optional.Option[ return issues, err } -func FindIssuesTitleKeywords(ctx context.Context, repoID int64, keyword string, isPull optional.Option[bool], pageSize int) (IssueList, error) { +func FindIssuesTitleKeywords(ctx context.Context, repoID int64, keyword string, isPull optional.Option[bool], nonIDs []int64, pageSize int) (IssueList, error) { issues := make([]*Issue, 0, pageSize) - err := db.GetEngine(ctx).Where("repo_id = ?", repoID). - And(isPullToCond(isPull)). - And("name LIKE ?", "%"+keyword+"%"). + sess := db.GetEngine(ctx).Where("repo_id = ?", repoID). + And(isPullToCond(isPull)) + if len(nonIDs) > 0 { + sess.NotIn("id", nonIDs) + } + err := sess.And("name LIKE ?", "%"+keyword+"%"). OrderBy("created_unix DESC"). Limit(pageSize). Find(&issues) diff --git a/services/issue/suggestion.go b/services/issue/suggestion.go index 9862c10dbff0c..0e6181751fd13 100644 --- a/services/issue/suggestion.go +++ b/services/issue/suggestion.go @@ -24,16 +24,20 @@ func GetSuggestion(ctx context.Context, repo *repo_model.Repository, isPull opti } } else { indexKeyword, _ := strconv.ParseInt(keyword, 10, 64) + nonIDs := []int64{} if indexKeyword > 0 { issues, err = issues_model.FindIssuesWithIndexPrefix(ctx, repo.ID, indexKeyword, isPull, pageSize) if err != nil { return nil, err } + for _, issue := range issues { + nonIDs = append(nonIDs, issue.ID) + } pageSize -= len(issues) } if pageSize > 0 { - newIssues, err := issues_model.FindIssuesTitleKeywords(ctx, repo.ID, keyword, isPull, pageSize) + newIssues, err := issues_model.FindIssuesTitleKeywords(ctx, repo.ID, keyword, isPull, nonIDs, pageSize) if err != nil { return nil, err } diff --git a/services/issue/suggestion_test.go b/services/issue/suggestion_test.go new file mode 100644 index 0000000000000..05a0f9b266ef3 --- /dev/null +++ b/services/issue/suggestion_test.go @@ -0,0 +1,57 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package issue + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/optional" + + "github.com/stretchr/testify/assert" +) + +func Test_Suggestion(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + testCases := []struct { + keyword string + isPull optional.Option[bool] + expectedIndexes []int64 + }{ + { + keyword: "", + expectedIndexes: []int64{5, 4, 3, 2, 1}, + }, + { + keyword: "1", + expectedIndexes: []int64{1}, + }, + { + keyword: "issue", + expectedIndexes: []int64{4, 3, 2, 1}, + }, + { + keyword: "pull", + expectedIndexes: []int64{5}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.keyword, func(t *testing.T) { + issues, err := GetSuggestion(db.DefaultContext, repo1, testCase.isPull, testCase.keyword) + assert.NoError(t, err) + + issueIndexes := make([]int64, 0, len(issues)) + for _, issue := range issues { + issueIndexes = append(issueIndexes, issue.Index) + } + assert.EqualValues(t, testCase.expectedIndexes, issueIndexes) + }) + } +} From f37f3897d04a8eaf51eabdb45818fe4a3b88e84e Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 9 Feb 2025 21:06:13 -0800 Subject: [PATCH 4/9] Remove prefix index search in suggestion feature --- models/issues/issue.go | 36 +++++++++--------------------------- services/issue/suggestion.go | 31 ++++++++++++++----------------- 2 files changed, 23 insertions(+), 44 deletions(-) diff --git a/models/issues/issue.go b/models/issues/issue.go index c6f9b6a52168b..46a83392526f1 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -10,6 +10,7 @@ import ( "html/template" "regexp" "slices" + "strconv" "code.gitea.io/gitea/models/db" project_model "code.gitea.io/gitea/models/project" @@ -519,38 +520,19 @@ func FindLatestIssues(ctx context.Context, repoID int64, isPull optional.Option[ return issues, err } -func FindIssuesTitleKeywords(ctx context.Context, repoID int64, keyword string, isPull optional.Option[bool], nonIDs []int64, pageSize int) (IssueList, error) { - issues := make([]*Issue, 0, pageSize) - sess := db.GetEngine(ctx).Where("repo_id = ?", repoID). - And(isPullToCond(isPull)) - if len(nonIDs) > 0 { - sess.NotIn("id", nonIDs) - } - err := sess.And("name LIKE ?", "%"+keyword+"%"). - OrderBy("created_unix DESC"). - Limit(pageSize). - Find(&issues) - return issues, err -} - -func FindIssuesWithIndexPrefix(ctx context.Context, repoID, index int64, isPull optional.Option[bool], pageSize int) (IssueList, error) { - var cond string - switch { - case setting.Database.Type.IsSQLite3(): - cond = "CAST(`index` AS TEXT) LIKE ?" - case setting.Database.Type.IsMySQL(): - cond = "CAST(`index` AS CHAR) LIKE ?" - case setting.Database.Type.IsPostgreSQL(): - cond = "index::TEXT LIKE ?" - case setting.Database.Type.IsMSSQL(): - cond = "CAST([index] AS VARCHAR) LIKE ?" +func FindIssuesSuggestionByKeyword(ctx context.Context, repoID int64, keyword string, isPull optional.Option[bool], pageSize int) (IssueList, error) { + indexKeyword, _ := strconv.ParseInt(keyword, 10, 64) + cond := builder.NewCond() + if indexKeyword > 0 { + cond = cond.Or(builder.Eq{"`index`": indexKeyword}) } + cond = cond.Or(builder.Expr("name LIKE ?", "%"+keyword+"%")) issues := make([]*Issue, 0, pageSize) err := db.GetEngine(ctx).Where("repo_id = ?", repoID). And(isPullToCond(isPull)). - Where(cond, fmt.Sprintf("%d%%", index)). - OrderBy("`index` ASC"). + And(cond). + OrderBy("`index` DESC"). Limit(pageSize). Find(&issues) return issues, err diff --git a/services/issue/suggestion.go b/services/issue/suggestion.go index 0e6181751fd13..b2f6bc69652d4 100644 --- a/services/issue/suggestion.go +++ b/services/issue/suggestion.go @@ -5,6 +5,7 @@ package issue import ( "context" + "sort" "strconv" issues_model "code.gitea.io/gitea/models/issues" @@ -23,25 +24,21 @@ func GetSuggestion(ctx context.Context, repo *repo_model.Repository, isPull opti return nil, err } } else { + issues, err = issues_model.FindIssuesSuggestionByKeyword(ctx, repo.ID, keyword, isPull, pageSize) + if err != nil { + return nil, err + } indexKeyword, _ := strconv.ParseInt(keyword, 10, 64) - nonIDs := []int64{} if indexKeyword > 0 { - issues, err = issues_model.FindIssuesWithIndexPrefix(ctx, repo.ID, indexKeyword, isPull, pageSize) - if err != nil { - return nil, err - } - for _, issue := range issues { - nonIDs = append(nonIDs, issue.ID) - } - pageSize -= len(issues) - } - - if pageSize > 0 { - newIssues, err := issues_model.FindIssuesTitleKeywords(ctx, repo.ID, keyword, isPull, nonIDs, pageSize) - if err != nil { - return nil, err - } - issues = append(issues, newIssues...) + sort.Slice(issues, func(i, j int) bool { + if issues[i].Index == indexKeyword { + return true + } + if issues[j].Index == indexKeyword { + return false + } + return issues[i].Index > issues[j].Index + }) } } From e24690d5962d83d58ecd16cb3e2429fdfe55be52 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 9 Feb 2025 21:57:58 -0800 Subject: [PATCH 5/9] Follow @wxiaoguang's suggestion --- models/issues/issue.go | 10 ++++------ services/issue/suggestion.go | 31 ++++++++++++++++++------------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/models/issues/issue.go b/models/issues/issue.go index 46a83392526f1..c578c1360a3d5 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -10,7 +10,6 @@ import ( "html/template" "regexp" "slices" - "strconv" "code.gitea.io/gitea/models/db" project_model "code.gitea.io/gitea/models/project" @@ -520,13 +519,12 @@ func FindLatestIssues(ctx context.Context, repoID int64, isPull optional.Option[ return issues, err } -func FindIssuesSuggestionByKeyword(ctx context.Context, repoID int64, keyword string, isPull optional.Option[bool], pageSize int) (IssueList, error) { - indexKeyword, _ := strconv.ParseInt(keyword, 10, 64) +func FindIssuesSuggestionByKeyword(ctx context.Context, repoID int64, keyword string, isPull optional.Option[bool], nonID int64, pageSize int) (IssueList, error) { cond := builder.NewCond() - if indexKeyword > 0 { - cond = cond.Or(builder.Eq{"`index`": indexKeyword}) + if nonID > 0 { + cond = cond.And(builder.Neq{"`id`": nonID}) } - cond = cond.Or(builder.Expr("name LIKE ?", "%"+keyword+"%")) + cond = cond.And(builder.Expr("name LIKE ?", "%"+keyword+"%")) issues := make([]*Issue, 0, pageSize) err := db.GetEngine(ctx).Where("repo_id = ?", repoID). diff --git a/services/issue/suggestion.go b/services/issue/suggestion.go index b2f6bc69652d4..468c2217105d6 100644 --- a/services/issue/suggestion.go +++ b/services/issue/suggestion.go @@ -5,7 +5,6 @@ package issue import ( "context" - "sort" "strconv" issues_model "code.gitea.io/gitea/models/issues" @@ -24,21 +23,27 @@ func GetSuggestion(ctx context.Context, repo *repo_model.Repository, isPull opti return nil, err } } else { - issues, err = issues_model.FindIssuesSuggestionByKeyword(ctx, repo.ID, keyword, isPull, pageSize) + indexKeyword, _ := strconv.ParseInt(keyword, 10, 64) + var issue *issues_model.Issue + var nonID int64 + if indexKeyword > 0 { + issue, err = issues_model.GetIssueByIndex(ctx, repo.ID, indexKeyword) + if err != nil && !issues_model.IsErrIssueNotExist(err) { + return nil, err + } + if issue != nil { + nonID = issue.ID + pageSize-- + } + } + + issues, err = issues_model.FindIssuesSuggestionByKeyword(ctx, repo.ID, keyword, isPull, nonID, pageSize) if err != nil { return nil, err } - indexKeyword, _ := strconv.ParseInt(keyword, 10, 64) - if indexKeyword > 0 { - sort.Slice(issues, func(i, j int) bool { - if issues[i].Index == indexKeyword { - return true - } - if issues[j].Index == indexKeyword { - return false - } - return issues[i].Index > issues[j].Index - }) + + if issue != nil { + issues = append([]*issues_model.Issue{issue}, issues...) } } From 8157fd6c8994e33d954f262203fd8ac7d9d8239f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 9 Feb 2025 22:52:52 -0800 Subject: [PATCH 6/9] Some improvements --- models/issues/issue.go | 8 ++++---- services/issue/suggestion.go | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/models/issues/issue.go b/models/issues/issue.go index c578c1360a3d5..a5bf45ff3052f 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -513,16 +513,16 @@ func FindLatestIssues(ctx context.Context, repoID int64, isPull optional.Option[ issues := make([]*Issue, 0, pageSize) err := db.GetEngine(ctx).Where("repo_id = ?", repoID). And(isPullToCond(isPull)). - OrderBy("created_unix DESC"). + OrderBy("updated_unix DESC"). Limit(pageSize). Find(&issues) return issues, err } -func FindIssuesSuggestionByKeyword(ctx context.Context, repoID int64, keyword string, isPull optional.Option[bool], nonID int64, pageSize int) (IssueList, error) { +func FindIssuesSuggestionByKeyword(ctx context.Context, repoID int64, keyword string, isPull optional.Option[bool], excludedID int64, pageSize int) (IssueList, error) { cond := builder.NewCond() - if nonID > 0 { - cond = cond.And(builder.Neq{"`id`": nonID}) + if excludedID > 0 { + cond = cond.And(builder.Neq{"`id`": excludedID}) } cond = cond.And(builder.Expr("name LIKE ?", "%"+keyword+"%")) diff --git a/services/issue/suggestion.go b/services/issue/suggestion.go index 468c2217105d6..18e5db0428267 100644 --- a/services/issue/suggestion.go +++ b/services/issue/suggestion.go @@ -25,19 +25,19 @@ func GetSuggestion(ctx context.Context, repo *repo_model.Repository, isPull opti } else { indexKeyword, _ := strconv.ParseInt(keyword, 10, 64) var issue *issues_model.Issue - var nonID int64 + var excludedID int64 if indexKeyword > 0 { issue, err = issues_model.GetIssueByIndex(ctx, repo.ID, indexKeyword) if err != nil && !issues_model.IsErrIssueNotExist(err) { return nil, err } if issue != nil { - nonID = issue.ID + excludedID = issue.ID pageSize-- } } - issues, err = issues_model.FindIssuesSuggestionByKeyword(ctx, repo.ID, keyword, isPull, nonID, pageSize) + issues, err = issues_model.FindIssuesSuggestionByKeyword(ctx, repo.ID, keyword, isPull, excludedID, pageSize) if err != nil { return nil, err } From e952ac73a60ec7b0ea822850a2a26da5f86aaa32 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 10 Feb 2025 14:59:49 +0800 Subject: [PATCH 7/9] use case-insensitive like --- models/issues/issue.go | 4 ++-- services/issue/suggestion.go | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/models/issues/issue.go b/models/issues/issue.go index a5bf45ff3052f..348444e0ed886 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -509,7 +509,7 @@ func isPullToCond(isPull optional.Option[bool]) builder.Cond { return builder.NewCond() } -func FindLatestIssues(ctx context.Context, repoID int64, isPull optional.Option[bool], pageSize int) (IssueList, error) { +func FindLatestUpdatedIssues(ctx context.Context, repoID int64, isPull optional.Option[bool], pageSize int) (IssueList, error) { issues := make([]*Issue, 0, pageSize) err := db.GetEngine(ctx).Where("repo_id = ?", repoID). And(isPullToCond(isPull)). @@ -524,7 +524,7 @@ func FindIssuesSuggestionByKeyword(ctx context.Context, repoID int64, keyword st if excludedID > 0 { cond = cond.And(builder.Neq{"`id`": excludedID}) } - cond = cond.And(builder.Expr("name LIKE ?", "%"+keyword+"%")) + cond = cond.And(db.BuildCaseInsensitiveLike("`name`", keyword)) issues := make([]*Issue, 0, pageSize) err := db.GetEngine(ctx).Where("repo_id = ?", repoID). diff --git a/services/issue/suggestion.go b/services/issue/suggestion.go index 18e5db0428267..22eddb19042bb 100644 --- a/services/issue/suggestion.go +++ b/services/issue/suggestion.go @@ -18,21 +18,21 @@ func GetSuggestion(ctx context.Context, repo *repo_model.Repository, isPull opti var err error pageSize := 5 if keyword == "" { - issues, err = issues_model.FindLatestIssues(ctx, repo.ID, isPull, pageSize) + issues, err = issues_model.FindLatestUpdatedIssues(ctx, repo.ID, isPull, pageSize) if err != nil { return nil, err } } else { indexKeyword, _ := strconv.ParseInt(keyword, 10, 64) - var issue *issues_model.Issue + var issueByIndex *issues_model.Issue var excludedID int64 if indexKeyword > 0 { - issue, err = issues_model.GetIssueByIndex(ctx, repo.ID, indexKeyword) + issueByIndex, err = issues_model.GetIssueByIndex(ctx, repo.ID, indexKeyword) if err != nil && !issues_model.IsErrIssueNotExist(err) { return nil, err } - if issue != nil { - excludedID = issue.ID + if issueByIndex != nil { + excludedID = issueByIndex.ID pageSize-- } } @@ -42,8 +42,8 @@ func GetSuggestion(ctx context.Context, repo *repo_model.Repository, isPull opti return nil, err } - if issue != nil { - issues = append([]*issues_model.Issue{issue}, issues...) + if issueByIndex != nil { + issues = append([]*issues_model.Issue{issueByIndex}, issues...) } } From b68eda516e22f3921e6ef936b68ebacd2faaa2cd Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 10 Feb 2025 15:04:43 +0800 Subject: [PATCH 8/9] prefer latest updated issues --- models/issues/issue.go | 2 +- services/issue/suggestion_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/models/issues/issue.go b/models/issues/issue.go index 348444e0ed886..175edbcd8defd 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -530,7 +530,7 @@ func FindIssuesSuggestionByKeyword(ctx context.Context, repoID int64, keyword st err := db.GetEngine(ctx).Where("repo_id = ?", repoID). And(isPullToCond(isPull)). And(cond). - OrderBy("`index` DESC"). + OrderBy("updated_unix DESC, `index` DESC"). Limit(pageSize). Find(&issues) return issues, err diff --git a/services/issue/suggestion_test.go b/services/issue/suggestion_test.go index 05a0f9b266ef3..84cfd520ac40a 100644 --- a/services/issue/suggestion_test.go +++ b/services/issue/suggestion_test.go @@ -26,7 +26,7 @@ func Test_Suggestion(t *testing.T) { }{ { keyword: "", - expectedIndexes: []int64{5, 4, 3, 2, 1}, + expectedIndexes: []int64{5, 1, 4, 2, 3}, }, { keyword: "1", @@ -34,7 +34,7 @@ func Test_Suggestion(t *testing.T) { }, { keyword: "issue", - expectedIndexes: []int64{4, 3, 2, 1}, + expectedIndexes: []int64{4, 1, 2, 3}, }, { keyword: "pull", From e99ee8ab3fdde00d673ecfd74e2b14609d56b118 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 10 Feb 2025 16:13:26 +0800 Subject: [PATCH 9/9] add comments --- models/issues/issue.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/models/issues/issue.go b/models/issues/issue.go index 175edbcd8defd..5d52f0dd5d7d3 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -524,6 +524,11 @@ func FindIssuesSuggestionByKeyword(ctx context.Context, repoID int64, keyword st if excludedID > 0 { cond = cond.And(builder.Neq{"`id`": excludedID}) } + + // It seems that GitHub searches both title and content (maybe sorting by the search engine's ranking system?) + // The first PR (https://github.com/go-gitea/gitea/pull/32327) uses "search indexer" to search "name(title) + content" + // But it seems that searching "content" (especially LIKE by DB engine) generates worse (unusable) results. + // So now (https://github.com/go-gitea/gitea/pull/33538) it only searches "name(title)", leave the improvements to the future. cond = cond.And(db.BuildCaseInsensitiveLike("`name`", keyword)) issues := make([]*Issue, 0, pageSize)