Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[API] ListIssues add more filters #16174

Merged
merged 8 commits into from
Jun 16, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 26 additions & 6 deletions integrations/api_issue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ func TestAPIListIssues(t *testing.T) {

session := loginUser(t, owner.Name)
token := getTokenForLoggedInUser(t, session)
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues?state=all&token=%s",
owner.Name, repo.Name, token)
resp := session.MakeRequest(t, req, http.StatusOK)
link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner.Name, repo.Name))

link.RawQuery = url.Values{"token": {token}, "state": {"all"}}.Encode()
resp := session.MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
var apiIssues []*api.Issue
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, models.GetCount(t, &models.Issue{RepoID: repo.ID}))
Expand All @@ -36,15 +37,34 @@ func TestAPIListIssues(t *testing.T) {
}

// test milestone filter
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues?state=all&type=all&milestones=ignore,milestone1,3,4&token=%s",
owner.Name, repo.Name, token)
resp = session.MakeRequest(t, req, http.StatusOK)
link.RawQuery = url.Values{"token": {token}, "state": {"all"}, "type": {"all"}, "milestones": {"ignore,milestone1,3,4"}}.Encode()
resp = session.MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
if assert.Len(t, apiIssues, 2) {
assert.EqualValues(t, 3, apiIssues[0].Milestone.ID)
assert.EqualValues(t, 1, apiIssues[1].Milestone.ID)
}

link.RawQuery = url.Values{"token": {token}, "state": {"all"}, "created_by": {"user2"}}.Encode()
resp = session.MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
if assert.Len(t, apiIssues, 1) {
assert.EqualValues(t, 5, apiIssues[0].ID)
}

link.RawQuery = url.Values{"token": {token}, "state": {"all"}, "assigned_by": {"user1"}}.Encode()
resp = session.MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
if assert.Len(t, apiIssues, 1) {
assert.EqualValues(t, 1, apiIssues[0].ID)
}

link.RawQuery = url.Values{"token": {token}, "state": {"all"}, "mentioned_by": {"user4"}}.Encode()
resp = session.MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
if assert.Len(t, apiIssues, 1) {
assert.EqualValues(t, 1, apiIssues[0].ID)
}
}

func TestAPICreateIssue(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion models/fixtures/issue_user.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@
uid: 4
issue_id: 1
is_read: false
is_mentioned: false
is_mentioned: true
82 changes: 74 additions & 8 deletions routers/api/v1/repo/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,30 @@ func ListIssues(ctx *context.APIContext) {
// in: query
// 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
// type: string
// - name: since
// in: query
// description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format
// type: string
// format: date-time
// required: false
// - name: before
// in: query
// description: Only show notifications updated before the given time. This is a timestamp in RFC 3339 format
// type: string
// format: date-time
// required: false
// - name: created_by
// in: query
// description: filter (issues / pulls) created to
// type: string
// - name: assigned_by
// in: query
// description: filter (issues / pulls) assigned to
// type: string
// - name: mentioned_by
// in: query
// description: filter (issues / pulls) mentioning to
// type: string
// - name: page
// in: query
// description: page number of results to return (1-based)
Expand All @@ -277,6 +301,11 @@ func ListIssues(ctx *context.APIContext) {
// responses:
// "200":
// "$ref": "#/responses/IssueList"
before, since, err := utils.GetQueryBeforeSince(ctx)
if err != nil {
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
return
}

var isClosed util.OptionalBool
switch ctx.Query("state") {
Expand All @@ -297,7 +326,6 @@ func ListIssues(ctx *context.APIContext) {
}
var issueIDs []int64
var labelIDs []int64
var err error
if len(keyword) > 0 {
issueIDs, err = issue_indexer.SearchIssuesByKeyword([]int64{ctx.Repo.Repository.ID}, keyword)
if err != nil {
Expand Down Expand Up @@ -356,17 +384,35 @@ func ListIssues(ctx *context.APIContext) {
isPull = util.OptionalBoolNone
}

createdByID := getUserIDForFilter(ctx, "created_by")
6543 marked this conversation as resolved.
Show resolved Hide resolved
if ctx.Written() {
return
}
assignedByID := getUserIDForFilter(ctx, "assigned_by")
if ctx.Written() {
return
}
mentionedByID := getUserIDForFilter(ctx, "mentioned_by")
if ctx.Written() {
return
}
6543 marked this conversation as resolved.
Show resolved Hide resolved

// Only fetch the issues if we either don't have a keyword or the search returned issues
// This would otherwise return all issues if no issues were found by the search.
if len(keyword) == 0 || len(issueIDs) > 0 || len(labelIDs) > 0 {
issuesOpt := &models.IssuesOptions{
ListOptions: listOptions,
RepoIDs: []int64{ctx.Repo.Repository.ID},
IsClosed: isClosed,
IssueIDs: issueIDs,
LabelIDs: labelIDs,
MilestoneIDs: mileIDs,
IsPull: isPull,
ListOptions: listOptions,
RepoIDs: []int64{ctx.Repo.Repository.ID},
IsClosed: isClosed,
IssueIDs: issueIDs,
LabelIDs: labelIDs,
MilestoneIDs: mileIDs,
IsPull: isPull,
UpdatedBeforeUnix: before,
UpdatedAfterUnix: since,
PosterID: createdByID,
AssigneeID: assignedByID,
MentionedID: mentionedByID,
}

if issues, err = models.Issues(issuesOpt); err != nil {
Expand All @@ -389,6 +435,26 @@ func ListIssues(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(issues))
}

func getUserIDForFilter(ctx *context.APIContext, queryName string) int64 {
userName := ctx.Query(queryName)
if len(userName) == 0 {
return 0
}

user, err := models.GetUserByName(userName)
if models.IsErrUserNotExist(err) {
ctx.NotFound(err)
return 0
}

if err != nil {
ctx.InternalServerError(err)
return 0
}

return user.ID
}

// GetIssue get an issue of a repository
func GetIssue(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/issues/{index} issue issueGetIssue
Expand Down
32 changes: 32 additions & 0 deletions templates/swagger/v1_json.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -4218,6 +4218,38 @@
"name": "milestones",
"in": "query"
},
{
"type": "string",
"format": "date-time",
"description": "Only show notifications updated after the given time. This is a timestamp in RFC 3339 format",
"name": "since",
"in": "query"
},
{
"type": "string",
"format": "date-time",
"description": "Only show notifications updated before the given time. This is a timestamp in RFC 3339 format",
"name": "before",
"in": "query"
},
{
"type": "string",
"description": "filter (issues / pulls) created to",
"name": "created_by",
"in": "query"
},
{
"type": "string",
"description": "filter (issues / pulls) assigned to",
"name": "assigned_by",
"in": "query"
},
{
"type": "string",
"description": "filter (issues / pulls) mentioning to",
"name": "mentioned_by",
"in": "query"
},
{
"type": "integer",
"description": "page number of results to return (1-based)",
Expand Down