appview: improve pagination.Page usage #519

closed
opened by ptr.pet targeting master from [deleted fork]: 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
··· 98 98 whereClause = " where " + strings.Join(conditions, " and ") 99 99 } 100 100 101 - pLower := FilterGte("row_num", page.Offset+1) 102 - pUpper := FilterLte("row_num", page.Offset+page.Limit) 101 + pLower := FilterGte("row_num", page.Count*page.No+1) 102 + pUpper := FilterLte("row_num", page.Count*(page.No+1)) 103 103 104 104 args = append(args, pLower.Arg()...) 105 105 args = append(args, pUpper.Arg()...) ··· 244 244 } 245 245 246 246 func GetIssues(e Execer, filters ...filter) ([]models.Issue, error) { 247 - return GetIssuesPaginated(e, pagination.FirstPage(), filters...) 247 + return GetIssuesPaginated(e, pagination.Page{No: 0, Count: 30}, filters...) 248 248 } 249 249 250 250 func GetIssue(e Execer, repoAt syntax.ATURI, issueId int) (*models.Issue, error) {
+3 -3
appview/db/notifications.go
··· 68 68 limit ? offset ? 69 69 `, whereClause) 70 70 71 - args = append(args, page.Limit, page.Offset) 71 + args = append(args, page.Count, page.Count*page.No+1) 72 72 73 73 rows, err := e.QueryContext(context.Background(), query, args...) 74 74 if err != nil { ··· 142 142 limit ? offset ? 143 143 `, whereClause) 144 144 145 - args = append(args, page.Limit, page.Offset) 145 + args = append(args, page.Count, page.Count*page.No+1) 146 146 147 147 rows, err := e.QueryContext(context.Background(), query, args...) 148 148 if err != nil { ··· 247 247 248 248 // GetNotifications retrieves notifications with filters 249 249 func GetNotifications(e Execer, filters ...filter) ([]*models.Notification, error) { 250 - return GetNotificationsPaginated(e, pagination.FirstPage(), filters...) 250 + return GetNotificationsPaginated(e, pagination.Page{No: 0, Count: 30}, filters...) 251 251 } 252 252 253 253 func CountNotifications(e Execer, filters ...filter) (int64, error) {
+19 -8
appview/issues/issues.go
··· 769 769 isOpen = true 770 770 } 771 771 772 - page, ok := r.Context().Value("page").(pagination.Page) 773 - if !ok { 774 - log.Println("failed to get page") 775 - page = pagination.FirstPage() 776 - } 777 - 778 772 user := rp.oauth.GetUser(r) 779 773 f, err := rp.repoResolver.Resolve(r) 780 774 if err != nil { ··· 782 776 return 783 777 } 784 778 779 + issueCounts, err := db.GetIssueCount(rp.db, f.RepoAt()) 780 + if err != nil { 781 + log.Println("failed to get issue count", err) 782 + rp.pages.Notice(w, "issues", "Failed to load issues. Try again later.") 783 + return 784 + } 785 + page, ok := r.Context().Value("page").(pagination.Page) 786 + if !ok { 787 + log.Println("failed to get page") 788 + page = pagination.Page{No: 0, Count: 10} 789 + } 790 + issueCount := issueCounts.Closed 791 + if isOpen { 792 + issueCount = issueCounts.Open 793 + } 794 + paginate := pagination.NewFromPage(page, issueCount) 795 + 785 796 openVal := 0 786 797 if isOpen { 787 798 openVal = 1 788 799 } 789 800 issues, err := db.GetIssuesPaginated( 790 801 rp.db, 791 - page, 802 + paginate.CurrentPage(), 792 803 db.FilterEq("repo_at", f.RepoAt()), 793 804 db.FilterEq("open", openVal), 794 805 ) ··· 820 831 Issues: issues, 821 832 LabelDefs: defs, 822 833 FilteringByOpen: isOpen, 823 - Page: page, 834 + Pagination: paginate, 824 835 }) 825 836 } 826 837
+1 -1
appview/issues/router.go
··· 11 11 r := chi.NewRouter() 12 12 13 13 r.Route("/", func(r chi.Router) { 14 - r.With(middleware.Paginate).Get("/", i.RepoIssues) 14 + r.With(middleware.Paginate(30)).Get("/", i.RepoIssues) 15 15 16 16 r.Route("/{issue}", func(r chi.Router) { 17 17 r.Use(mw.ResolveIssue)
+24 -22
appview/middleware/middleware.go
··· 81 81 } 82 82 } 83 83 84 - func Paginate(next http.Handler) http.Handler { 85 - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 86 - page := pagination.FirstPage() 84 + func Paginate(paginationCount int) func(next http.Handler) http.Handler { 85 + return func(next http.Handler) http.Handler { 86 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 87 + page := pagination.Page{No: 0, Count: paginationCount} 87 88 88 - offsetVal := r.URL.Query().Get("offset") 89 - if offsetVal != "" { 90 - offset, err := strconv.Atoi(offsetVal) 91 - if err != nil { 92 - log.Println("invalid offset") 93 - } else { 94 - page.Offset = offset 89 + pageNoVal := r.URL.Query().Get("page") 90 + if pageNoVal != "" { 91 + pageNo, err := strconv.Atoi(pageNoVal) 92 + if err != nil { 93 + log.Println("invalid page no") 94 + } else if pageNo > 0 { 95 + page.No = pageNo - 1 96 + } 95 97 } 96 - } 97 98 98 - limitVal := r.URL.Query().Get("limit") 99 - if limitVal != "" { 100 - limit, err := strconv.Atoi(limitVal) 101 - if err != nil { 102 - log.Println("invalid limit") 103 - } else { 104 - page.Limit = limit 99 + pageCountVal := r.URL.Query().Get("count") 100 + if pageCountVal != "" { 101 + pageCount, err := strconv.Atoi(pageCountVal) 102 + if err != nil { 103 + log.Println("invalid page count") 104 + } else { 105 + page.Count = pageCount 106 + } 105 107 } 106 - } 107 108 108 - ctx := context.WithValue(r.Context(), "page", page) 109 - next.ServeHTTP(w, r.WithContext(ctx)) 110 - }) 109 + ctx := context.WithValue(r.Context(), "page", page) 110 + next.ServeHTTP(w, r.WithContext(ctx)) 111 + }) 112 + } 111 113 } 112 114 113 115 func (mw Middleware) knotRoleMiddleware(group string) middlewareFunc {
+4 -3
appview/notifications/notifications.go
··· 34 34 35 35 r.Group(func(r chi.Router) { 36 36 r.Use(middleware.AuthMiddleware(n.oauth)) 37 - r.With(middleware.Paginate).Get("/", n.notificationsPage) 37 + r.With(middleware.Paginate(30)).Get("/", n.notificationsPage) 38 38 r.Post("/{id}/read", n.markRead) 39 39 r.Post("/read-all", n.markAllRead) 40 40 r.Delete("/{id}", n.deleteNotification) ··· 49 49 page, ok := r.Context().Value("page").(pagination.Page) 50 50 if !ok { 51 51 log.Println("failed to get page") 52 - page = pagination.FirstPage() 52 + page = pagination.Page{No: 0, Count: 30} 53 53 } 54 54 55 55 total, err := db.CountNotifications( ··· 61 61 n.pages.Error500(w) 62 62 return 63 63 } 64 + paginate := pagination.NewFromPage(page, int(total)) 64 65 65 66 notifications, err := db.GetNotificationsWithEntities( 66 67 n.db, ··· 84 85 LoggedInUser: user, 85 86 Notifications: notifications, 86 87 UnreadCount: unreadCount, 87 - Page: page, 88 + Pagination: paginate, 88 89 Total: total, 89 90 }) 90 91 }
+3 -3
appview/pages/pages.go
··· 320 320 RepoGroups []*models.RepoGroup 321 321 LabelDefs map[string]*models.LabelDefinition 322 322 GfiLabel *models.LabelDefinition 323 - Page pagination.Page 323 + Pagination pagination.Pagination 324 324 } 325 325 326 326 func (p *Pages) GoodFirstIssues(w io.Writer, params GoodFirstIssuesParams) error { ··· 341 341 LoggedInUser *oauth.User 342 342 Notifications []*models.NotificationWithEntity 343 343 UnreadCount int 344 - Page pagination.Page 344 + Pagination pagination.Pagination 345 345 Total int64 346 346 } 347 347 ··· 968 968 Active string 969 969 Issues []models.Issue 970 970 LabelDefs map[string]*models.LabelDefinition 971 - Page pagination.Page 971 + Pagination pagination.Pagination 972 972 FilteringByOpen bool 973 973 } 974 974
+7 -7
appview/pages/templates/goodfirstissues/index.html
··· 130 130 </div> 131 131 {{ end }} 132 132 133 - {{ if or (gt .Page.Offset 0) (eq (len .RepoGroups) .Page.Limit) }} 133 + {{ if or .Pagination.HasPreviousPage .Pagination.HasNextPage }} 134 134 <div class="flex justify-center mt-8"> 135 135 <div class="flex gap-2"> 136 - {{ if gt .Page.Offset 0 }} 137 - {{ $prev := .Page.Previous }} 136 + {{ if .Pagination.HasPreviousPage }} 137 + {{ $prev := .Pagination.PreviousPage }} 138 138 <a 139 139 class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700" 140 140 hx-boost="true" 141 - href="/goodfirstissues?offset={{ $prev.Offset }}&limit={{ $prev.Limit }}" 141 + href="/goodfirstissues?page={{ add $prev.No 1 }}&count={{ $prev.Count }}" 142 142 > 143 143 {{ i "chevron-left" "w-4 h-4" }} 144 144 previous ··· 147 147 <div></div> 148 148 {{ end }} 149 149 150 - {{ if eq (len .RepoGroups) .Page.Limit }} 151 - {{ $next := .Page.Next }} 150 + {{ if .Pagination.HasNextPage }} 151 + {{ $next := .Pagination.NextPage }} 152 152 <a 153 153 class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700" 154 154 hx-boost="true" 155 - href="/goodfirstissues?offset={{ $next.Offset }}&limit={{ $next.Limit }}" 155 + href="/goodfirstissues?page={{ add $next.No 1 }}&count={{ $next.Count }}" 156 156 > 157 157 next 158 158 {{ i "chevron-right" "w-4 h-4" }}
+6 -7
appview/pages/templates/notifications/list.html
··· 35 35 36 36 {{ define "pagination" }} 37 37 <div class="flex justify-end mt-4 gap-2"> 38 - {{ if gt .Page.Offset 0 }} 39 - {{ $prev := .Page.Previous }} 38 + {{ if .Pagination.HasPreviousPage }} 39 + {{ $prev := .Pagination.PreviousPage }} 40 40 <a 41 41 class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700" 42 42 hx-boost="true" 43 - href = "/notifications?offset={{ $prev.Offset }}&limit={{ $prev.Limit }}" 43 + href = "/notifications?page={{ add $prev.No 1 }}&count={{ $prev.Count }}" 44 44 > 45 45 {{ i "chevron-left" "w-4 h-4" }} 46 46 previous ··· 49 49 <div></div> 50 50 {{ end }} 51 51 52 - {{ $next := .Page.Next }} 53 - {{ if lt $next.Offset .Total }} 54 - {{ $next := .Page.Next }} 52 + {{ if .Pagination.HasNextPage }} 53 + {{ $next := .Pagination.NextPage }} 55 54 <a 56 55 class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700" 57 56 hx-boost="true" 58 - href = "/notifications?offset={{ $next.Offset }}&limit={{ $next.Limit }}" 57 + href = "/notifications?page={{ add $next.No 1 }}&count={{ $next.Count }}" 59 58 > 60 59 next 61 60 {{ i "chevron-right" "w-4 h-4" }}
+6 -6
appview/pages/templates/repo/issues/issues.html
··· 50 50 {{ $currentState = "open" }} 51 51 {{ end }} 52 52 53 - {{ if gt .Page.Offset 0 }} 54 - {{ $prev := .Page.Previous }} 53 + {{ if .Pagination.HasPreviousPage }} 54 + {{ $prev := .Pagination.PreviousPage }} 55 55 <a 56 56 class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700" 57 57 hx-boost="true" 58 - href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&offset={{ $prev.Offset }}&limit={{ $prev.Limit }}" 58 + href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&page={{ add $prev.No 1 }}&count={{ $prev.Count }}" 59 59 > 60 60 {{ i "chevron-left" "w-4 h-4" }} 61 61 previous ··· 64 64 <div></div> 65 65 {{ end }} 66 66 67 - {{ if eq (len .Issues) .Page.Limit }} 68 - {{ $next := .Page.Next }} 67 + {{ if .Pagination.HasNextPage }} 68 + {{ $next := .Pagination.NextPage }} 69 69 <a 70 70 class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700" 71 71 hx-boost="true" 72 - href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&offset={{ $next.Offset }}&limit={{ $next.Limit }}" 72 + href = "/{{ $.RepoInfo.FullName }}/issues?state={{ $currentState }}&page={{ add $next.No 1 }}&count={{ $next.Count }}" 73 73 > 74 74 next 75 75 {{ i "chevron-right" "w-4 h-4" }}
+61 -13
appview/pagination/page.go
··· 1 1 package pagination 2 2 3 - type Page struct { 3 + type Pagination struct { 4 4 Offset int // where to start from 5 - Limit int // number of items in a page 5 + Limit int // number of items on a single page 6 + Total int // total number of items 7 + } 8 + 9 + type Page struct { 10 + No int // page number 11 + Count int // number of items on this page 12 + } 13 + 14 + func New(offset, limit, total int) Pagination { 15 + return Pagination{ 16 + Offset: offset, 17 + Limit: limit, 18 + Total: total, 19 + } 6 20 } 7 21 8 - func FirstPage() Page { 22 + func NewFromPage(page Page, total int) Pagination { 23 + return New(page.No*page.Count, page.Count, total) 24 + } 25 + 26 + func (p Pagination) FirstPage() Page { 9 27 return Page{ 10 - Offset: 0, 11 - Limit: 30, 28 + No: 0, 29 + Count: p.Limit, 12 30 } 13 31 } 14 32 15 - func (p Page) Previous() Page { 33 + func (p Pagination) CurrentPage() Page { 34 + return Page{ 35 + No: p.Offset / p.Limit, 36 + Count: p.Limit, 37 + } 38 + } 39 + 40 + func (p Pagination) LastPage() Page { 41 + return Page{ 42 + No: (p.Total - 1) / p.Limit, 43 + Count: p.Limit, 44 + } 45 + } 46 + 47 + func (p Pagination) PreviousPage() Page { 16 48 if p.Offset-p.Limit < 0 { 17 - return FirstPage() 49 + return p.FirstPage() 18 50 } else { 19 51 return Page{ 20 - Offset: p.Offset - p.Limit, 21 - Limit: p.Limit, 52 + No: (p.Offset - p.Limit) / p.Limit, 53 + Count: p.Limit, 22 54 } 23 55 } 24 56 } 25 57 26 - func (p Page) Next() Page { 27 - return Page{ 28 - Offset: p.Offset + p.Limit, 29 - Limit: p.Limit, 58 + func (p Pagination) NextPage() Page { 59 + if p.Offset+p.Limit >= p.Total { 60 + return p.LastPage() 61 + } else { 62 + return Page{ 63 + No: (p.Offset + p.Limit) / p.Limit, 64 + Count: p.Limit, 65 + } 30 66 } 31 67 } 68 + 69 + func (p Pagination) HasPreviousPage() bool { 70 + return p.CurrentPage().No > 0 71 + } 72 + 73 + func (p Pagination) HasNextPage() bool { 74 + return p.CurrentPage().No+1 < p.TotalPageCount() 75 + } 76 + 77 + func (p Pagination) TotalPageCount() int { 78 + return (p.Total + p.Limit - 1) / p.Limit 79 + }
+1 -1
appview/repo/feed.go
··· 27 27 28 28 issues, err := db.GetIssuesPaginated( 29 29 rp.db, 30 - pagination.Page{Limit: feedLimitPerType}, 30 + pagination.Page{No: 0, Count: feedLimitPerType}, 31 31 db.FilterEq("repo_at", f.RepoAt()), 32 32 ) 33 33 if err != nil {
+9 -6
appview/state/gfi.go
··· 20 20 21 21 page, ok := r.Context().Value("page").(pagination.Page) 22 22 if !ok { 23 - page = pagination.FirstPage() 23 + page = pagination.Page{No: 0, Count: 30} 24 24 } 25 25 26 26 goodFirstIssueLabel := fmt.Sprintf("at://%s/%s/%s", consts.TangledDid, tangled.LabelDefinitionNSID, "good-first-issue") ··· 37 37 LoggedInUser: user, 38 38 RepoGroups: []*models.RepoGroup{}, 39 39 LabelDefs: make(map[string]*models.LabelDefinition), 40 - Page: page, 40 + Pagination: pagination.NewFromPage(page, 0), 41 41 }) 42 42 return 43 43 } ··· 50 50 allIssues, err := db.GetIssuesPaginated( 51 51 s.db, 52 52 pagination.Page{ 53 - Limit: 500, 53 + No: 0, 54 + Count: 500, 54 55 }, 55 56 db.FilterIn("repo_at", repoUris), 56 57 db.FilterEq("open", 1), ··· 98 99 return sortedGroups[i].Repo.Name < sortedGroups[j].Repo.Name 99 100 }) 100 101 101 - groupStart := page.Offset 102 - groupEnd := page.Offset + page.Limit 102 + pagination := pagination.NewFromPage(page, len(sortedGroups)) 103 + 104 + groupStart := page.Count * page.No 105 + groupEnd := page.Count * (page.No + 1) 103 106 if groupStart > len(sortedGroups) { 104 107 groupStart = len(sortedGroups) 105 108 } ··· 145 148 LoggedInUser: user, 146 149 RepoGroups: paginatedGroups, 147 150 LabelDefs: labelDefsMap, 148 - Page: page, 151 + Pagination: pagination, 149 152 GfiLabel: labelDefsMap[goodFirstIssueLabel], 150 153 }) 151 154 }