Skip to content

Add the ability to use multiple labels as filters #3438

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

Closed
wants to merge 7 commits into from
Closed
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
24 changes: 22 additions & 2 deletions models/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -1140,10 +1140,20 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) error {
if err != nil {
return err
}
if len(labelIDs) > 0 {
if len(labelIDs) == 1 {
sess.
Join("INNER", "issue_label", "issue.id = issue_label.issue_id").
In("issue_label.label_id", labelIDs)
} else if len(labelIDs) > 1 {
cond, args, _ := builder.ToSQL(builder.In("issue_label.label_id", labelIDs))
sess.
Where(fmt.Sprintf(`issue.id IN (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't this be built using builder? cc: @lunny

SELECT issue_label.issue_id
FROM issue_label
WHERE %s
GROUP BY issue_label.issue_id
HAVING COUNT(issue_label.label_id) = %d
)`, cond, len(labelIDs)), args...)
}
}
return nil
Expand Down Expand Up @@ -1322,9 +1332,19 @@ 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 {
} else if len(labelIDs) == 1 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This query could be fixed to do > 0 instead of having 2 different queries

sess.Join("INNER", "issue_label", "issue.id = issue_label.issue_id").
In("issue_label.label_id", labelIDs)
} else if len(labelIDs) > 1 {
cond, args, _ := builder.ToSQL(builder.In("issue_label.label_id", labelIDs))
sess.
Where(fmt.Sprintf(`issue.id IN (
SELECT issue_label.issue_id
FROM issue_label
WHERE %s
GROUP BY issue_label.issue_id
HAVING COUNT(issue_label.label_id) = %d
)`, cond, len(labelIDs)), args...)
}
}

Expand Down
21 changes: 21 additions & 0 deletions models/issue_label.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,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 @@ -76,6 +78,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 []string) {
var labelQuerySlice []string
labelSelected := false
labelID := fmt.Sprint(label.ID)
for _, s := range currentSelectedLabels {
if s == labelID {
labelSelected = true
} else if s != "" {
labelQuerySlice = append(labelQuerySlice, s)
}
}
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
10 changes: 9 additions & 1 deletion models/issue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,21 @@ func TestIssues(t *testing.T) {
},
[]int64{1, 2, 3, 5},
},
{
IssuesOptions{
Labels: "1",
Page: 1,
PageSize: 4,
},
[]int64{2, 1},
},
{
IssuesOptions{
Labels: "1,2",
Page: 1,
PageSize: 4,
},
[]int64{5, 2, 1},
[]int64{}, // issues with **both** label 1 and 2, none of these issues matches, TODO: add more tests
},
} {
issues, err := Issues(&test.Opts)
Expand Down
19 changes: 19 additions & 0 deletions routers/repo/issue_label.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
package repo

import (
"strings"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
Expand Down Expand Up @@ -69,6 +71,23 @@ func RetrieveLabels(ctx *context.Context) {
ctx.Data["SortType"] = ctx.Query("sort")
}

// RetrieveLabelsAndLoadSelectedLabels calculate query string when filtering issues/pulls
func RetrieveLabelsAndLoadSelectedLabels(ctx *context.Context) {
labels, err := models.GetLabelsByRepoID(ctx.Repo.Repository.ID, ctx.Query("sort"))
if err != nil {
ctx.ServerError("RetrieveLabelsAndLoadSelectedLabels.GetLabels", err)
return
}
selectLabels := strings.Split(ctx.Query("labels"), ",")
for _, l := range labels {
l.CalOpenIssues()
l.LoadSelectedLabelsAfterClick(selectLabels)
}
ctx.Data["Labels"] = labels
ctx.Data["NumLabels"] = len(labels)
ctx.Data["SortType"] = ctx.Query("sort")
}

// NewLabel create new label for repository
func NewLabel(ctx *context.Context, form auth.CreateLabelForm) {
ctx.Data["Title"] = ctx.Tr("repo.labels")
Expand Down
2 changes: 1 addition & 1 deletion routers/routes/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,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.RetrieveLabelsAndLoadSelectedLabels, repo.Issues)
m.Get("/^:type(issues|pulls)$/:index", repo.ViewIssue)
m.Get("/labels/", context.CheckAnyUnit(models.UnitTypeIssues, models.UnitTypePullRequests), repo.RetrieveLabels, repo.Labels)
m.Get("/milestones", context.CheckAnyUnit(models.UnitTypeIssues, models.UnitTypePullRequests), repo.Milestones)
Expand Down
2 changes: 1 addition & 1 deletion templates/repo/issue/list.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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