}
// 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))
}
page,
isClosed,
sortType,
+ "",
)
}
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).
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,
import (
"net/http"
+ "strings"
"time"
"code.gitea.io/gitea/models"
ctx.Data["ClosedCount"] = stats.ClosedCount
sortType := ctx.Query("sort")
+
+ keyword := strings.Trim(ctx.Query("q"), " ")
+
page := ctx.QueryInt("page")
if page <= 1 {
page = 1
RepoID: ctx.Repo.Repository.ID,
State: state,
SortType: sortType,
+ Name: keyword,
})
if err != nil {
ctx.ServerError("GetMilestones", err)
}
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)
isShowClosed = ctx.Query("state") == "closed"
sortType = ctx.Query("sort")
page = ctx.QueryInt("page")
+ keyword = strings.Trim(ctx.Query("q"), " ")
)
if page <= 1 {
}
}
- 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
}
i++
}
- milestoneStats, err := models.GetMilestonesStatsByRepoCond(repoCond)
+ milestoneStats, err := models.GetMilestonesStatsByRepoCondAndKw(repoCond, keyword)
if err != nil {
ctx.ServerError("GetMilestoneStats", err)
return
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
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")
</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">
<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>
{{$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>
</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">