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] ListReleases add filter for draft and pre-releases #16175

53 changes: 53 additions & 0 deletions integrations/api_releases_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package integrations
import (
"fmt"
"net/http"
"net/url"
"testing"

"code.gitea.io/gitea/models"
Expand All @@ -16,6 +17,58 @@ import (
"github.com/stretchr/testify/assert"
)

func TestAPIListReleases(t *testing.T) {
defer prepareTestEnv(t)()

repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
session := loginUser(t, user2.LowerName)
token := getTokenForLoggedInUser(t, session)

link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/releases", user2.Name, repo.Name))
link.RawQuery = url.Values{"token": {token}}.Encode()
resp := session.MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
var apiReleases []*api.Release
DecodeJSON(t, resp, &apiReleases)
if assert.Len(t, apiReleases, 3) {
for _, release := range apiReleases {
switch release.ID {
case 1:
assert.False(t, release.IsDraft)
assert.False(t, release.IsPrerelease)
case 4:
assert.True(t, release.IsDraft)
assert.False(t, release.IsPrerelease)
case 5:
assert.False(t, release.IsDraft)
assert.True(t, release.IsPrerelease)
default:
assert.NoError(t, fmt.Errorf("unexpected release: %v", release))
}
}
}

// test filter
testFilterByLen := func(auth bool, query url.Values, expectedLength int, msgAndArgs ...string) {
link.RawQuery = query.Encode()
if auth {
query.Set("token", token)
resp = session.MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
} else {
resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
}
DecodeJSON(t, resp, &apiReleases)
assert.Len(t, apiReleases, expectedLength, msgAndArgs)
}

testFilterByLen(false, url.Values{"draft": {"true"}}, 0, "anon should not see drafts")
testFilterByLen(true, url.Values{"draft": {"true"}}, 1, "repo owner should see drafts")
testFilterByLen(true, url.Values{"draft": {"false"}}, 2, "exclude drafts")
testFilterByLen(true, url.Values{"draft": {"false"}, "pre-release": {"false"}}, 1, "exclude drafts and pre-releases")
testFilterByLen(true, url.Values{"pre-release": {"true"}}, 1, "only get pre-release")
testFilterByLen(true, url.Values{"draft": {"true"}, "pre-release": {"true"}}, 0, "there is no pre-release draft")
}

func createNewReleaseUsingAPI(t *testing.T, session *TestSession, token string, owner *models.User, repo *models.Repository, name, target, title, desc string) *api.Release {
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases?token=%s",
owner.Name, repo.Name, token)
Expand Down
2 changes: 1 addition & 1 deletion integrations/api_repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ func TestAPIViewRepo(t *testing.T) {
DecodeJSON(t, resp, &repo)
assert.EqualValues(t, 1, repo.ID)
assert.EqualValues(t, "repo1", repo.Name)
assert.EqualValues(t, 2, repo.Releases)
assert.EqualValues(t, 3, repo.Releases)
assert.EqualValues(t, 1, repo.OpenIssues)
assert.EqualValues(t, 3, repo.OpenPulls)

Expand Down
23 changes: 13 additions & 10 deletions integrations/release_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func TestCreateRelease(t *testing.T) {
session := loginUser(t, "user2")
createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, false)

checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.stable"), 3)
checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.stable"), 4)
}

func TestCreateReleasePreRelease(t *testing.T) {
Expand All @@ -94,7 +94,7 @@ func TestCreateReleasePreRelease(t *testing.T) {
session := loginUser(t, "user2")
createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", true, false)

checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.prerelease"), 3)
checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.prerelease"), 4)
}

func TestCreateReleaseDraft(t *testing.T) {
Expand All @@ -103,7 +103,7 @@ func TestCreateReleaseDraft(t *testing.T) {
session := loginUser(t, "user2")
createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, true)

checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.draft"), 3)
checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.draft"), 4)
}

func TestCreateReleasePaging(t *testing.T) {
Expand Down Expand Up @@ -142,7 +142,7 @@ func TestViewReleaseListNoLogin(t *testing.T) {

htmlDoc := NewHTMLParser(t, rsp.Body)
releases := htmlDoc.Find("#release-list li.ui.grid")
assert.Equal(t, 1, releases.Length())
assert.Equal(t, 2, releases.Length())

links := make([]string, 0, 5)
releases.Each(func(i int, s *goquery.Selection) {
Expand All @@ -153,7 +153,7 @@ func TestViewReleaseListNoLogin(t *testing.T) {
links = append(links, link)
})

assert.EqualValues(t, []string{"/user2/repo1/releases/tag/v1.1"}, links)
assert.EqualValues(t, []string{"/user2/repo1/releases/tag/v1.0", "/user2/repo1/releases/tag/v1.1"}, links)
}

func TestViewReleaseListLogin(t *testing.T) {
Expand All @@ -169,7 +169,7 @@ func TestViewReleaseListLogin(t *testing.T) {

htmlDoc := NewHTMLParser(t, rsp.Body)
releases := htmlDoc.Find("#release-list li.ui.grid")
assert.Equal(t, 2, releases.Length())
assert.Equal(t, 3, releases.Length())

links := make([]string, 0, 5)
releases.Each(func(i int, s *goquery.Selection) {
Expand All @@ -180,8 +180,11 @@ func TestViewReleaseListLogin(t *testing.T) {
links = append(links, link)
})

assert.EqualValues(t, []string{"/user2/repo1/releases/tag/draft-release",
"/user2/repo1/releases/tag/v1.1"}, links)
assert.EqualValues(t, []string{
"/user2/repo1/releases/tag/draft-release",
"/user2/repo1/releases/tag/v1.0",
"/user2/repo1/releases/tag/v1.1",
}, links)
}

func TestViewTagsList(t *testing.T) {
Expand All @@ -197,12 +200,12 @@ func TestViewTagsList(t *testing.T) {

htmlDoc := NewHTMLParser(t, rsp.Body)
tags := htmlDoc.Find(".tag-list tr")
assert.Equal(t, 2, tags.Length())
assert.Equal(t, 3, tags.Length())

tagNames := make([]string, 0, 5)
tags.Each(func(i int, s *goquery.Selection) {
tagNames = append(tagNames, s.Find(".tag a.df.ac").Text())
})

assert.EqualValues(t, []string{"delete-tag", "v1.1"}, tagNames)
assert.EqualValues(t, []string{"v1.0", "delete-tag", "v1.1"}, tagNames)
}
27 changes: 19 additions & 8 deletions models/fixtures/release.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
-
id: 1
- id: 1
repo_id: 1
publisher_id: 2
tag_name: "v1.1"
Expand All @@ -13,8 +12,7 @@
is_tag: false
created_unix: 946684800

-
id: 2
- id: 2
repo_id: 40
publisher_id: 2
tag_name: "v1.1"
Expand All @@ -28,8 +26,7 @@
is_tag: false
created_unix: 946684800

-
id: 3
- id: 3
repo_id: 1
publisher_id: 2
tag_name: "delete-tag"
Expand All @@ -43,8 +40,7 @@
is_tag: true
created_unix: 946684800

-
id: 4
- id: 4
repo_id: 1
publisher_id: 2
tag_name: "draft-release"
Expand All @@ -55,3 +51,18 @@
is_prerelease: false
is_tag: false
created_unix: 1619524806

- id: 5
repo_id: 1
publisher_id: 2
tag_name: "v1.0"
lower_tag_name: "v1.0"
target: "master"
title: "pre-release"
note: "some text for a pre release"
sha1: "65f1bf27bc3bf70f64657658635e66094edbcb4d"
num_commits: 1
is_draft: false
is_prerelease: true
is_tag: false
created_unix: 946684800
14 changes: 14 additions & 0 deletions models/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"

"xorm.io/builder"
)
Expand Down Expand Up @@ -173,6 +174,8 @@ type FindReleasesOptions struct {
ListOptions
IncludeDrafts bool
IncludeTags bool
IsPreRelease util.OptionalBool
IsDraft util.OptionalBool
TagNames []string
}

Expand All @@ -189,6 +192,12 @@ func (opts *FindReleasesOptions) toConds(repoID int64) builder.Cond {
if len(opts.TagNames) > 0 {
cond = cond.And(builder.In("tag_name", opts.TagNames))
}
if !opts.IsPreRelease.IsNone() {
cond = cond.And(builder.Eq{"is_prerelease": opts.IsPreRelease.IsTrue()})
}
if !opts.IsDraft.IsNone() {
cond = cond.And(builder.Eq{"is_draft": opts.IsDraft.IsTrue()})
}
return cond
}

Expand All @@ -206,6 +215,11 @@ func GetReleasesByRepoID(repoID int64, opts FindReleasesOptions) ([]*Release, er
return rels, sess.Find(&rels)
}

// CountReleasesByRepoID returns a number of releases matching FindReleaseOptions and RepoID.
func CountReleasesByRepoID(repoID int64, opts FindReleasesOptions) (int64, error) {
return x.Where(opts.toConds(repoID)).Count(new(Release))
}

// GetLatestReleaseByRepoID returns the latest release for a repository
func GetLatestReleaseByRepoID(repoID int64) (*Release, error) {
cond := builder.NewCond().
Expand Down
6 changes: 6 additions & 0 deletions modules/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/services/auth"

Expand Down Expand Up @@ -319,6 +320,11 @@ func (ctx *Context) QueryBool(key string, defaults ...bool) bool {
return (*Forms)(ctx.Req).MustBool(key, defaults...)
}

// QueryOptionalBool returns request form as OptionalBool with default
func (ctx *Context) QueryOptionalBool(key string, defaults ...util.OptionalBool) util.OptionalBool {
return (*Forms)(ctx.Req).MustOptionalBool(key, defaults...)
}

// HandleText handles HTTP status code
func (ctx *Context) HandleText(status int, title string) {
if (status/100 == 4) || (status/100 == 5) {
Expand Down
14 changes: 14 additions & 0 deletions modules/context/form.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"text/template"

"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
)

// Forms a new enhancement of http.Request
Expand Down Expand Up @@ -225,3 +226,16 @@ func (f *Forms) MustBool(key string, defaults ...bool) bool {
}
return v
}

// MustOptionalBool returns request form as OptionalBool with default
func (f *Forms) MustOptionalBool(key string, defaults ...util.OptionalBool) util.OptionalBool {
value := (*http.Request)(f).FormValue(key)
if len(value) == 0 {
return util.OptionalBoolNone
}
v, err := strconv.ParseBool((*http.Request)(f).FormValue(key))
if len(defaults) > 0 && err != nil {
return defaults[0]
}
return util.OptionalBoolOf(v)
}
29 changes: 26 additions & 3 deletions routers/api/v1/repo/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package repo

import (
"fmt"
"net/http"

"code.gitea.io/gitea/models"
Expand Down Expand Up @@ -83,6 +84,14 @@ func ListReleases(ctx *context.APIContext) {
// description: name of the repo
// type: string
// required: true
// - name: draft
// in: query
// description: filter (exclude / include) drafts, if you dont have repo write access none will show
// type: boolean
// - name: pre-release
// in: query
// description: filter (exclude / include) pre-releases
// type: boolean
// - name: per_page
// in: query
// description: page size of results, deprecated - use limit
Expand All @@ -100,15 +109,19 @@ func ListReleases(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/ReleaseList"
listOptions := utils.GetListOptions(ctx)
if ctx.QueryInt("per_page") != 0 {
if listOptions.PageSize == 0 && ctx.QueryInt("per_page") != 0 {
listOptions.PageSize = ctx.QueryInt("per_page")
}

releases, err := models.GetReleasesByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{
opts := models.FindReleasesOptions{
ListOptions: listOptions,
IncludeDrafts: ctx.Repo.AccessMode >= models.AccessModeWrite,
IncludeTags: false,
})
IsDraft: ctx.QueryOptionalBool("draft"),
IsPreRelease: ctx.QueryOptionalBool("pre-release"),
}

releases, err := models.GetReleasesByRepoID(ctx.Repo.Repository.ID, opts)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetReleasesByRepoID", err)
return
Expand All @@ -121,6 +134,16 @@ func ListReleases(ctx *context.APIContext) {
}
rels[i] = convert.ToRelease(release)
}

filteredCount, err := models.CountReleasesByRepoID(ctx.Repo.Repository.ID, opts)
if err != nil {
ctx.InternalServerError(err)
return
}

ctx.SetLinkHeader(int(filteredCount), listOptions.PageSize)
ctx.Header().Set("X-Total-Count", fmt.Sprint(filteredCount))
ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link")
ctx.JSON(http.StatusOK, rels)
}

Expand Down
12 changes: 12 additions & 0 deletions templates/swagger/v1_json.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -8038,6 +8038,18 @@
"in": "path",
"required": true
},
{
"type": "boolean",
"description": "filter (exclude / include) drafts, if you dont have repo write access none will show",
"name": "draft",
"in": "query"
},
{
"type": "boolean",
"description": "filter (exclude / include) pre-releases",
"name": "pre-release",
"in": "query"
},
{
"type": "integer",
"description": "page size of results, deprecated - use limit",
Expand Down