diff --git a/integrations/api_issue_test.go b/integrations/api_issue_test.go index 604e6d638..74512cafa 100644 --- a/integrations/api_issue_test.go +++ b/integrations/api_issue_test.go @@ -222,6 +222,20 @@ func TestAPISearchIssues(t *testing.T) { resp = session.MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiIssues) assert.Len(t, apiIssues, 1) + + query = url.Values{"milestones": {"milestone1"}, "state": {"all"}} + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 1) + + query = url.Values{"milestones": {"milestone1,milestone3"}, "state": {"all"}} + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) } func TestAPISearchIssuesWithLabels(t *testing.T) { diff --git a/models/issue.go b/models/issue.go index f1a092deb..ffbc110a6 100644 --- a/models/issue.go +++ b/models/issue.go @@ -1100,6 +1100,7 @@ type IssuesOptions struct { LabelIDs []int64 IncludedLabelNames []string ExcludedLabelNames []string + IncludeMilestones []string SortType string IssueIDs []int64 UpdatedAfterUnix int64 @@ -1241,6 +1242,13 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) { if len(opts.ExcludedLabelNames) > 0 { sess.And(builder.NotIn("issue.id", BuildLabelNamesIssueIDsCondition(opts.ExcludedLabelNames))) } + + if len(opts.IncludeMilestones) > 0 { + sess.In("issue.milestone_id", + builder.Select("id"). + From("milestone"). + Where(builder.In("name", opts.IncludeMilestones))) + } } func applyReposCondition(sess *xorm.Session, repoIDs []int64) *xorm.Session { diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 5932765ab..5a7d10b36 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -42,6 +42,10 @@ func SearchIssues(ctx *context.APIContext) { // in: query // description: comma separated list of labels. Fetch only issues that have any of this labels. Non existent labels are discarded // type: string + // - name: milestones + // in: query + // description: comma separated list of milestone names. Fetch only issues that have any of this milestones. Non existent are discarded + // type: string // - name: q // in: query // description: search string @@ -164,6 +168,12 @@ func SearchIssues(ctx *context.APIContext) { includedLabelNames = strings.Split(labels, ",") } + milestones := strings.TrimSpace(ctx.Query("milestones")) + var includedMilestones []string + if len(milestones) > 0 { + includedMilestones = strings.Split(milestones, ",") + } + // this api is also used in UI, // so the default limit is set to fit UI needs limit := ctx.QueryInt("limit") @@ -175,7 +185,7 @@ func SearchIssues(ctx *context.APIContext) { // Only fetch the issues if we either don't have a keyword or the search returned issues // This would otherwise return all issues if no issues were found by the search. - if len(keyword) == 0 || len(issueIDs) > 0 || len(includedLabelNames) > 0 { + if len(keyword) == 0 || len(issueIDs) > 0 || len(includedLabelNames) > 0 || len(includedMilestones) > 0 { issuesOpt := &models.IssuesOptions{ ListOptions: models.ListOptions{ Page: ctx.QueryInt("page"), @@ -185,6 +195,7 @@ func SearchIssues(ctx *context.APIContext) { IsClosed: isClosed, IssueIDs: issueIDs, IncludedLabelNames: includedLabelNames, + IncludeMilestones: includedMilestones, SortType: "priorityrepo", PriorityRepoID: ctx.QueryInt64("priority_repo_id"), IsPull: isPull, diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 8ea5edb6f..4f54b9049 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -1876,6 +1876,12 @@ "name": "labels", "in": "query" }, + { + "type": "string", + "description": "comma separated list of milestone names. Fetch only issues that have any of this milestones. Non existent are discarded", + "name": "milestones", + "in": "query" + }, { "type": "string", "description": "search string",