Add ability to see open and closed issues at the same time (#28757)

By clicking the currently active "Open" or "Closed" filter button in the
issue list, the user can toggle that filter off in order to see all
issues regardless of state. The URL "state" parameter will be set to
"all" and the "Open"/"Closed" button will not show as active.
This commit is contained in:
Gwyneth Morgan 2024-01-15 07:07:22 -08:00 committed by GitHub
parent 2d343f8987
commit 2c3da59e27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 42 additions and 19 deletions

View File

@ -340,7 +340,7 @@ func GetTrackedTimeByID(ctx context.Context, id int64) (*TrackedTime, error) {
} }
// GetIssueTotalTrackedTime returns the total tracked time for issues by given conditions. // GetIssueTotalTrackedTime returns the total tracked time for issues by given conditions.
func GetIssueTotalTrackedTime(ctx context.Context, opts *IssuesOptions, isClosed bool) (int64, error) { func GetIssueTotalTrackedTime(ctx context.Context, opts *IssuesOptions, isClosed util.OptionalBool) (int64, error) {
if len(opts.IssueIDs) <= MaxQueryParameters { if len(opts.IssueIDs) <= MaxQueryParameters {
return getIssueTotalTrackedTimeChunk(ctx, opts, isClosed, opts.IssueIDs) return getIssueTotalTrackedTimeChunk(ctx, opts, isClosed, opts.IssueIDs)
} }
@ -363,7 +363,7 @@ func GetIssueTotalTrackedTime(ctx context.Context, opts *IssuesOptions, isClosed
return accum, nil return accum, nil
} }
func getIssueTotalTrackedTimeChunk(ctx context.Context, opts *IssuesOptions, isClosed bool, issueIDs []int64) (int64, error) { func getIssueTotalTrackedTimeChunk(ctx context.Context, opts *IssuesOptions, isClosed util.OptionalBool, issueIDs []int64) (int64, error) {
sumSession := func(opts *IssuesOptions, issueIDs []int64) *xorm.Session { sumSession := func(opts *IssuesOptions, issueIDs []int64) *xorm.Session {
sess := db.GetEngine(ctx). sess := db.GetEngine(ctx).
Table("tracked_time"). Table("tracked_time").
@ -377,7 +377,9 @@ func getIssueTotalTrackedTimeChunk(ctx context.Context, opts *IssuesOptions, isC
Time int64 Time int64
} }
return sumSession(opts, issueIDs). session := sumSession(opts, issueIDs)
And("issue.is_closed = ?", isClosed). if !isClosed.IsNone() {
SumInt(new(trackedTime), "tracked_time.time") session = session.And("issue.is_closed = ?", isClosed.IsTrue())
}
return session.SumInt(new(trackedTime), "tracked_time.time")
} }

View File

@ -11,6 +11,7 @@ import (
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -119,11 +120,15 @@ func TestTotalTimesForEachUser(t *testing.T) {
func TestGetIssueTotalTrackedTime(t *testing.T) { func TestGetIssueTotalTrackedTime(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
ttt, err := issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, false) ttt, err := issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, util.OptionalBoolFalse)
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 3682, ttt) assert.EqualValues(t, 3682, ttt)
ttt, err = issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, true) ttt, err = issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, util.OptionalBoolTrue)
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 0, ttt) assert.EqualValues(t, 0, ttt)
ttt, err = issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, util.OptionalBoolNone)
assert.NoError(t, err)
assert.EqualValues(t, 3682, ttt)
} }

View File

