Skip to content

Commit bfda0f3

Browse files
authored
[API] ListIssues add filter for milestones (#10148)
* Refactor Issue Filter Func * ListIssues add filter for milestones * as per @lafriks * documentation ...
1 parent cbf5dff commit bfda0f3

File tree

10 files changed

+102
-29
lines changed

10 files changed

+102
-29
lines changed

integrations/api_issue_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@ func TestAPIListIssues(t *testing.T) {
3333
for _, apiIssue := range apiIssues {
3434
models.AssertExistsAndLoadBean(t, &models.Issue{ID: apiIssue.ID, RepoID: repo.ID})
3535
}
36+
37+
// test milestone filter
38+
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues?state=all&type=all&milestones=ignore,milestone1,3,4&token=%s",
39+
owner.Name, repo.Name, token)
40+
resp = session.MakeRequest(t, req, http.StatusOK)
41+
DecodeJSON(t, resp, &apiIssues)
42+
if assert.Len(t, apiIssues, 2) {
43+
assert.EqualValues(t, 3, apiIssues[0].Milestone.ID)
44+
assert.EqualValues(t, 1, apiIssues[1].Milestone.ID)
45+
}
46+
3647
}
3748

3849
func TestAPICreateIssue(t *testing.T) {

models/error.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,6 +1560,7 @@ func (err ErrLabelNotExist) Error() string {
15601560
type ErrMilestoneNotExist struct {
15611561
ID int64
15621562
RepoID int64
1563+
Name string
15631564
}
15641565

15651566
// IsErrMilestoneNotExist checks if an error is a ErrMilestoneNotExist.
@@ -1569,6 +1570,9 @@ func IsErrMilestoneNotExist(err error) bool {
15691570
}
15701571

15711572
func (err ErrMilestoneNotExist) Error() string {
1573+
if len(err.Name) > 0 {
1574+
return fmt.Sprintf("milestone does not exist [name: %s, repo_id: %d]", err.Name, err.RepoID)
1575+
}
15721576
return fmt.Sprintf("milestone does not exist [id: %d, repo_id: %d]", err.ID, err.RepoID)
15731577
}
15741578

models/fixtures/issue.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
poster_id: 1
3333
name: issue3
3434
content: content for the third issue
35+
milestone_id: 3
3536
is_closed: false
3637
is_pull: true
3738
created_unix: 946684820

models/fixtures/milestone.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
name: milestone3
2121
content: content3
2222
is_closed: true
23-
num_issues: 0
23+
num_issues: 1
2424

2525
-
2626
id: 4

models/issue.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,7 +1058,7 @@ type IssuesOptions struct {
10581058
AssigneeID int64
10591059
PosterID int64
10601060
MentionedID int64
1061-
MilestoneID int64
1061+
MilestoneIDs []int64
10621062
IsClosed util.OptionalBool
10631063
IsPull util.OptionalBool
10641064
LabelIDs []int64
@@ -1143,8 +1143,8 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) {
11431143
And("issue_user.uid = ?", opts.MentionedID)
11441144
}
11451145

1146-
if opts.MilestoneID > 0 {
1147-
sess.And("issue.milestone_id=?", opts.MilestoneID)
1146+
if len(opts.MilestoneIDs) > 0 {
1147+
sess.In("issue.milestone_id", opts.MilestoneIDs)
11481148
}
11491149

11501150
switch opts.IsPull {

models/issue_milestone.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,15 +109,12 @@ func NewMilestone(m *Milestone) (err error) {
109109
}
110110

111111
func getMilestoneByRepoID(e Engine, repoID, id int64) (*Milestone, error) {
112-
m := &Milestone{
113-
ID: id,
114-
RepoID: repoID,
115-
}
116-
has, err := e.Get(m)
112+
m := new(Milestone)
113+
has, err := e.ID(id).Where("repo_id=?", repoID).Get(m)
117114
if err != nil {
118115
return nil, err
119116
} else if !has {
120-
return nil, ErrMilestoneNotExist{id, repoID}
117+
return nil, ErrMilestoneNotExist{ID: id, RepoID: repoID}
121118
}
122119
return m, nil
123120
}
@@ -127,14 +124,27 @@ func GetMilestoneByRepoID(repoID, id int64) (*Milestone, error) {
127124
return getMilestoneByRepoID(x, repoID, id)
128125
}
129126

127+
// GetMilestoneByRepoIDANDName return a milestone if one exist by name and repo
128+
func GetMilestoneByRepoIDANDName(repoID int64, name string) (*Milestone, error) {
129+
var mile Milestone
130+
has, err := x.Where("repo_id=? AND name=?", repoID, name).Get(&mile)
131+
if err != nil {
132+
return nil, err
133+
}
134+
if !has {
135+
return nil, ErrMilestoneNotExist{Name: name, RepoID: repoID}
136+
}
137+
return &mile, nil
138+
}
139+
130140
// GetMilestoneByID returns the milestone via id .
131141
func GetMilestoneByID(id int64) (*Milestone, error) {
132142
var m Milestone
133143
has, err := x.ID(id).Get(&m)
134144
if err != nil {
135145
return nil, err
136146
} else if !has {
137-
return nil, ErrMilestoneNotExist{id, 0}
147+
return nil, ErrMilestoneNotExist{ID: id, RepoID: 0}
138148
}
139149
return &m, nil
140150
}

models/issue_milestone_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,14 +240,14 @@ func TestUpdateMilestoneClosedNum(t *testing.T) {
240240

241241
issue.IsClosed = true
242242
issue.ClosedUnix = timeutil.TimeStampNow()
243-
_, err := x.Cols("is_closed", "closed_unix").Update(issue)
243+
_, err := x.ID(issue.ID).Cols("is_closed", "closed_unix").Update(issue)
244244
assert.NoError(t, err)
245245
assert.NoError(t, updateMilestoneClosedNum(x, issue.MilestoneID))
246246
CheckConsistencyFor(t, &Milestone{})
247247

248248
issue.IsClosed = false
249249
issue.ClosedUnix = 0
250-
_, err = x.Cols("is_closed", "closed_unix").Update(issue)
250+
_, err = x.ID(issue.ID).Cols("is_closed", "closed_unix").Update(issue)
251251
assert.NoError(t, err)
252252
assert.NoError(t, updateMilestoneClosedNum(x, issue.MilestoneID))
253253
CheckConsistencyFor(t, &Milestone{})

routers/api/v1/repo/issue.go

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package repo
88
import (
99
"fmt"
1010
"net/http"
11+
"strconv"
1112
"strings"
1213
"time"
1314

@@ -208,6 +209,10 @@ func ListIssues(ctx *context.APIContext) {
208209
// in: query
209210
// description: filter by type (issues / pulls) if set
210211
// type: string
212+
// - name: milestones
213+
// in: query
214+
// description: comma separated list of milestone names or ids. It uses names and fall back to ids. Fetch only issues that have any of this milestones. Non existent milestones are discarded
215+
// type: string
211216
// - name: page
212217
// in: query
213218
// description: page number of results to return (1-based)
@@ -251,6 +256,36 @@ func ListIssues(ctx *context.APIContext) {
251256
}
252257
}
253258

259+
var mileIDs []int64
260+
if part := strings.Split(ctx.Query("milestones"), ","); len(part) > 0 {
261+
for i := range part {
262+
// uses names and fall back to ids
263+
// non existent milestones are discarded
264+
mile, err := models.GetMilestoneByRepoIDANDName(ctx.Repo.Repository.ID, part[i])
265+
if err == nil {
266+
mileIDs = append(mileIDs, mile.ID)
267+
continue
268+
}
269+
if !models.IsErrMilestoneNotExist(err) {
270+
ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoIDANDName", err)
271+
return
272+
}
273+
id, err := strconv.ParseInt(part[i], 10, 64)
274+
if err != nil {
275+
continue
276+
}
277+
mile, err = models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, id)
278+
if err == nil {
279+
mileIDs = append(mileIDs, mile.ID)
280+
continue
281+
}
282+
if models.IsErrMilestoneNotExist(err) {
283+
continue
284+
}
285+
ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
286+
}
287+
}
288+
254289
listOptions := utils.GetListOptions(ctx)
255290
if ctx.QueryInt("limit") == 0 {
256291
listOptions.PageSize = setting.UI.IssuePagingNum
@@ -270,12 +305,13 @@ func ListIssues(ctx *context.APIContext) {
270305
// This would otherwise return all issues if no issues were found by the search.
271306
if len(keyword) == 0 || len(issueIDs) > 0 || len(labelIDs) > 0 {
272307
issues, err = models.Issues(&models.IssuesOptions{
273-
ListOptions: listOptions,
274-
RepoIDs: []int64{ctx.Repo.Repository.ID},
275-
IsClosed: isClosed,
276-
IssueIDs: issueIDs,
277-
LabelIDs: labelIDs,
278-
IsPull: isPull,
308+
ListOptions: listOptions,
309+
RepoIDs: []int64{ctx.Repo.Repository.ID},
310+
IsClosed: isClosed,
311+
IssueIDs: issueIDs,
312+
LabelIDs: labelIDs,
313+
MilestoneIDs: mileIDs,
314+
IsPull: isPull,
279315
})
280316
}
281317

routers/repo/issue.go

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,11 @@ func issues(ctx *context.Context, milestoneID int64, isPullOption util.OptionalB
190190
}
191191
pager := context.NewPagination(total, setting.UI.IssuePagingNum, page, 5)
192192

193+
var mileIDs []int64
194+
if milestoneID > 0 {
195+
mileIDs = []int64{milestoneID}
196+
}
197+
193198
var issues []*models.Issue
194199
if forceEmpty {
195200
issues = []*models.Issue{}
@@ -199,16 +204,16 @@ func issues(ctx *context.Context, milestoneID int64, isPullOption util.OptionalB
199204
Page: pager.Paginater.Current(),
200205
PageSize: setting.UI.IssuePagingNum,
201206
},
202-
RepoIDs: []int64{repo.ID},
203-
AssigneeID: assigneeID,
204-
PosterID: posterID,
205-
MentionedID: mentionedID,
206-
MilestoneID: milestoneID,
207-
IsClosed: util.OptionalBoolOf(isShowClosed),
208-
IsPull: isPullOption,
209-
LabelIDs: labelIDs,
210-
SortType: sortType,
211-
IssueIDs: issueIDs,
207+
RepoIDs: []int64{repo.ID},
208+
AssigneeID: assigneeID,
209+
PosterID: posterID,
210+
MentionedID: mentionedID,
211+
MilestoneIDs: mileIDs,
212+
IsClosed: util.OptionalBoolOf(isShowClosed),
213+
IsPull: isPullOption,
214+
LabelIDs: labelIDs,
215+
SortType: sortType,
216+
IssueIDs: issueIDs,
212217
})
213218
if err != nil {
214219
ctx.ServerError("Issues", err)

templates/swagger/v1_json.tmpl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3768,6 +3768,12 @@
37683768
"name": "type",
37693769
"in": "query"
37703770
},
3771+
{
3772+
"type": "string",
3773+
"description": "comma separated list of milestone names or ids. It uses names and fall back to ids. Fetch only issues that have any of this milestones. Non existent milestones are discarded",
3774+
"name": "milestones",
3775+
"in": "query"
3776+
},
37713777
{
37723778
"type": "integer",
37733779
"description": "page number of results to return (1-based)",

0 commit comments

Comments
 (0)