]> source.dussan.org Git - gitea.git/commitdiff
Add dashboard milestone search and repo milestone search by name (#14866)
authorRoger Luo <rogerluo410@gmail.com>
Thu, 8 Apr 2021 11:53:59 +0000 (19:53 +0800)
committerGitHub <noreply@github.com>
Thu, 8 Apr 2021 11:53:59 +0000 (13:53 +0200)
Feature for issue #13845:
 - Add milestones search by name on dashboard milestones page.
 - Add milestones search by name on repo issue/milestones page.

models/issue_milestone.go
routers/repo/milestone.go
routers/user/home.go
templates/repo/issue/milestones.tmpl
templates/user/dashboard/milestones.tmpl

index ec3cbb91dbc15701e9ab37d51c063167f99d3f20..5aa83ea691d54c343f263773849b2e0b5ef64093 100644 (file)
@@ -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,
index 4d1fc022c26783ffc5dd12cad8619894598f9a3d..5a9d2351bcfb55c635ac1a1201d8ba6b602bb725 100644 (file)
@@ -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)
index 178ad57a79acf5f8170643d190773aa524461f55..3436a44baedc32b37a3e4ce45927715eda8afc6f 100644 (file)
@@ -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")
index ecb7cd95f1683d2f111f4edb3be4feb816a56e06..c572e0c3f3de2c0378a6ec0f5d11b411c42765ba 100644 (file)
                </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">
index b9cf0ec1d223c14739c44f750df43a245ed7a5fe..5c3aa55062649a7928c755f88a89352083472f2e 100644 (file)
@@ -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>
                                </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">