appview: improve pagination.Page usage #519

closed
opened by ptr.pet targeting master from ptr.pet/core: pipeline-paginated
Changed files
+147 -83
appview
db
issues
middleware
notifications
pages
templates
goodfirstissues
notifications
repo
issues
pagination
repo
state
+3 -3
appview/db/issues.go
···
whereClause = " where " + strings.Join(conditions, " and ")
}
-
pLower := FilterGte("row_num", page.Offset+1)
-
pUpper := FilterLte("row_num", page.Offset+page.Limit)
+
pLower := FilterGte("row_num", page.Count*page.No+1)
+
pUpper := FilterLte("row_num", page.Count*(page.No+1))
args = append(args, pLower.Arg()...)
args = append(args, pUpper.Arg()...)
···
}
func GetIssues(e Execer, filters ...filter) ([]models.Issue, error) {
-
return GetIssuesPaginated(e, pagination.FirstPage(), filters...)
+
return GetIssuesPaginated(e, pagination.Page{No: 0, Count: 30}, filters...)
}
func GetIssue(e Execer, repoAt syntax.ATURI, issueId int) (*models.Issue, error) {
+3 -3
appview/db/notifications.go
···
limit ? offset ?
`, whereClause)
-
args = append(args, page.Limit, page.Offset)
+
args = append(args, page.Count, page.Count*page.No+1)
rows, err := e.QueryContext(context.Background(), query, args...)
if err != nil {
···
limit ? offset ?
`, whereClause)
-
args = append(args, page.Limit, page.Offset)
+
args = append(args, page.Count, page.Count*page.No+1)
rows, err := e.QueryContext(context.Background(), query, args...)
if err != nil {
···
// GetNotifications retrieves notifications with filters
func GetNotifications(e Execer, filters ...filter) ([]*models.Notification, error) {
-
return GetNotificationsPaginated(e, pagination.FirstPage(), filters...)
+
return GetNotificationsPaginated(e, pagination.Page{No: 0, Count: 30}, filters...)
}
func CountNotifications(e Execer, filters ...filter) (int64, error) {
+19 -8
appview/issues/issues.go
···
isOpen = true
}
-
page, ok := r.Context().Value("page").(pagination.Page)
-
if !ok {
-
log.Println("failed to get page")
-
page = pagination.FirstPage()
-
}
-
user := rp.oauth.GetUser(r)
f, err := rp.repoResolver.Resolve(r)
if err != nil {
···
return
}
+
issueCounts, err := db.GetIssueCount(rp.db, f.RepoAt())
+
if err != nil {
+
log.Println("failed to get issue count", err)
+
rp.pages.Notice(w, "issues", "Failed to load issues. Try again later.")
+
return
+
}
+
page, ok := r.Context().Value("page").(pagination.Page)
+
if !ok {
+
log.Println("failed to get page")
+
page = pagination.Page{No: 0, Count: 10}
+
}
+
issueCount := issueCounts.Closed
+
if isOpen {
+
issueCount = issueCounts.Open
+
}
+
paginate := pagination.NewFromPage(page, issueCount)
+
openVal := 0
if isOpen {
openVal = 1
}
issues, err := db.GetIssuesPaginated(
rp.db,
-
page,
+
paginate.CurrentPage(),
db.FilterEq("repo_at", f.RepoAt()),
db.FilterEq("open", openVal),
)
···
Issues: issues,
LabelDefs: defs,
FilteringByOpen: isOpen,
-
Page: page,
+
Pagination: paginate,
})
}
+1 -1
appview/issues/router.go
···
r := chi.NewRouter()
r.Route("/", func(r chi.Router) {
-
r.With(middleware.Paginate).Get("/", i.RepoIssues)
+
r.With(middleware.Paginate(30)).Get("/", i.RepoIssues)
r.Route("/{issue}", func(r chi.Router) {
r.Use(mw.ResolveIssue)
+24 -22
appview/middleware/middleware.go
···
}
}
-
func Paginate(next http.Handler) http.Handler {
-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-
page := pagination.FirstPage()
+
func Paginate(paginationCount int) func(next http.Handler) http.Handler {
+
return func(next http.Handler) http.Handler {
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
page := pagination.Page{No: 0, Count: paginationCount}
-
offsetVal := r.URL.Query().Get("offset")
-
if offsetVal != "" {
-
offset, err := strconv.Atoi(offsetVal)
-
if err != nil {
-
log.Println("invalid offset")
-
} else {
-
page.Offset = offset
+
pageNoVal := r.URL.Query().Get("page")
+
if pageNoVal != "" {
+
pageNo, err := strconv.Atoi(pageNoVal)
+
if err != nil {
+
log.Println("invalid page no")
+
} else if pageNo > 0 {
+
page.No = pageNo - 1
+
}
}
-
}
-
limitVal := r.URL.Query().Get("limit")
-
if limitVal != "" {
-
limit, err := strconv.Atoi(limitVal)
-
if err != nil {
-
log.Println("invalid limit")
-
} else {
-
page.Limit = limit
+
pageCountVal := r.URL.Query().Get("count")
+
if pageCountVal != "" {
+
pageCount, err := strconv.Atoi(pageCountVal)
+
if err != nil {
+
log.Println("invalid page count")
+
} else {
+
page.Count = pageCount
+
}
}
-
}
-
ctx := context.WithValue(r.Context(), "page", page)
-
next.ServeHTTP(w, r.WithContext(ctx))
-
})
+
ctx := context.WithValue(r.Context(), "page", page)
+
next.ServeHTTP(w, r.WithContext(ctx))
+
})
+
}
}
func (mw Middleware) knotRoleMiddleware(group string) middlewareFunc {
+4 -3
appview/notifications/notifications.go
···
r.Group(func(r chi.Router) {
r.Use(middleware.AuthMiddleware(n.oauth))
-
r.With(middleware.Paginate).Get("/", n.notificationsPage)
+
r.With(middleware.Paginate(30)).Get("/", n.notificationsPage)
r.Post("/{id}/read", n.markRead)
r.Post("/read-all", n.markAllRead)
r.Delete("/{id}", n.deleteNotification)
···
page, ok := r.Context().Value("page").(pagination.Page)
if !ok {
log.Println("failed to get page")
-
page = pagination.FirstPage()
+
page = pagination.Page{No: 0, Count: 30}
}
total, err := db.CountNotifications(
···
n.pages.Error500(w)
return
}
+
paginate := pagination.NewFromPage(page, int(total))
notifications, err := db.GetNotificationsWithEntities(
n.db,
···
LoggedInUser: user,
Notifications: notifications,
UnreadCount: unreadCount,
-
Page: page,
+
Pagination: paginate,
Total: total,
})
}
+3 -3
appview/pages/pages.go
···
RepoGroups []*models.RepoGroup
LabelDefs map[string]*models.LabelDefinition
GfiLabel *models.LabelDefinition
-
Page pagination.Page
+
Pagination pagination.Pagination
}
func (p *Pages) GoodFirstIssues(w io.Writer, params GoodFirstIssuesParams) error {
···
LoggedInUser *oauth.User
Notifications []*models.NotificationWithEntity
UnreadCount int
-
Page pagination.Page
+
Pagination pagination.Pagination
Total int64
}
···
Active string
Issues []models.Issue
LabelDefs map[string]*models.LabelDefinition
-
Page pagination.Page
+
Pagination pagination.Pagination
FilteringByOpen bool
}
+7 -7
appview/pages/templates/goodfirstissues/index.html
···
</div>
{{ end }}
-
{{ if or (gt .Page.Offset 0) (eq (len .RepoGroups) .Page.Limit) }}
+
{{ if or .Pagination.HasPreviousPage .Pagination.HasNextPage }}
<div class="flex justify-center mt-8">
<div class="flex gap-2">
-
{{ if gt .Page.Offset 0 }}
-
{{ $prev := .Page.Previous }}
+
{{ if .Pagination.HasPreviousPage }}
+
{{ $prev := .Pagination.PreviousPage }}
<a
class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700"
hx-boost="true"
-
href="/goodfirstissues?offset={{ $prev.Offset }}&limit={{ $prev.Limit }}"
+
href="/goodfirstissues?page={{ add $prev.No 1 }}&count={{ $prev.Count }}"
>
{{ i "chevron-left" "w-4 h-4" }}
previous
···
<div></div>
{{ end }}
-
{{ if eq (len .RepoGroups) .Page.Limit }}
-
{{ $next := .Page.Next }}
+
{{ if .Pagination.HasNextPage }}
+
{{ $next := .Pagination.NextPage }}
<a
class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700"
hx-boost="true"
-
href="/goodfirstissues?offset={{ $next.Offset }}&limit={{ $next.Limit }}"
+
href="/goodfirstissues?page={{ add $next.No 1 }}&count={{ $next.Count }}"
>
next
{{ i "chevron-right" "w-4 h-4" }}
+6 -7
appview/pages/templates/notifications/list.html
···
{{ define "pagination" }}
<div class="flex justify-end mt-4 gap-2">
-
{{ if gt .Page.Offset 0 }}
-
{{ $prev := .Page.Previous }}
+
{{ if .Pagination.HasPreviousPage }}
+
{{ $prev := .Pagination.PreviousPage }}
<a
class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700"
hx-boost="true"
-
href = "/notifications?offset={{ $prev.Offset }}&limit={{ $prev.Limit }}"
+
href = "/notifications?page={{ add $prev.No 1 }}&count={{ $prev.Count }}"
>
{{ i "chevron-left" "w-4 h-4" }}
previous
···
<div></div>
{{ end }}
-
{{ $next := .Page.Next }}
-
{{ if lt $next.Offset .Total }}
-
{{ $next := .Page.Next }}
+
{{ if .Pagination.HasNextPage }}
+
{{ $next := .Pagination.NextPage }}
<a
class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700"
hx-boost="true"
-
href = "/notifications?offset={{ $next.Offset }}&limit={{ $next.Limit }}"
+
href = "/notifications?page={{ add $next.No 1 }}&count={{ $next.Count }}"
>
next
{{ i "chevron-right" "w-4 h-4" }}
+6 -6
appview/pages/templates/repo/issues/issues.html
···
{{ $currentState = "open" }}
{{ end }}
-
{{ if gt .Page.Offset 0 }}
-
{{ $prev := .Page.Previous }}
+
{{ if .Pagination.HasPreviousPage }}
+
{{ $prev := .Pagination.PreviousPage }}
<a
class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700"
hx-boost="true"
-
href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&offset={{ $prev.Offset }}&limit={{ $prev.Limit }}"
+
href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&page={{ add $prev.No 1 }}&count={{ $prev.Count }}"
>
{{ i "chevron-left" "w-4 h-4" }}
previous
···
<div></div>
{{ end }}
-
{{ if eq (len .Issues) .Page.Limit }}
-
{{ $next := .Page.Next }}
+
{{ if .Pagination.HasNextPage }}
+
{{ $next := .Pagination.NextPage }}
<a
class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700"
hx-boost="true"
-
href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&offset={{ $next.Offset }}&limit={{ $next.Limit }}"
+
href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&page={{ add $next.No 1 }}&count={{ $next.Count }}"
>
next
{{ i "chevron-right" "w-4 h-4" }}
+61 -13
appview/pagination/page.go
···
package pagination
-
type Page struct {
+
type Pagination struct {
Offset int // where to start from
-
Limit int // number of items in a page
+
Limit int // number of items on a single page
+
Total int // total number of items
+
}
+
+
type Page struct {
+
No int // page number
+
Count int // number of items on this page
+
}
+
+
func New(offset, limit, total int) Pagination {
+
return Pagination{
+
Offset: offset,
+
Limit: limit,
+
Total: total,
+
}
}
-
func FirstPage() Page {
+
func NewFromPage(page Page, total int) Pagination {
+
return New(page.No*page.Count, page.Count, total)
+
}
+
+
func (p Pagination) FirstPage() Page {
return Page{
-
Offset: 0,
-
Limit: 30,
+
No: 0,
+
Count: p.Limit,
}
}
-
func (p Page) Previous() Page {
+
func (p Pagination) CurrentPage() Page {
+
return Page{
+
No: p.Offset / p.Limit,
+
Count: p.Limit,
+
}
+
}
+
+
func (p Pagination) LastPage() Page {
+
return Page{
+
No: (p.Total - 1) / p.Limit,
+
Count: p.Limit,
+
}
+
}
+
+
func (p Pagination) PreviousPage() Page {
if p.Offset-p.Limit < 0 {
-
return FirstPage()
+
return p.FirstPage()
} else {
return Page{
-
Offset: p.Offset - p.Limit,
-
Limit: p.Limit,
+
No: (p.Offset - p.Limit) / p.Limit,
+
Count: p.Limit,
}
}
}
-
func (p Page) Next() Page {
-
return Page{
-
Offset: p.Offset + p.Limit,
-
Limit: p.Limit,
+
func (p Pagination) NextPage() Page {
+
if p.Offset+p.Limit >= p.Total {
+
return p.LastPage()
+
} else {
+
return Page{
+
No: (p.Offset + p.Limit) / p.Limit,
+
Count: p.Limit,
+
}
}
}
+
+
func (p Pagination) HasPreviousPage() bool {
+
return p.CurrentPage().No > 0
+
}
+
+
func (p Pagination) HasNextPage() bool {
+
return p.CurrentPage().No+1 < p.TotalPageCount()
+
}
+
+
func (p Pagination) TotalPageCount() int {
+
return (p.Total + p.Limit - 1) / p.Limit
+
}
+1 -1
appview/repo/feed.go
···
issues, err := db.GetIssuesPaginated(
rp.db,
-
pagination.Page{Limit: feedLimitPerType},
+
pagination.Page{No: 0, Count: feedLimitPerType},
db.FilterEq("repo_at", f.RepoAt()),
)
if err != nil {
+9 -6
appview/state/gfi.go
···
page, ok := r.Context().Value("page").(pagination.Page)
if !ok {
-
page = pagination.FirstPage()
+
page = pagination.Page{No: 0, Count: 30}
}
goodFirstIssueLabel := fmt.Sprintf("at://%s/%s/%s", consts.TangledDid, tangled.LabelDefinitionNSID, "good-first-issue")
···
LoggedInUser: user,
RepoGroups: []*models.RepoGroup{},
LabelDefs: make(map[string]*models.LabelDefinition),
-
Page: page,
+
Pagination: pagination.NewFromPage(page, 0),
})
return
}
···
allIssues, err := db.GetIssuesPaginated(
s.db,
pagination.Page{
-
Limit: 500,
+
No: 0,
+
Count: 500,
},
db.FilterIn("repo_at", repoUris),
db.FilterEq("open", 1),
···
return
}
+
pagination := pagination.NewFromPage(page, len(allIssues))
+
var goodFirstIssues []models.Issue
for _, issue := range allIssues {
if issue.Labels.ContainsLabel(goodFirstIssueLabel) {
···
return sortedGroups[i].Repo.Name < sortedGroups[j].Repo.Name
})
-
groupStart := page.Offset
-
groupEnd := page.Offset + page.Limit
+
groupStart := page.Count * page.No
+
groupEnd := page.Count * (page.No + 1)
if groupStart > len(sortedGroups) {
groupStart = len(sortedGroups)
}
···
LoggedInUser: user,
RepoGroups: paginatedGroups,
LabelDefs: labelDefsMap,
-
Page: page,
+
Pagination: pagination,
GfiLabel: labelDefsMap[goodFirstIssueLabel],
})
}