Allow searching issues by ID (#31479)

When you are entering a number in the issue search, you likely want the
issue with the given ID (code internal concept: issue index).
As such, when a number is detected, the issue with the corresponding ID
will now be added to the results.

Fixes #4479

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Carsten Klein 2024-07-17 00:49:05 +02:00 committed by GitHub
parent 416c36f303
commit 3571b7e3dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 54 additions and 2 deletions

@ -71,6 +71,12 @@ func (i *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
)), )),
), ),
) )
if options.IsKeywordNumeric() {
cond = cond.Or(
builder.Eq{"`index`": options.Keyword},
)
}
} }
opt, err := ToDBOptions(ctx, options) opt, err := ToDBOptions(ctx, options)

@ -283,9 +283,9 @@ const (
func SearchIssues(ctx context.Context, opts *SearchOptions) ([]int64, int64, error) { func SearchIssues(ctx context.Context, opts *SearchOptions) ([]int64, int64, error) {
indexer := *globalIndexer.Load() indexer := *globalIndexer.Load()
if opts.Keyword == "" { if opts.Keyword == "" || opts.IsKeywordNumeric() {
// This is a conservative shortcut. // This is a conservative shortcut.
// If the keyword is empty, db has better (at least not worse) performance to filter issues. // If the keyword is empty or an integer, db has better (at least not worse) performance to filter issues.
// When the keyword is empty, it tends to listing rather than searching 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. // 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, // Even worse, the external indexer like elastic search may not be available for a while,

@ -31,6 +31,7 @@ func TestDBSearchIssues(t *testing.T) {
InitIssueIndexer(true) InitIssueIndexer(true)
t.Run("search issues with keyword", searchIssueWithKeyword) t.Run("search issues with keyword", searchIssueWithKeyword)
t.Run("search issues by index", searchIssueByIndex)
t.Run("search issues in repo", searchIssueInRepo) t.Run("search issues in repo", searchIssueInRepo)
t.Run("search issues by ID", searchIssueByID) t.Run("search issues by ID", searchIssueByID)
t.Run("search issues is pr", searchIssueIsPull) t.Run("search issues is pr", searchIssueIsPull)
@ -87,6 +88,43 @@ func searchIssueWithKeyword(t *testing.T) {
} }
} }
func searchIssueByIndex(t *testing.T) {
tests := []struct {
opts SearchOptions
expectedIDs []int64
}{
{
SearchOptions{
Keyword: "1000",
RepoIDs: []int64{1},
},
[]int64{},
},
{
SearchOptions{
Keyword: "2",
RepoIDs: []int64{1, 2, 3, 32},
},
[]int64{17, 12, 7, 2},
},
{
SearchOptions{
Keyword: "1",
RepoIDs: []int64{58},
},
[]int64{19},
},
}
for _, test := range tests {
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
if !assert.NoError(t, err) {
return
}
assert.Equal(t, test.expectedIDs, issueIDs)
}
}
func searchIssueInRepo(t *testing.T) { func searchIssueInRepo(t *testing.T) {
tests := []struct { tests := []struct {
opts SearchOptions opts SearchOptions

@ -4,6 +4,8 @@
package internal package internal
import ( import (
"strconv"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
@ -124,6 +126,12 @@ func (o *SearchOptions) Copy(edit ...func(options *SearchOptions)) *SearchOption
return &v return &v
} }
// used for optimized issue index based search
func (o *SearchOptions) IsKeywordNumeric() bool {
_, err := strconv.Atoi(o.Keyword)
return err == nil
}
type SortBy string type SortBy string
const ( const (