loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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.

authored by

Roger Luo and committed by
GitHub
fa06e985 0d1a5e0f

+165 -58
+65 -1
models/issue_milestone.go
··· 426 426 } 427 427 428 428 // SearchMilestones search milestones 429 - func SearchMilestones(repoCond builder.Cond, page int, isClosed bool, sortType string) (MilestoneList, error) { 429 + func SearchMilestones(repoCond builder.Cond, page int, isClosed bool, sortType string, keyword string) (MilestoneList, error) { 430 430 miles := make([]*Milestone, 0, setting.UI.IssuePagingNum) 431 431 sess := x.Where("is_closed = ?", isClosed) 432 + if len(keyword) > 0 { 433 + sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)}) 434 + } 432 435 if repoCond.IsValid() { 433 436 sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond)) 434 437 } ··· 460 463 page, 461 464 isClosed, 462 465 sortType, 466 + "", 463 467 ) 464 468 } 465 469 ··· 506 510 return stats, nil 507 511 } 508 512 513 + // GetMilestonesStatsByRepoCondAndKw returns milestone statistic information for dashboard by given repo conditions and name keyword. 514 + func GetMilestonesStatsByRepoCondAndKw(repoCond builder.Cond, keyword string) (*MilestonesStats, error) { 515 + var err error 516 + stats := &MilestonesStats{} 517 + 518 + sess := x.Where("is_closed = ?", false) 519 + if len(keyword) > 0 { 520 + sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)}) 521 + } 522 + if repoCond.IsValid() { 523 + sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond))) 524 + } 525 + stats.OpenCount, err = sess.Count(new(Milestone)) 526 + if err != nil { 527 + return nil, err 528 + } 529 + 530 + sess = x.Where("is_closed = ?", true) 531 + if len(keyword) > 0 { 532 + sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)}) 533 + } 534 + if repoCond.IsValid() { 535 + sess.And(builder.In("repo_id", builder.Select("id").From("repository").Where(repoCond))) 536 + } 537 + stats.ClosedCount, err = sess.Count(new(Milestone)) 538 + if err != nil { 539 + return nil, err 540 + } 541 + 542 + return stats, nil 543 + } 544 + 509 545 func countRepoMilestones(e Engine, repoID int64) (int64, error) { 510 546 return e. 511 547 Where("repo_id=?", repoID). ··· 526 562 // CountMilestonesByRepoCond map from repo conditions to number of milestones matching the options` 527 563 func CountMilestonesByRepoCond(repoCond builder.Cond, isClosed bool) (map[int64]int64, error) { 528 564 sess := x.Where("is_closed = ?", isClosed) 565 + if repoCond.IsValid() { 566 + sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond)) 567 + } 568 + 569 + countsSlice := make([]*struct { 570 + RepoID int64 571 + Count int64 572 + }, 0, 10) 573 + if err := sess.GroupBy("repo_id"). 574 + Select("repo_id AS repo_id, COUNT(*) AS count"). 575 + Table("milestone"). 576 + Find(&countsSlice); err != nil { 577 + return nil, err 578 + } 579 + 580 + countMap := make(map[int64]int64, len(countsSlice)) 581 + for _, c := range countsSlice { 582 + countMap[c.RepoID] = c.Count 583 + } 584 + return countMap, nil 585 + } 586 + 587 + // CountMilestonesByRepoCondAndKw map from repo conditions and the keyword of milestones' name to number of milestones matching the options` 588 + func CountMilestonesByRepoCondAndKw(repoCond builder.Cond, keyword string, isClosed bool) (map[int64]int64, error) { 589 + sess := x.Where("is_closed = ?", isClosed) 590 + if len(keyword) > 0 { 591 + sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)}) 592 + } 529 593 if repoCond.IsValid() { 530 594 sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond)) 531 595 }
+7
routers/repo/milestone.go
··· 6 6 7 7 import ( 8 8 "net/http" 9 + "strings" 9 10 "time" 10 11 11 12 "code.gitea.io/gitea/models" ··· 44 45 ctx.Data["ClosedCount"] = stats.ClosedCount 45 46 46 47 sortType := ctx.Query("sort") 48 + 49 + keyword := strings.Trim(ctx.Query("q"), " ") 50 + 47 51 page := ctx.QueryInt("page") 48 52 if page <= 1 { 49 53 page = 1 ··· 67 71 RepoID: ctx.Repo.Repository.ID, 68 72 State: state, 69 73 SortType: sortType, 74 + Name: keyword, 70 75 }) 71 76 if err != nil { 72 77 ctx.ServerError("GetMilestones", err) ··· 90 95 } 91 96 92 97 ctx.Data["SortType"] = sortType 98 + ctx.Data["Keyword"] = keyword 93 99 ctx.Data["IsShowClosed"] = isShowClosed 94 100 95 101 pager := context.NewPagination(total, setting.UI.IssuePagingNum, page, 5) 96 102 pager.AddParam(ctx, "state", "State") 103 + pager.AddParam(ctx, "q", "Keyword") 97 104 ctx.Data["Page"] = pager 98 105 99 106 ctx.HTML(http.StatusOK, tplMilestone)
+8 -5
routers/user/home.go
··· 202 202 isShowClosed = ctx.Query("state") == "closed" 203 203 sortType = ctx.Query("sort") 204 204 page = ctx.QueryInt("page") 205 + keyword = strings.Trim(ctx.Query("q"), " ") 205 206 ) 206 207 207 208 if page <= 1 { ··· 234 235 } 235 236 } 236 237 237 - counts, err := models.CountMilestonesByRepoCond(userRepoCond, isShowClosed) 238 + counts, err := models.CountMilestonesByRepoCondAndKw(userRepoCond, keyword, isShowClosed) 238 239 if err != nil { 239 240 ctx.ServerError("CountMilestonesByRepoIDs", err) 240 241 return 241 242 } 242 243 243 - milestones, err := models.SearchMilestones(repoCond, page, isShowClosed, sortType) 244 + milestones, err := models.SearchMilestones(repoCond, page, isShowClosed, sortType, keyword) 244 245 if err != nil { 245 - ctx.ServerError("GetMilestonesByRepoIDs", err) 246 + ctx.ServerError("SearchMilestones", err) 246 247 return 247 248 } 248 249 ··· 277 278 i++ 278 279 } 279 280 280 - milestoneStats, err := models.GetMilestonesStatsByRepoCond(repoCond) 281 + milestoneStats, err := models.GetMilestonesStatsByRepoCondAndKw(repoCond, keyword) 281 282 if err != nil { 282 283 ctx.ServerError("GetMilestoneStats", err) 283 284 return ··· 287 288 if len(repoIDs) == 0 { 288 289 totalMilestoneStats = milestoneStats 289 290 } else { 290 - totalMilestoneStats, err = models.GetMilestonesStatsByRepoCond(userRepoCond) 291 + totalMilestoneStats, err = models.GetMilestonesStatsByRepoCondAndKw(userRepoCond, keyword) 291 292 if err != nil { 292 293 ctx.ServerError("GetMilestoneStats", err) 293 294 return ··· 310 311 ctx.Data["Counts"] = counts 311 312 ctx.Data["MilestoneStats"] = milestoneStats 312 313 ctx.Data["SortType"] = sortType 314 + ctx.Data["Keyword"] = keyword 313 315 if milestoneStats.Total() != totalMilestoneStats.Total() { 314 316 ctx.Data["RepoIDs"] = repoIDs 315 317 } 316 318 ctx.Data["IsShowClosed"] = isShowClosed 317 319 318 320 pager := context.NewPagination(pagerCount, setting.UI.IssuePagingNum, page, 5) 321 + pager.AddParam(ctx, "q", "Keyword") 319 322 pager.AddParam(ctx, "repos", "RepoIDs") 320 323 pager.AddParam(ctx, "sort", "SortType") 321 324 pager.AddParam(ctx, "state", "State")
+42 -24
templates/repo/issue/milestones.tmpl
··· 12 12 </div> 13 13 <div class="ui divider"></div> 14 14 {{template "base/alert" .}} 15 - <div class="ui compact tiny menu"> 16 - <a class="item{{if not .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/milestones?state=open"> 17 - {{svg "octicon-milestone" 16 "mr-3"}} 18 - {{.i18n.Tr "repo.milestones.open_tab" .OpenCount}} 19 - </a> 20 - <a class="item{{if .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/milestones?state=closed"> 21 - {{svg "octicon-milestone" 16 "mr-3"}} 22 - {{.i18n.Tr "repo.milestones.close_tab" .ClosedCount}} 23 - </a> 24 - </div> 25 15 26 - <div class="ui right floated secondary filter menu"> 27 - <!-- Sort --> 28 - <div class="ui dropdown type jump item"> 29 - <span class="text"> 30 - {{.i18n.Tr "repo.issues.filter_sort"}} 31 - {{svg "octicon-triangle-down" 14 "dropdown icon"}} 32 - </span> 33 - <div class="menu"> 34 - <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> 35 - <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> 36 - <a class="{{if eq .SortType "leastcomplete"}}active{{end}} item" href="{{$.Link}}?sort=leastcomplete&state={{$.State}}">{{.i18n.Tr "repo.milestones.filter_sort.least_complete"}}</a> 37 - <a class="{{if eq .SortType "mostcomplete"}}active{{end}} item" href="{{$.Link}}?sort=mostcomplete&state={{$.State}}">{{.i18n.Tr "repo.milestones.filter_sort.most_complete"}}</a> 38 - <a class="{{if eq .SortType "mostissues"}}active{{end}} item" href="{{$.Link}}?sort=mostissues&state={{$.State}}">{{.i18n.Tr "repo.milestones.filter_sort.most_issues"}}</a> 39 - <a class="{{if eq .SortType "leastissues"}}active{{end}} item" href="{{$.Link}}?sort=leastissues&state={{$.State}}">{{.i18n.Tr "repo.milestones.filter_sort.least_issues"}}</a> 16 + <div class="ui three column stackable grid"> 17 + <div class="column"> 18 + <div class="ui compact tiny menu"> 19 + <a class="item{{if not .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/milestones?state=open&q={{$.Keyword}}"> 20 + {{svg "octicon-milestone" 16 "mr-3"}} 21 + {{.i18n.Tr "repo.milestones.open_tab" .OpenCount}} 22 + </a> 23 + <a class="item{{if .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/milestones?state=closed&q={{$.Keyword}}"> 24 + {{svg "octicon-milestone" 16 "mr-3"}} 25 + {{.i18n.Tr "repo.milestones.close_tab" .ClosedCount}} 26 + </a> 27 + </div> 28 + </div> 29 + 30 + <!-- Search --> 31 + <div class="column center aligned"> 32 + <form class="ui form ignore-dirty"> 33 + <div class="ui search fluid action input"> 34 + <input type="hidden" name="state" value="{{$.State}}"/> 35 + <input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..."> 36 + <button class="ui blue button" type="submit">{{.i18n.Tr "explore.search"}}</button> 37 + </div> 38 + </form> 39 + </div> 40 + 41 + <div class="column right aligned df ac je"> 42 + <!-- Sort --> 43 + <div class="ui dropdown type jump item"> 44 + <span class="text"> 45 + {{.i18n.Tr "repo.issues.filter_sort"}} 46 + {{svg "octicon-triangle-down" 14 "dropdown icon"}} 47 + </span> 48 + <div class="menu"> 49 + <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> 50 + <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> 51 + <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> 52 + <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> 53 + <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> 54 + <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> 55 + </div> 40 56 </div> 41 57 </div> 42 58 </div> 59 + 60 + <!-- milestone list --> 43 61 <div class="milestone list"> 44 62 {{range .Milestones}} 45 63 <li class="item">
+43 -28
templates/user/dashboard/milestones.tmpl
··· 5 5 <div class="ui stackable grid"> 6 6 <div class="four wide column"> 7 7 <div class="ui secondary vertical filter menu"> 8 - <a class="item" href="{{.Link}}?type=your_repositories&sort={{$.SortType}}&state={{.State}}"> 8 + <a class="item" href="{{.Link}}?type=your_repositories&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}"> 9 9 {{.i18n.Tr "home.issues.in_your_repos"}} 10 10 <strong class="ui right">{{.Total}}</strong> 11 11 </a> ··· 25 25 {{$Repo.ID}}%2C 26 26 {{end}} 27 27 {{end}} 28 - ]&sort={{$.SortType}}&state={{$.State}}" title="{{.FullName}}"> 28 + ]&sort={{$.SortType}}&state={{$.State}}&q={{$.Keyword}}" title="{{.FullName}}"> 29 29 <span class="text truncate">{{$Repo.FullName}}</span> 30 30 <div class="ui {{if $.IsShowClosed}}red{{else}}green{{end}} label">{{index $.Counts $Repo.ID}}</div> 31 31 </a> ··· 34 34 </div> 35 35 </div> 36 36 <div class="twelve wide column content"> 37 - <div class="ui compact tiny menu"> 38 - <a class="item{{if not .IsShowClosed}} active{{end}}" href="{{.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state=open"> 39 - {{svg "octicon-issue-opened" 16 "mr-3"}} 40 - {{.i18n.Tr "repo.milestones.open_tab" .MilestoneStats.OpenCount}} 41 - </a> 42 - <a class="item{{if .IsShowClosed}} active{{end}}" href="{{.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state=closed"> 43 - {{svg "octicon-issue-closed" 16 "mr-3"}} 44 - {{.i18n.Tr "repo.milestones.close_tab" .MilestoneStats.ClosedCount}} 45 - </a> 46 - </div> 47 - <div class="ui right floated secondary filter menu"> 48 - <!-- Sort --> 49 - <div class="ui dropdown type jump item"> 50 - <span class="text"> 51 - {{.i18n.Tr "repo.issues.filter_sort"}} 52 - {{svg "octicon-triangle-down" 14 "dropdown icon"}} 53 - </span> 54 - <div class="menu"> 55 - <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> 56 - <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> 57 - <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> 58 - <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> 59 - <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> 60 - <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> 37 + <div class="ui three column stackable grid"> 38 + <div class="column"> 39 + <div class="ui compact tiny menu"> 40 + <a class="item{{if not .IsShowClosed}} active{{end}}" href="{{.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state=open&q={{$.Keyword}}"> 41 + {{svg "octicon-issue-opened" 16 "mr-3"}} 42 + {{.i18n.Tr "repo.milestones.open_tab" .MilestoneStats.OpenCount}} 43 + </a> 44 + <a class="item{{if .IsShowClosed}} active{{end}}" href="{{.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state=closed&q={{$.Keyword}}"> 45 + {{svg "octicon-issue-closed" 16 "mr-3"}} 46 + {{.i18n.Tr "repo.milestones.close_tab" .MilestoneStats.ClosedCount}} 47 + </a> 48 + </div> 49 + </div> 50 + <div class="column center aligned"> 51 + <form class="ui form ignore-dirty"> 52 + <div class="ui search fluid action input"> 53 + <input type="hidden" name="type" value="{{$.ViewType}}"/> 54 + <input type="hidden" name="repos" value="[{{range $.RepoIDs}}{{.}},{{end}}]"/> 55 + <input type="hidden" name="sort" value="{{$.SortType}}"/> 56 + <input type="hidden" name="state" value="{{$.State}}"/> 57 + <input name="q" value="{{$.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..."> 58 + <button class="ui blue button" type="submit">{{.i18n.Tr "explore.search"}}</button> 59 + </div> 60 + </form> 61 + </div> 62 + <div class="column right aligned df ac je"> 63 + <!-- Sort --> 64 + <div class="ui dropdown type jump item"> 65 + <span class="text"> 66 + {{.i18n.Tr "repo.issues.filter_sort"}} 67 + {{svg "octicon-triangle-down" 14 "dropdown icon"}} 68 + </span> 69 + <div class="menu"> 70 + <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> 71 + <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> 72 + <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> 73 + <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> 74 + <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> 75 + <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> 76 + </div> 61 77 </div> 62 78 </div> 63 - </div> 64 - 79 + </div> 65 80 <div class="milestone list"> 66 81 {{range .Milestones}} 67 82 <li class="item">