diff --git a/integrations/api_issue_milestone_test.go b/integrations/api_issue_milestone_test.go index f1f306b768c..4cc574b9ebc 100644 --- a/integrations/api_issue_milestone_test.go +++ b/integrations/api_issue_milestone_test.go @@ -55,6 +55,18 @@ func TestAPIIssuesMilestone(t *testing.T) { assert.Equal(t, "wow", apiMilestone.Title) assert.Equal(t, structs.StateClosed, apiMilestone.State) + var apiMilestones []structs.Milestone + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/milestones?state=%s&token=%s", owner.Name, repo.Name, "all", token)) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiMilestones) + assert.Len(t, apiMilestones, 4) + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/milestones?state=%s&name=%s&token=%s", owner.Name, repo.Name, "all", "milestone2", token)) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiMilestones) + assert.Len(t, apiMilestones, 1) + assert.Equal(t, int64(2), apiMilestones[0].ID) + req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/milestones/%d?token=%s", owner.Name, repo.Name, apiMilestone.ID, token)) resp = session.MakeRequest(t, req, http.StatusNoContent) } diff --git a/models/issue_milestone.go b/models/issue_milestone.go index 4648274459a..824b939a56d 100644 --- a/models/issue_milestone.go +++ b/models/issue_milestone.go @@ -330,41 +330,38 @@ func (milestones MilestoneList) getMilestoneIDs() []int64 { return ids } -// GetMilestonesByRepoID returns all opened milestones of a repository. -func GetMilestonesByRepoID(repoID int64, state api.StateType, listOptions ListOptions) (MilestoneList, error) { - sess := x.Where("repo_id = ?", repoID) +// GetMilestonesOption contain options to get milestones +type GetMilestonesOption struct { + ListOptions + RepoID int64 + State api.StateType + Name string + SortType string +} + +// GetMilestones returns milestones filtered by GetMilestonesOption's +func GetMilestones(opts GetMilestonesOption) (MilestoneList, error) { + sess := x.Where("repo_id = ?", opts.RepoID) - switch state { + switch opts.State { case api.StateClosed: sess = sess.And("is_closed = ?", true) - case api.StateAll: break - - case api.StateOpen: - fallthrough - + // api.StateOpen: default: sess = sess.And("is_closed = ?", false) } - if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) + if len(opts.Name) != 0 { + sess = sess.And(builder.Like{"name", opts.Name}) } - miles := make([]*Milestone, 0, listOptions.PageSize) - return miles, sess.Asc("deadline_unix").Asc("id").Find(&miles) -} - -// GetMilestones returns a list of milestones of given repository and status. -func GetMilestones(repoID int64, page int, isClosed bool, sortType string) (MilestoneList, error) { - miles := make([]*Milestone, 0, setting.UI.IssuePagingNum) - sess := x.Where("repo_id = ? AND is_closed = ?", repoID, isClosed) - if page > 0 { - sess = sess.Limit(setting.UI.IssuePagingNum, (page-1)*setting.UI.IssuePagingNum) + if opts.Page != 0 { + sess = opts.setSessionPagination(sess) } - switch sortType { + switch opts.SortType { case "furthestduedate": sess.Desc("deadline_unix") case "leastcomplete": @@ -375,9 +372,13 @@ func GetMilestones(repoID int64, page int, isClosed bool, sortType string) (Mile sess.Asc("num_issues") case "mostissues": sess.Desc("num_issues") + case "id": + sess.Asc("id") default: - sess.Asc("deadline_unix") + sess.Asc("deadline_unix").Asc("id") } + + miles := make([]*Milestone, 0, opts.PageSize) return miles, sess.Find(&miles) } diff --git a/models/issue_milestone_test.go b/models/issue_milestone_test.go index 07dd8d57c53..af264aa2745 100644 --- a/models/issue_milestone_test.go +++ b/models/issue_milestone_test.go @@ -8,6 +8,7 @@ import ( "sort" "testing" + "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" @@ -49,7 +50,10 @@ func TestGetMilestonesByRepoID(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) test := func(repoID int64, state api.StateType) { repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) - milestones, err := GetMilestonesByRepoID(repo.ID, state, ListOptions{}) + milestones, err := GetMilestones(GetMilestonesOption{ + RepoID: repo.ID, + State: state, + }) assert.NoError(t, err) var n int @@ -83,7 +87,10 @@ func TestGetMilestonesByRepoID(t *testing.T) { test(3, api.StateClosed) test(3, api.StateAll) - milestones, err := GetMilestonesByRepoID(NonexistentID, api.StateOpen, ListOptions{}) + milestones, err := GetMilestones(GetMilestonesOption{ + RepoID: NonexistentID, + State: api.StateOpen, + }) assert.NoError(t, err) assert.Len(t, milestones, 0) } @@ -93,7 +100,15 @@ func TestGetMilestones(t *testing.T) { repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) test := func(sortType string, sortCond func(*Milestone) int) { for _, page := range []int{0, 1} { - milestones, err := GetMilestones(repo.ID, page, false, sortType) + milestones, err := GetMilestones(GetMilestonesOption{ + ListOptions: ListOptions{ + Page: page, + PageSize: setting.UI.IssuePagingNum, + }, + RepoID: repo.ID, + State: api.StateOpen, + SortType: sortType, + }) assert.NoError(t, err) assert.Len(t, milestones, repo.NumMilestones-repo.NumClosedMilestones) values := make([]int, len(milestones)) @@ -102,7 +117,16 @@ func TestGetMilestones(t *testing.T) { } assert.True(t, sort.IntsAreSorted(values)) - milestones, err = GetMilestones(repo.ID, page, true, sortType) + milestones, err = GetMilestones(GetMilestonesOption{ + ListOptions: ListOptions{ + Page: page, + PageSize: setting.UI.IssuePagingNum, + }, + RepoID: repo.ID, + State: api.StateClosed, + Name: "", + SortType: sortType, + }) assert.NoError(t, err) assert.Len(t, milestones, repo.NumClosedMilestones) values = make([]int, len(milestones)) diff --git a/modules/migrations/gitea_test.go b/modules/migrations/gitea_test.go index 3f03cbab313..c0d2dcd1802 100644 --- a/modules/migrations/gitea_test.go +++ b/modules/migrations/gitea_test.go @@ -51,11 +51,17 @@ func TestGiteaUploadRepo(t *testing.T) { repo := models.AssertExistsAndLoadBean(t, &models.Repository{OwnerID: user.ID, Name: repoName}).(*models.Repository) assert.True(t, repo.HasWiki()) - milestones, err := models.GetMilestones(repo.ID, 0, false, "") + milestones, err := models.GetMilestones(models.GetMilestonesOption{ + RepoID: repo.ID, + State: structs.StateOpen, + }) assert.NoError(t, err) assert.EqualValues(t, 1, len(milestones)) - milestones, err = models.GetMilestones(repo.ID, 0, true, "") + milestones, err = models.GetMilestones(models.GetMilestonesOption{ + RepoID: repo.ID, + State: structs.StateClosed, + }) assert.NoError(t, err) assert.EqualValues(t, 0, len(milestones)) diff --git a/routers/api/v1/repo/milestone.go b/routers/api/v1/repo/milestone.go index adb9deaee4f..f6f6b29465b 100644 --- a/routers/api/v1/repo/milestone.go +++ b/routers/api/v1/repo/milestone.go @@ -39,6 +39,10 @@ func ListMilestones(ctx *context.APIContext) { // in: query // description: Milestone state, Recognised values are open, closed and all. Defaults to "open" // type: string + // - name: name + // in: query + // description: filter by milestone name + // type: string // - name: page // in: query // description: page number of results to return (1-based) @@ -51,9 +55,14 @@ func ListMilestones(ctx *context.APIContext) { // "200": // "$ref": "#/responses/MilestoneList" - milestones, err := models.GetMilestonesByRepoID(ctx.Repo.Repository.ID, api.StateType(ctx.Query("state")), utils.GetListOptions(ctx)) + milestones, err := models.GetMilestones(models.GetMilestonesOption{ + ListOptions: utils.GetListOptions(ctx), + RepoID: ctx.Repo.Repository.ID, + State: api.StateType(ctx.Query("state")), + Name: ctx.Query("name"), + }) if err != nil { - ctx.Error(http.StatusInternalServerError, "GetMilestonesByRepoID", err) + ctx.Error(http.StatusInternalServerError, "GetMilestones", err) return } diff --git a/routers/repo/issue.go b/routers/repo/issue.go index e3ba5692c49..b3af6705ca8 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -360,8 +360,11 @@ func Issues(ctx *context.Context) { issues(ctx, ctx.QueryInt64("milestone"), util.OptionalBoolOf(isPullList)) var err error - // Get milestones. - ctx.Data["Milestones"], err = models.GetMilestonesByRepoID(ctx.Repo.Repository.ID, api.StateType(ctx.Query("state")), models.ListOptions{}) + // Get milestones + ctx.Data["Milestones"], err = models.GetMilestones(models.GetMilestonesOption{ + RepoID: ctx.Repo.Repository.ID, + State: api.StateType(ctx.Query("state")), + }) if err != nil { ctx.ServerError("GetAllRepoMilestones", err) return @@ -375,12 +378,18 @@ func Issues(ctx *context.Context) { // RetrieveRepoMilestonesAndAssignees find all the milestones and assignees of a repository func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *models.Repository) { var err error - ctx.Data["OpenMilestones"], err = models.GetMilestones(repo.ID, -1, false, "") + ctx.Data["OpenMilestones"], err = models.GetMilestones(models.GetMilestonesOption{ + RepoID: repo.ID, + State: api.StateOpen, + }) if err != nil { ctx.ServerError("GetMilestones", err) return } - ctx.Data["ClosedMilestones"], err = models.GetMilestones(repo.ID, -1, true, "") + ctx.Data["ClosedMilestones"], err = models.GetMilestones(models.GetMilestonesOption{ + RepoID: repo.ID, + State: api.StateClosed, + }) if err != nil { ctx.ServerError("GetMilestones", err) return diff --git a/routers/repo/milestone.go b/routers/repo/milestone.go index 9d8f34268d3..0bd7344878f 100644 --- a/routers/repo/milestone.go +++ b/routers/repo/milestone.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -47,13 +48,24 @@ func Milestones(ctx *context.Context) { } var total int + var state structs.StateType if !isShowClosed { total = int(stats.OpenCount) + state = structs.StateOpen } else { total = int(stats.ClosedCount) + state = structs.StateClosed } - miles, err := models.GetMilestones(ctx.Repo.Repository.ID, page, isShowClosed, sortType) + miles, err := models.GetMilestones(models.GetMilestonesOption{ + ListOptions: models.ListOptions{ + Page: page, + PageSize: setting.UI.IssuePagingNum, + }, + RepoID: ctx.Repo.Repository.ID, + State: state, + SortType: sortType, + }) if err != nil { ctx.ServerError("GetMilestones", err) return diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 795e179cb99..28a33fb3d32 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -6164,6 +6164,12 @@ "name": "state", "in": "query" }, + { + "type": "string", + "description": "filter by milestone name", + "name": "name", + "in": "query" + }, { "type": "integer", "description": "page number of results to return (1-based)",