Skip to content

Commit f88a2ea

Browse files
authored
[API] Add more filters to issues search (#13514)
* Add time filter for issue search * Add limit option for paggination * Add Filter for: Created by User, Assigned to User, Mentioning User * update swagger * Add Tests for limit, before & since
1 parent 78204a7 commit f88a2ea

File tree

4 files changed

+130
-9
lines changed

4 files changed

+130
-9
lines changed

integrations/api_issue_test.go

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net/http"
1010
"net/url"
1111
"testing"
12+
"time"
1213

1314
"code.gitea.io/gitea/models"
1415
api "code.gitea.io/gitea/modules/structs"
@@ -152,17 +153,27 @@ func TestAPISearchIssues(t *testing.T) {
152153
resp := session.MakeRequest(t, req, http.StatusOK)
153154
var apiIssues []*api.Issue
154155
DecodeJSON(t, resp, &apiIssues)
155-
156156
assert.Len(t, apiIssues, 10)
157157

158-
query := url.Values{}
159-
query.Add("token", token)
158+
query := url.Values{"token": {token}}
160159
link.RawQuery = query.Encode()
161160
req = NewRequest(t, "GET", link.String())
162161
resp = session.MakeRequest(t, req, http.StatusOK)
163162
DecodeJSON(t, resp, &apiIssues)
164163
assert.Len(t, apiIssues, 10)
165164

165+
since := "2000-01-01T00%3A50%3A01%2B00%3A00" // 946687801
166+
before := time.Unix(999307200, 0).Format(time.RFC3339)
167+
query.Add("since", since)
168+
query.Add("before", before)
169+
link.RawQuery = query.Encode()
170+
req = NewRequest(t, "GET", link.String())
171+
resp = session.MakeRequest(t, req, http.StatusOK)
172+
DecodeJSON(t, resp, &apiIssues)
173+
assert.Len(t, apiIssues, 8)
174+
query.Del("since")
175+
query.Del("before")
176+
166177
query.Add("state", "closed")
167178
link.RawQuery = query.Encode()
168179
req = NewRequest(t, "GET", link.String())
@@ -175,14 +186,22 @@ func TestAPISearchIssues(t *testing.T) {
175186
req = NewRequest(t, "GET", link.String())
176187
resp = session.MakeRequest(t, req, http.StatusOK)
177188
DecodeJSON(t, resp, &apiIssues)
189+
assert.EqualValues(t, "12", resp.Header().Get("X-Total-Count"))
178190
assert.Len(t, apiIssues, 10) //there are more but 10 is page item limit
179191

180-
query.Add("page", "2")
192+
query.Add("limit", "20")
181193
link.RawQuery = query.Encode()
182194
req = NewRequest(t, "GET", link.String())
183195
resp = session.MakeRequest(t, req, http.StatusOK)
184196
DecodeJSON(t, resp, &apiIssues)
185-
assert.Len(t, apiIssues, 2)
197+
assert.Len(t, apiIssues, 12)
198+
199+
query = url.Values{"assigned": {"true"}, "state": {"all"}}
200+
link.RawQuery = query.Encode()
201+
req = NewRequest(t, "GET", link.String())
202+
resp = session.MakeRequest(t, req, http.StatusOK)
203+
DecodeJSON(t, resp, &apiIssues)
204+
assert.Len(t, apiIssues, 1)
186205
}
187206

188207
func TestAPISearchIssuesWithLabels(t *testing.T) {

models/issue.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,8 @@ type IssuesOptions struct {
11001100
ExcludedLabelNames []string
11011101
SortType string
11021102
IssueIDs []int64
1103+
UpdatedAfterUnix int64
1104+
UpdatedBeforeUnix int64
11031105
// prioritize issues from this repo
11041106
PriorityRepoID int64
11051107
}
@@ -1178,6 +1180,13 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) {
11781180
sess.In("issue.milestone_id", opts.MilestoneIDs)
11791181
}
11801182

1183+
if opts.UpdatedAfterUnix != 0 {
1184+
sess.And(builder.Gte{"issue.updated_unix": opts.UpdatedAfterUnix})
1185+
}
1186+
if opts.UpdatedBeforeUnix != 0 {
1187+
sess.And(builder.Lte{"issue.updated_unix": opts.UpdatedBeforeUnix})
1188+
}
1189+
11811190
if opts.ProjectID > 0 {
11821191
sess.Join("INNER", "project_issue", "issue.id = project_issue.issue_id").
11831192
And("project_issue.project_id=?", opts.ProjectID)

routers/api/v1/repo/issue.go

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,48 @@ func SearchIssues(ctx *context.APIContext) {
5555
// in: query
5656
// description: filter by type (issues / pulls) if set
5757
// type: string
58+
// - name: since
59+
// in: query
60+
// description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format
61+
// type: string
62+
// format: date-time
63+
// required: false
64+
// - name: before
65+
// in: query
66+
// description: Only show notifications updated before the given time. This is a timestamp in RFC 3339 format
67+
// type: string
68+
// format: date-time
69+
// required: false
70+
// - name: assigned
71+
// in: query
72+
// description: filter (issues / pulls) assigned to you, default is false
73+
// type: boolean
74+
// - name: created
75+
// in: query
76+
// description: filter (issues / pulls) created by you, default is false
77+
// type: boolean
78+
// - name: mentioned
79+
// in: query
80+
// description: filter (issues / pulls) mentioning you, default is false
81+
// type: boolean
5882
// - name: page
5983
// in: query
60-
// description: page number of requested issues
84+
// description: page number of results to return (1-based)
85+
// type: integer
86+
// - name: limit
87+
// in: query
88+
// description: page size of results
6189
// type: integer
6290
// responses:
6391
// "200":
6492
// "$ref": "#/responses/IssueList"
6593

94+
before, since, err := utils.GetQueryBeforeSince(ctx)
95+
if err != nil {
96+
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
97+
return
98+
}
99+
66100
var isClosed util.OptionalBool
67101
switch ctx.Query("state") {
68102
case "closed":
@@ -119,7 +153,6 @@ func SearchIssues(ctx *context.APIContext) {
119153
}
120154
var issueIDs []int64
121155
var labelIDs []int64
122-
var err error
123156
if len(keyword) > 0 && len(repoIDs) > 0 {
124157
if issueIDs, err = issue_indexer.SearchIssuesByKeyword(repoIDs, keyword); err != nil {
125158
ctx.Error(http.StatusInternalServerError, "SearchIssuesByKeyword", err)
@@ -143,13 +176,22 @@ func SearchIssues(ctx *context.APIContext) {
143176
includedLabelNames = strings.Split(labels, ",")
144177
}
145178

179+
// this api is also used in UI,
180+
// so the default limit is set to fit UI needs
181+
limit := ctx.QueryInt("limit")
182+
if limit == 0 {
183+
limit = setting.UI.IssuePagingNum
184+
} else if limit > setting.API.MaxResponseItems {
185+
limit = setting.API.MaxResponseItems
186+
}
187+
146188
// Only fetch the issues if we either don't have a keyword or the search returned issues
147189
// This would otherwise return all issues if no issues were found by the search.
148190
if len(keyword) == 0 || len(issueIDs) > 0 || len(labelIDs) > 0 {
149191
issuesOpt := &models.IssuesOptions{
150192
ListOptions: models.ListOptions{
151193
Page: ctx.QueryInt("page"),
152-
PageSize: setting.UI.IssuePagingNum,
194+
PageSize: limit,
153195
},
154196
RepoIDs: repoIDs,
155197
IsClosed: isClosed,
@@ -158,6 +200,19 @@ func SearchIssues(ctx *context.APIContext) {
158200
SortType: "priorityrepo",
159201
PriorityRepoID: ctx.QueryInt64("priority_repo_id"),
160202
IsPull: isPull,
203+
UpdatedBeforeUnix: before,
204+
UpdatedAfterUnix: since,
205+
}
206+
207+
// Filter for: Created by User, Assigned to User, Mentioning User
208+
if ctx.QueryBool("created") {
209+
issuesOpt.PosterID = ctx.User.ID
210+
}
211+
if ctx.QueryBool("assigned") {
212+
issuesOpt.AssigneeID = ctx.User.ID
213+
}
214+
if ctx.QueryBool("mentioned") {
215+
issuesOpt.MentionedID = ctx.User.ID
161216
}
162217

163218
if issues, err = models.Issues(issuesOpt); err != nil {

templates/swagger/v1_json.tmpl

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1879,11 +1879,49 @@
18791879
"name": "type",
18801880
"in": "query"
18811881
},
1882+
{
1883+
"type": "string",
1884+
"format": "date-time",
1885+
"description": "Only show notifications updated after the given time. This is a timestamp in RFC 3339 format",
1886+
"name": "since",
1887+
"in": "query"
1888+
},
1889+
{
1890+
"type": "string",
1891+
"format": "date-time",
1892+
"description": "Only show notifications updated before the given time. This is a timestamp in RFC 3339 format",
1893+
"name": "before",
1894+
"in": "query"
1895+
},
1896+
{
1897+
"type": "boolean",
1898+
"description": "filter (issues / pulls) assigned to you, default is false",
1899+
"name": "assigned",
1900+
"in": "query"
1901+
},
1902+
{
1903+
"type": "boolean",
1904+
"description": "filter (issues / pulls) created by you, default is false",
1905+
"name": "created",
1906+
"in": "query"
1907+
},
1908+
{
1909+
"type": "boolean",
1910+
"description": "filter (issues / pulls) mentioning you, default is false",
1911+
"name": "mentioned",
1912+
"in": "query"
1913+
},
18821914
{
18831915
"type": "integer",
1884-
"description": "page number of requested issues",
1916+
"description": "page number of results to return (1-based)",
18851917
"name": "page",
18861918
"in": "query"
1919+
},
1920+
{
1921+
"type": "integer",
1922+
"description": "page size of results",
1923+
"name": "limit",
1924+
"in": "query"
18871925
}
18881926
],
18891927
"responses": {

0 commit comments

Comments
 (0)