Skip to content

Commit 259f134

Browse files
committed
fix
1 parent a713b4c commit 259f134

File tree

14 files changed

+55
-71
lines changed

14 files changed

+55
-71
lines changed

models/issues/issue_search.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,10 @@ func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) {
7676
if strings.HasPrefix(sortType, ScopeSortPrefix) {
7777
scope := strings.TrimPrefix(sortType, ScopeSortPrefix)
7878
sess.Join("LEFT", "issue_label", "issue.id = issue_label.issue_id")
79-
sess.Join("LEFT", "label", "label.id = issue_label.label_id and label.name LIKE ?", scope+"/%")
80-
// Use COALESCE to make sure we sort NULL last regardless of backend DB (9223372036854775807 == max bigint)
81-
sess.OrderBy("COALESCE(label.exclusive_order, 9223372036854775807) ASC").Desc("issue.id")
79+
// "exclusive_order=0" means "no order is set", so exclude it from the JOIN criteria and then "LEFT JOIN" result is also null
80+
sess.Join("LEFT", "label", "label.id = issue_label.label_id AND label.exclusive_order <> 0 AND label.name LIKE ?", scope+"/%")
81+
// Use COALESCE to make sure we sort NULL last regardless of backend DB (2147483647 == max int)
82+
sess.OrderBy("COALESCE(label.exclusive_order, 2147483647) ASC").Desc("issue.id")
8283
return
8384
}
8485

models/issues/label.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ type Label struct {
8787
OrgID int64 `xorm:"INDEX"`
8888
Name string
8989
Exclusive bool
90-
ExclusiveOrder int `xorm:"DEFAULT 0"`
90+
ExclusiveOrder int `xorm:"DEFAULT 0"` // 0 means no exclusive order
9191
Description string
9292
Color string `xorm:"VARCHAR(7)"`
9393
NumIssues int

models/migrations/v1_24/v319.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99

1010
func AddExclusiveOrderColumnToLabelTable(x *xorm.Engine) error {
1111
type Label struct {
12-
ExclusiveOrder int64 `xorm:"DEFAULT 0"`
12+
ExclusiveOrder int `xorm:"DEFAULT 0"`
1313
}
1414

1515
return x.Sync(new(Label))

modules/indexer/issues/bleve/bleve.go

-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package bleve
55

66
import (
77
"context"
8-
"errors"
98
"strconv"
109

1110
"code.gitea.io/gitea/modules/indexer"
@@ -287,10 +286,6 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
287286
"updated_unix"))
288287
}
289288

290-
if len(options.IssueIDs) > 0 {
291-
return nil, errors.New("options.IssueIDs is not yet supported")
292-
}
293-
294289
var indexerQuery query.Query = bleve.NewConjunctionQuery(queries...)
295290
if len(queries) == 0 {
296291
indexerQuery = bleve.NewMatchAllQuery()

modules/indexer/issues/db/db.go

+5-6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package db
66
import (
77
"context"
88
"strings"
9+
"sync"
910

1011
"code.gitea.io/gitea/models/db"
1112
issue_model "code.gitea.io/gitea/models/issues"
@@ -18,7 +19,7 @@ import (
1819
"xorm.io/builder"
1920
)
2021

21-
var _ internal.Indexer = &Indexer{}
22+
var _ internal.Indexer = (*Indexer)(nil)
2223

2324
// Indexer implements Indexer interface to use database's like search
2425
type Indexer struct {
@@ -29,11 +30,9 @@ func (i *Indexer) SupportedSearchModes() []indexer.SearchMode {
2930
return indexer.SearchModesExactWords()
3031
}
3132

32-
func NewIndexer() *Indexer {
33-
return &Indexer{
34-
Indexer: &inner_db.Indexer{},
35-
}
36-
}
33+
var GetIndexer = sync.OnceValue(func() *Indexer {
34+
return &Indexer{Indexer: &inner_db.Indexer{}}
35+
})
3736

3837
// Index dummy function
3938
func (i *Indexer) Index(_ context.Context, _ ...*internal.IndexerData) error {

modules/indexer/issues/db/options.go

-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m
7373
ExcludedLabelNames: nil,
7474
IncludeMilestones: nil,
7575
SortType: sortType,
76-
IssueIDs: options.IssueIDs,
7776
UpdatedAfterUnix: options.UpdatedAfterUnix.Value(),
7877
UpdatedBeforeUnix: options.UpdatedBeforeUnix.Value(),
7978
PriorityRepoID: 0,

modules/indexer/issues/dboptions.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,20 @@ import (
1010
issues_model "code.gitea.io/gitea/models/issues"
1111
"code.gitea.io/gitea/modules/indexer/issues/internal"
1212
"code.gitea.io/gitea/modules/optional"
13+
"code.gitea.io/gitea/modules/setting"
1314
)
1415

1516
func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOptions {
17+
if opts.IssueIDs != nil {
18+
setting.PanicInDevOrTesting("Indexer SearchOptions doesn't support IssueIDs")
19+
}
1620
searchOpt := &SearchOptions{
1721
Keyword: keyword,
1822
RepoIDs: opts.RepoIDs,
1923
AllPublic: opts.AllPublic,
2024
IsPull: opts.IsPull,
2125
IsClosed: opts.IsClosed,
2226
IsArchived: opts.IsArchived,
23-
IssueIDs: opts.IssueIDs,
2427
}
2528

2629
if len(opts.LabelIDs) == 1 && opts.LabelIDs[0] == 0 {

modules/indexer/issues/elasticsearch/elasticsearch.go

-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package elasticsearch
55

66
import (
77
"context"
8-
"errors"
98
"strconv"
109
"strings"
1110

@@ -201,10 +200,6 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
201200
}
202201
}
203202

204-
if len(options.IssueIDs) > 0 {
205-
return nil, errors.New("options.IssueIDs is not yet supported")
206-
}
207-
208203
if len(options.MilestoneIDs) > 0 {
209204
query.Must(elastic.NewTermsQuery("milestone_id", toAnySlice(options.MilestoneIDs)...))
210205
}

modules/indexer/issues/indexer.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ func InitIssueIndexer(syncReindex bool) {
103103
log.Fatal("Unable to issueIndexer.Init with connection %s Error: %v", setting.Indexer.IssueConnStr, err)
104104
}
105105
case "db":
106-
issueIndexer = db.NewIndexer()
106+
issueIndexer = db.GetIndexer()
107107
case "meilisearch":
108108
issueIndexer = meilisearch.NewIndexer(setting.Indexer.IssueConnStr, setting.Indexer.IssueConnAuth, setting.Indexer.IssueIndexerName)
109109
existed, err = issueIndexer.Init(ctx)
@@ -291,20 +291,22 @@ func SearchIssues(ctx context.Context, opts *SearchOptions) ([]int64, int64, err
291291
// 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.
292292
// Even worse, the external indexer like elastic search may not be available for a while,
293293
// and the user may not be able to list issues completely until it is available again.
294-
ix = db.NewIndexer()
294+
ix = db.GetIndexer()
295295
}
296296

297297
result, err := ix.Search(ctx, opts)
298298
if err != nil {
299299
return nil, 0, err
300300
}
301+
return SearchResultToIDSlice(result), result.Total, nil
302+
}
301303

304+
func SearchResultToIDSlice(result *internal.SearchResult) []int64 {
302305
ret := make([]int64, 0, len(result.Hits))
303306
for _, hit := range result.Hits {
304307
ret = append(ret, hit.ID)
305308
}
306-
307-
return ret, result.Total, nil
309+
return ret
308310
}
309311

310312
// CountIssues counts issues by options. It is a shortcut of SearchIssues(ctx, opts) but only returns the total count.

modules/indexer/issues/internal/model.go

-2
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,6 @@ type SearchOptions struct {
9494

9595
MilestoneIDs []int64 // milestones the issues have
9696

97-
IssueIDs []int64 // restrict search to these issues
98-
9997
ProjectID optional.Option[int64] // project the issues belong to
10098
ProjectColumnID optional.Option[int64] // project column the issues belong to
10199

modules/indexer/issues/meilisearch/meilisearch.go

-4
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,6 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
176176
}
177177
}
178178

179-
if len(options.IssueIDs) > 0 {
180-
return nil, errors.New("options.IssueIDs is not yet supported")
181-
}
182-
183179
if len(options.MilestoneIDs) > 0 {
184180
query.And(inner_meilisearch.NewFilterIn("milestone_id", options.MilestoneIDs...))
185181
}

routers/web/repo/issue_label.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,12 @@ func NewLabel(ctx *context.Context) {
111111
}
112112

113113
l := &issues_model.Label{
114-
RepoID: ctx.Repo.Repository.ID,
115-
Name: form.Title,
116-
Exclusive: form.Exclusive,
117-
Description: form.Description,
118-
Color: form.Color,
114+
RepoID: ctx.Repo.Repository.ID,
115+
Name: form.Title,
116+
Exclusive: form.Exclusive,
117+
ExclusiveOrder: form.ExclusiveOrder,
118+
Description: form.Description,
119+
Color: form.Color,
119120
}
120121
if err := issues_model.NewLabel(ctx, l); err != nil {
121122
ctx.ServerError("NewLabel", err)

routers/web/repo/issue_list.go

+27-32
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"code.gitea.io/gitea/models/unit"
2222
user_model "code.gitea.io/gitea/models/user"
2323
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
24+
db_indexer "code.gitea.io/gitea/modules/indexer/issues/db"
2425
"code.gitea.io/gitea/modules/log"
2526
"code.gitea.io/gitea/modules/optional"
2627
"code.gitea.io/gitea/modules/setting"
@@ -515,7 +516,7 @@ func renderMilestones(ctx *context.Context) {
515516
ctx.Data["ClosedMilestones"] = closedMilestones
516517
}
517518

518-
func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption optional.Option[bool]) {
519+
func prepareIssueFilterAndList(ctx *context.Context, milestoneID, projectID int64, isPullOption optional.Option[bool]) {
519520
var err error
520521
viewType := ctx.FormString("type")
521522
sortType := ctx.FormString("sort")
@@ -585,14 +586,17 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
585586
ctx.Data["IssueIndexerUnavailable"] = true
586587
return
587588
}
589+
if len(keywordMatchedIssueIDs) == 0 {
590+
// It did search with the keyword, but no issue found, just set issueStats to empty, then no need to do query again.
591+
issueStats = &issues_model.IssueStats{}
592+
// set keywordMatchedIssueIDs to empty slice, so we can distinguish it from "nil"
593+
keywordMatchedIssueIDs = []int64{}
594+
}
588595
statsOpts.IssueIDs = keywordMatchedIssueIDs
589596
}
590-
if keyword != "" && len(statsOpts.IssueIDs) == 0 {
591-
// So it did search with the keyword, but no issue found.
592-
// Just set issueStats to empty.
593-
issueStats = &issues_model.IssueStats{}
594-
} else {
595-
// So it did search with the keyword, and found some issues. It needs to get issueStats of these issues.
597+
598+
if issueStats == nil {
599+
// Either it did search with the keyword, and found some issues, it needs to get issueStats of these issues.
596600
// Or the keyword is empty, so it doesn't need issueIDs as filter, just get issueStats with statsOpts.
597601
issueStats, err = issues_model.GetIssueStats(ctx, statsOpts)
598602
if err != nil {
@@ -624,27 +628,21 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
624628
ctx.Data["TotalTrackedTime"] = totalTrackedTime
625629
}
626630

627-
page := ctx.FormInt("page")
628-
if page <= 1 {
629-
page = 1
630-
}
631-
632-
var total int
633-
switch {
634-
case isShowClosed.Value():
635-
total = int(issueStats.ClosedCount)
636-
case !isShowClosed.Has():
637-
total = int(issueStats.OpenCount + issueStats.ClosedCount)
638-
default:
639-
total = int(issueStats.OpenCount)
631+
// prepare pager
632+
total := int(issueStats.OpenCount + issueStats.ClosedCount)
633+
if isShowClosed.Value() {
634+
total = util.Iif(isShowClosed.Value(), int(issueStats.ClosedCount), int(issueStats.OpenCount))
640635
}
636+
page := max(ctx.FormInt("page"), 1)
641637
pager := context.NewPagination(total, setting.UI.IssuePagingNum, page, 5)
642638

639+
// prepare real issue list:
643640
var issues issues_model.IssueList
644-
{
645-
// Do not repeat the keyword search, since if we had any keyword matches we should
646-
// already have their IDs available in keywordMatchedIssueIDs.
647-
ids, err := issueIDsFromSearch(ctx, "", &issues_model.IssuesOptions{
641+
if keywordMatchedIssueIDs == nil || len(keywordMatchedIssueIDs) > 0 {
642+
// Either it did search with the keyword, and found some issues, then keywordMatchedIssueIDs is not null, it needs to use db indexer.
643+
// Or the keyword is empty, it also needs to usd db indexer.
644+
// In either case, no need to use keyword anymore
645+
searchResult, err := db_indexer.GetIndexer().Search(ctx, issue_indexer.ToSearchOptions("", &issues_model.IssuesOptions{
648646
Paginator: &db.ListOptions{
649647
Page: pager.Paginater.Current(),
650648
PageSize: setting.UI.IssuePagingNum,
@@ -662,16 +660,13 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
662660
LabelIDs: labelIDs,
663661
SortType: sortType,
664662
IssueIDs: keywordMatchedIssueIDs,
665-
})
663+
}))
666664
if err != nil {
667-
if issue_indexer.IsAvailable(ctx) {
668-
ctx.ServerError("issueIDsFromSearch", err)
669-
return
670-
}
671-
ctx.Data["IssueIndexerUnavailable"] = true
665+
ctx.ServerError("DBIndexer.Search", err)
672666
return
673667
}
674-
issues, err = issues_model.GetIssuesByIDs(ctx, ids, true)
668+
issueIDs := issue_indexer.SearchResultToIDSlice(searchResult)
669+
issues, err = issues_model.GetIssuesByIDs(ctx, issueIDs, true)
675670
if err != nil {
676671
ctx.ServerError("GetIssuesByIDs", err)
677672
return
@@ -807,7 +802,7 @@ func Issues(ctx *context.Context) {
807802
ctx.Data["NewIssueChooseTemplate"] = issue_service.HasTemplatesOrContactLinks(ctx.Repo.Repository, ctx.Repo.GitRepo)
808803
}
809804

810-
issues(ctx, ctx.FormInt64("milestone"), ctx.FormInt64("project"), optional.Some(isPullList))
805+
prepareIssueFilterAndList(ctx, ctx.FormInt64("milestone"), ctx.FormInt64("project"), optional.Some(isPullList))
811806
if ctx.Written() {
812807
return
813808
}

routers/web/repo/milestone.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ func MilestoneIssuesAndPulls(ctx *context.Context) {
263263
ctx.Data["Title"] = milestone.Name
264264
ctx.Data["Milestone"] = milestone
265265

266-
issues(ctx, milestoneID, projectID, optional.None[bool]())
266+
prepareIssueFilterAndList(ctx, milestoneID, projectID, optional.None[bool]())
267267

268268
ret := issue.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
269269
ctx.Data["NewIssueChooseTemplate"] = len(ret.IssueTemplates) > 0

0 commit comments

Comments
 (0)