appview: implement pagination for pipelines index #521

closed
opened by ptr.pet targeting master from [deleted fork]: pipeline-paginated
+2 -2
appview/db/artifact.go
··· 57 57 return err 58 58 } 59 59 60 - func GetArtifact(e Execer, filters ...filter) ([]Artifact, error) { 60 + func GetArtifact(e Execer, filters ...Filter) ([]Artifact, error) { 61 61 var artifacts []Artifact 62 62 63 63 var conditions []string ··· 130 130 return artifacts, nil 131 131 } 132 132 133 - func DeleteArtifact(e Execer, filters ...filter) error { 133 + func DeleteArtifact(e Execer, filters ...Filter) error { 134 134 var conditions []string 135 135 var args []any 136 136 for _, filter := range filters {
+1 -1
appview/db/collaborators.go
··· 30 30 return err 31 31 } 32 32 33 - func DeleteCollaborator(e Execer, filters ...filter) error { 33 + func DeleteCollaborator(e Execer, filters ...Filter) error { 34 34 var conditions []string 35 35 var args []any 36 36 for _, filter := range filters {
+13 -13
appview/db/db.go
··· 918 918 return d.DB.Close() 919 919 } 920 920 921 - type filter struct { 921 + type Filter struct { 922 922 key string 923 923 arg any 924 924 cmp string 925 925 } 926 926 927 - func newFilter(key, cmp string, arg any) filter { 928 - return filter{ 927 + func newFilter(key, cmp string, arg any) Filter { 928 + return Filter{ 929 929 key: key, 930 930 arg: arg, 931 931 cmp: cmp, 932 932 } 933 933 } 934 934 935 - func FilterEq(key string, arg any) filter { return newFilter(key, "=", arg) } 936 - func FilterNotEq(key string, arg any) filter { return newFilter(key, "<>", arg) } 937 - func FilterGte(key string, arg any) filter { return newFilter(key, ">=", arg) } 938 - func FilterLte(key string, arg any) filter { return newFilter(key, "<=", arg) } 939 - func FilterIs(key string, arg any) filter { return newFilter(key, "is", arg) } 940 - func FilterIsNot(key string, arg any) filter { return newFilter(key, "is not", arg) } 941 - func FilterIn(key string, arg any) filter { return newFilter(key, "in", arg) } 942 - func FilterBetween(key string, arg1, arg2 any) filter { 935 + func FilterEq(key string, arg any) Filter { return newFilter(key, "=", arg) } 936 + func FilterNotEq(key string, arg any) Filter { return newFilter(key, "<>", arg) } 937 + func FilterGte(key string, arg any) Filter { return newFilter(key, ">=", arg) } 938 + func FilterLte(key string, arg any) Filter { return newFilter(key, "<=", arg) } 939 + func FilterIs(key string, arg any) Filter { return newFilter(key, "is", arg) } 940 + func FilterIsNot(key string, arg any) Filter { return newFilter(key, "is not", arg) } 941 + func FilterIn(key string, arg any) Filter { return newFilter(key, "in", arg) } 942 + func FilterBetween(key string, arg1, arg2 any) Filter { 943 943 return newFilter(key, "between", []any{arg1, arg2}) 944 944 } 945 945 946 - func (f filter) Condition() string { 946 + func (f Filter) Condition() string { 947 947 rv := reflect.ValueOf(f.arg) 948 948 kind := rv.Kind() 949 949 ··· 969 969 return fmt.Sprintf("%s %s ?", f.key, f.cmp) 970 970 } 971 971 972 - func (f filter) Arg() []any { 972 + func (f Filter) Arg() []any { 973 973 rv := reflect.ValueOf(f.arg) 974 974 kind := rv.Kind() 975 975 if (kind == reflect.Slice && rv.Type().Elem().Kind() != reflect.Uint8) || kind == reflect.Array {
+1 -1
appview/db/follow.go
··· 144 144 return result, nil 145 145 } 146 146 147 - func GetFollows(e Execer, limit int, filters ...filter) ([]Follow, error) { 147 + func GetFollows(e Execer, limit int, filters ...Filter) ([]Follow, error) { 148 148 var follows []Follow 149 149 150 150 var conditions []string
+7 -7
appview/db/issues.go
··· 245 245 return err 246 246 } 247 247 248 - func GetIssuesPaginated(e Execer, page pagination.Page, filters ...filter) ([]Issue, error) { 248 + func GetIssuesPaginated(e Execer, page pagination.Page, filters ...Filter) ([]Issue, error) { 249 249 issueMap := make(map[string]*Issue) // at-uri -> issue 250 250 251 251 var conditions []string ··· 395 395 return issues, nil 396 396 } 397 397 398 - func GetIssues(e Execer, filters ...filter) ([]Issue, error) { 398 + func GetIssues(e Execer, filters ...Filter) ([]Issue, error) { 399 399 return GetIssuesPaginated(e, pagination.Page{No: 0, Count: 10}, filters...) 400 400 } 401 401 ··· 462 462 return id, nil 463 463 } 464 464 465 - func DeleteIssueComments(e Execer, filters ...filter) error { 465 + func DeleteIssueComments(e Execer, filters ...Filter) error { 466 466 var conditions []string 467 467 var args []any 468 468 for _, filter := range filters { ··· 481 481 return err 482 482 } 483 483 484 - func GetIssueComments(e Execer, filters ...filter) ([]IssueComment, error) { 484 + func GetIssueComments(e Execer, filters ...Filter) ([]IssueComment, error) { 485 485 var comments []IssueComment 486 486 487 487 var conditions []string ··· 571 571 return comments, nil 572 572 } 573 573 574 - func DeleteIssues(e Execer, filters ...filter) error { 574 + func DeleteIssues(e Execer, filters ...Filter) error { 575 575 var conditions []string 576 576 var args []any 577 577 for _, filter := range filters { ··· 589 589 return err 590 590 } 591 591 592 - func CloseIssues(e Execer, filters ...filter) error { 592 + func CloseIssues(e Execer, filters ...Filter) error { 593 593 var conditions []string 594 594 var args []any 595 595 for _, filter := range filters { ··· 607 607 return err 608 608 } 609 609 610 - func ReopenIssues(e Execer, filters ...filter) error { 610 + func ReopenIssues(e Execer, filters ...Filter) error { 611 611 var conditions []string 612 612 var args []any 613 613 for _, filter := range filters {
+1 -1
appview/db/language.go
··· 16 16 Bytes int64 17 17 } 18 18 19 - func GetRepoLanguages(e Execer, filters ...filter) ([]RepoLanguage, error) { 19 + func GetRepoLanguages(e Execer, filters ...Filter) ([]RepoLanguage, error) { 20 20 var conditions []string 21 21 var args []any 22 22 for _, filter := range filters {
+62 -14
appview/db/pipeline.go
··· 131 131 ExitCode int 132 132 } 133 133 134 - func GetPipelines(e Execer, filters ...filter) ([]Pipeline, error) { 134 + func GetPipelines(e Execer, filters ...Filter) ([]Pipeline, error) { 135 135 var pipelines []Pipeline 136 136 137 137 var conditions []string ··· 290 290 291 291 // this is a mega query, but the most useful one: 292 292 // get N pipelines, for each one get the latest status of its N workflows 293 - func GetPipelineStatuses(e Execer, filters ...filter) ([]Pipeline, error) { 293 + func GetPipelineStatuses(e Execer, filters ...Filter) ([]Pipeline, error) { 294 294 var conditions []string 295 295 var args []any 296 296 for _, filter := range filters { ··· 305 305 } 306 306 307 307 query := fmt.Sprintf(` 308 + with ranked_pipelines as ( 309 + select 310 + p.id, 311 + p.knot, 312 + p.rkey, 313 + p.repo_owner, 314 + p.repo_name, 315 + p.sha, 316 + p.created, 317 + t.id, 318 + t.kind, 319 + t.push_ref, 320 + t.push_new_sha, 321 + t.push_old_sha, 322 + t.pr_source_branch, 323 + t.pr_target_branch, 324 + t.pr_source_sha, 325 + t.pr_action, 326 + row_number() over (order by p.created desc) as row_num 327 + from 328 + pipelines p 329 + join 330 + triggers t ON p.trigger_id = t.id 331 + ) 308 332 select 309 333 p.id, 310 334 p.knot, ··· 313 337 p.repo_name, 314 338 p.sha, 315 339 p.created, 316 - t.id, 317 - t.kind, 318 - t.push_ref, 319 - t.push_new_sha, 320 - t.push_old_sha, 321 - t.pr_source_branch, 322 - t.pr_target_branch, 323 - t.pr_source_sha, 324 - t.pr_action 340 + p.id, 341 + p.kind, 342 + p.push_ref, 343 + p.push_new_sha, 344 + p.push_old_sha, 345 + p.pr_source_branch, 346 + p.pr_target_branch, 347 + p.pr_source_sha, 348 + p.pr_action 325 349 from 326 - pipelines p 327 - join 328 - triggers t ON p.trigger_id = t.id 350 + ranked_pipelines p 329 351 %s 330 352 `, whereClause) 331 353 ··· 485 507 486 508 return all, nil 487 509 } 510 + 511 + // get pipeline counts, implement with filters 512 + func GetPipelineCount(e Execer, filters ...Filter) (int, error) { 513 + var conditions []string 514 + var args []any 515 + for _, filter := range filters { 516 + conditions = append(conditions, filter.Condition()) 517 + args = append(args, filter.Arg()...) 518 + } 519 + 520 + whereClause := "" 521 + if conditions != nil { 522 + whereClause = " where " + strings.Join(conditions, " and ") 523 + } 524 + 525 + query := fmt.Sprintf(`select count(*) as count from pipelines %s`, whereClause) 526 + 527 + row := e.QueryRow(query, args...) 528 + 529 + var count int 530 + if err := row.Scan(&count); err != nil { 531 + return 0, err 532 + } 533 + 534 + return count, nil 535 + }
+1 -1
appview/db/profile.go
··· 366 366 return tx.Commit() 367 367 } 368 368 369 - func GetProfiles(e Execer, filters ...filter) (map[string]*Profile, error) { 369 + func GetProfiles(e Execer, filters ...Filter) (map[string]*Profile, error) { 370 370 var conditions []string 371 371 var args []any 372 372 for _, filter := range filters {
+4 -4
appview/db/pulls.go
··· 311 311 return pullId - 1, err 312 312 } 313 313 314 - func GetPullsWithLimit(e Execer, limit int, filters ...filter) ([]*Pull, error) { 314 + func GetPullsWithLimit(e Execer, limit int, filters ...Filter) ([]*Pull, error) { 315 315 pulls := make(map[int]*Pull) 316 316 317 317 var conditions []string ··· 529 529 return orderedByPullId, nil 530 530 } 531 531 532 - func GetPulls(e Execer, filters ...filter) ([]*Pull, error) { 532 + func GetPulls(e Execer, filters ...Filter) ([]*Pull, error) { 533 533 return GetPullsWithLimit(e, 0, filters...) 534 534 } 535 535 ··· 884 884 return err 885 885 } 886 886 887 - func SetPullParentChangeId(e Execer, parentChangeId string, filters ...filter) error { 887 + func SetPullParentChangeId(e Execer, parentChangeId string, filters ...Filter) error { 888 888 var conditions []string 889 889 var args []any 890 890 ··· 908 908 909 909 // Only used when stacking to update contents in the event of a rebase (the interdiff should be empty). 910 910 // otherwise submissions are immutable 911 - func UpdatePull(e Execer, newPatch, sourceRev string, filters ...filter) error { 911 + func UpdatePull(e Execer, newPatch, sourceRev string, filters ...Filter) error { 912 912 var conditions []string 913 913 var args []any 914 914
+1 -1
appview/db/punchcard.go
··· 29 29 Punches []Punch 30 30 } 31 31 32 - func MakePunchcard(e Execer, filters ...filter) (*Punchcard, error) { 32 + func MakePunchcard(e Execer, filters ...Filter) (*Punchcard, error) { 33 33 punchcard := &Punchcard{} 34 34 now := time.Now() 35 35 startOfYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC)
+3 -3
appview/db/registration.go
··· 48 48 NeedsUpgrade 49 49 ) 50 50 51 - func GetRegistrations(e Execer, filters ...filter) ([]Registration, error) { 51 + func GetRegistrations(e Execer, filters ...Filter) ([]Registration, error) { 52 52 var registrations []Registration 53 53 54 54 var conditions []string ··· 108 108 return registrations, nil 109 109 } 110 110 111 - func MarkRegistered(e Execer, filters ...filter) error { 111 + func MarkRegistered(e Execer, filters ...Filter) error { 112 112 var conditions []string 113 113 var args []any 114 114 for _, filter := range filters { ··· 133 133 return err 134 134 } 135 135 136 - func DeleteKnot(e Execer, filters ...filter) error { 136 + func DeleteKnot(e Execer, filters ...Filter) error { 137 137 var conditions []string 138 138 var args []any 139 139 for _, filter := range filters {
+2 -2
appview/db/repos.go
··· 39 39 return p 40 40 } 41 41 42 - func GetRepos(e Execer, limit int, filters ...filter) ([]Repo, error) { 42 + func GetRepos(e Execer, limit int, filters ...Filter) ([]Repo, error) { 43 43 repoMap := make(map[syntax.ATURI]*Repo) 44 44 45 45 var conditions []string ··· 285 285 return repos, nil 286 286 } 287 287 288 - func CountRepos(e Execer, filters ...filter) (int64, error) { 288 + func CountRepos(e Execer, filters ...Filter) (int64, error) { 289 289 var conditions []string 290 290 var args []any 291 291 for _, filter := range filters {
+5 -5
appview/db/spindle.go
··· 27 27 Created time.Time 28 28 } 29 29 30 - func GetSpindles(e Execer, filters ...filter) ([]Spindle, error) { 30 + func GetSpindles(e Execer, filters ...Filter) ([]Spindle, error) { 31 31 var spindles []Spindle 32 32 33 33 var conditions []string ··· 109 109 return err 110 110 } 111 111 112 - func VerifySpindle(e Execer, filters ...filter) (int64, error) { 112 + func VerifySpindle(e Execer, filters ...Filter) (int64, error) { 113 113 var conditions []string 114 114 var args []any 115 115 for _, filter := range filters { ··· 132 132 return res.RowsAffected() 133 133 } 134 134 135 - func DeleteSpindle(e Execer, filters ...filter) error { 135 + func DeleteSpindle(e Execer, filters ...Filter) error { 136 136 var conditions []string 137 137 var args []any 138 138 for _, filter := range filters { ··· 162 162 return err 163 163 } 164 164 165 - func RemoveSpindleMember(e Execer, filters ...filter) error { 165 + func RemoveSpindleMember(e Execer, filters ...Filter) error { 166 166 var conditions []string 167 167 var args []any 168 168 for _, filter := range filters { ··· 181 181 return err 182 182 } 183 183 184 - func GetSpindleMembers(e Execer, filters ...filter) ([]SpindleMember, error) { 184 + func GetSpindleMembers(e Execer, filters ...Filter) ([]SpindleMember, error) { 185 185 var members []SpindleMember 186 186 187 187 var conditions []string
+2 -2
appview/db/star.go
··· 102 102 } 103 103 } 104 104 105 - func GetStars(e Execer, limit int, filters ...filter) ([]Star, error) { 105 + func GetStars(e Execer, limit int, filters ...Filter) ([]Star, error) { 106 106 var conditions []string 107 107 var args []any 108 108 for _, filter := range filters { ··· 185 185 return stars, nil 186 186 } 187 187 188 - func CountStars(e Execer, filters ...filter) (int64, error) { 188 + func CountStars(e Execer, filters ...Filter) (int64, error) { 189 189 var conditions []string 190 190 var args []any 191 191 for _, filter := range filters {
+3 -3
appview/db/strings.go
··· 123 123 return err 124 124 } 125 125 126 - func GetStrings(e Execer, limit int, filters ...filter) ([]String, error) { 126 + func GetStrings(e Execer, limit int, filters ...Filter) ([]String, error) { 127 127 var all []String 128 128 129 129 var conditions []string ··· 206 206 return all, nil 207 207 } 208 208 209 - func CountStrings(e Execer, filters ...filter) (int64, error) { 209 + func CountStrings(e Execer, filters ...Filter) (int64, error) { 210 210 var conditions []string 211 211 var args []any 212 212 for _, filter := range filters { ··· 230 230 return count, nil 231 231 } 232 232 233 - func DeleteString(e Execer, filters ...filter) error { 233 + func DeleteString(e Execer, filters ...Filter) error { 234 234 var conditions []string 235 235 var args []any 236 236 for _, filter := range filters {
+1
appview/pages/pages.go
··· 1212 1212 LoggedInUser *oauth.User 1213 1213 RepoInfo repoinfo.RepoInfo 1214 1214 Pipelines []db.Pipeline 1215 + Pagination pagination.Pagination 1215 1216 Active string 1216 1217 } 1217 1218
+33 -1
appview/pages/templates/repo/pipelines/pipelines.html
··· 7 7 {{ end }} 8 8 9 9 {{ define "repoContent" }} 10 - <div class="flex justify-between items-center gap-4"> 10 + <div class="flex flex-col justify-between items-center gap-4"> 11 11 <div class="w-full flex flex-col gap-2"> 12 12 {{ range .Pipelines }} 13 13 {{ block "pipeline" (list $ .) }} {{ end }} ··· 17 17 </p> 18 18 {{ end }} 19 19 </div> 20 + {{ block "pagination" . }} {{ end }} 20 21 </div> 21 22 {{ end }} 22 23 ··· 100 101 </div> 101 102 {{ end }} 102 103 {{ end }} 104 + 105 + {{ define "pagination" }} 106 + {{ $currentPage := .Pagination.CurrentPage }} 107 + <div class="flex place-self-end mt-4 gap-2"> 108 + {{ if gt $currentPage.No 0 }} 109 + {{ $prev := .Pagination.PreviousPage }} 110 + <a 111 + class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700" 112 + hx-boost="true" 113 + href = "/{{ $.RepoInfo.FullName }}/pipelines?page={{ add $prev.No 1 }}&count={{ $prev.Count }}" 114 + > 115 + {{ i "chevron-left" "w-4 h-4" }} 116 + previous 117 + </a> 118 + {{ else }} 119 + <div></div> 120 + {{ end }} 121 + 122 + {{ if lt (add $currentPage.No 1) .Pagination.TotalPageCount }} 123 + {{ $next := .Pagination.NextPage }} 124 + <a 125 + class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700" 126 + hx-boost="true" 127 + href = "/{{ $.RepoInfo.FullName }}/pipelines?page={{ add $next.No 1 }}&count={{ $next.Count }}" 128 + > 129 + next 130 + {{ i "chevron-right" "w-4 h-4" }} 131 + </a> 132 + {{ end }} 133 + </div> 134 + {{ end }}
+29 -3
appview/pipelines/pipelines.go
··· 13 13 "tangled.sh/tangled.sh/core/appview/db" 14 14 "tangled.sh/tangled.sh/core/appview/oauth" 15 15 "tangled.sh/tangled.sh/core/appview/pages" 16 + "tangled.sh/tangled.sh/core/appview/pagination" 16 17 "tangled.sh/tangled.sh/core/appview/reporesolver" 17 18 "tangled.sh/tangled.sh/core/eventconsumer" 18 19 "tangled.sh/tangled.sh/core/idresolver" ··· 71 72 } 72 73 73 74 repoInfo := f.RepoInfo(user) 74 - 75 - ps, err := db.GetPipelineStatuses( 76 - p.db, 75 + filters := []db.Filter{ 77 76 db.FilterEq("repo_owner", repoInfo.OwnerDid), 78 77 db.FilterEq("repo_name", repoInfo.Name), 79 78 db.FilterEq("knot", repoInfo.Knot), 79 + } 80 + 81 + page, ok := r.Context().Value("page").(pagination.Page) 82 + if !ok { 83 + l.Error("failed to get page") 84 + page = pagination.Page{No: 0, Count: 16} 85 + } 86 + pipelinesCount, err := db.GetPipelineCount(p.db, filters...) 87 + if err != nil { 88 + l.Error("failed to get pipeline count", "err", err) 89 + p.pages.Notice(w, "pipelines", "Failed to load pipelines. Try again later.") 90 + return 91 + } 92 + paginate := pagination.NewFromPage(page, pipelinesCount) 93 + 94 + currentPage := paginate.CurrentPage() 95 + filters = append(filters, db.FilterBetween( 96 + "row_num", 97 + currentPage.Count*currentPage.No+1, 98 + currentPage.Count*(currentPage.No+1), 99 + )) 100 + 101 + ps, err := db.GetPipelineStatuses( 102 + p.db, 103 + filters..., 80 104 ) 81 105 if err != nil { 82 106 l.Error("failed to query db", "err", err) 107 + p.pages.Notice(w, "pipelines", "Failed to load pipelines. Try again later.") 83 108 return 84 109 } 85 110 86 111 p.pages.Pipelines(w, pages.PipelinesParams{ 87 112 LoggedInUser: user, 88 113 RepoInfo: repoInfo, 114 + Pagination: paginate, 89 115 Pipelines: ps, 90 116 }) 91 117 }
+1 -1
appview/pipelines/router.go
··· 9 9 10 10 func (p *Pipelines) Router(mw *middleware.Middleware) http.Handler { 11 11 r := chi.NewRouter() 12 - r.Get("/", p.Index) 12 + r.With(middleware.Paginate(16)).Get("/", p.Index) 13 13 r.Get("/{pipeline}/workflow/{workflow}", p.Workflow) 14 14 r.Get("/{pipeline}/workflow/{workflow}/logs", p.Logs) 15 15