diff --git a/models/issue.go b/models/issue.go index d10c521db6d8f..01252a6d24c77 100644 --- a/models/issue.go +++ b/models/issue.go @@ -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 ( + 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 @@ -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 { 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...) } } diff --git a/models/issue_label.go b/models/issue_label.go index 6e48b4633bd31..216c5e7218c54 100644 --- a/models/issue_label.go +++ b/models/issue_label.go @@ -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 @@ -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 { diff --git a/models/issue_test.go b/models/issue_test.go index 851fe684fbc79..c30d4cbd129c7 100644 --- a/models/issue_test.go +++ b/models/issue_test.go @@ -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) diff --git a/routers/repo/issue_label.go b/routers/repo/issue_label.go index a197256be86db..09280448fc635 100644 --- a/routers/repo/issue_label.go +++ b/routers/repo/issue_label.go @@ -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" @@ -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") diff --git a/routers/routes/routes.go b/routers/routes/routes.go index e51bfb946ab72..212373a5d0f63 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -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) diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl index 642458ff1e43c..117b7e7111827 100644 --- a/templates/repo/issue/list.tmpl +++ b/templates/repo/issue/list.tmpl @@ -42,7 +42,7 @@