diff options
author | Roger Luo <rogerluo410@gmail.com> | 2021-04-08 19:53:59 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-04-08 13:53:59 +0200 |
commit | fa06e98553b78da66cb75e13cffe56b3ef013447 (patch) | |
tree | a448a47ea1f0f7643d705a9da6bbe106d7e9eff7 | |
parent | 0d1a5e0ffcf689e6e6b2aaa4a4251db74f6b487b (diff) | |
download | gitea-fa06e98553b78da66cb75e13cffe56b3ef013447.tar.gz gitea-fa06e98553b78da66cb75e13cffe56b3ef013447.zip |
Add dashboard milestone search and repo milestone search by name (#14866)
Feature for issue #13845:
- Add milestones search by name on dashboard milestones page.
- Add milestones search by name on repo issue/milestones page.
-rw-r--r-- | models/issue_milestone.go | 66 | ||||
-rw-r--r-- | routers/repo/milestone.go | 7 | ||||
-rw-r--r-- | routers/user/home.go | 13 | ||||
-rw-r--r-- | templates/repo/issue/milestones.tmpl | 66 | ||||
-rw-r--r-- | templates/user/dashboard/milestones.tmpl | 71 |
5 files changed, 165 insertions, 58 deletions
diff --git a/models/issue_milestone.go b/models/issue_milestone.go index ec3cbb91db..5aa83ea691 100644 --- a/models/issue_milestone.go +++ b/models/issue_milestone.go @@ -426,9 +426,12 @@ func GetMilestones(opts GetMilestonesOption) (MilestoneList, error) { } // SearchMilestones search milestones -func SearchMilestones(repoCond builder.Cond, page int, isClosed bool, sortType string) (MilestoneList, error) { +func SearchMilestones(repoCond builder.Cond, page int, isClosed bool, sortType string, keyword string) (MilestoneList, error) { miles := make([]*Milestone, 0, setting.UI.IssuePagingNum) sess := x.Where("is_closed = ?", isClosed) + if len(keyword) > 0 { + sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)}) + } if repoCond.IsValid() { sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond)) } @@ -460,6 +463,7 @@ func GetMilestonesByRepoIDs(repoIDs []int64, page int, isClosed bool, sortType s page, isClosed, sortType, + "", ) } @@ -506,6 +510,38 @@ func GetMilestonesStatsByRepoCond(repoCond builder.Cond) (*MilestonesStats, erro return stats, nil } +// GetMilestonesStatsByRepoCondAndKw returns milestone statistic information for dashboard by given repo conditions and name keyword. +func GetMilestonesStatsByRepoCondAndKw(repoCond builder.Cond, keyword string) (*MilestonesStats, error) { + var err error + stats := &MilestonesStats{} + + sess := x.Where("is_closed = ?", false) + if len(keyword) > 0 { + sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)}) + } + if repoCond.IsValid() { + sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond))) + } + stats.OpenCount, err = sess.Count(new(Milestone)) + if err != nil { + return nil, err + } + + sess = x.Where("is_closed = ?", true) + if len(keyword) > 0 { + sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)}) + } + if repoCond.IsValid() { + sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond))) + } + stats.ClosedCount, err = sess.Count(new(Milestone)) + if err != nil { + return nil, err + } + + return stats, nil +} + func countRepoMilestones(e Engine, repoID int64) (int64, error) { return e. Where("repo_id=?", repoID). @@ -548,6 +584,34 @@ func CountMilestonesByRepoCond(repoCond builder.Cond, isClosed bool) (map[int64] return countMap, nil } +// CountMilestonesByRepoCondAndKw map from repo conditions and the keyword of milestones' name to number of milestones matching the options` +func CountMilestonesByRepoCondAndKw(repoCond builder.Cond, keyword string, isClosed bool) (map[int64]int64, error) { + sess := x.Where("is_closed = ?", isClosed) + if len(keyword) > 0 { + sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)}) + } + if repoCond.IsValid() { + sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond)) + } + + countsSlice := make([]*struct { + RepoID int64 + Count int64 + }, 0, 10) + if err := sess.GroupBy("repo_id"). + Select("repo_id AS repo_id, COUNT(*) AS count"). + Table("milestone"). + Find(&countsSlice); err != nil { + return nil, err + } + + countMap := make(map[int64]int64, len(countsSlice)) + for _, c := range countsSlice { + countMap[c.RepoID] = c.Count + } + return countMap, nil +} + func updateRepoMilestoneNum(e Engine, repoID int64) error { _, err := e.Exec("UPDATE `repository` SET num_milestones=(SELECT count(*) FROM milestone WHERE repo_id=?),num_closed_milestones=(SELECT count(*) FROM milestone WHERE repo_id=? AND is_closed=?) WHERE id=?", repoID, diff --git a/routers/repo/milestone.go b/routers/repo/milestone.go index 4d1fc022c2..5a9d2351bc 100644 --- a/routers/repo/milestone.go +++ b/routers/repo/milestone.go @@ -6,6 +6,7 @@ package repo import ( "net/http" + "strings" "time" "code.gitea.io/gitea/models" @@ -44,6 +45,9 @@ func Milestones(ctx *context.Context) { ctx.Data["ClosedCount"] = stats.ClosedCount sortType := ctx.Query("sort") + + keyword := strings.Trim(ctx.Query("q"), " ") + page := ctx.QueryInt("page") if page <= 1 { page = 1 @@ -67,6 +71,7 @@ func Milestones(ctx *context.Context) { RepoID: ctx.Repo.Repository.ID, State: state, SortType: sortType, + Name: keyword, }) if err != nil { ctx.ServerError("GetMilestones", err) @@ -90,10 +95,12 @@ func Milestones(ctx *context.Context) { } ctx.Data["SortType"] = sortType + ctx.Data["Keyword"] = keyword ctx.Data["IsShowClosed"] = isShowClosed pager := context.NewPagination(total, setting.UI.IssuePagingNum, page, 5) pager.AddParam(ctx, "state", "State") + pager.AddParam(ctx, "q", "Keyword") ctx.Data["Page"] = pager ctx.HTML(http.StatusOK, tplMilestone) diff --git a/routers/user/home.go b/routers/user/home.go index 178ad57a79..3436a44bae 100644 --- a/routers/user/home.go +++ b/routers/user/home.go @@ -202,6 +202,7 @@ func Milestones(ctx *context.Context) { isShowClosed = ctx.Query("state") == "closed" sortType = ctx.Query("sort") page = ctx.QueryInt("page") + keyword = strings.Trim(ctx.Query("q"), " ") ) if page <= 1 { @@ -234,15 +235,15 @@ func Milestones(ctx *context.Context) { } } - counts, err := models.CountMilestonesByRepoCond(userRepoCond, isShowClosed) + counts, err := models.CountMilestonesByRepoCondAndKw(userRepoCond, keyword, isShowClosed) if err != nil { ctx.ServerError("CountMilestonesByRepoIDs", err) return } - milestones, err := models.SearchMilestones(repoCond, page, isShowClosed, sortType) + milestones, err := models.SearchMilestones(repoCond, page, isShowClosed, sortType, keyword) if err != nil { - ctx.ServerError("GetMilestonesByRepoIDs", err) + ctx.ServerError("SearchMilestones", err) return } @@ -277,7 +278,7 @@ func Milestones(ctx *context.Context) { i++ } - milestoneStats, err := models.GetMilestonesStatsByRepoCond(repoCond) + milestoneStats, err := models.GetMilestonesStatsByRepoCondAndKw(repoCond, keyword) if err != nil { ctx.ServerError("GetMilestoneStats", err) return @@ -287,7 +288,7 @@ func Milestones(ctx *context.Context) { if len(repoIDs) == 0 { totalMilestoneStats = milestoneStats } else { - totalMilestoneStats, err = models.GetMilestonesStatsByRepoCond(userRepoCond) + totalMilestoneStats, err = models.GetMilestonesStatsByRepoCondAndKw(userRepoCond, keyword) if err != nil { ctx.ServerError("GetMilestoneStats", err) return @@ -310,12 +311,14 @@ func Milestones(ctx *context.Context) { ctx.Data["Counts"] = counts ctx.Data["MilestoneStats"] = milestoneStats ctx.Data["SortType"] = sortType + ctx.Data["Keyword"] = keyword if milestoneStats.Total() != totalMilestoneStats.Total() { ctx.Data["RepoIDs"] = repoIDs } ctx.Data["IsShowClosed"] = isShowClosed pager := context.NewPagination(pagerCount, setting.UI.IssuePagingNum, page, 5) + pager.AddParam(ctx, "q", "Keyword") pager.AddParam(ctx, "repos", "RepoIDs") pager.AddParam(ctx, "sort", "SortType") pager.AddParam(ctx, "state", "State") diff --git a/templates/repo/issue/milestones.tmpl b/templates/repo/issue/milestones.tmpl index ecb7cd95f1..c572e0c3f3 100644 --- a/templates/repo/issue/milestones.tmpl +++ b/templates/repo/issue/milestones.tmpl @@ -12,34 +12,52 @@ </div> <div class="ui divider"></div> {{template "base/alert" .}} - <div class="ui compact tiny menu"> - <a class="item{{if not .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/milestones?state=open"> - {{svg "octicon-milestone" 16 "mr-3"}} - {{.i18n.Tr "repo.milestones.open_tab" .OpenCount}} - </a> - <a class="item{{if .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/milestones?state=closed"> - {{svg "octicon-milestone" 16 "mr-3"}} - {{.i18n.Tr "repo.milestones.close_tab" .ClosedCount}} - </a> - </div> - <div class="ui right floated secondary filter menu"> - <!-- Sort --> - <div class="ui dropdown type jump item"> - <span class="text"> - {{.i18n.Tr "repo.issues.filter_sort"}} - {{svg "octicon-triangle-down" 14 "dropdown icon"}} - </span> - <div class="menu"> - <a class="{{if or (eq .SortType "closestduedate") (not .SortType)}}active{{end}} item" href="{{$.Link}}?sort=closestduedate&state={{$.State}}">{{.i18n.Tr "repo.milestones.filter_sort.closest_due_date"}}</a> - <a class="{{if eq .SortType "furthestduedate"}}active{{end}} item" href="{{$.Link}}?sort=furthestduedate&state={{$.State}}">{{.i18n.Tr "repo.milestones.filter_sort.furthest_due_date"}}</a> - <a class="{{if eq .SortType "leastcomplete"}}active{{end}} item" href="{{$.Link}}?sort=leastcomplete&state={{$.State}}">{{.i18n.Tr "repo.milestones.filter_sort.least_complete"}}</a> - <a class="{{if eq .SortType "mostcomplete"}}active{{end}} item" href="{{$.Link}}?sort=mostcomplete&state={{$.State}}">{{.i18n.Tr "repo.milestones.filter_sort.most_complete"}}</a> - <a class="{{if eq .SortType "mostissues"}}active{{end}} item" href="{{$.Link}}?sort=mostissues&state={{$.State}}">{{.i18n.Tr "repo.milestones.filter_sort.most_issues"}}</a> - <a class="{{if eq .SortType "leastissues"}}active{{end}} item" href="{{$.Link}}?sort=leastissues&state={{$.State}}">{{.i18n.Tr "repo.milestones.filter_sort.least_issues"}}</a> + <div class="ui three column stackable grid"> + <div class="column"> + <div class="ui compact tiny menu"> + <a class="item{{if not .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/milestones?state=open&q={{$.Keyword}}"> + {{svg "octicon-milestone" 16 "mr-3"}} + {{.i18n.Tr "repo.milestones.open_tab" .OpenCount}} + </a> + <a class="item{{if .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/milestones?state=closed&q={{$.Keyword}}"> + {{svg "octicon-milestone" 16 "mr-3"}} + {{.i18n.Tr "repo.milestones.close_tab" .ClosedCount}} + </a> + </div> + </div> + + <!-- Search --> + <div class="column center aligned"> + <form class="ui form ignore-dirty"> + <div class="ui search fluid action input"> + <input type="hidden" name="state" value="{{$.State}}"/> + <input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..."> + <button class="ui blue button" type="submit">{{.i18n.Tr "explore.search"}}</button> + </div> + </form> + </div> + + <div class="column right aligned df ac je"> + <!-- Sort --> + <div class="ui dropdown type jump item"> + <span class="text"> + {{.i18n.Tr "repo.issues.filter_sort"}} + {{svg "octicon-triangle-down" 14 "dropdown icon"}} + </span> + <div class="menu"> + <a class="{{if or (eq .SortType "closestduedate") (not .SortType)}}active{{end}} item" href="{{$.Link}}?sort=closestduedate&state={{$.State}}&q={{$.Keyword}}">{{.i18n.Tr "repo.milestones.filter_sort.closest_due_date"}}</a> + <a class="{{if eq .SortType "furthestduedate"}}active{{end}} item" href="{{$.Link}}?sort=furthestduedate&state={{$.State}}&q={{$.Keyword}}">{{.i18n.Tr "repo.milestones.filter_sort.furthest_due_date"}}</a> + <a class="{{if eq .SortType "leastcomplete"}}active{{end}} item" href="{{$.Link}}?sort=leastcomplete&state={{$.State}}&q={{$.Keyword}}">{{.i18n.Tr "repo.milestones.filter_sort.least_complete"}}</a> + <a class="{{if eq .SortType "mostcomplete"}}active{{end}} item" href="{{$.Link}}?sort=mostcomplete&state={{$.State}}&q={{$.Keyword}}">{{.i18n.Tr "repo.milestones.filter_sort.most_complete"}}</a> + <a class="{{if eq .SortType "mostissues"}}active{{end}} item" href="{{$.Link}}?sort=mostissues&state={{$.State}}&q={{$.Keyword}}">{{.i18n.Tr "repo.milestones.filter_sort.most_issues"}}</a> + <a class="{{if eq .SortType "leastissues"}}active{{end}} item" href="{{$.Link}}?sort=leastissues&state={{$.State}}&q={{$.Keyword}}">{{.i18n.Tr "repo.milestones.filter_sort.least_issues"}}</a> + </div> </div> </div> </div> + + <!-- milestone list --> <div class="milestone list"> {{range .Milestones}} <li class="item"> diff --git a/templates/user/dashboard/milestones.tmpl b/templates/user/dashboard/milestones.tmpl index b9cf0ec1d2..5c3aa55062 100644 --- a/templates/user/dashboard/milestones.tmpl +++ b/templates/user/dashboard/milestones.tmpl @@ -5,7 +5,7 @@ <div class="ui stackable grid"> <div class="four wide column"> <div class="ui secondary vertical filter menu"> - <a class="item" href="{{.Link}}?type=your_repositories&sort={{$.SortType}}&state={{.State}}"> + <a class="item" href="{{.Link}}?type=your_repositories&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}"> {{.i18n.Tr "home.issues.in_your_repos"}} <strong class="ui right">{{.Total}}</strong> </a> @@ -25,7 +25,7 @@ {{$Repo.ID}}%2C {{end}} {{end}} - ]&sort={{$.SortType}}&state={{$.State}}" title="{{.FullName}}"> + ]&sort={{$.SortType}}&state={{$.State}}&q={{$.Keyword}}" title="{{.FullName}}"> <span class="text truncate">{{$Repo.FullName}}</span> <div class="ui {{if $.IsShowClosed}}red{{else}}green{{end}} label">{{index $.Counts $Repo.ID}}</div> </a> @@ -34,34 +34,49 @@ </div> </div> <div class="twelve wide column content"> - <div class="ui compact tiny menu"> - <a class="item{{if not .IsShowClosed}} active{{end}}" href="{{.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state=open"> - {{svg "octicon-issue-opened" 16 "mr-3"}} - {{.i18n.Tr "repo.milestones.open_tab" .MilestoneStats.OpenCount}} - </a> - <a class="item{{if .IsShowClosed}} active{{end}}" href="{{.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state=closed"> - {{svg "octicon-issue-closed" 16 "mr-3"}} - {{.i18n.Tr "repo.milestones.close_tab" .MilestoneStats.ClosedCount}} - </a> - </div> - <div class="ui right floated secondary filter menu"> - <!-- Sort --> - <div class="ui dropdown type jump item"> - <span class="text"> - {{.i18n.Tr "repo.issues.filter_sort"}} - {{svg "octicon-triangle-down" 14 "dropdown icon"}} - </span> - <div class="menu"> - <a class="{{if or (eq .SortType "closestduedate") (not .SortType)}}active{{end}} item" href="{{$.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort=closestduedate&state={{$.State}}">{{.i18n.Tr "repo.milestones.filter_sort.closest_due_date"}}</a> - <a class="{{if eq .SortType "furthestduedate"}}active{{end}} item" href="{{$.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort=furthestduedate&state={{$.State}}">{{.i18n.Tr "repo.milestones.filter_sort.furthest_due_date"}}</a> - <a class="{{if eq .SortType "leastcomplete"}}active{{end}} item" href="{{$.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort=leastcomplete&state={{$.State}}">{{.i18n.Tr "repo.milestones.filter_sort.least_complete"}}</a> - <a class="{{if eq .SortType "mostcomplete"}}active{{end}} item" href="{{$.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort=mostcomplete&state={{$.State}}">{{.i18n.Tr "repo.milestones.filter_sort.most_complete"}}</a> - <a class="{{if eq .SortType "mostissues"}}active{{end}} item" href="{{$.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort=mostissues&state={{$.State}}">{{.i18n.Tr "repo.milestones.filter_sort.most_issues"}}</a> - <a class="{{if eq .SortType "leastissues"}}active{{end}} item" href="{{$.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort=leastissues&state={{$.State}}">{{.i18n.Tr "repo.milestones.filter_sort.least_issues"}}</a> + <div class="ui three column stackable grid"> + <div class="column"> + <div class="ui compact tiny menu"> + <a class="item{{if not .IsShowClosed}} active{{end}}" href="{{.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state=open&q={{$.Keyword}}"> + {{svg "octicon-issue-opened" 16 "mr-3"}} + {{.i18n.Tr "repo.milestones.open_tab" .MilestoneStats.OpenCount}} + </a> + <a class="item{{if .IsShowClosed}} active{{end}}" href="{{.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state=closed&q={{$.Keyword}}"> + {{svg "octicon-issue-closed" 16 "mr-3"}} + {{.i18n.Tr "repo.milestones.close_tab" .MilestoneStats.ClosedCount}} + </a> </div> </div> - </div> - + <div class="column center aligned"> + <form class="ui form ignore-dirty"> + <div class="ui search fluid action input"> + <input type="hidden" name="type" value="{{$.ViewType}}"/> + <input type="hidden" name="repos" value="[{{range $.RepoIDs}}{{.}},{{end}}]"/> + <input type="hidden" name="sort" value="{{$.SortType}}"/> + <input type="hidden" name="state" value="{{$.State}}"/> + <input name="q" value="{{$.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..."> + <button class="ui blue button" type="submit">{{.i18n.Tr "explore.search"}}</button> + </div> + </form> + </div> + <div class="column right aligned df ac je"> + <!-- Sort --> + <div class="ui dropdown type jump item"> + <span class="text"> + {{.i18n.Tr "repo.issues.filter_sort"}} + {{svg "octicon-triangle-down" 14 "dropdown icon"}} + </span> + <div class="menu"> + <a class="{{if or (eq .SortType "closestduedate") (not .SortType)}}active{{end}} item" href="{{$.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort=closestduedate&state={{$.State}}&q={{$.Keyword}}">{{.i18n.Tr "repo.milestones.filter_sort.closest_due_date"}}</a> + <a class="{{if eq .SortType "furthestduedate"}}active{{end}} item" href="{{$.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort=furthestduedate&state={{$.State}}&q={{$.Keyword}}">{{.i18n.Tr "repo.milestones.filter_sort.furthest_due_date"}}</a> + <a class="{{if eq .SortType "leastcomplete"}}active{{end}} item" href="{{$.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort=leastcomplete&state={{$.State}}&q={{$.Keyword}}">{{.i18n.Tr "repo.milestones.filter_sort.least_complete"}}</a> + <a class="{{if eq .SortType "mostcomplete"}}active{{end}} item" href="{{$.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort=mostcomplete&state={{$.State}}&q={{$.Keyword}}">{{.i18n.Tr "repo.milestones.filter_sort.most_complete"}}</a> + <a class="{{if eq .SortType "mostissues"}}active{{end}} item" href="{{$.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort=mostissues&state={{$.State}}&q={{$.Keyword}}">{{.i18n.Tr "repo.milestones.filter_sort.most_issues"}}</a> + <a class="{{if eq .SortType "leastissues"}}active{{end}} item" href="{{$.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort=leastissues&state={{$.State}}&q={{$.Keyword}}">{{.i18n.Tr "repo.milestones.filter_sort.least_issues"}}</a> + </div> + </div> + </div> + </div> <div class="milestone list"> {{range .Milestones}} <li class="item"> |