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