@ -237,10 +237,18 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
} }
} }
isShowClosed := ctx.FormString("state") == "closed" var isShowClosed util.OptionalBool
// if open issues are zero and close don't, use closed as default switch ctx.FormString("state") {
case "closed":
isShowClosed = util.OptionalBoolTrue
case "all":
isShowClosed = util.OptionalBoolNone
default:
isShowClosed = util.OptionalBoolFalse
}
// if there are closed issues and no open issues, default to showing all issues
if len(ctx.FormString("state")) == 0 && issueStats.OpenCount == 0 && issueStats.ClosedCount != 0 { if len(ctx.FormString("state")) == 0 && issueStats.OpenCount == 0 && issueStats.ClosedCount != 0 {
isShowClosed = true isShowClosed = util.OptionalBoolNone
} }
if repo.IsTimetrackerEnabled(ctx) { if repo.IsTimetrackerEnabled(ctx) {
@ -260,10 +268,13 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
} }
var total int var total int
if !isShowClosed { switch isShowClosed {
total = int(issueStats.OpenCount) case util.OptionalBoolTrue:
} else {
total = int(issueStats.ClosedCount) total = int(issueStats.ClosedCount)
case util.OptionalBoolNone:
total = int(issueStats.OpenCount + issueStats.ClosedCount)
default:
total = int(issueStats.OpenCount)
} }
pager := context.NewPagination(total, setting.UI.IssuePagingNum, page, 5) pager := context.NewPagination(total, setting.UI.IssuePagingNum, page, 5)
@ -282,7 +293,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
ReviewedID: reviewedID, ReviewedID: reviewedID,
MilestoneIDs: mileIDs, MilestoneIDs: mileIDs,
ProjectID: projectID, ProjectID: projectID,
IsClosed: util.OptionalBoolOf(isShowClosed), IsClosed: isShowClosed,
IsPull: isPullOption, IsPull: isPullOption,
LabelIDs: labelIDs, LabelIDs: labelIDs,
SortType: sortType, SortType: sortType,
@ -428,6 +439,9 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
ctx.Data["OpenCount"] = issueStats.OpenCount ctx.Data["OpenCount"] = issueStats.OpenCount
ctx.Data["ClosedCount"] = issueStats.ClosedCount ctx.Data["ClosedCount"] = issueStats.ClosedCount
linkStr := "%s?q=%s&type=%s&sort=%s&state=%s&labels=%s&milestone=%d&project=%d&assignee=%d&poster=%d&archived=%t" linkStr := "%s?q=%s&type=%s&sort=%s&state=%s&labels=%s&milestone=%d&project=%d&assignee=%d&poster=%d&archived=%t"
ctx.Data["AllStatesLink"] = fmt.Sprintf(linkStr, ctx.Link,
url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "all", url.QueryEscape(selectLabels),
mentionedID, projectID, assigneeID, posterID, archived)
ctx.Data["OpenLink"] = fmt.Sprintf(linkStr, ctx.Link, ctx.Data["OpenLink"] = fmt.Sprintf(linkStr, ctx.Link,
url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "open", url.QueryEscape(selectLabels), url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "open", url.QueryEscape(selectLabels),
mentionedID, projectID, assigneeID, posterID, archived) mentionedID, projectID, assigneeID, posterID, archived)
@ -442,11 +456,13 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
ctx.Data["ProjectID"] = projectID ctx.Data["ProjectID"] = projectID
ctx.Data["AssigneeID"] = assigneeID ctx.Data["AssigneeID"] = assigneeID
ctx.Data["PosterID"] = posterID ctx.Data["PosterID"] = posterID
ctx.Data["IsShowClosed"] = isShowClosed
ctx.Data["Keyword"] = keyword ctx.Data["Keyword"] = keyword
if isShowClosed { switch isShowClosed {
case util.OptionalBoolTrue:
ctx.Data["State"] = "closed" ctx.Data["State"] = "closed"
} else { case util.OptionalBoolNone:
ctx.Data["State"] = "all"
default:
ctx.Data["State"] = "open" ctx.Data["State"] = "open"
} }
ctx.Data["ShowArchivedLabels"] = archived ctx.Data["ShowArchivedLabels"] = archived

View File

@ -1,5 +1,5 @@
<div class="small-menu-items ui compact tiny menu"> <div class="small-menu-items ui compact tiny menu">
<a class="{{if not .IsShowClosed}}active {{end}}item" href="{{.OpenLink}}"> <a class="{{if eq .State "open"}}active {{end}}item" href="{{if eq .State "open"}}{{.AllStatesLink}}{{else}}{{.OpenLink}}{{end}}">
{{if .PageIsMilestones}} {{if .PageIsMilestones}}
{{svg "octicon-milestone" 16 "gt-mr-3"}} {{svg "octicon-milestone" 16 "gt-mr-3"}}
{{else if .PageIsPullList}} {{else if .PageIsPullList}}
@ -9,7 +9,7 @@
{{end}} {{end}}
{{ctx.Locale.PrettyNumber .OpenCount}}&nbsp;{{ctx.Locale.Tr "repo.issues.open_title"}} {{ctx.Locale.PrettyNumber .OpenCount}}&nbsp;{{ctx.Locale.Tr "repo.issues.open_title"}}
</a> </a>
<a class="{{if .IsShowClosed}}active {{end}}item" href="{{.ClosedLink}}"> <a class="{{if eq .State "closed"}}active {{end}}item" href="{{if eq .State "closed"}}{{.AllStatesLink}}{{else}}{{.ClosedLink}}{{end}}">
{{svg "octicon-check" 16 "gt-mr-3"}} {{svg "octicon-check" 16 "gt-mr-3"}}
{{ctx.Locale.PrettyNumber .ClosedCount}}&nbsp;{{ctx.Locale.Tr "repo.issues.closed_title"}} {{ctx.Locale.PrettyNumber .ClosedCount}}&nbsp;{{ctx.Locale.Tr "repo.issues.closed_title"}}
</a> </a>