Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to use multiple labels as filters #5786

Merged
merged 2 commits into from
Jan 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 10 additions & 13 deletions models/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -1210,7 +1210,7 @@ type IssuesOptions struct {
PageSize int
IsClosed util.OptionalBool
IsPull util.OptionalBool
Labels string
LabelIDs []int64
SortType string
IssueIDs []int64
}
Expand Down Expand Up @@ -1289,15 +1289,10 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) error {
sess.And("issue.is_pull=?", false)
}

if len(opts.Labels) > 0 && opts.Labels != "0" {
labelIDs, err := base.StringsToInt64s(strings.Split(opts.Labels, ","))
if err != nil {
return err
}
if len(labelIDs) > 0 {
sess.
Join("INNER", "issue_label", "issue.id = issue_label.issue_id").
In("issue_label.label_id", labelIDs)
if opts.LabelIDs != nil {
for i, labelID := range opts.LabelIDs {
sess.Join("INNER", fmt.Sprintf("issue_label il%d", i),
fmt.Sprintf("issue.id = il%[1]d.issue_id AND il%[1]d.label_id = %[2]d", i, labelID))
}
}
return nil
Expand Down Expand Up @@ -1475,9 +1470,11 @@ func GetIssueStats(opts *IssueStatsOptions) (*IssueStats, error) {
labelIDs, err := base.StringsToInt64s(strings.Split(opts.Labels, ","))
if err != nil {
log.Warn("Malformed Labels argument: %s", opts.Labels)
} else if len(labelIDs) > 0 {
sess.Join("INNER", "issue_label", "issue.id = issue_label.issue_id").
In("issue_label.label_id", labelIDs)
} else {
for i, labelID := range labelIDs {
sess.Join("INNER", fmt.Sprintf("issue_label il%d", i),
fmt.Sprintf("issue.id = il%[1]d.issue_id AND il%[1]d.label_id = %[2]d", i, labelID))
}
}
}

Expand Down
21 changes: 21 additions & 0 deletions models/issue_label.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ type Label struct {
NumClosedIssues int
NumOpenIssues int `xorm:"-"`
IsChecked bool `xorm:"-"`
QueryString string
IsSelected bool
}

// APIFormat converts a Label to the api.Label format
Expand All @@ -85,6 +87,25 @@ func (label *Label) CalOpenIssues() {
label.NumOpenIssues = label.NumIssues - label.NumClosedIssues
}

// LoadSelectedLabelsAfterClick calculates the set of selected labels when a label is clicked
func (label *Label) LoadSelectedLabelsAfterClick(currentSelectedLabels []int64) {
var labelQuerySlice []string
labelSelected := false
labelID := strconv.FormatInt(label.ID, 10)
for _, s := range currentSelectedLabels {
if s == label.ID {
labelSelected = true
} else if s > 0 {
labelQuerySlice = append(labelQuerySlice, strconv.FormatInt(s, 10))
}
}
if !labelSelected {
labelQuerySlice = append(labelQuerySlice, labelID)
}
label.IsSelected = labelSelected
label.QueryString = strings.Join(labelQuerySlice, ",")
}

// ForegroundColor calculates the text color for labels based
// on their background color.
func (label *Label) ForegroundColor() template.CSS {
Expand Down
12 changes: 10 additions & 2 deletions models/issue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,19 @@ func TestIssues(t *testing.T) {
},
{
IssuesOptions{
Labels: "1,2",
LabelIDs: []int64{1},
Page: 1,
PageSize: 4,
},
[]int64{5, 2, 1},
[]int64{2, 1},
},
{
IssuesOptions{
LabelIDs: []int64{1, 2},
Page: 1,
PageSize: 4,
},
[]int64{}, // issues with **both** label 1 and 2, none of these issues matches, TODO: add more tests
},
} {
issues, err := Issues(&test.Opts)
Expand Down
2 changes: 1 addition & 1 deletion public/css/index.css

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions public/less/_repository.less
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,11 @@
margin: 5px -7px 0 -5px;
width: 16px;
}
.text{
margin-left: 0.9em;
&.labels .octicon {
margin: -2px -7px 0 -5px;
}
.text {
margin-left: 0.9em;
}
.menu {
max-height: 300px;
Expand Down
15 changes: 13 additions & 2 deletions routers/repo/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,15 @@ func issues(ctx *context.Context, milestoneID int64, isPullOption util.OptionalB
}

repo := ctx.Repo.Repository
var labelIDs []int64
selectLabels := ctx.Query("labels")

if len(selectLabels) > 0 && selectLabels != "0" {
labelIDs, err = base.StringsToInt64s(strings.Split(selectLabels, ","))
if err != nil {
ctx.ServerError("StringsToInt64s", err)
return
}
}
isShowClosed := ctx.Query("state") == "closed"

keyword := strings.Trim(ctx.Query("q"), " ")
Expand Down Expand Up @@ -176,7 +183,7 @@ func issues(ctx *context.Context, milestoneID int64, isPullOption util.OptionalB
PageSize: setting.UI.IssuePagingNum,
IsClosed: util.OptionalBoolOf(isShowClosed),
IsPull: isPullOption,
Labels: selectLabels,
LabelIDs: labelIDs,
SortType: sortType,
IssueIDs: issueIDs,
})
Expand Down Expand Up @@ -210,7 +217,11 @@ func issues(ctx *context.Context, milestoneID int64, isPullOption util.OptionalB
ctx.ServerError("GetLabelsByRepoID", err)
return
}
for _, l := range labels {
l.LoadSelectedLabelsAfterClick(labelIDs)
}
ctx.Data["Labels"] = labels
ctx.Data["NumLabels"] = len(labels)

if ctx.QueryInt64("assignee") == 0 {
assigneeID = 0 // Reset ID to prevent unexpected selection of assignee.
Expand Down
2 changes: 1 addition & 1 deletion routers/routes/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -656,7 +656,7 @@ func RegisterRoutes(m *macaron.Macaron) {

m.Group("/:username/:reponame", func() {
m.Group("", func() {
m.Get("/^:type(issues|pulls)$", repo.RetrieveLabels, repo.Issues)
m.Get("/^:type(issues|pulls)$", repo.Issues)
m.Get("/^:type(issues|pulls)$/:index", repo.ViewIssue)
m.Get("/labels/", reqRepoIssuesOrPullsReader, repo.RetrieveLabels, repo.Labels)
m.Get("/milestones", reqRepoIssuesOrPullsReader, repo.Milestones)
Expand Down
12 changes: 11 additions & 1 deletion routers/user/home.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"bytes"
"fmt"
"sort"
"strings"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
Expand Down Expand Up @@ -256,7 +257,16 @@ func Issues(ctx *context.Context) {

opts.Page = page
opts.PageSize = setting.UI.IssuePagingNum
opts.Labels = ctx.Query("labels")
var labelIDs []int64
selectLabels := ctx.Query("labels")
if len(selectLabels) > 0 && selectLabels != "0" {
labelIDs, err = base.StringsToInt64s(strings.Split(selectLabels, ","))
if err != nil {
ctx.ServerError("StringsToInt64s", err)
return
}
}
opts.LabelIDs = labelIDs

issues, err := models.Issues(opts)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions templates/repo/issue/list.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
</div>
</div>
<div class="ten wide right aligned column">
<div class="ui secondary filter stackable menu">
<div class="ui secondary filter stackable menu labels">
<!-- Label -->
<div class="ui {{if not .Labels}}disabled{{end}} dropdown jump item" style="margin-left: auto">
<span class="text">
Expand All @@ -42,7 +42,7 @@
<div class="menu">
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_label_no_select"}}</a>
{{range .Labels}}
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.ID}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}"><span class="octicon {{if eq $.SelectLabels .ID}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name}}</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}"><span class="octicon {{if .IsSelected}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name}}</a>
{{end}}
</div>
</div>
Expand Down