Adds a name filter to the API for GetMilestoneList Includes a small refactor: merge GetMilestones and GetMilestonesByRepoID Close #12260 Needed for https://gitea.com/gitea/go-sdk/issues/383 and https://gitea.com/gitea/tea/pulls/149tags/v1.13.0-rc1
@@ -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) | |||
} |
@@ -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) | |||
} | |||
@@ -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)) | |||
@@ -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)) | |||
@@ -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 | |||
} | |||
@@ -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 | |||
@@ -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 | |||
@@ -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)", | |||