Skip to content

Commit fa06e98

Browse files
authored
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.
1 parent 0d1a5e0 commit fa06e98

File tree

5 files changed

+165
-58
lines changed

5 files changed

+165
-58
lines changed

models/issue_milestone.go

+65-1
Original file line numberDiff line numberDiff line change
@@ -426,9 +426,12 @@ func GetMilestones(opts GetMilestonesOption) (MilestoneList, error) {
426426
}
427427

428428
// 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) {
430430
miles := make([]*Milestone, 0, setting.UI.IssuePagingNum)
431431
sess := x.Where("is_closed = ?", isClosed)
432+
if len(keyword) > 0 {
433+
sess = sess.And(builder.Like{"UPPER(name)", strings.ToUpper(keyword)})
434+
}
432435
if repoCond.IsValid() {
433436
sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond))
434437
}
@@ -460,6 +463,7 @@ func GetMilestonesByRepoIDs(repoIDs []int64, page int, isClosed bool, sortType s
460463
page,
461464
isClosed,
462465
sortType,
466+
"",
463467
)
464468
}
465469

@@ -506,6 +510,38 @@ func GetMilestonesStatsByRepoCond(repoCond builder.Cond) (*MilestonesStats, erro
506510
return stats, nil
507511
}
508512

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+
509545
func countRepoMilestones(e Engine, repoID int64) (int64, error) {
510546
return e.
511547
Where("repo_id=?", repoID).
@@ -548,6 +584,34 @@ func CountMilestonesByRepoCond(repoCond builder.Cond, isClosed bool) (map[int64]
548584
return countMap, nil
549585
}
550586

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+
}
593+
if repoCond.IsValid() {
594+
sess.In("repo_id", builder.Select("id").From("repository").Where(repoCond))
595+
}
596+
597+
countsSlice := make([]*struct {
598+
RepoID int64
599+
Count int64
600+
}, 0, 10)
601+
if err := sess.GroupBy("repo_id").
602+
Select("repo_id AS repo_id, COUNT(*) AS count").
603+
Table("milestone").
604+
Find(&countsSlice); err != nil {
605+
return nil, err
606+
}
607+
608+
countMap := make(map[int64]int64, len(countsSlice))
609+
for _, c := range countsSlice {
610+
countMap[c.RepoID] = c.Count
611+
}
612+
return countMap, nil
613+
}
614+
551615
func updateRepoMilestoneNum(e Engine, repoID int64) error {
552616
_, 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=?",
553617
repoID,

routers/repo/milestone.go

+7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package repo
66

77
import (
88
"net/http"
9+
"strings"
910
"time"
1011

1112
"code.gitea.io/gitea/models"
@@ -44,6 +45,9 @@ func Milestones(ctx *context.Context) {
4445
ctx.Data["ClosedCount"] = stats.ClosedCount
4546

4647
sortType := ctx.Query("sort")
48+
49+
keyword := strings.Trim(ctx.Query("q"), " ")
50+
4751
page := ctx.QueryInt("page")
4852
if page <= 1 {
4953
page = 1
@@ -67,6 +71,7 @@ func Milestones(ctx *context.Context) {
6771
RepoID: ctx.Repo.Repository.ID,
6872
State: state,
6973
SortType: sortType,
74+
Name: keyword,
7075
})
7176
if err != nil {
7277
ctx.ServerError("GetMilestones", err)
@@ -90,10 +95,12 @@ func Milestones(ctx *context.Context) {
9095
}
9196

9297
ctx.Data["SortType"] = sortType
98+
ctx.Data["Keyword"] = keyword
9399
ctx.Data["IsShowClosed"] = isShowClosed
94100

95101
pager := context.NewPagination(total, setting.UI.IssuePagingNum, page, 5)
96102
pager.AddParam(ctx, "state", "State")
103+
pager.AddParam(ctx, "q", "Keyword")
97104
ctx.Data["Page"] = pager
98105

99106
ctx.HTML(http.StatusOK, tplMilestone)

routers/user/home.go

+8-5
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ func Milestones(ctx *context.Context) {
202202
isShowClosed = ctx.Query("state") == "closed"
203203
sortType = ctx.Query("sort")
204204
page = ctx.QueryInt("page")
205+
keyword = strings.Trim(ctx.Query("q"), " ")
205206
)
206207

207208
if page <= 1 {
@@ -234,15 +235,15 @@ func Milestones(ctx *context.Context) {
234235
}
235236
}
236237

237-
counts, err := models.CountMilestonesByRepoCond(userRepoCond, isShowClosed)
238+
counts, err := models.CountMilestonesByRepoCondAndKw(userRepoCond, keyword, isShowClosed)
238239
if err != nil {
239240
ctx.ServerError("CountMilestonesByRepoIDs", err)
240241
return
241242
}
242243

243-
milestones, err := models.SearchMilestones(repoCond, page, isShowClosed, sortType)
244+
milestones, err := models.SearchMilestones(repoCond, page, isShowClosed, sortType, keyword)
244245
if err != nil {
245-
ctx.ServerError("GetMilestonesByRepoIDs", err)
246+
ctx.ServerError("SearchMilestones", err)
246247
return
247248
}
248249

@@ -277,7 +278,7 @@ func Milestones(ctx *context.Context) {
277278
i++
278279
}
279280

280-
milestoneStats, err := models.GetMilestonesStatsByRepoCond(repoCond)
281+
milestoneStats, err := models.GetMilestonesStatsByRepoCondAndKw(repoCond, keyword)
281282
if err != nil {
282283
ctx.ServerError("GetMilestoneStats", err)
283284
return
@@ -287,7 +288,7 @@ func Milestones(ctx *context.Context) {
287288
if len(repoIDs) == 0 {
288289
totalMilestoneStats = milestoneStats
289290
} else {
290-
totalMilestoneStats, err = models.GetMilestonesStatsByRepoCond(userRepoCond)
291+
totalMilestoneStats, err = models.GetMilestonesStatsByRepoCondAndKw(userRepoCond, keyword)
291292
if err != nil {
292293
ctx.ServerError("GetMilestoneStats", err)
293294
return
@@ -310,12 +311,14 @@ func Milestones(ctx *context.Context) {
310311
ctx.Data["Counts"] = counts
311312
ctx.Data["MilestoneStats"] = milestoneStats
312313
ctx.Data["SortType"] = sortType
314+
ctx.Data["Keyword"] = keyword
313315
if milestoneStats.Total() != totalMilestoneStats.Total() {
314316
ctx.Data["RepoIDs"] = repoIDs
315317
}
316318
ctx.Data["IsShowClosed"] = isShowClosed
317319

318320
pager := context.NewPagination(pagerCount, setting.UI.IssuePagingNum, page, 5)
321+
pager.AddParam(ctx, "q", "Keyword")
319322
pager.AddParam(ctx, "repos", "RepoIDs")
320323
pager.AddParam(ctx, "sort", "SortType")
321324
pager.AddParam(ctx, "state", "State")

templates/repo/issue/milestones.tmpl

+42-24
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,52 @@
1212
</div>
1313
<div class="ui divider"></div>
1414
{{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>
2515

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>
4056
</div>
4157
</div>
4258
</div>
59+
60+
<!-- milestone list -->
4361
<div class="milestone list">
4462
{{range .Milestones}}
4563
<li class="item">

templates/user/dashboard/milestones.tmpl

+43-28
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<div class="ui stackable grid">
66
<div class="four wide column">
77
<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}}">
99
{{.i18n.Tr "home.issues.in_your_repos"}}
1010
<strong class="ui right">{{.Total}}</strong>
1111
</a>
@@ -25,7 +25,7 @@
2525
{{$Repo.ID}}%2C
2626
{{end}}
2727
{{end}}
28-
]&sort={{$.SortType}}&state={{$.State}}" title="{{.FullName}}">
28+
]&sort={{$.SortType}}&state={{$.State}}&q={{$.Keyword}}" title="{{.FullName}}">
2929
<span class="text truncate">{{$Repo.FullName}}</span>
3030
<div class="ui {{if $.IsShowClosed}}red{{else}}green{{end}} label">{{index $.Counts $Repo.ID}}</div>
3131
</a>
@@ -34,34 +34,49 @@
3434
</div>
3535
</div>
3636
<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>
6148
</div>
6249
</div>
63-
</div>
64-
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>
77+
</div>
78+
</div>
79+
</div>
6580
<div class="milestone list">
6681
{{range .Milestones}}
6782
<li class="item">

0 commit comments

Comments
 (0)