mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	[API] ListIssues add more filters (#16174)
* [API] ListIssues add more filters: optional filter repo issues by: - since - before - created_by - assigned_by - mentioned_by * Add Tests * Update routers/api/v1/repo/issue.go Co-authored-by: Lanre Adelowo <adelowomailbox@gmail.com> * Apply suggestions from code review Co-authored-by: Lanre Adelowo <adelowomailbox@gmail.com> Co-authored-by: techknowlogick <techknowlogick@gitea.io>
This commit is contained in:
		@@ -25,9 +25,10 @@ func TestAPIListIssues(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	session := loginUser(t, owner.Name)
 | 
						session := loginUser(t, owner.Name)
 | 
				
			||||||
	token := getTokenForLoggedInUser(t, session)
 | 
						token := getTokenForLoggedInUser(t, session)
 | 
				
			||||||
	req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues?state=all&token=%s",
 | 
						link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner.Name, repo.Name))
 | 
				
			||||||
		owner.Name, repo.Name, token)
 | 
					
 | 
				
			||||||
	resp := session.MakeRequest(t, req, http.StatusOK)
 | 
						link.RawQuery = url.Values{"token": {token}, "state": {"all"}}.Encode()
 | 
				
			||||||
 | 
						resp := session.MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
 | 
				
			||||||
	var apiIssues []*api.Issue
 | 
						var apiIssues []*api.Issue
 | 
				
			||||||
	DecodeJSON(t, resp, &apiIssues)
 | 
						DecodeJSON(t, resp, &apiIssues)
 | 
				
			||||||
	assert.Len(t, apiIssues, models.GetCount(t, &models.Issue{RepoID: repo.ID}))
 | 
						assert.Len(t, apiIssues, models.GetCount(t, &models.Issue{RepoID: repo.ID}))
 | 
				
			||||||
