diff options
author | Lunny Xiao <xiaolunwen@gmail.com> | 2023-04-30 21:12:49 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-30 09:12:49 -0400 |
commit | 3f0651d4d61d62a16e1bb672056014ab02db5746 (patch) | |
tree | 035e1fd9edf7383b0abad6e89c2db4cd2b8a2355 | |
parent | e3750370df3be1413b1526668cbee60dc2a39f03 (diff) | |
download | gitea-3f0651d4d61d62a16e1bb672056014ab02db5746.tar.gz gitea-3f0651d4d61d62a16e1bb672056014ab02db5746.zip |
Improve milestone filter on issues page (#22423)
Now we have `All milestones`, `No milestones`, `Open milestones` and
`Closed milestones`.
Fix #11924
Fix #22411
<img width="1166" alt="image"
src="https://user-images.githubusercontent.com/81045/212243375-95eea035-a972-44b8-8088-53db614cb07e.png">
-rw-r--r-- | models/db/search.go | 2 | ||||
-rw-r--r-- | models/issues/issue.go | 8 | ||||
-rw-r--r-- | options/locale/locale_en-US.ini | 5 | ||||
-rw-r--r-- | routers/web/repo/issue.go | 31 | ||||
-rw-r--r-- | templates/repo/issue/list.tmpl | 27 | ||||
-rw-r--r-- | templates/repo/issue/milestone/select_menu.tmpl | 39 | ||||
-rw-r--r-- | templates/repo/issue/new_form.tmpl | 39 | ||||
-rw-r--r-- | templates/repo/issue/view_content/sidebar.tmpl | 39 |
8 files changed, 99 insertions, 91 deletions
diff --git a/models/db/search.go b/models/db/search.go index 26e082756a..105cb64c41 100644 --- a/models/db/search.go +++ b/models/db/search.go @@ -31,5 +31,5 @@ const ( const ( // Which means a condition to filter the records which don't match any id. // It's different from zero which means the condition could be ignored. - NoneID = -1 + NoConditionID = -1 ) diff --git a/models/issues/issue.go b/models/issues/issue.go index 4f8e9e161d..8c173433f2 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -1273,7 +1273,9 @@ func (opts *IssuesOptions) setupSessionNoLimit(sess *xorm.Session) { applySubscribedCondition(sess, opts.SubscriberID) } - if len(opts.MilestoneIDs) > 0 { + if len(opts.MilestoneIDs) == 1 && opts.MilestoneIDs[0] == db.NoConditionID { + sess.And("issue.milestone_id = 0") + } else if len(opts.MilestoneIDs) > 0 { sess.In("issue.milestone_id", opts.MilestoneIDs) } @@ -1287,7 +1289,7 @@ func (opts *IssuesOptions) setupSessionNoLimit(sess *xorm.Session) { if opts.ProjectID > 0 { sess.Join("INNER", "project_issue", "issue.id = project_issue.issue_id"). And("project_issue.project_id=?", opts.ProjectID) - } else if opts.ProjectID == db.NoneID { // show those that are in no project + } else if opts.ProjectID == db.NoConditionID { // show those that are in no project sess.And(builder.NotIn("issue.id", builder.Select("issue_id").From("project_issue"))) } @@ -1721,6 +1723,8 @@ func getIssueStatsChunk(opts *IssueStatsOptions, issueIDs []int64) (*IssueStats, if opts.MilestoneID > 0 { sess.And("issue.milestone_id = ?", opts.MilestoneID) + } else if opts.MilestoneID == db.NoConditionID { + sess.And("issue.milestone_id = 0") } if opts.ProjectID > 0 { diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 32d9bebc8b..1d2a0edfb3 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1352,7 +1352,10 @@ issues.filter_label = Label issues.filter_label_exclude = `Use <code>alt</code> + <code>click/enter</code> to exclude labels` issues.filter_label_no_select = All labels issues.filter_milestone = Milestone -issues.filter_milestone_no_select = All milestones +issues.filter_milestone_all = All milestones +issues.filter_milestone_none = No milestones +issues.filter_milestone_open = Open milestones +issues.filter_milestone_closed = Closed milestones issues.filter_project = Project issues.filter_project_all = All projects issues.filter_project_none = No project diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 49ea4f2a1f..5c96d326a7 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -241,7 +241,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti pager := context.NewPagination(total, setting.UI.IssuePagingNum, page, 5) var mileIDs []int64 - if milestoneID > 0 { + if milestoneID > 0 || milestoneID == db.NoConditionID { // -1 to get those issues which have no any milestone assigned mileIDs = []int64{milestoneID} } @@ -438,20 +438,37 @@ func Issues(ctx *context.Context) { return } - var err error + renderMilestones(ctx) + if ctx.Written() { + return + } + + ctx.Data["CanWriteIssuesOrPulls"] = ctx.Repo.CanWriteIssuesOrPulls(isPullList) + + ctx.HTML(http.StatusOK, tplIssues) +} + +func renderMilestones(ctx *context.Context) { // Get milestones - ctx.Data["Milestones"], _, err = issues_model.GetMilestones(issues_model.GetMilestonesOption{ + milestones, _, err := issues_model.GetMilestones(issues_model.GetMilestonesOption{ RepoID: ctx.Repo.Repository.ID, - State: api.StateType(ctx.FormString("state")), + State: api.StateAll, }) if err != nil { ctx.ServerError("GetAllRepoMilestones", err) return } - ctx.Data["CanWriteIssuesOrPulls"] = ctx.Repo.CanWriteIssuesOrPulls(isPullList) - - ctx.HTML(http.StatusOK, tplIssues) + openMilestones, closedMilestones := issues_model.MilestoneList{}, issues_model.MilestoneList{} + for _, milestone := range milestones { + if milestone.IsClosed { + closedMilestones = append(closedMilestones, milestone) + } else { + openMilestones = append(openMilestones, milestone) + } + } + ctx.Data["OpenMilestones"] = openMilestones + ctx.Data["ClosedMilestones"] = closedMilestones } // RetrieveRepoMilestonesAndAssignees find all the milestones and assignees of a repository diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl index ba927b8498..fb2d8ee3bd 100644 --- a/templates/repo/issue/list.tmpl +++ b/templates/repo/issue/list.tmpl @@ -53,7 +53,7 @@ </div> <!-- Milestone --> - <div class="ui {{if not .Milestones}}disabled{{end}} dropdown jump item"> + <div class="ui {{if not (or .OpenMilestones .ClosedMilestones)}}disabled{{end}} dropdown jump item"> <span class="text"> {{.locale.Tr "repo.issues.filter_milestone"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}} @@ -63,9 +63,28 @@ <i class="icon gt-df gt-ac gt-jc">{{svg "octicon-search" 16}}</i> <input type="text" placeholder="{{.locale.Tr "repo.issues.filter_milestone"}}"> </div> - <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_milestone_no_select"}}</a> - {{range .Milestones}} - <a class="{{if $.MilestoneID}}{{if eq $.MilestoneID .ID}}active selected{{end}}{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{.ID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.Name}}</a> + <div class="divider"></div> + <a class="{{if not $.MilestoneID}}active selected {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone=0&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_milestone_all"}}</a> + <a class="{{if $.MilestoneID}}{{if eq $.MilestoneID -1}}active selected {{end}}{{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone=-1&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_milestone_none"}}</a> + {{if .OpenMilestones}} + <div class="divider"></div> + <div class="header">{{.locale.Tr "repo.issues.filter_milestone_open"}}</div> + {{range .OpenMilestones}} + <a class="{{if $.MilestoneID}}{{if eq $.MilestoneID .ID}}active selected {{end}}{{end}}item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{.ID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}"> + {{svg "octicon-milestone" 16 "mr-2"}} + {{.Name}} + </a> + {{end}} + {{end}} + {{if .ClosedMilestones}} + <div class="divider"></div> + <div class="header">{{.locale.Tr "repo.issues.filter_milestone_closed"}}</div> + {{range .ClosedMilestones}} + <a class="{{if $.MilestoneID}}{{if eq $.MilestoneID .ID}}active selected {{end}}{{end}}item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{.ID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}"> + {{svg "octicon-milestone" 16 "mr-2"}} + {{.Name}} + </a> + {{end}} {{end}} </div> </div> diff --git a/templates/repo/issue/milestone/select_menu.tmpl b/templates/repo/issue/milestone/select_menu.tmpl new file mode 100644 index 0000000000..6f67b5d523 --- /dev/null +++ b/templates/repo/issue/milestone/select_menu.tmpl @@ -0,0 +1,39 @@ +<div class="header" style="text-transform: none;font-size:16px;">{{.locale.Tr "repo.issues.new.add_milestone_title"}}</div> +{{if or .OpenMilestones .ClosedMilestones}} + <div class="ui icon search input"> + <i class="icon gt-df gt-ac gt-jc">{{svg "octicon-search" 16}}</i> + <input type="text" placeholder="{{.locale.Tr "repo.issues.filter_milestones"}}"> + </div> + <div class="divider"></div> +{{end}} +<div class="no-select item">{{.locale.Tr "repo.issues.new.clear_milestone"}}</div> +{{if and (not .OpenMilestones) (not .ClosedMilestones)}} + <div class="header" style="text-transform: none;font-size:14px;"> + {{.locale.Tr "repo.issues.new.no_items"}} + </div> +{{else}} + {{if .OpenMilestones}} + <div class="divider"></div> + <div class="header"> + {{.locale.Tr "repo.issues.new.open_milestone"}} + </div> + {{range .OpenMilestones}} + <a class="item" data-id="{{.ID}}" data-href="{{$.RepoLink}}/issues?milestone={{.ID}}"> + {{svg "octicon-milestone" 16 "gt-mr-2"}} + {{.Name}} + </a> + {{end}} + {{end}} + {{if .ClosedMilestones}} + <div class="divider"></div> + <div class="header"> + {{.locale.Tr "repo.issues.new.closed_milestone"}} + </div> + {{range .ClosedMilestones}} + <a class="item" data-id="{{.ID}}" data-href="{{$.RepoLink}}/issues?milestone={{.ID}}"> + {{svg "octicon-milestone" 16 "gt-mr-2"}} + {{.Name}} + </a> + {{end}} + {{end}} +{{end}} diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl index d673e89a39..196e911061 100644 --- a/templates/repo/issue/new_form.tmpl +++ b/templates/repo/issue/new_form.tmpl @@ -72,44 +72,7 @@ {{end}} </span> <div class="menu"> - <div class="header" style="text-transform: none;font-size:16px;">{{.locale.Tr "repo.issues.new.add_milestone_title"}}</div> - {{if or .OpenMilestones .ClosedMilestones}} - <div class="ui icon search input"> - <i class="icon gt-df gt-ac gt-jc">{{svg "octicon-search" 16}}</i> - <input type="text" placeholder="{{.locale.Tr "repo.issues.filter_milestones"}}"> - </div> - {{end}} - <div class="no-select item">{{.locale.Tr "repo.issues.new.clear_milestone"}}</div> - {{if and (not .OpenMilestones) (not .ClosedMilestones)}} - <div class="header" style="text-transform: none;font-size:14px;"> - {{.locale.Tr "repo.issues.new.no_items"}} - </div> - {{else}} - {{if .OpenMilestones}} - <div class="divider"></div> - <div class="header"> - {{.locale.Tr "repo.issues.new.open_milestone"}} - </div> - {{range .OpenMilestones}} - <a class="item" data-id="{{.ID}}" data-href="{{$.RepoLink}}/issues?milestone={{.ID}}"> - {{svg "octicon-milestone" 16 "gt-mr-2"}} - {{.Name}} - </a> - {{end}} - {{end}} - {{if .ClosedMilestones}} - <div class="divider"></div> - <div class="header"> - {{.locale.Tr "repo.issues.new.closed_milestone"}} - </div> - {{range .ClosedMilestones}} - <a class="item" data-id="{{.ID}}" data-href="{{$.RepoLink}}/issues?milestone={{.ID}}"> - {{svg "octicon-milestone" 16 "gt-mr-2"}} - {{.Name}} - </a> - {{end}} - {{end}} - {{end}} + {{template "repo/issue/milestone/select_menu" .}} </div> </div> <div class="ui select-milestone list"> diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index fad0aadafe..e07ef0fa37 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -118,44 +118,7 @@ {{end}} </a> <div class="menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/milestone"> - <div class="header" style="text-transform: none;font-size:16px;">{{.locale.Tr "repo.issues.new.add_milestone_title"}}</div> - {{if or .OpenMilestones .ClosedMilestones}} - <div class="ui icon search input"> - <i class="icon gt-df gt-ac gt-jc">{{svg "octicon-search" 16}}</i> - <input type="text" placeholder="{{.locale.Tr "repo.issues.filter_milestones"}}"> - </div> - {{end}} - <div class="no-select item">{{.locale.Tr "repo.issues.new.clear_milestone"}}</div> - {{if and (not .OpenMilestones) (not .ClosedMilestones)}} - <div class="header" style="text-transform: none;font-size:14px;"> - {{.locale.Tr "repo.issues.new.no_items"}} - </div> - {{else}} - {{if .OpenMilestones}} - <div class="divider"></div> - <div class="header"> - {{.locale.Tr "repo.issues.new.open_milestone"}} - </div> - {{range .OpenMilestones}} - <a class="item" data-id="{{.ID}}" data-href="{{$.RepoLink}}/issues?milestone={{.ID}}"> - {{svg "octicon-milestone" 16 "gt-mr-2"}} - {{.Name}} - </a> - {{end}} - {{end}} - {{if .ClosedMilestones}} - <div class="divider"></div> - <div class="header"> - {{.locale.Tr "repo.issues.new.closed_milestone"}} - </div> - {{range .ClosedMilestones}} - <a class="item" data-id="{{.ID}}" data-href="{{$.RepoLink}}/issues?milestone={{.ID}}"> - {{svg "octicon-milestone" 16 "gt-mr-2"}} - {{.Name}} - </a> - {{end}} - {{end}} - {{end}} + {{template "repo/issue/milestone/select_menu" .}} </div> </div> <div class="ui select-milestone list"> |