From 9381a425f907a42cda003e0a11fba7ac6804fb2a Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Fri, 8 May 2026 02:43:47 +0200 Subject: [PATCH] [v15.0/forgejo] fix: paginate team members list (#12461) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/12447 Fixes #12103. Paginate the list of team members on the page for that team. Co-authored-by: Antonin Delpeuch Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12461 Reviewed-by: Gusted --- models/organization/team.go | 10 +++++-- routers/web/org/teams.go | 10 ++++++- templates/org/team/members.tmpl | 1 + tests/integration/org_team_test.go | 48 ++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 tests/integration/org_team_test.go diff --git a/models/organization/team.go b/models/organization/team.go index 209471e013..b7b93821ad 100644 --- a/models/organization/team.go +++ b/models/organization/team.go @@ -161,10 +161,16 @@ func (t *Team) LoadRepositories(ctx context.Context) (err error) { return err } -// LoadMembers returns paginated members in team of organization. +// LoadMembers loads the members of the team in t.Members. func (t *Team) LoadMembers(ctx context.Context) (err error) { + return t.LoadPaginatedMembers(ctx, db.ListOptionsAll) +} + +// LoadPaginatedMembers loads paginated members of the team in t.Members. +func (t *Team) LoadPaginatedMembers(ctx context.Context, listOptions db.ListOptions) (err error) { t.Members, err = GetTeamMembers(ctx, &SearchMembersOptions{ - TeamID: t.ID, + ListOptions: listOptions, + TeamID: t.ID, }) return err } diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index 1785623855..1b76a6abf9 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -377,10 +377,18 @@ func TeamMembers(ctx *context.Context) { return } - if err := ctx.Org.Team.LoadMembers(ctx); err != nil { + page := max(ctx.FormInt("page"), 1) + total := ctx.Org.Team.NumMembers + pager := context.NewPagination(total, setting.UI.MembersPagingNum, page, 5) + opts := db.ListOptions{} + opts.Page = page + opts.PageSize = setting.UI.MembersPagingNum + + if err := ctx.Org.Team.LoadPaginatedMembers(ctx, opts); err != nil { ctx.ServerError("GetMembers", err) return } + ctx.Data["Page"] = pager ctx.Data["Units"] = unit_model.Units invites, err := org_model.GetInvitesByTeamID(ctx, ctx.Org.Team.ID) diff --git a/templates/org/team/members.tmpl b/templates/org/team/members.tmpl index 60667e963d..e027db68da 100644 --- a/templates/org/team/members.tmpl +++ b/templates/org/team/members.tmpl @@ -49,6 +49,7 @@ {{end}} + {{template "base/paginate" .}} {{if and .Invites $.IsOrganizationOwner}}

{{ctx.Locale.Tr "org.teams.invite_team_member.list"}}

diff --git a/tests/integration/org_team_test.go b/tests/integration/org_team_test.go new file mode 100644 index 0000000000..2ed3025785 --- /dev/null +++ b/tests/integration/org_team_test.go @@ -0,0 +1,48 @@ +// Copyright 2026 The Forgejo Authors +// SPDX-License-Identifier: GPL-3.0-or-later + +package integration + +import ( + "fmt" + "net/http" + "strings" + "testing" + + "forgejo.org/models/db" + "forgejo.org/models/organization" + "forgejo.org/models/unittest" + user_model "forgejo.org/models/user" + "forgejo.org/modules/setting" + "forgejo.org/modules/test" + "forgejo.org/tests" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPaginatedMembers(t *testing.T) { + defer tests.PrepareTestEnv(t)() + // To make sure that pagination kicks in even though the test team has few members + defer test.MockVariableValue(&setting.UI.MembersPagingNum, 2)() + + org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 17}) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 9}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29}) + + assert.GreaterOrEqual(t, org.NumMembers, 3) + isOrgMember, err := organization.IsOrganizationMember(db.DefaultContext, org.ID, user.ID) + require.NoError(t, err) + assert.True(t, isOrgMember) + isTeamMember, err := organization.IsTeamMember(db.DefaultContext, team.OrgID, team.ID, user.ID) + require.NoError(t, err) + assert.True(t, isTeamMember) + assert.Equal(t, org.ID, team.OrgID) + + session := loginUser(t, user.Name) + + teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.LowerName) + newVar := session.MakeRequest(t, NewRequest(t, "GET", teamURL), http.StatusOK).Body + doc := NewHTMLParser(t, newVar) + assert.Contains(t, strings.TrimSpace(doc.Find("a.item.navigation:contains('Next')").AttrOr("href", "")), fmt.Sprintf("%s?page=2", teamURL)) +}