aboutsummaryrefslogtreecommitdiffstats
path: root/routers
diff options
context:
space:
mode:
authorJason Song <i@wolfogre.com>2023-08-23 10:29:17 +0800
committerGitHub <noreply@github.com>2023-08-23 02:29:17 +0000
commit5db21ce7e10ba78ede8841bea9db7a63adbececb (patch)
tree0e73df14b63b18becf7af115706105ad0be6a6f8 /routers
parent3b91b2d6b12b9c9c18406f484775925bbd557618 (diff)
downloadgitea-5db21ce7e10ba78ede8841bea9db7a63adbececb.tar.gz
gitea-5db21ce7e10ba78ede8841bea9db7a63adbececb.zip
Fix counting and filtering on the dashboard page for issues (#26657)
This PR has multiple parts, and I didn't split them because it's not easy to test them separately since they are all about the dashboard page for issues. 1. Support counting issues via indexer to fix #26361 2. Fix repo selection so it also fixes #26653 3. Keep keywords in filter links. The first two are regressions of #26012. After: https://github.com/go-gitea/gitea/assets/9418365/71dfea7e-d9e2-42b6-851a-cc081435c946 Thanks to @CaiCandong for helping with some tests.
Diffstat (limited to 'routers')
-rw-r--r--routers/web/user/home.go195
1 files changed, 107 insertions, 88 deletions
diff --git a/routers/web/user/home.go b/routers/web/user/home.go
index 8c1447f707..d1a4877e6d 100644
--- a/routers/web/user/home.go
+++ b/routers/web/user/home.go
@@ -448,21 +448,26 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
// - Team org's owns the repository.
// - Team has read permission to repository.
repoOpts := &repo_model.SearchRepoOptions{
- Actor: ctx.Doer,
- OwnerID: ctx.Doer.ID,
- Private: true,
- AllPublic: false,
- AllLimited: false,
+ Actor: ctx.Doer,
+ OwnerID: ctx.Doer.ID,
+ Private: true,
+ AllPublic: false,
+ AllLimited: false,
+ Collaborate: util.OptionalBoolNone,
+ UnitType: unitType,
+ Archived: util.OptionalBoolFalse,
}
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,40 +494,16 @@ 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)
// 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.
@@ -551,13 +532,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 {
- // Remove repo IDs that are not accessible to the user.
- repoIDs = util.SliceRemoveAllFunc(repoIDs, func(v int64) bool {
- return !accessibleRepos.Contains(v)
- })
- opts.RepoIDs = repoIDs
+ selectedRepoIDs := getRepoIDs(ctx.FormString("repos"))
+ // Remove repo IDs that are not accessible to the user.
+ selectedRepoIDs = util.SliceRemoveAllFunc(selectedRepoIDs, func(v int64) bool {
+ return !accessibleRepos.Contains(v)
+ })
+ if len(selectedRepoIDs) > 0 {
+ opts.RepoIDs = selectedRepoIDs
}
// ------------------------------
@@ -568,7 +549,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
@@ -584,6 +565,18 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
// Add repository pointers to Issues.
// ----------------------------------
+ // Remove repositories that should not be shown,
+ // which are repositories that have no issues and are not selected by the user.
+ selectedReposMap := make(map[int64]struct{}, len(selectedRepoIDs))
+ for _, repoID := range selectedRepoIDs {
+ selectedReposMap[repoID] = struct{}{}
+ }
+ for k, v := range issueCountByRepo {
+ if _, ok := selectedReposMap[k]; !ok && v == 0 {
+ delete(issueCountByRepo, k)
+ }
+ }
+
// showReposMap maps repository IDs to their Repository pointers.
showReposMap, err := loadRepoByIDs(ctxUser, issueCountByRepo, unitType)
if err != nil {
@@ -615,44 +608,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, filterMode, issue_indexer.ToSearchOptions(keyword, opts), ctx.Doer.ID)
+ if err != nil {
+ ctx.ServerError("getUserIssueStats", err)
+ return
}
// Will be posted to ctx.Data.
@@ -722,7 +681,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
@@ -777,14 +736,6 @@ func getRepoIDs(reposQuery string) []int64 {
return repoIDs
}
-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)
@@ -913,3 +864,71 @@ 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.AssigneeID = nil
+ o.PosterID = nil
+ o.MentionID = nil
+ o.ReviewRequestedID = nil
+ o.ReviewedID = nil
+ })
+
+ var (
+ err error
+ ret = &issues_model.IssueStats{}
+ )
+
+ {
+ 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, openClosedOpts)
+ if err != nil {
+ return nil, err
+ }
+ openClosedOpts.IsClosed = util.OptionalBoolTrue
+ ret.ClosedCount, err = issue_indexer.CountIssues(ctx, openClosedOpts)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ ret.YourRepositoriesCount, err = issue_indexer.CountIssues(ctx, opts)
+ if err != nil {
+ return nil, err
+ }
+ ret.AssignCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.AssigneeID = &doerID }))
+ if err != nil {
+ return nil, err
+ }
+ ret.CreateCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.PosterID = &doerID }))
+ if err != nil {
+ return nil, err
+ }
+ ret.MentionCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.MentionID = &doerID }))
+ if err != nil {
+ return nil, err
+ }
+ ret.ReviewRequestedCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.ReviewRequestedID = &doerID }))
+ if err != nil {
+ return nil, err
+ }
+ 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
+}