satellite/console: Add pagination fields for ListProjectsByOwnerID

Add ProjectsCursor type for pagination
Add PageCount, CurrentPage, and TotalCount ProjectsPage
This allows us to mimic the logic of GetBucketTotals and the
implementation of BucketUsages in graphql for the new ProjectsByOwnerID
functionality.

Change-Id: I4e1613859085db65971b44fcacd9813d9ddad8eb
This commit is contained in:
Moby von Briesen 2021-01-15 17:21:05 -05:00 committed by Maximillian von Briesen
parent e6c0383477
commit 0a48071854
3 changed files with 63 additions and 13 deletions

View File

@ -34,7 +34,7 @@ type Projects interface {
// List returns paginated projects, created before provided timestamp.
List(ctx context.Context, offset int64, limit int, before time.Time) (ProjectsPage, error)
// ListByOwnerID is a method for querying all projects from the database by ownerID. It also includes the number of members for each project.
ListByOwnerID(ctx context.Context, userID uuid.UUID, limit int, offset int64) (ProjectsPage, error)
ListByOwnerID(ctx context.Context, userID uuid.UUID, cursor ProjectsCursor) (ProjectsPage, error)
// UpdateRateLimit is a method for updating projects rate limit.
UpdateRateLimit(ctx context.Context, id uuid.UUID, newLimit int) error
@ -67,6 +67,13 @@ type ProjectInfo struct {
CreatedAt time.Time `json:"createdAt"`
}
// ProjectsCursor holds info for project
// cursor pagination.
type ProjectsCursor struct {
Limit int
Page int
}
// ProjectsPage returns paginated projects,
// providing next offset if there are more projects
// to retrieve.
@ -74,6 +81,13 @@ type ProjectsPage struct {
Projects []Project
Next bool
NextOffset int64
Limit int
Offset int64
PageCount int
CurrentPage int
TotalCount int64
}
// ValidateNameAndDescription validates project name and description strings.

View File

@ -228,7 +228,7 @@ func TestProjectsList(t *testing.T) {
}
require.False(t, projsPage.Next)
require.Equal(t, int64(0), projsPage.NextOffset)
require.EqualValues(t, 0, projsPage.NextOffset)
require.Equal(t, length, len(projectsList))
require.Empty(t, cmp.Diff(projects[0], projectsList[0],
cmp.Transformer("Sort", func(xs []console.Project) []console.Project {
@ -243,8 +243,9 @@ func TestProjectsList(t *testing.T) {
func TestProjectsListByOwner(t *testing.T) {
const (
limit = 5
length = limit*4 - 1 // make length offset from page size so we can test incomplete page at end
limit = 5
length = limit*4 - 1 // make length offset from page size so we can test incomplete page at end
totalPages = 4
)
rateLimit := 100
@ -333,23 +334,34 @@ func TestProjectsListByOwner(t *testing.T) {
{id: owner2.ID, originalProjects: owner2Projects},
}
for _, tt := range testCases {
projsPage, err := projectsDB.ListByOwnerID(ctx, tt.id, limit, 0)
cursor := &console.ProjectsCursor{
Limit: limit,
Page: 1,
}
projsPage, err := projectsDB.ListByOwnerID(ctx, tt.id, *cursor)
require.NoError(t, err)
require.Len(t, projsPage.Projects, limit)
require.EqualValues(t, 1, projsPage.CurrentPage)
require.EqualValues(t, totalPages, projsPage.PageCount)
require.EqualValues(t, length, projsPage.TotalCount)
ownerProjectsDB := projsPage.Projects
for projsPage.Next {
projsPage, err = projectsDB.ListByOwnerID(ctx, tt.id, limit, projsPage.NextOffset)
cursor.Page++
projsPage, err = projectsDB.ListByOwnerID(ctx, tt.id, *cursor)
require.NoError(t, err)
// number of projects should not exceed page limit
require.True(t, len(projsPage.Projects) > 0 && len(projsPage.Projects) <= limit)
require.EqualValues(t, cursor.Page, projsPage.CurrentPage)
require.EqualValues(t, totalPages, projsPage.PageCount)
require.EqualValues(t, length, projsPage.TotalCount)
ownerProjectsDB = append(ownerProjectsDB, projsPage.Projects...)
}
require.False(t, projsPage.Next)
require.Equal(t, int64(0), projsPage.NextOffset)
require.EqualValues(t, 0, projsPage.NextOffset)
require.Equal(t, length, len(ownerProjectsDB))
// sort originalProjects by Name in alphabetical order
originalProjects := tt.originalProjects

View File

@ -203,10 +203,34 @@ func (projects *projects) List(ctx context.Context, offset int64, limit int, bef
}
// ListByOwnerID is a method for querying all projects from the database by ownerID. It also includes the number of members for each project.
func (projects *projects) ListByOwnerID(ctx context.Context, ownerID uuid.UUID, limit int, offset int64) (_ console.ProjectsPage, err error) {
// cursor.Limit is set to 50 if it exceeds 50.
func (projects *projects) ListByOwnerID(ctx context.Context, ownerID uuid.UUID, cursor console.ProjectsCursor) (_ console.ProjectsPage, err error) {
defer mon.Task()(&ctx)(&err)
var page console.ProjectsPage
if cursor.Limit > 50 {
cursor.Limit = 50
}
if cursor.Page == 0 {
return console.ProjectsPage{}, errs.New("page can not be 0")
}
page := console.ProjectsPage{
CurrentPage: cursor.Page,
Limit: cursor.Limit,
Offset: int64((cursor.Page - 1) * cursor.Limit),
}
countRow := projects.sdb.QueryRowContext(ctx, projects.sdb.Rebind(`
SELECT COUNT(*) FROM projects WHERE owner_id = ?
`), ownerID)
err = countRow.Scan(&page.TotalCount)
if err != nil {
return console.ProjectsPage{}, err
}
page.PageCount = int(page.TotalCount / int64(cursor.Limit))
if page.TotalCount%int64(cursor.Limit) != 0 {
page.PageCount++
}
rows, err := projects.sdb.Query(ctx, projects.sdb.Rebind(`
SELECT id, name, description, owner_id, rate_limit, max_buckets, created_at,
@ -216,20 +240,20 @@ func (projects *projects) ListByOwnerID(ctx context.Context, ownerID uuid.UUID,
ORDER BY name ASC
OFFSET ? ROWS
LIMIT ?
`), ownerID, offset, limit+1) // add 1 to limit to see if there is another page
`), ownerID, page.Offset, page.Limit+1) // add 1 to limit to see if there is another page
if err != nil {
return console.ProjectsPage{}, err
}
defer func() { err = errs.Combine(err, rows.Close()) }()
count := 0
projectsToSend := make([]console.Project, 0, limit)
projectsToSend := make([]console.Project, 0, page.Limit)
for rows.Next() {
count++
if count == limit+1 {
if count == page.Limit+1 {
// we are done with this page; do not include this project
page.Next = true
page.NextOffset = offset + int64(limit)
page.NextOffset = page.Offset + int64(page.Limit)
break
}
var rateLimit, maxBuckets sql.NullInt32