From b40e298643d871a1d893a8ccf9742cbdb1440f05 Mon Sep 17 00:00:00 2001 From: Jason Song Date: Tue, 22 Aug 2023 12:35:43 +0800 Subject: [PATCH 01/17] feat: CountIssues --- models/issues/issue_stats.go | 1 + modules/indexer/issues/indexer.go | 47 ++++++++- routers/web/user/home.go | 155 +++++++++++++++++++----------- 3 files changed, 148 insertions(+), 55 deletions(-) diff --git a/models/issues/issue_stats.go b/models/issues/issue_stats.go index 6c249c2244160..dd5d7db375dbb 100644 --- a/models/issues/issue_stats.go +++ b/models/issues/issue_stats.go @@ -181,6 +181,7 @@ func getIssueStatsChunk(opts *IssuesOptions, issueIDs []int64) (*IssueStats, err } // GetUserIssueStats returns issue statistic information for dashboard by given conditions. +// TBC: remove it func GetUserIssueStats(filterMode int, opts IssuesOptions) (*IssueStats, error) { if opts.User == nil { return nil, errors.New("issue stats without user") diff --git a/modules/indexer/issues/indexer.go b/modules/indexer/issues/indexer.go index 6619949104f49..659750d986e69 100644 --- a/modules/indexer/issues/indexer.go +++ b/modules/indexer/issues/indexer.go @@ -291,7 +291,6 @@ const ( ) // 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() @@ -317,3 +316,49 @@ func SearchIssues(ctx context.Context, opts *SearchOptions) ([]int64, int64, err return ret, result.Total, nil } + +// CountIssues counts issues by options. It is a shortcut of SearchIssues(ctx, opts) but only returns the total count. +func CountIssues(ctx context.Context, opts *SearchOptions) (int64, error) { + paginator := opts.Paginator + opts.Paginator = &db_model.ListOptions{ + PageSize: 0, + } + defer func() { + opts.Paginator = paginator + }() + + _, total, err := SearchIssues(ctx, opts) + return total, err +} + +// CountIssuesByRepo counts issues by options and group by repo id. +// It's not a complete implementation, since it requires the caller should provide the repo ids. +// That means opts.RepoIDs must be specified, and opts.AllPublic must be false. +// It's good enough for the current usage, and it can be improved if needed. +// TODO: use "group by" of the indexer engines to implement it. +func CountIssuesByRepo(ctx context.Context, opts *SearchOptions) (map[int64]int64, error) { + if len(opts.RepoIDs) == 0 { + return nil, fmt.Errorf("opts.RepoIDs must be specified") + } + if opts.AllPublic { + return nil, fmt.Errorf("opts.AllPublic must be false") + } + + resoIDs := opts.RepoIDs + defer func() { + opts.RepoIDs = resoIDs + }() + + ret := make(map[int64]int64, len(opts.RepoIDs)) + // TODO: it could be faster if do it in parallel for some indexer engines. Improve it if users report it's slow. + for _, repoID := range resoIDs { + opts.RepoIDs = []int64{repoID} + count, err := CountIssues(ctx, opts) + if err != nil { + return nil, err + } + ret[repoID] = count + } + + return ret, nil +} diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 8c1447f707863..6e5ae611eef0c 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -507,22 +507,10 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { // Filter repos and count issues in them. Count will be used later. // USING NON-FINAL STATE OF opts FOR A QUERY. - var issueCountByRepo map[int64]int64 - { - issueIDs, err := issueIDsFromSearch(ctx, keyword, opts) - if err != nil { - ctx.ServerError("issueIDsFromSearch", err) - return - } - if len(issueIDs) > 0 { // else, no issues found, just leave issueCountByRepo empty - opts.IssueIDs = issueIDs - issueCountByRepo, err = issues_model.CountIssuesByRepo(ctx, opts) - if err != nil { - ctx.ServerError("CountIssuesByRepo", err) - return - } - opts.IssueIDs = nil // reset, the opts will be used later - } + issueCountByRepo, err := issue_indexer.CountIssuesByRepo(ctx, issue_indexer.ToSearchOptions(keyword, opts)) + if err != nil { + ctx.ServerError("CountIssuesByRepo", err) + return } // Make sure page number is at least 1. Will be posted to ctx.Data. @@ -615,44 +603,10 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { // ------------------------------- // Fill stats to post to ctx.Data. // ------------------------------- - var issueStats *issues_model.IssueStats - { - statsOpts := issues_model.IssuesOptions{ - RepoIDs: repoIDs, - User: ctx.Doer, - IsPull: util.OptionalBoolOf(isPullList), - IsClosed: util.OptionalBoolOf(isShowClosed), - IssueIDs: nil, - IsArchived: util.OptionalBoolFalse, - LabelIDs: opts.LabelIDs, - Org: org, - Team: team, - RepoCond: opts.RepoCond, - } - - if keyword != "" { - statsOpts.RepoIDs = opts.RepoIDs - allIssueIDs, err := issueIDsFromSearch(ctx, keyword, &statsOpts) - if err != nil { - ctx.ServerError("issueIDsFromSearch", err) - return - } - statsOpts.IssueIDs = allIssueIDs - } - - if keyword != "" && len(statsOpts.IssueIDs) == 0 { - // So it did search with the keyword, but no issue found. - // Just set issueStats to empty. - issueStats = &issues_model.IssueStats{} - } else { - // So it did search with the keyword, and found some issues. It needs to get issueStats of these issues. - // Or the keyword is empty, so it doesn't need issueIDs as filter, just get issueStats with statsOpts. - issueStats, err = issues_model.GetUserIssueStats(filterMode, statsOpts) - if err != nil { - ctx.ServerError("GetUserIssueStats", err) - return - } - } + issueStats, err := getUserIssueStats(ctx, issue_indexer.ToSearchOptions(keyword, opts), ctx.Doer.ID) + if err != nil { + ctx.ServerError("getUserIssueStats", err) + return } // Will be posted to ctx.Data. @@ -777,6 +731,7 @@ func getRepoIDs(reposQuery string) []int64 { return repoIDs } +// TBC: remove it func issueIDsFromSearch(ctx *context.Context, keyword string, opts *issues_model.IssuesOptions) ([]int64, error) { ids, _, err := issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, opts)) if err != nil { @@ -913,3 +868,95 @@ func UsernameSubRoute(ctx *context.Context) { } } } + +func getUserIssueStats(ctx *context.Context, opts *issue_indexer.SearchOptions, doerID int64) (*issues_model.IssueStats, error) { + isClosed := opts.IsClosed + assigneeID := opts.AssigneeID + posterID := opts.PosterID + mentionID := opts.MentionID + reviewRequestedID := opts.ReviewRequestedID + reviewedID := opts.ReviewedID + defer func() { + opts.IsClosed = isClosed + opts.AssigneeID = assigneeID + opts.PosterID = posterID + opts.MentionID = mentionID + opts.ReviewRequestedID = reviewRequestedID + opts.ReviewedID = reviewedID + }() + opts.IsClosed = util.OptionalBoolNone + opts.AssigneeID = nil + opts.PosterID = nil + opts.MentionID = nil + opts.ReviewRequestedID = nil + opts.ReviewedID = nil + + var ( + err error + ret = &issues_model.IssueStats{} + ) + + { + opts.IsClosed = util.OptionalBoolFalse + ret.OpenCount, err = issue_indexer.CountIssues(ctx, opts) + if err != nil { + return nil, err + } + opts.IsClosed = util.OptionalBoolNone + } + { + opts.IsClosed = util.OptionalBoolTrue + ret.ClosedCount, err = issue_indexer.CountIssues(ctx, opts) + if err != nil { + return nil, err + } + opts.IsClosed = util.OptionalBoolNone + } + { + ret.YourRepositoriesCount, err = issue_indexer.CountIssues(ctx, opts) + if err != nil { + return nil, err + } + } + { + opts.AssigneeID = &doerID + ret.AssignCount, err = issue_indexer.CountIssues(ctx, opts) + if err != nil { + return nil, err + } + opts.AssigneeID = nil + } + { + opts.PosterID = &doerID + ret.CreateCount, err = issue_indexer.CountIssues(ctx, opts) + if err != nil { + return nil, err + } + opts.PosterID = nil + } + { + opts.MentionID = &doerID + ret.MentionCount, err = issue_indexer.CountIssues(ctx, opts) + if err != nil { + return nil, err + } + opts.MentionID = nil + } + { + opts.ReviewRequestedID = &doerID + ret.ReviewRequestedCount, err = issue_indexer.CountIssues(ctx, opts) + if err != nil { + return nil, err + } + opts.ReviewRequestedID = nil + } + { + opts.ReviewedID = &doerID + ret.ReviewedCount, err = issue_indexer.CountIssues(ctx, opts) + if err != nil { + return nil, err + } + opts.ReviewedID = nil + } + return ret, nil +} From 769326799facbfc829a05b4d35e951587d204a86 Mon Sep 17 00:00:00 2001 From: Jason Song Date: Tue, 22 Aug 2023 13:30:15 +0800 Subject: [PATCH 02/17] chore: remove useless code --- models/issues/issue_stats.go | 162 ----------------------------------- models/issues/issue_test.go | 124 --------------------------- routers/web/user/home.go | 13 +-- 3 files changed, 2 insertions(+), 297 deletions(-) diff --git a/models/issues/issue_stats.go b/models/issues/issue_stats.go index dd5d7db375dbb..a9da2cb4545fe 100644 --- a/models/issues/issue_stats.go +++ b/models/issues/issue_stats.go @@ -5,7 +5,6 @@ package issues import ( "context" - "errors" "fmt" "code.gitea.io/gitea/models/db" @@ -180,167 +179,6 @@ func getIssueStatsChunk(opts *IssuesOptions, issueIDs []int64) (*IssueStats, err return stats, err } -// GetUserIssueStats returns issue statistic information for dashboard by given conditions. -// TBC: remove it -func GetUserIssueStats(filterMode int, opts IssuesOptions) (*IssueStats, error) { - if opts.User == nil { - return nil, errors.New("issue stats without user") - } - if opts.IsPull.IsNone() { - return nil, errors.New("unaccepted ispull option") - } - - var err error - stats := &IssueStats{} - - cond := builder.NewCond() - - cond = cond.And(builder.Eq{"issue.is_pull": opts.IsPull.IsTrue()}) - - if len(opts.RepoIDs) > 0 { - cond = cond.And(builder.In("issue.repo_id", opts.RepoIDs)) - } - if len(opts.IssueIDs) > 0 { - cond = cond.And(builder.In("issue.id", opts.IssueIDs)) - } - if opts.RepoCond != nil { - cond = cond.And(opts.RepoCond) - } - - if opts.User != nil { - cond = cond.And(issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.IsTrue())) - } - - sess := func(cond builder.Cond) *xorm.Session { - s := db.GetEngine(db.DefaultContext). - Join("INNER", "repository", "`issue`.repo_id = `repository`.id"). - Where(cond) - if len(opts.LabelIDs) > 0 { - s.Join("INNER", "issue_label", "issue_label.issue_id = issue.id"). - In("issue_label.label_id", opts.LabelIDs) - } - - if opts.IsArchived != util.OptionalBoolNone { - s.And(builder.Eq{"repository.is_archived": opts.IsArchived.IsTrue()}) - } - return s - } - - switch filterMode { - case FilterModeAll, FilterModeYourRepositories: - stats.OpenCount, err = sess(cond). - And("issue.is_closed = ?", false). - Count(new(Issue)) - if err != nil { - return nil, err - } - stats.ClosedCount, err = sess(cond). - And("issue.is_closed = ?", true). - Count(new(Issue)) - if err != nil { - return nil, err - } - case FilterModeAssign: - stats.OpenCount, err = applyAssigneeCondition(sess(cond), opts.User.ID). - And("issue.is_closed = ?", false). - Count(new(Issue)) - if err != nil { - return nil, err - } - stats.ClosedCount, err = applyAssigneeCondition(sess(cond), opts.User.ID). - And("issue.is_closed = ?", true). - Count(new(Issue)) - if err != nil { - return nil, err - } - case FilterModeCreate: - stats.OpenCount, err = applyPosterCondition(sess(cond), opts.User.ID). - And("issue.is_closed = ?", false). - Count(new(Issue)) - if err != nil { - return nil, err - } - stats.ClosedCount, err = applyPosterCondition(sess(cond), opts.User.ID). - And("issue.is_closed = ?", true). - Count(new(Issue)) - if err != nil { - return nil, err - } - case FilterModeMention: - stats.OpenCount, err = applyMentionedCondition(sess(cond), opts.User.ID). - And("issue.is_closed = ?", false). - Count(new(Issue)) - if err != nil { - return nil, err - } - stats.ClosedCount, err = applyMentionedCondition(sess(cond), opts.User.ID). - And("issue.is_closed = ?", true). - Count(new(Issue)) - if err != nil { - return nil, err - } - case FilterModeReviewRequested: - stats.OpenCount, err = applyReviewRequestedCondition(sess(cond), opts.User.ID). - And("issue.is_closed = ?", false). - Count(new(Issue)) - if err != nil { - return nil, err - } - stats.ClosedCount, err = applyReviewRequestedCondition(sess(cond), opts.User.ID). - And("issue.is_closed = ?", true). - Count(new(Issue)) - if err != nil { - return nil, err - } - case FilterModeReviewed: - stats.OpenCount, err = applyReviewedCondition(sess(cond), opts.User.ID). - And("issue.is_closed = ?", false). - Count(new(Issue)) - if err != nil { - return nil, err - } - stats.ClosedCount, err = applyReviewedCondition(sess(cond), opts.User.ID). - And("issue.is_closed = ?", true). - Count(new(Issue)) - if err != nil { - return nil, err - } - } - - cond = cond.And(builder.Eq{"issue.is_closed": opts.IsClosed.IsTrue()}) - stats.AssignCount, err = applyAssigneeCondition(sess(cond), opts.User.ID).Count(new(Issue)) - if err != nil { - return nil, err - } - - stats.CreateCount, err = applyPosterCondition(sess(cond), opts.User.ID).Count(new(Issue)) - if err != nil { - return nil, err - } - - stats.MentionCount, err = applyMentionedCondition(sess(cond), opts.User.ID).Count(new(Issue)) - if err != nil { - return nil, err - } - - stats.YourRepositoriesCount, err = sess(cond).Count(new(Issue)) - if err != nil { - return nil, err - } - - stats.ReviewRequestedCount, err = applyReviewRequestedCondition(sess(cond), opts.User.ID).Count(new(Issue)) - if err != nil { - return nil, err - } - - stats.ReviewedCount, err = applyReviewedCondition(sess(cond), opts.User.ID).Count(new(Issue)) - if err != nil { - return nil, err - } - - return stats, nil -} - // GetRepoIssueStats returns number of open and closed repository issues by given filter mode. func GetRepoIssueStats(repoID, uid int64, filterMode int, isPull bool) (numOpen, numClosed int64) { countSession := func(isClosed, isPull bool, repoID int64) *xorm.Session { diff --git a/models/issues/issue_test.go b/models/issues/issue_test.go index 0f2ceadc6b6e1..d1d116e83839f 100644 --- a/models/issues/issue_test.go +++ b/models/issues/issue_test.go @@ -13,12 +13,10 @@ import ( "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" - "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" "xorm.io/builder" @@ -204,128 +202,6 @@ func TestIssues(t *testing.T) { } } -func TestGetUserIssueStats(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - for _, test := range []struct { - FilterMode int - Opts issues_model.IssuesOptions - ExpectedIssueStats issues_model.IssueStats - }{ - { - issues_model.FilterModeAll, - issues_model.IssuesOptions{ - User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}), - RepoIDs: []int64{1}, - IsPull: util.OptionalBoolFalse, - }, - issues_model.IssueStats{ - YourRepositoriesCount: 1, // 6 - AssignCount: 1, // 6 - CreateCount: 1, // 6 - OpenCount: 1, // 6 - ClosedCount: 1, // 1 - }, - }, - { - issues_model.FilterModeAll, - issues_model.IssuesOptions{ - User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}), - RepoIDs: []int64{1}, - IsPull: util.OptionalBoolFalse, - IsClosed: util.OptionalBoolTrue, - }, - issues_model.IssueStats{ - YourRepositoriesCount: 1, // 6 - AssignCount: 0, - CreateCount: 0, - OpenCount: 1, // 6 - ClosedCount: 1, // 1 - }, - }, - { - issues_model.FilterModeAssign, - issues_model.IssuesOptions{ - User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}), - IsPull: util.OptionalBoolFalse, - }, - issues_model.IssueStats{ - YourRepositoriesCount: 1, // 6 - AssignCount: 1, // 6 - CreateCount: 1, // 6 - OpenCount: 1, // 6 - ClosedCount: 0, - }, - }, - { - issues_model.FilterModeCreate, - issues_model.IssuesOptions{ - User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}), - IsPull: util.OptionalBoolFalse, - }, - issues_model.IssueStats{ - YourRepositoriesCount: 1, // 6 - AssignCount: 1, // 6 - CreateCount: 1, // 6 - OpenCount: 1, // 6 - ClosedCount: 0, - }, - }, - { - issues_model.FilterModeMention, - issues_model.IssuesOptions{ - User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}), - IsPull: util.OptionalBoolFalse, - }, - issues_model.IssueStats{ - YourRepositoriesCount: 1, // 6 - AssignCount: 1, // 6 - CreateCount: 1, // 6 - MentionCount: 0, - OpenCount: 0, - ClosedCount: 0, - }, - }, - { - issues_model.FilterModeCreate, - issues_model.IssuesOptions{ - User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}), - IssueIDs: []int64{1}, - IsPull: util.OptionalBoolFalse, - }, - issues_model.IssueStats{ - YourRepositoriesCount: 1, // 1 - AssignCount: 1, // 1 - CreateCount: 1, // 1 - OpenCount: 1, // 1 - ClosedCount: 0, - }, - }, - { - issues_model.FilterModeAll, - issues_model.IssuesOptions{ - User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}), - Org: unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}), - Team: unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 7}), - IsPull: util.OptionalBoolFalse, - }, - issues_model.IssueStats{ - YourRepositoriesCount: 2, - AssignCount: 1, - CreateCount: 1, - OpenCount: 2, - }, - }, - } { - t.Run(fmt.Sprintf("%#v", test.Opts), func(t *testing.T) { - stats, err := issues_model.GetUserIssueStats(test.FilterMode, test.Opts) - if !assert.NoError(t, err) { - return - } - assert.Equal(t, test.ExpectedIssueStats, *stats) - }) - } -} - func TestIssue_loadTotalTimes(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) ms, err := issues_model.GetIssueByID(db.DefaultContext, 2) diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 6e5ae611eef0c..7bb5a237b2f1b 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -556,9 +556,9 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { // USING FINAL STATE OF opts FOR A QUERY. var issues issues_model.IssueList { - issueIDs, err := issueIDsFromSearch(ctx, keyword, opts) + issueIDs, _, err := issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, opts)) if err != nil { - ctx.ServerError("issueIDsFromSearch", err) + ctx.ServerError("SearchIssues", err) return } issues, err = issues_model.GetIssuesByIDs(ctx, issueIDs, true) @@ -731,15 +731,6 @@ func getRepoIDs(reposQuery string) []int64 { return repoIDs } -// TBC: remove it -func issueIDsFromSearch(ctx *context.Context, keyword string, opts *issues_model.IssuesOptions) ([]int64, error) { - ids, _, err := issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, opts)) - if err != nil { - return nil, fmt.Errorf("SearchIssues: %w", err) - } - return ids, nil -} - func loadRepoByIDs(ctxUser *user_model.User, issueCountByRepo map[int64]int64, unitType unit.Type) (map[int64]*repo_model.Repository, error) { totalRes := make(map[int64]*repo_model.Repository, len(issueCountByRepo)) repoIDs := make([]int64, 0, 500) From 8a58f4f35e0edef7abb41f34800bc7baab46654e Mon Sep 17 00:00:00 2001 From: Jason Song Date: Tue, 22 Aug 2023 13:49:17 +0800 Subject: [PATCH 03/17] Revert "chore: remove useless code" This reverts commit 769326799facbfc829a05b4d35e951587d204a86. --- models/issues/issue_stats.go | 162 +++++++++++++++++++++++++++++++++++ models/issues/issue_test.go | 124 +++++++++++++++++++++++++++ routers/web/user/home.go | 13 ++- 3 files changed, 297 insertions(+), 2 deletions(-) diff --git a/models/issues/issue_stats.go b/models/issues/issue_stats.go index a9da2cb4545fe..dd5d7db375dbb 100644 --- a/models/issues/issue_stats.go +++ b/models/issues/issue_stats.go @@ -5,6 +5,7 @@ package issues import ( "context" + "errors" "fmt" "code.gitea.io/gitea/models/db" @@ -179,6 +180,167 @@ func getIssueStatsChunk(opts *IssuesOptions, issueIDs []int64) (*IssueStats, err return stats, err } +// GetUserIssueStats returns issue statistic information for dashboard by given conditions. +// TBC: remove it +func GetUserIssueStats(filterMode int, opts IssuesOptions) (*IssueStats, error) { + if opts.User == nil { + return nil, errors.New("issue stats without user") + } + if opts.IsPull.IsNone() { + return nil, errors.New("unaccepted ispull option") + } + + var err error + stats := &IssueStats{} + + cond := builder.NewCond() + + cond = cond.And(builder.Eq{"issue.is_pull": opts.IsPull.IsTrue()}) + + if len(opts.RepoIDs) > 0 { + cond = cond.And(builder.In("issue.repo_id", opts.RepoIDs)) + } + if len(opts.IssueIDs) > 0 { + cond = cond.And(builder.In("issue.id", opts.IssueIDs)) + } + if opts.RepoCond != nil { + cond = cond.And(opts.RepoCond) + } + + if opts.User != nil { + cond = cond.And(issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.IsTrue())) + } + + sess := func(cond builder.Cond) *xorm.Session { + s := db.GetEngine(db.DefaultContext). + Join("INNER", "repository", "`issue`.repo_id = `repository`.id"). + Where(cond) + if len(opts.LabelIDs) > 0 { + s.Join("INNER", "issue_label", "issue_label.issue_id = issue.id"). + In("issue_label.label_id", opts.LabelIDs) + } + + if opts.IsArchived != util.OptionalBoolNone { + s.And(builder.Eq{"repository.is_archived": opts.IsArchived.IsTrue()}) + } + return s + } + + switch filterMode { + case FilterModeAll, FilterModeYourRepositories: + stats.OpenCount, err = sess(cond). + And("issue.is_closed = ?", false). + Count(new(Issue)) + if err != nil { + return nil, err + } + stats.ClosedCount, err = sess(cond). + And("issue.is_closed = ?", true). + Count(new(Issue)) + if err != nil { + return nil, err + } + case FilterModeAssign: + stats.OpenCount, err = applyAssigneeCondition(sess(cond), opts.User.ID). + And("issue.is_closed = ?", false). + Count(new(Issue)) + if err != nil { + return nil, err + } + stats.ClosedCount, err = applyAssigneeCondition(sess(cond), opts.User.ID). + And("issue.is_closed = ?", true). + Count(new(Issue)) + if err != nil { + return nil, err + } + case FilterModeCreate: + stats.OpenCount, err = applyPosterCondition(sess(cond), opts.User.ID). + And("issue.is_closed = ?", false). + Count(new(Issue)) + if err != nil { + return nil, err + } + stats.ClosedCount, err = applyPosterCondition(sess(cond), opts.User.ID). + And("issue.is_closed = ?", true). + Count(new(Issue)) + if err != nil { + return nil, err + } + case FilterModeMention: + stats.OpenCount, err = applyMentionedCondition(sess(cond), opts.User.ID). + And("issue.is_closed = ?", false). + Count(new(Issue)) + if err != nil { + return nil, err + } + stats.ClosedCount, err = applyMentionedCondition(sess(cond), opts.User.ID). + And("issue.is_closed = ?", true). + Count(new(Issue)) + if err != nil { + return nil, err + } + case FilterModeReviewRequested: + stats.OpenCount, err = applyReviewRequestedCondition(sess(cond), opts.User.ID). + And("issue.is_closed = ?", false). + Count(new(Issue)) + if err != nil { + return nil, err + } + stats.ClosedCount, err = applyReviewRequestedCondition(sess(cond), opts.User.ID). + And("issue.is_closed = ?", true). + Count(new(Issue)) + if err != nil { + return nil, err + } + case FilterModeReviewed: + stats.OpenCount, err = applyReviewedCondition(sess(cond), opts.User.ID). + And("issue.is_closed = ?", false). + Count(new(Issue)) + if err != nil { + return nil, err + } + stats.ClosedCount, err = applyReviewedCondition(sess(cond), opts.User.ID). + And("issue.is_closed = ?", true). + Count(new(Issue)) + if err != nil { + return nil, err + } + } + + cond = cond.And(builder.Eq{"issue.is_closed": opts.IsClosed.IsTrue()}) + stats.AssignCount, err = applyAssigneeCondition(sess(cond), opts.User.ID).Count(new(Issue)) + if err != nil { + return nil, err + } + + stats.CreateCount, err = applyPosterCondition(sess(cond), opts.User.ID).Count(new(Issue)) + if err != nil { + return nil, err + } + + stats.MentionCount, err = applyMentionedCondition(sess(cond), opts.User.ID).Count(new(Issue)) + if err != nil { + return nil, err + } + + stats.YourRepositoriesCount, err = sess(cond).Count(new(Issue)) + if err != nil { + return nil, err + } + + stats.ReviewRequestedCount, err = applyReviewRequestedCondition(sess(cond), opts.User.ID).Count(new(Issue)) + if err != nil { + return nil, err + } + + stats.ReviewedCount, err = applyReviewedCondition(sess(cond), opts.User.ID).Count(new(Issue)) + if err != nil { + return nil, err + } + + return stats, nil +} + // GetRepoIssueStats returns number of open and closed repository issues by given filter mode. func GetRepoIssueStats(repoID, uid int64, filterMode int, isPull bool) (numOpen, numClosed int64) { countSession := func(isClosed, isPull bool, repoID int64) *xorm.Session { diff --git a/models/issues/issue_test.go b/models/issues/issue_test.go index d1d116e83839f..0f2ceadc6b6e1 100644 --- a/models/issues/issue_test.go +++ b/models/issues/issue_test.go @@ -13,10 +13,12 @@ import ( "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" "xorm.io/builder" @@ -202,6 +204,128 @@ func TestIssues(t *testing.T) { } } +func TestGetUserIssueStats(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + for _, test := range []struct { + FilterMode int + Opts issues_model.IssuesOptions + ExpectedIssueStats issues_model.IssueStats + }{ + { + issues_model.FilterModeAll, + issues_model.IssuesOptions{ + User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}), + RepoIDs: []int64{1}, + IsPull: util.OptionalBoolFalse, + }, + issues_model.IssueStats{ + YourRepositoriesCount: 1, // 6 + AssignCount: 1, // 6 + CreateCount: 1, // 6 + OpenCount: 1, // 6 + ClosedCount: 1, // 1 + }, + }, + { + issues_model.FilterModeAll, + issues_model.IssuesOptions{ + User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}), + RepoIDs: []int64{1}, + IsPull: util.OptionalBoolFalse, + IsClosed: util.OptionalBoolTrue, + }, + issues_model.IssueStats{ + YourRepositoriesCount: 1, // 6 + AssignCount: 0, + CreateCount: 0, + OpenCount: 1, // 6 + ClosedCount: 1, // 1 + }, + }, + { + issues_model.FilterModeAssign, + issues_model.IssuesOptions{ + User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}), + IsPull: util.OptionalBoolFalse, + }, + issues_model.IssueStats{ + YourRepositoriesCount: 1, // 6 + AssignCount: 1, // 6 + CreateCount: 1, // 6 + OpenCount: 1, // 6 + ClosedCount: 0, + }, + }, + { + issues_model.FilterModeCreate, + issues_model.IssuesOptions{ + User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}), + IsPull: util.OptionalBoolFalse, + }, + issues_model.IssueStats{ + YourRepositoriesCount: 1, // 6 + AssignCount: 1, // 6 + CreateCount: 1, // 6 + OpenCount: 1, // 6 + ClosedCount: 0, + }, + }, + { + issues_model.FilterModeMention, + issues_model.IssuesOptions{ + User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}), + IsPull: util.OptionalBoolFalse, + }, + issues_model.IssueStats{ + YourRepositoriesCount: 1, // 6 + AssignCount: 1, // 6 + CreateCount: 1, // 6 + MentionCount: 0, + OpenCount: 0, + ClosedCount: 0, + }, + }, + { + issues_model.FilterModeCreate, + issues_model.IssuesOptions{ + User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}), + IssueIDs: []int64{1}, + IsPull: util.OptionalBoolFalse, + }, + issues_model.IssueStats{ + YourRepositoriesCount: 1, // 1 + AssignCount: 1, // 1 + CreateCount: 1, // 1 + OpenCount: 1, // 1 + ClosedCount: 0, + }, + }, + { + issues_model.FilterModeAll, + issues_model.IssuesOptions{ + User: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}), + Org: unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}), + Team: unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 7}), + IsPull: util.OptionalBoolFalse, + }, + issues_model.IssueStats{ + YourRepositoriesCount: 2, + AssignCount: 1, + CreateCount: 1, + OpenCount: 2, + }, + }, + } { + t.Run(fmt.Sprintf("%#v", test.Opts), func(t *testing.T) { + stats, err := issues_model.GetUserIssueStats(test.FilterMode, test.Opts) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, test.ExpectedIssueStats, *stats) + }) + } +} + func TestIssue_loadTotalTimes(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) ms, err := issues_model.GetIssueByID(db.DefaultContext, 2) diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 7bb5a237b2f1b..6e5ae611eef0c 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -556,9 +556,9 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { // USING FINAL STATE OF opts FOR A QUERY. var issues issues_model.IssueList { - issueIDs, _, err := issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, opts)) + issueIDs, err := issueIDsFromSearch(ctx, keyword, opts) if err != nil { - ctx.ServerError("SearchIssues", err) + ctx.ServerError("issueIDsFromSearch", err) return } issues, err = issues_model.GetIssuesByIDs(ctx, issueIDs, true) @@ -731,6 +731,15 @@ func getRepoIDs(reposQuery string) []int64 { return repoIDs } +// TBC: remove it +func issueIDsFromSearch(ctx *context.Context, keyword string, opts *issues_model.IssuesOptions) ([]int64, error) { + ids, _, err := issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, opts)) + if err != nil { + return nil, fmt.Errorf("SearchIssues: %w", err) + } + return ids, nil +} + func loadRepoByIDs(ctxUser *user_model.User, issueCountByRepo map[int64]int64, unitType unit.Type) (map[int64]*repo_model.Repository, error) { totalRes := make(map[int64]*repo_model.Repository, len(issueCountByRepo)) repoIDs := make([]int64, 0, 500) From 7aed79cf2936ab63ed2cc4f63f7fc604657b4cc5 Mon Sep 17 00:00:00 2001 From: Jason Song Date: Tue, 22 Aug 2023 13:51:02 +0800 Subject: [PATCH 04/17] chore: remove issueIDsFromSearch --- models/issues/issue_stats.go | 1 - routers/web/user/home.go | 11 +---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/models/issues/issue_stats.go b/models/issues/issue_stats.go index dd5d7db375dbb..6c249c2244160 100644 --- a/models/issues/issue_stats.go +++ b/models/issues/issue_stats.go @@ -181,7 +181,6 @@ func getIssueStatsChunk(opts *IssuesOptions, issueIDs []int64) (*IssueStats, err } // GetUserIssueStats returns issue statistic information for dashboard by given conditions. -// TBC: remove it func GetUserIssueStats(filterMode int, opts IssuesOptions) (*IssueStats, error) { if opts.User == nil { return nil, errors.New("issue stats without user") diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 6e5ae611eef0c..e8c2498b0a8be 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -556,7 +556,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { // USING FINAL STATE OF opts FOR A QUERY. var issues issues_model.IssueList { - issueIDs, err := issueIDsFromSearch(ctx, keyword, opts) + issueIDs, _, err := issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, opts)) if err != nil { ctx.ServerError("issueIDsFromSearch", err) return @@ -731,15 +731,6 @@ func getRepoIDs(reposQuery string) []int64 { return repoIDs } -// TBC: remove it -func issueIDsFromSearch(ctx *context.Context, keyword string, opts *issues_model.IssuesOptions) ([]int64, error) { - ids, _, err := issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, opts)) - if err != nil { - return nil, fmt.Errorf("SearchIssues: %w", err) - } - return ids, nil -} - func loadRepoByIDs(ctxUser *user_model.User, issueCountByRepo map[int64]int64, unitType unit.Type) (map[int64]*repo_model.Repository, error) { totalRes := make(map[int64]*repo_model.Repository, len(issueCountByRepo)) repoIDs := make([]int64, 0, 500) From d940836e7b8775bd2fcfb7703e8df03149f4eaad Mon Sep 17 00:00:00 2001 From: Jason Song Date: Tue, 22 Aug 2023 14:19:21 +0800 Subject: [PATCH 05/17] fix: getUserIssueStats --- modules/indexer/issues/indexer.go | 4 +- modules/indexer/issues/internal/model.go | 11 +++ routers/web/user/home.go | 115 +++++++++-------------- 3 files changed, 60 insertions(+), 70 deletions(-) diff --git a/modules/indexer/issues/indexer.go b/modules/indexer/issues/indexer.go index 659750d986e69..a4d07fec5253a 100644 --- a/modules/indexer/issues/indexer.go +++ b/modules/indexer/issues/indexer.go @@ -277,7 +277,7 @@ func IsAvailable(ctx context.Context) bool { } // SearchOptions indicates the options for searching issues -type SearchOptions internal.SearchOptions +type SearchOptions = internal.SearchOptions const ( SortByCreatedDesc = internal.SortByCreatedDesc @@ -304,7 +304,7 @@ func SearchIssues(ctx context.Context, opts *SearchOptions) ([]int64, int64, err indexer = db.NewIndexer() } - result, err := indexer.Search(ctx, (*internal.SearchOptions)(opts)) + result, err := indexer.Search(ctx, opts) if err != nil { return nil, 0, err } diff --git a/modules/indexer/issues/internal/model.go b/modules/indexer/issues/internal/model.go index 2de1e0e2bf20b..52bbd32bb8114 100644 --- a/modules/indexer/issues/internal/model.go +++ b/modules/indexer/issues/internal/model.go @@ -109,6 +109,17 @@ type SearchOptions struct { SortBy SortBy // sort by field } +func (o *SearchOptions) Copy(edit ...func(options *SearchOptions)) *SearchOptions { + if o == nil { + return nil + } + v := *o + for _, e := range edit { + e(&v) + } + return &v +} + type SortBy string const ( diff --git a/routers/web/user/home.go b/routers/web/user/home.go index e8c2498b0a8be..a0e8ba47618f2 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -603,7 +603,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { // ------------------------------- // Fill stats to post to ctx.Data. // ------------------------------- - issueStats, err := getUserIssueStats(ctx, issue_indexer.ToSearchOptions(keyword, opts), ctx.Doer.ID) + issueStats, err := getUserIssueStats(ctx, filterMode, issue_indexer.ToSearchOptions(keyword, opts), ctx.Doer.ID) if err != nil { ctx.ServerError("getUserIssueStats", err) return @@ -860,27 +860,15 @@ func UsernameSubRoute(ctx *context.Context) { } } -func getUserIssueStats(ctx *context.Context, opts *issue_indexer.SearchOptions, doerID int64) (*issues_model.IssueStats, error) { - isClosed := opts.IsClosed - assigneeID := opts.AssigneeID - posterID := opts.PosterID - mentionID := opts.MentionID - reviewRequestedID := opts.ReviewRequestedID - reviewedID := opts.ReviewedID - defer func() { - opts.IsClosed = isClosed - opts.AssigneeID = assigneeID - opts.PosterID = posterID - opts.MentionID = mentionID - opts.ReviewRequestedID = reviewRequestedID - opts.ReviewedID = reviewedID - }() - opts.IsClosed = util.OptionalBoolNone - opts.AssigneeID = nil - opts.PosterID = nil - opts.MentionID = nil - opts.ReviewRequestedID = nil - opts.ReviewedID = nil +func getUserIssueStats(ctx *context.Context, filterMode int, opts *issue_indexer.SearchOptions, doerID int64) (*issues_model.IssueStats, error) { + opts = opts.Copy(func(o *issue_indexer.SearchOptions) { + o.IsClosed = util.OptionalBoolNone + o.AssigneeID = nil + o.PosterID = nil + o.MentionID = nil + o.ReviewRequestedID = nil + o.ReviewedID = nil + }) var ( err error @@ -888,66 +876,57 @@ func getUserIssueStats(ctx *context.Context, opts *issue_indexer.SearchOptions, ) { - opts.IsClosed = util.OptionalBoolFalse + openClosedOpts := opts.Copy() + switch filterMode { + case issues_model.FilterModeAll, issues_model.FilterModeYourRepositories: + case issues_model.FilterModeAssign: + openClosedOpts.AssigneeID = &doerID + case issues_model.FilterModeCreate: + openClosedOpts.PosterID = &doerID + case issues_model.FilterModeMention: + openClosedOpts.MentionID = &doerID + case issues_model.FilterModeReviewRequested: + openClosedOpts.ReviewRequestedID = &doerID + case issues_model.FilterModeReviewed: + openClosedOpts.ReviewedID = &doerID + } + openClosedOpts.IsClosed = util.OptionalBoolFalse ret.OpenCount, err = issue_indexer.CountIssues(ctx, opts) if err != nil { return nil, err } - opts.IsClosed = util.OptionalBoolNone - } - { - opts.IsClosed = util.OptionalBoolTrue + openClosedOpts.IsClosed = util.OptionalBoolTrue ret.ClosedCount, err = issue_indexer.CountIssues(ctx, opts) if err != nil { return nil, err } - opts.IsClosed = util.OptionalBoolNone } - { - ret.YourRepositoriesCount, err = issue_indexer.CountIssues(ctx, opts) - if err != nil { - return nil, err - } + + opts.IsClosed = util.OptionalBoolFalse + + ret.YourRepositoriesCount, err = issue_indexer.CountIssues(ctx, opts) + if err != nil { + return nil, err } - { - opts.AssigneeID = &doerID - ret.AssignCount, err = issue_indexer.CountIssues(ctx, opts) - if err != nil { - return nil, err - } - opts.AssigneeID = nil + ret.AssignCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.AssigneeID = &doerID })) + if err != nil { + return nil, err } - { - opts.PosterID = &doerID - ret.CreateCount, err = issue_indexer.CountIssues(ctx, opts) - if err != nil { - return nil, err - } - opts.PosterID = nil + ret.CreateCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.PosterID = &doerID })) + if err != nil { + return nil, err } - { - opts.MentionID = &doerID - ret.MentionCount, err = issue_indexer.CountIssues(ctx, opts) - if err != nil { - return nil, err - } - opts.MentionID = nil + ret.MentionCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.MentionID = &doerID })) + if err != nil { + return nil, err } - { - opts.ReviewRequestedID = &doerID - ret.ReviewRequestedCount, err = issue_indexer.CountIssues(ctx, opts) - if err != nil { - return nil, err - } - opts.ReviewRequestedID = nil + ret.ReviewRequestedCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.ReviewRequestedID = &doerID })) + if err != nil { + return nil, err } - { - opts.ReviewedID = &doerID - ret.ReviewedCount, err = issue_indexer.CountIssues(ctx, opts) - if err != nil { - return nil, err - } - opts.ReviewedID = nil + ret.ReviewedCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.ReviewedID = &doerID })) + if err != nil { + return nil, err } return ret, nil } From 285cfd372df5246ac5c0fc7f87f2984d703f3484 Mon Sep 17 00:00:00 2001 From: Jason Song Date: Tue, 22 Aug 2023 14:27:26 +0800 Subject: [PATCH 06/17] feat: use Copy --- modules/indexer/issues/indexer.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/modules/indexer/issues/indexer.go b/modules/indexer/issues/indexer.go index a4d07fec5253a..ae3a5c09d95c8 100644 --- a/modules/indexer/issues/indexer.go +++ b/modules/indexer/issues/indexer.go @@ -319,13 +319,7 @@ func SearchIssues(ctx context.Context, opts *SearchOptions) ([]int64, int64, err // CountIssues counts issues by options. It is a shortcut of SearchIssues(ctx, opts) but only returns the total count. func CountIssues(ctx context.Context, opts *SearchOptions) (int64, error) { - paginator := opts.Paginator - opts.Paginator = &db_model.ListOptions{ - PageSize: 0, - } - defer func() { - opts.Paginator = paginator - }() + opts = opts.Copy(func(options *SearchOptions) { opts.Paginator = &db_model.ListOptions{PageSize: 0} }) _, total, err := SearchIssues(ctx, opts) return total, err From a7857de61232043a87ffbefbc5732c7812bdbc16 Mon Sep 17 00:00:00 2001 From: Jason Song Date: Tue, 22 Aug 2023 14:42:53 +0800 Subject: [PATCH 07/17] docs: comment for Copy --- modules/indexer/issues/internal/model.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/indexer/issues/internal/model.go b/modules/indexer/issues/internal/model.go index 52bbd32bb8114..031745dd2fcc1 100644 --- a/modules/indexer/issues/internal/model.go +++ b/modules/indexer/issues/internal/model.go @@ -109,6 +109,8 @@ type SearchOptions struct { SortBy SortBy // sort by field } +// Copy returns a copy of the options. +// Be careful, it's not a deep copy, so `SearchOptions.RepoIDs = {...}` is OK while `SearchOptions.RepoIDs[0] = ...` is not. func (o *SearchOptions) Copy(edit ...func(options *SearchOptions)) *SearchOptions { if o == nil { return nil From c2dcab2213b300f2467a83d55f1ca3d2ed17e3fb Mon Sep 17 00:00:00 2001 From: Jason Song Date: Tue, 22 Aug 2023 14:43:53 +0800 Subject: [PATCH 08/17] fix: bug --- routers/web/user/home.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routers/web/user/home.go b/routers/web/user/home.go index a0e8ba47618f2..2ca08722db129 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -891,12 +891,12 @@ func getUserIssueStats(ctx *context.Context, filterMode int, opts *issue_indexer openClosedOpts.ReviewedID = &doerID } openClosedOpts.IsClosed = util.OptionalBoolFalse - ret.OpenCount, err = issue_indexer.CountIssues(ctx, opts) + ret.OpenCount, err = issue_indexer.CountIssues(ctx, openClosedOpts) if err != nil { return nil, err } openClosedOpts.IsClosed = util.OptionalBoolTrue - ret.ClosedCount, err = issue_indexer.CountIssues(ctx, opts) + ret.ClosedCount, err = issue_indexer.CountIssues(ctx, openClosedOpts) if err != nil { return nil, err } From eaab29dfcce1f5b4f606a1b5f73741600816623d Mon Sep 17 00:00:00 2001 From: Jason Song Date: Tue, 22 Aug 2023 15:24:52 +0800 Subject: [PATCH 09/17] fix: select repo --- routers/web/user/home.go | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 2ca08722db129..59c01eeee1af1 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -457,12 +457,14 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { if team != nil { repoOpts.TeamID = team.ID } + accessibleRepos := container.Set[int64]{} { ids, _, err := repo_model.SearchRepositoryIDs(repoOpts) if err != nil { ctx.ServerError("SearchRepositoryIDs", err) return } + accessibleRepos.AddMultiple(ids...) opts.RepoIDs = ids if len(opts.RepoIDs) == 0 { // no repos found, don't let the indexer return all repos @@ -489,18 +491,6 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { keyword := strings.Trim(ctx.FormString("q"), " ") ctx.Data["Keyword"] = keyword - accessibleRepos := container.Set[int64]{} - { - ids, err := issues_model.GetRepoIDsForIssuesOptions(opts, ctxUser) - if err != nil { - ctx.ServerError("GetRepoIDsForIssuesOptions", err) - return - } - for _, id := range ids { - accessibleRepos.Add(id) - } - } - // Educated guess: Do or don't show closed issues. isShowClosed := ctx.FormString("state") == "closed" opts.IsClosed = util.OptionalBoolOf(isShowClosed) @@ -539,13 +529,13 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { // Parse ctx.FormString("repos") and remember matched repo IDs for later. // Gets set when clicking filters on the issues overview page. - repoIDs := getRepoIDs(ctx.FormString("repos")) - if len(repoIDs) > 0 { + selectedRepoIDs := getRepoIDs(ctx.FormString("repos")) + if len(selectedRepoIDs) > 0 { // Remove repo IDs that are not accessible to the user. - repoIDs = util.SliceRemoveAllFunc(repoIDs, func(v int64) bool { + selectedRepoIDs = util.SliceRemoveAllFunc(selectedRepoIDs, func(v int64) bool { return !accessibleRepos.Contains(v) }) - opts.RepoIDs = repoIDs + opts.RepoIDs = selectedRepoIDs } // ------------------------------ @@ -676,7 +666,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { ctx.Data["IssueStats"] = issueStats ctx.Data["ViewType"] = viewType ctx.Data["SortType"] = sortType - ctx.Data["RepoIDs"] = opts.RepoIDs + ctx.Data["RepoIDs"] = selectedRepoIDs ctx.Data["IsShowClosed"] = isShowClosed ctx.Data["SelectLabels"] = selectedLabels From cf7fd15bbc3abc77daa0f28c381b0fbbf58545b8 Mon Sep 17 00:00:00 2001 From: Jason Song Date: Tue, 22 Aug 2023 15:38:22 +0800 Subject: [PATCH 10/17] fix: getUserIssueStats --- routers/web/user/home.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 59c01eeee1af1..6c7ea4726b777 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -852,7 +852,6 @@ func UsernameSubRoute(ctx *context.Context) { func getUserIssueStats(ctx *context.Context, filterMode int, opts *issue_indexer.SearchOptions, doerID int64) (*issues_model.IssueStats, error) { opts = opts.Copy(func(o *issue_indexer.SearchOptions) { - o.IsClosed = util.OptionalBoolNone o.AssigneeID = nil o.PosterID = nil o.MentionID = nil @@ -892,8 +891,6 @@ func getUserIssueStats(ctx *context.Context, filterMode int, opts *issue_indexer } } - opts.IsClosed = util.OptionalBoolFalse - ret.YourRepositoriesCount, err = issue_indexer.CountIssues(ctx, opts) if err != nil { return nil, err From 809bd2bbf9bfd806c804a1731cbc90d0474a8a6b Mon Sep 17 00:00:00 2001 From: Jason Song Date: Tue, 22 Aug 2023 15:44:26 +0800 Subject: [PATCH 11/17] fix: keep key word --- templates/user/dashboard/issues.tmpl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/templates/user/dashboard/issues.tmpl b/templates/user/dashboard/issues.tmpl index 8d6cc67afe17e..a89098c6ab31b 100644 --- a/templates/user/dashboard/issues.tmpl +++ b/templates/user/dashboard/issues.tmpl @@ -5,29 +5,29 @@