Add name filter to API for GetMilestoneList (#12336)

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/149
release/v1.15
6543 2020-07-28 13:30:40 +02:00 committed by GitHub
parent 78cbd0ca72
commit 8bdc9795d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 115 additions and 36 deletions

View File

@ -55,6 +55,18 @@ func TestAPIIssuesMilestone(t *testing.T) {
assert.Equal(t, "wow", apiMilestone.Title) assert.Equal(t, "wow", apiMilestone.Title)
assert.Equal(t, structs.StateClosed, apiMilestone.State) 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)) 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) resp = session.MakeRequest(t, req, http.StatusNoContent)
} }

View File

@ -330,41 +330,38 @@ func (milestones MilestoneList) getMilestoneIDs() []int64 {
return ids return ids
} }
// GetMilestonesByRepoID returns all opened milestones of a repository. // GetMilestonesOption contain options to get milestones
func GetMilestonesByRepoID(repoID int64, state api.StateType, listOptions ListOptions) (MilestoneList, error) { type GetMilestonesOption struct {
sess := x.Where("repo_id = ?", repoID) ListOptions
RepoID int64
State api.StateType
Name string
SortType string
}
switch state { // GetMilestones returns milestones filtered by GetMilestonesOption's
func GetMilestones(opts GetMilestonesOption) (MilestoneList, error) {
sess := x.Where("repo_id = ?", opts.RepoID)
switch opts.State {
case api.StateClosed: case api.StateClosed:
sess = sess.And("is_closed = ?", true) sess = sess.And("is_closed = ?", true)
case api.StateAll: case api.StateAll:
break break
// api.StateOpen:
case api.StateOpen:
fallthrough
default: default:
sess = sess.And("is_closed = ?", false) sess = sess.And("is_closed = ?", false)
} }
if listOptions.Page != 0 { if len(opts.Name) != 0 {
sess = listOptions.setSessionPagination(sess) sess = sess.And(builder.Like{"name", opts.Name})
} }
miles := make([]*Milestone, 0, listOptions.PageSize) if opts.Page != 0 {
return miles, sess.Asc("deadline_unix").Asc("id").Find(&miles) sess = opts.setSessionPagination(sess)
} }
// GetMilestones returns a list of milestones of given repository and status. switch opts.SortType {
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)
}
switch sortType {
case "furthestduedate": case "furthestduedate":
sess.Desc("deadline_unix") sess.Desc("deadline_unix")
case "leastcomplete": case "leastcomplete":
@ -375,9 +372,13 @@ func GetMilestones(repoID int64, page int, isClosed bool, sortType string) (Mile
sess.Asc("num_issues") sess.Asc("num_issues")
case "mostissues": case "mostissues":
sess.Desc("num_issues") sess.Desc("num_issues")
case "id":
sess.Asc("id")
default: default:
sess.Asc("deadline_unix") sess.Asc("deadline_unix").Asc("id")
} }
miles := make([]*Milestone, 0, opts.PageSize)
return miles, sess.Find(&miles) return miles, sess.Find(&miles)
} }

View File

