feat(api): add sort parameter to list issues API

- Add the `sort` parameter to the `/api/v1/{repo}/{owner}/issues` API
endpoint. Default behavior is preserved.
- Resolves forgejo/forgejo#4173
- Add (non-exhaustive) integration testing.
This commit is contained in:
Gusted 2025-03-12 17:50:56 +01:00
parent d53dfcbccb
commit 95a895955a
No known key found for this signature in database
GPG key ID: FD821B732837125F
4 changed files with 97 additions and 1 deletions

View file

@ -8,6 +8,7 @@ import (
"fmt"
"os"
"runtime/pprof"
"strings"
"sync/atomic"
"time"
@ -280,6 +281,33 @@ const (
SortByDeadlineAsc = internal.SortByDeadlineAsc
)
// ParseSortBy parses the `sortBy` string and returns the associated `SortBy`
// value, if one exists. Otherwise return `defaultSortBy`.
func ParseSortBy(sortBy string, defaultSortBy internal.SortBy) internal.SortBy {
switch strings.ToLower(sortBy) {
case "relevance":
return SortByScore
case "latest":
return SortByCreatedDesc
case "oldest":
return SortByCreatedAsc
case "recentupdate":
return SortByUpdatedDesc
case "leastupdate":
return SortByUpdatedAsc
case "mostcomment":
return SortByCommentsDesc
case "leastcomment":
return SortByCommentsAsc
case "nearduedate":
return SortByDeadlineAsc
case "farduedate":
return SortByDeadlineDesc
default:
return defaultSortBy
}
}
// SearchIssues search issues by options.
func SearchIssues(ctx context.Context, opts *SearchOptions) ([]int64, int64, error) {
indexer := *globalIndexer.Load()

View file

@ -397,6 +397,12 @@ func ListIssues(ctx *context.APIContext) {
// in: query
// description: page size of results
// type: integer
// - name: sort
// in: query
// description: Type of sort
// type: string
// enum: [relevance, latest, oldest, recentupdate, leastupdate, mostcomment, leastcomment, nearduedate, farduedate]
// default: latest
// responses:
// "200":
// "$ref": "#/responses/IssueList"
@ -510,7 +516,7 @@ func ListIssues(ctx *context.APIContext) {
RepoIDs: []int64{ctx.Repo.Repository.ID},
IsPull: isPull,
IsClosed: isClosed,
SortBy: issue_indexer.SortByCreatedDesc,
SortBy: issue_indexer.ParseSortBy(ctx.FormString("sort"), issue_indexer.SortByCreatedDesc),
}
if since != 0 {
searchOpt.UpdatedAfterUnix = optional.Some(since)

View file

@ -8628,6 +8628,24 @@
"description": "page size of results",
"name": "limit",
"in": "query"
},
{
"enum": [
"relevance",
"latest",
"oldest",
"recentupdate",
"leastupdate",
"mostcomment",
"leastcomment",
"nearduedate",
"farduedate"
],
"type": "string",
"default": "latest",
"description": "Type of sort",
"name": "sort",
"in": "query"
}
],
"responses": {

View file

@ -74,6 +74,50 @@ func TestAPIListIssues(t *testing.T) {
if assert.Len(t, apiIssues, 1) {
assert.EqualValues(t, 1, apiIssues[0].ID)
}
t.Run("Sort", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
link.RawQuery = url.Values{"token": {token}, "sort": {"oldest"}}.Encode()
resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
if assert.Len(t, apiIssues, 4) {
assert.EqualValues(t, 1, apiIssues[0].ID)
assert.EqualValues(t, 2, apiIssues[1].ID)
assert.EqualValues(t, 3, apiIssues[2].ID)
assert.EqualValues(t, 11, apiIssues[3].ID)
}
link.RawQuery = url.Values{"token": {token}, "sort": {"newest"}}.Encode()
resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
if assert.Len(t, apiIssues, 4) {
assert.EqualValues(t, 11, apiIssues[0].ID)
assert.EqualValues(t, 3, apiIssues[1].ID)
assert.EqualValues(t, 2, apiIssues[2].ID)
assert.EqualValues(t, 1, apiIssues[3].ID)
}
link.RawQuery = url.Values{"token": {token}, "sort": {"recentupdate"}}.Encode()
resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
if assert.Len(t, apiIssues, 4) {
assert.EqualValues(t, 11, apiIssues[0].ID)
assert.EqualValues(t, 1, apiIssues[1].ID)
assert.EqualValues(t, 2, apiIssues[2].ID)
assert.EqualValues(t, 3, apiIssues[3].ID)
}
link.RawQuery = url.Values{"token": {token}, "sort": {"leastupdate"}}.Encode()
resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
if assert.Len(t, apiIssues, 4) {
assert.EqualValues(t, 3, apiIssues[0].ID)
assert.EqualValues(t, 2, apiIssues[1].ID)
assert.EqualValues(t, 1, apiIssues[2].ID)
assert.EqualValues(t, 11, apiIssues[3].ID)
}
})
}
func TestAPIListIssuesPublicOnly(t *testing.T) {