@@ -36,15 +37,34 @@ func TestAPIListIssues(t *testing.T) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// test milestone filter
 | 
						// test milestone filter
 | 
				
			||||||
	req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues?state=all&type=all&milestones=ignore,milestone1,3,4&token=%s",
 | 
						link.RawQuery = url.Values{"token": {token}, "state": {"all"}, "type": {"all"}, "milestones": {"ignore,milestone1,3,4"}}.Encode()
 | 
				
			||||||
		owner.Name, repo.Name, token)
 | 
						resp = session.MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
 | 
				
			||||||
	resp = session.MakeRequest(t, req, http.StatusOK)
 | 
					 | 
				
			||||||
	DecodeJSON(t, resp, &apiIssues)
 | 
						DecodeJSON(t, resp, &apiIssues)
 | 
				
			||||||
	if assert.Len(t, apiIssues, 2) {
 | 
						if assert.Len(t, apiIssues, 2) {
 | 
				
			||||||
		assert.EqualValues(t, 3, apiIssues[0].Milestone.ID)
 | 
							assert.EqualValues(t, 3, apiIssues[0].Milestone.ID)
 | 
				
			||||||
		assert.EqualValues(t, 1, apiIssues[1].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) {
 | 
					func TestAPICreateIssue(t *testing.T) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,4 +17,4 @@
 | 
				
			|||||||
  uid: 4
 | 
					  uid: 4
 | 
				
			||||||
  issue_id: 1
 | 
					  issue_id: 1
 | 
				
			||||||
  is_read: false
 | 
					  is_read: false
 | 
				
			||||||
  is_mentioned: false
 | 
					  is_mentioned: true
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -266,6 +266,30 @@ func ListIssues(ctx *context.APIContext) {
 | 
				
			|||||||
	//   in: query
 | 
						//   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
 | 
						//   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
 | 
						//   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
 | 
						// - name: page
 | 
				
			||||||
	//   in: query
 | 
						//   in: query
 | 
				
			||||||
	//   description: page number of results to return (1-based)
 | 
						//   description: page number of results to return (1-based)
 | 
				
			||||||
@@ -277,6 +301,11 @@ func ListIssues(ctx *context.APIContext) {
 | 
				
			|||||||
	// responses:
 | 
						// responses:
 | 
				
			||||||
	//   "200":
 | 
						//   "200":
 | 
				
			||||||
	//     "$ref": "#/responses/IssueList"
 | 
						//     "$ref": "#/responses/IssueList"
 | 
				
			||||||
 | 
						before, since, err := utils.GetQueryBeforeSince(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var isClosed util.OptionalBool
 | 
						var isClosed util.OptionalBool
 | 
				
			||||||
	switch ctx.Query("state") {
 | 
						switch ctx.Query("state") {
 | 
				
			||||||
@@ -297,7 +326,6 @@ func ListIssues(ctx *context.APIContext) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	var issueIDs []int64
 | 
						var issueIDs []int64
 | 
				
			||||||
	var labelIDs []int64
 | 
						var labelIDs []int64
 | 
				
			||||||
	var err error
 | 
					 | 
				
			||||||
	if len(keyword) > 0 {
 | 
						if len(keyword) > 0 {
 | 
				
			||||||
		issueIDs, err = issue_indexer.SearchIssuesByKeyword([]int64{ctx.Repo.Repository.ID}, keyword)
 | 
							issueIDs, err = issue_indexer.SearchIssuesByKeyword([]int64{ctx.Repo.Repository.ID}, keyword)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
@@ -356,6 +384,20 @@ func ListIssues(ctx *context.APIContext) {
 | 
				
			|||||||
		isPull = util.OptionalBoolNone
 | 
							isPull = util.OptionalBoolNone
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// FIXME: we should be more efficient here
 | 
				
			||||||
 | 
						createdByID := getUserIDForFilter(ctx, "created_by")
 | 
				
			||||||
 | 
						if ctx.Written() {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						assignedByID := getUserIDForFilter(ctx, "assigned_by")
 | 
				
			||||||
 | 
						if ctx.Written() {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						mentionedByID := getUserIDForFilter(ctx, "mentioned_by")
 | 
				
			||||||
 | 
						if ctx.Written() {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Only fetch the issues if we either don't have a keyword or the search returned issues
 | 
						// 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.
 | 
						// This would otherwise return all issues if no issues were found by the search.
 | 
				
			||||||
	if len(keyword) == 0 || len(issueIDs) > 0 || len(labelIDs) > 0 {
 | 
						if len(keyword) == 0 || len(issueIDs) > 0 || len(labelIDs) > 0 {
 | 
				
			||||||
@@ -367,6 +409,11 @@ func ListIssues(ctx *context.APIContext) {
 | 
				
			|||||||
			LabelIDs:          labelIDs,
 | 
								LabelIDs:          labelIDs,
 | 
				
			||||||
			MilestoneIDs:      mileIDs,
 | 
								MilestoneIDs:      mileIDs,
 | 
				
			||||||
			IsPull:            isPull,
 | 
								IsPull:            isPull,
 | 
				
			||||||
 | 
								UpdatedBeforeUnix: before,
 | 
				
			||||||
 | 
								UpdatedAfterUnix:  since,
 | 
				
			||||||
 | 
								PosterID:          createdByID,
 | 
				
			||||||
 | 
								AssigneeID:        assignedByID,
 | 
				
			||||||
 | 
								MentionedID:       mentionedByID,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if issues, err = models.Issues(issuesOpt); err != nil {
 | 
							if issues, err = models.Issues(issuesOpt); err != nil {
 | 
				
			||||||
@@ -389,6 +436,26 @@ func ListIssues(ctx *context.APIContext) {
 | 
				
			|||||||
	ctx.JSON(http.StatusOK, convert.ToAPIIssueList(issues))
 | 
						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
 | 
					// GetIssue get an issue of a repository
 | 
				
			||||||
func GetIssue(ctx *context.APIContext) {
 | 
					func GetIssue(ctx *context.APIContext) {
 | 
				
			||||||
	// swagger:operation GET /repos/{owner}/{repo}/issues/{index} issue issueGetIssue
 | 
						// swagger:operation GET /repos/{owner}/{repo}/issues/{index} issue issueGetIssue
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4234,6 +4234,38 @@
 | 
				
			|||||||
            "name": "milestones",
 | 
					            "name": "milestones",
 | 
				
			||||||
            "in": "query"
 | 
					            "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",
 | 
					            "type": "integer",
 | 
				
			||||||
            "description": "page number of results to return (1-based)",
 | 
					            "description": "page number of results to return (1-based)",
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user