@ -8,6 +8,7 @@ import (
"sort" "sort"
"testing" "testing"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
@ -49,7 +50,10 @@ func TestGetMilestonesByRepoID(t *testing.T) {
assert.NoError(t, PrepareTestDatabase()) assert.NoError(t, PrepareTestDatabase())
test := func(repoID int64, state api.StateType) { test := func(repoID int64, state api.StateType) {
repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) 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) assert.NoError(t, err)
var n int var n int
@ -83,7 +87,10 @@ func TestGetMilestonesByRepoID(t *testing.T) {
test(3, api.StateClosed) test(3, api.StateClosed)
test(3, api.StateAll) 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.NoError(t, err)
assert.Len(t, milestones, 0) assert.Len(t, milestones, 0)
} }
@ -93,7 +100,15 @@ func TestGetMilestones(t *testing.T) {
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
test := func(sortType string, sortCond func(*Milestone) int) { test := func(sortType string, sortCond func(*Milestone) int) {
for _, page := range []int{0, 1} { 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.NoError(t, err)
assert.Len(t, milestones, repo.NumMilestones-repo.NumClosedMilestones) assert.Len(t, milestones, repo.NumMilestones-repo.NumClosedMilestones)
values := make([]int, len(milestones)) values := make([]int, len(milestones))
@ -102,7 +117,16 @@ func TestGetMilestones(t *testing.T) {
} }
assert.True(t, sort.IntsAreSorted(values)) 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.NoError(t, err)
assert.Len(t, milestones, repo.NumClosedMilestones) assert.Len(t, milestones, repo.NumClosedMilestones)
values = make([]int, len(milestones)) values = make([]int, len(milestones))

View File

@ -51,11 +51,17 @@ func TestGiteaUploadRepo(t *testing.T) {
repo := models.AssertExistsAndLoadBean(t, &models.Repository{OwnerID: user.ID, Name: repoName}).(*models.Repository) repo := models.AssertExistsAndLoadBean(t, &models.Repository{OwnerID: user.ID, Name: repoName}).(*models.Repository)
assert.True(t, repo.HasWiki()) 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.NoError(t, err)
assert.EqualValues(t, 1, len(milestones)) 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.NoError(t, err)
assert.EqualValues(t, 0, len(milestones)) assert.EqualValues(t, 0, len(milestones))

View File

@ -39,6 +39,10 @@ func ListMilestones(ctx *context.APIContext) {
// in: query // in: query
// description: Milestone state, Recognised values are open, closed and all. Defaults to "open" // description: Milestone state, Recognised values are open, closed and all. Defaults to "open"
// type: string // type: string
// - name: name
// in: query
// description: filter by milestone name
// type: string
// - name: page // - name: page
// in: query // in: query
// description: page number of results to return (1-based) // description: page number of results to return (1-based)
@ -51,9 +55,14 @@ func ListMilestones(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/MilestoneList" // "$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 { if err != nil {
ctx.Error(http.StatusInternalServerError, "GetMilestonesByRepoID", err) ctx.Error(http.StatusInternalServerError, "GetMilestones", err)
return return
} }

View File

@ -360,8 +360,11 @@ func Issues(ctx *context.Context) {
issues(ctx, ctx.QueryInt64("milestone"), util.OptionalBoolOf(isPullList)) issues(ctx, ctx.QueryInt64("milestone"), util.OptionalBoolOf(isPullList))
var err error var err error
// Get milestones. // Get milestones
ctx.Data["Milestones"], err = models.GetMilestonesByRepoID(ctx.Repo.Repository.ID, api.StateType(ctx.Query("state")), models.ListOptions{}) ctx.Data["Milestones"], err = models.GetMilestones(models.GetMilestonesOption{
RepoID: ctx.Repo.Repository.ID,
State: api.StateType(ctx.Query("state")),
})
if err != nil { if err != nil {
ctx.ServerError("GetAllRepoMilestones", err) ctx.ServerError("GetAllRepoMilestones", err)
return return
@ -375,12 +378,18 @@ func Issues(ctx *context.Context) {
// RetrieveRepoMilestonesAndAssignees find all the milestones and assignees of a repository // RetrieveRepoMilestonesAndAssignees find all the milestones and assignees of a repository
func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *models.Repository) { func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *models.Repository) {
var err error 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 { if err != nil {
ctx.ServerError("GetMilestones", err) ctx.ServerError("GetMilestones", err)
return 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 { if err != nil {
ctx.ServerError("GetMilestones", err) ctx.ServerError("GetMilestones", err)
return return

View File

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -47,13 +48,24 @@ func Milestones(ctx *context.Context) {
} }
var total int var total int
var state structs.StateType
if !isShowClosed { if !isShowClosed {
total = int(stats.OpenCount) total = int(stats.OpenCount)
state = structs.StateOpen
} else { } else {
total = int(stats.ClosedCount) 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 { if err != nil {
ctx.ServerError("GetMilestones", err) ctx.ServerError("GetMilestones", err)
return return

View File

@ -6164,6 +6164,12 @@
"name": "state", "name": "state",
"in": "query" "in": "query"
}, },
{
"type": "string",
"description": "filter by milestone name",
"name": "name",
"in": "query"
},
{ {
"type": "integer", "type": "integer",
"description": "page number of results to return (1-based)", "description": "page number of results to return (1-based)",