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
This commit is contained in:
parent
78cbd0ca72
commit
8bdc9795d8
8 changed files with 115 additions and 36 deletions
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
|
||||||
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 {
|
switch opts.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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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))
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)",
|
||||||
|
|
Loading…
Reference in a new issue