From 0ce33c11be13f8a8327d840ca54aeb659f0ee05c Mon Sep 17 00:00:00 2001 From: dusk Date: Wed, 20 Aug 2025 23:27:42 +0300 Subject: [PATCH] appview: implement pagination for pipelines index Change-Id: usozmpkmzzzlvrotyyumquvwtrwuulqt Signed-off-by: dusk --- appview/db/artifact.go | 4 +- appview/db/collaborators.go | 2 +- appview/db/db.go | 26 +++---- appview/db/follow.go | 2 +- appview/db/issues.go | 14 ++-- appview/db/language.go | 2 +- appview/db/pipeline.go | 76 +++++++++++++++---- appview/db/profile.go | 2 +- appview/db/pulls.go | 8 +- appview/db/punchcard.go | 2 +- appview/db/registration.go | 6 +- appview/db/repos.go | 4 +- appview/db/spindle.go | 10 +-- appview/db/star.go | 4 +- appview/db/strings.go | 6 +- appview/pages/pages.go | 1 + .../templates/repo/pipelines/pipelines.html | 34 ++++++++- appview/pipelines/pipelines.go | 32 +++++++- appview/pipelines/router.go | 2 +- 19 files changed, 172 insertions(+), 65 deletions(-) diff --git a/appview/db/artifact.go b/appview/db/artifact.go index e021d9c7..3c085967 100644 --- a/appview/db/artifact.go +++ b/appview/db/artifact.go @@ -57,7 +57,7 @@ func AddArtifact(e Execer, artifact Artifact) error { return err } -func GetArtifact(e Execer, filters ...filter) ([]Artifact, error) { +func GetArtifact(e Execer, filters ...Filter) ([]Artifact, error) { var artifacts []Artifact var conditions []string @@ -130,7 +130,7 @@ func GetArtifact(e Execer, filters ...filter) ([]Artifact, error) { return artifacts, nil } -func DeleteArtifact(e Execer, filters ...filter) error { +func DeleteArtifact(e Execer, filters ...Filter) error { var conditions []string var args []any for _, filter := range filters { diff --git a/appview/db/collaborators.go b/appview/db/collaborators.go index cf3b2ceb..1b4108a2 100644 --- a/appview/db/collaborators.go +++ b/appview/db/collaborators.go @@ -30,7 +30,7 @@ func AddCollaborator(e Execer, c Collaborator) error { return err } -func DeleteCollaborator(e Execer, filters ...filter) error { +func DeleteCollaborator(e Execer, filters ...Filter) error { var conditions []string var args []any for _, filter := range filters { diff --git a/appview/db/db.go b/appview/db/db.go index fb88381a..d79ee58e 100644 --- a/appview/db/db.go +++ b/appview/db/db.go @@ -918,32 +918,32 @@ func (d *DB) Close() error { return d.DB.Close() } -type filter struct { +type Filter struct { key string arg any cmp string } -func newFilter(key, cmp string, arg any) filter { - return filter{ +func newFilter(key, cmp string, arg any) Filter { + return Filter{ key: key, arg: arg, cmp: cmp, } } -func FilterEq(key string, arg any) filter { return newFilter(key, "=", arg) } -func FilterNotEq(key string, arg any) filter { return newFilter(key, "<>", arg) } -func FilterGte(key string, arg any) filter { return newFilter(key, ">=", arg) } -func FilterLte(key string, arg any) filter { return newFilter(key, "<=", arg) } -func FilterIs(key string, arg any) filter { return newFilter(key, "is", arg) } -func FilterIsNot(key string, arg any) filter { return newFilter(key, "is not", arg) } -func FilterIn(key string, arg any) filter { return newFilter(key, "in", arg) } -func FilterBetween(key string, arg1, arg2 any) filter { +func FilterEq(key string, arg any) Filter { return newFilter(key, "=", arg) } +func FilterNotEq(key string, arg any) Filter { return newFilter(key, "<>", arg) } +func FilterGte(key string, arg any) Filter { return newFilter(key, ">=", arg) } +func FilterLte(key string, arg any) Filter { return newFilter(key, "<=", arg) } +func FilterIs(key string, arg any) Filter { return newFilter(key, "is", arg) } +func FilterIsNot(key string, arg any) Filter { return newFilter(key, "is not", arg) } +func FilterIn(key string, arg any) Filter { return newFilter(key, "in", arg) } +func FilterBetween(key string, arg1, arg2 any) Filter { return newFilter(key, "between", []any{arg1, arg2}) } -func (f filter) Condition() string { +func (f Filter) Condition() string { rv := reflect.ValueOf(f.arg) kind := rv.Kind() @@ -969,7 +969,7 @@ func (f filter) Condition() string { return fmt.Sprintf("%s %s ?", f.key, f.cmp) } -func (f filter) Arg() []any { +func (f Filter) Arg() []any { rv := reflect.ValueOf(f.arg) kind := rv.Kind() if (kind == reflect.Slice && rv.Type().Elem().Kind() != reflect.Uint8) || kind == reflect.Array { diff --git a/appview/db/follow.go b/appview/db/follow.go index 6d4ba04f..51dfec74 100644 --- a/appview/db/follow.go +++ b/appview/db/follow.go @@ -144,7 +144,7 @@ func GetFollowerFollowingCounts(e Execer, dids []string) (map[string]FollowStats return result, nil } -func GetFollows(e Execer, limit int, filters ...filter) ([]Follow, error) { +func GetFollows(e Execer, limit int, filters ...Filter) ([]Follow, error) { var follows []Follow var conditions []string diff --git a/appview/db/issues.go b/appview/db/issues.go index e437a6b3..cb9b0c5b 100644 --- a/appview/db/issues.go +++ b/appview/db/issues.go @@ -245,7 +245,7 @@ func updateIssue(tx *sql.Tx, issue *Issue) error { return err } -func GetIssuesPaginated(e Execer, page pagination.Page, filters ...filter) ([]Issue, error) { +func GetIssuesPaginated(e Execer, page pagination.Page, filters ...Filter) ([]Issue, error) { issueMap := make(map[string]*Issue) // at-uri -> issue var conditions []string @@ -395,7 +395,7 @@ func GetIssuesPaginated(e Execer, page pagination.Page, filters ...filter) ([]Is return issues, nil } -func GetIssues(e Execer, filters ...filter) ([]Issue, error) { +func GetIssues(e Execer, filters ...Filter) ([]Issue, error) { return GetIssuesPaginated(e, pagination.Page{No: 0, Count: 10}, filters...) } @@ -462,7 +462,7 @@ func AddIssueComment(e Execer, c IssueComment) (int64, error) { return id, nil } -func DeleteIssueComments(e Execer, filters ...filter) error { +func DeleteIssueComments(e Execer, filters ...Filter) error { var conditions []string var args []any for _, filter := range filters { @@ -481,7 +481,7 @@ func DeleteIssueComments(e Execer, filters ...filter) error { return err } -func GetIssueComments(e Execer, filters ...filter) ([]IssueComment, error) { +func GetIssueComments(e Execer, filters ...Filter) ([]IssueComment, error) { var comments []IssueComment var conditions []string @@ -571,7 +571,7 @@ func GetIssueComments(e Execer, filters ...filter) ([]IssueComment, error) { return comments, nil } -func DeleteIssues(e Execer, filters ...filter) error { +func DeleteIssues(e Execer, filters ...Filter) error { var conditions []string var args []any for _, filter := range filters { @@ -589,7 +589,7 @@ func DeleteIssues(e Execer, filters ...filter) error { return err } -func CloseIssues(e Execer, filters ...filter) error { +func CloseIssues(e Execer, filters ...Filter) error { var conditions []string var args []any for _, filter := range filters { @@ -607,7 +607,7 @@ func CloseIssues(e Execer, filters ...filter) error { return err } -func ReopenIssues(e Execer, filters ...filter) error { +func ReopenIssues(e Execer, filters ...Filter) error { var conditions []string var args []any for _, filter := range filters { diff --git a/appview/db/language.go b/appview/db/language.go index 73450acb..ce199514 100644 --- a/appview/db/language.go +++ b/appview/db/language.go @@ -16,7 +16,7 @@ type RepoLanguage struct { Bytes int64 } -func GetRepoLanguages(e Execer, filters ...filter) ([]RepoLanguage, error) { +func GetRepoLanguages(e Execer, filters ...Filter) ([]RepoLanguage, error) { var conditions []string var args []any for _, filter := range filters { diff --git a/appview/db/pipeline.go b/appview/db/pipeline.go index b13647ff..636b9952 100644 --- a/appview/db/pipeline.go +++ b/appview/db/pipeline.go @@ -131,7 +131,7 @@ type PipelineStatus struct { ExitCode int } -func GetPipelines(e Execer, filters ...filter) ([]Pipeline, error) { +func GetPipelines(e Execer, filters ...Filter) ([]Pipeline, error) { var pipelines []Pipeline var conditions []string @@ -290,7 +290,7 @@ func AddPipelineStatus(e Execer, status PipelineStatus) error { // this is a mega query, but the most useful one: // get N pipelines, for each one get the latest status of its N workflows -func GetPipelineStatuses(e Execer, filters ...filter) ([]Pipeline, error) { +func GetPipelineStatuses(e Execer, filters ...Filter) ([]Pipeline, error) { var conditions []string var args []any for _, filter := range filters { @@ -305,6 +305,30 @@ func GetPipelineStatuses(e Execer, filters ...filter) ([]Pipeline, error) { } query := fmt.Sprintf(` + with ranked_pipelines as ( + select + p.id, + p.knot, + p.rkey, + p.repo_owner, + p.repo_name, + p.sha, + p.created, + t.id, + t.kind, + t.push_ref, + t.push_new_sha, + t.push_old_sha, + t.pr_source_branch, + t.pr_target_branch, + t.pr_source_sha, + t.pr_action, + row_number() over (order by p.created desc) as row_num + from + pipelines p + join + triggers t ON p.trigger_id = t.id + ) select p.id, p.knot, @@ -313,19 +337,17 @@ func GetPipelineStatuses(e Execer, filters ...filter) ([]Pipeline, error) { p.repo_name, p.sha, p.created, - t.id, - t.kind, - t.push_ref, - t.push_new_sha, - t.push_old_sha, - t.pr_source_branch, - t.pr_target_branch, - t.pr_source_sha, - t.pr_action + p.id, + p.kind, + p.push_ref, + p.push_new_sha, + p.push_old_sha, + p.pr_source_branch, + p.pr_target_branch, + p.pr_source_sha, + p.pr_action from - pipelines p - join - triggers t ON p.trigger_id = t.id + ranked_pipelines p %s `, whereClause) @@ -485,3 +507,29 @@ func GetPipelineStatuses(e Execer, filters ...filter) ([]Pipeline, error) { return all, nil } + +// get pipeline counts, implement with filters +func GetPipelineCount(e Execer, filters ...Filter) (int, error) { + var conditions []string + var args []any + for _, filter := range filters { + conditions = append(conditions, filter.Condition()) + args = append(args, filter.Arg()...) + } + + whereClause := "" + if conditions != nil { + whereClause = " where " + strings.Join(conditions, " and ") + } + + query := fmt.Sprintf(`select count(*) as count from pipelines %s`, whereClause) + + row := e.QueryRow(query, args...) + + var count int + if err := row.Scan(&count); err != nil { + return 0, err + } + + return count, nil +} diff --git a/appview/db/profile.go b/appview/db/profile.go index fed673cf..f5aeeaa3 100644 --- a/appview/db/profile.go +++ b/appview/db/profile.go @@ -366,7 +366,7 @@ func UpsertProfile(tx *sql.Tx, profile *Profile) error { return tx.Commit() } -func GetProfiles(e Execer, filters ...filter) (map[string]*Profile, error) { +func GetProfiles(e Execer, filters ...Filter) (map[string]*Profile, error) { var conditions []string var args []any for _, filter := range filters { diff --git a/appview/db/pulls.go b/appview/db/pulls.go index ae332815..45891f9e 100644 --- a/appview/db/pulls.go +++ b/appview/db/pulls.go @@ -311,7 +311,7 @@ func NextPullId(e Execer, repoAt syntax.ATURI) (int, error) { return pullId - 1, err } -func GetPullsWithLimit(e Execer, limit int, filters ...filter) ([]*Pull, error) { +func GetPullsWithLimit(e Execer, limit int, filters ...Filter) ([]*Pull, error) { pulls := make(map[int]*Pull) var conditions []string @@ -529,7 +529,7 @@ func GetPullsWithLimit(e Execer, limit int, filters ...filter) ([]*Pull, error) return orderedByPullId, nil } -func GetPulls(e Execer, filters ...filter) ([]*Pull, error) { +func GetPulls(e Execer, filters ...Filter) ([]*Pull, error) { return GetPullsWithLimit(e, 0, filters...) } @@ -884,7 +884,7 @@ func ResubmitPull(e Execer, pull *Pull, newPatch, sourceRev string) error { return err } -func SetPullParentChangeId(e Execer, parentChangeId string, filters ...filter) error { +func SetPullParentChangeId(e Execer, parentChangeId string, filters ...Filter) error { var conditions []string var args []any @@ -908,7 +908,7 @@ func SetPullParentChangeId(e Execer, parentChangeId string, filters ...filter) e // Only used when stacking to update contents in the event of a rebase (the interdiff should be empty). // otherwise submissions are immutable -func UpdatePull(e Execer, newPatch, sourceRev string, filters ...filter) error { +func UpdatePull(e Execer, newPatch, sourceRev string, filters ...Filter) error { var conditions []string var args []any diff --git a/appview/db/punchcard.go b/appview/db/punchcard.go index 91dde121..9bd837b3 100644 --- a/appview/db/punchcard.go +++ b/appview/db/punchcard.go @@ -29,7 +29,7 @@ type Punchcard struct { Punches []Punch } -func MakePunchcard(e Execer, filters ...filter) (*Punchcard, error) { +func MakePunchcard(e Execer, filters ...Filter) (*Punchcard, error) { punchcard := &Punchcard{} now := time.Now() startOfYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC) diff --git a/appview/db/registration.go b/appview/db/registration.go index 371ee04e..02242423 100644 --- a/appview/db/registration.go +++ b/appview/db/registration.go @@ -48,7 +48,7 @@ const ( NeedsUpgrade ) -func GetRegistrations(e Execer, filters ...filter) ([]Registration, error) { +func GetRegistrations(e Execer, filters ...Filter) ([]Registration, error) { var registrations []Registration var conditions []string @@ -108,7 +108,7 @@ func GetRegistrations(e Execer, filters ...filter) ([]Registration, error) { return registrations, nil } -func MarkRegistered(e Execer, filters ...filter) error { +func MarkRegistered(e Execer, filters ...Filter) error { var conditions []string var args []any for _, filter := range filters { @@ -133,7 +133,7 @@ func AddKnot(e Execer, domain, did string) error { return err } -func DeleteKnot(e Execer, filters ...filter) error { +func DeleteKnot(e Execer, filters ...Filter) error { var conditions []string var args []any for _, filter := range filters { diff --git a/appview/db/repos.go b/appview/db/repos.go index 17c5a159..b3aa1219 100644 --- a/appview/db/repos.go +++ b/appview/db/repos.go @@ -39,7 +39,7 @@ func (r Repo) DidSlashRepo() string { return p } -func GetRepos(e Execer, limit int, filters ...filter) ([]Repo, error) { +func GetRepos(e Execer, limit int, filters ...Filter) ([]Repo, error) { repoMap := make(map[syntax.ATURI]*Repo) var conditions []string @@ -285,7 +285,7 @@ func GetRepos(e Execer, limit int, filters ...filter) ([]Repo, error) { return repos, nil } -func CountRepos(e Execer, filters ...filter) (int64, error) { +func CountRepos(e Execer, filters ...Filter) (int64, error) { var conditions []string var args []any for _, filter := range filters { diff --git a/appview/db/spindle.go b/appview/db/spindle.go index d587fcfe..d10c8673 100644 --- a/appview/db/spindle.go +++ b/appview/db/spindle.go @@ -27,7 +27,7 @@ type SpindleMember struct { Created time.Time } -func GetSpindles(e Execer, filters ...filter) ([]Spindle, error) { +func GetSpindles(e Execer, filters ...Filter) ([]Spindle, error) { var spindles []Spindle var conditions []string @@ -109,7 +109,7 @@ func AddSpindle(e Execer, spindle Spindle) error { return err } -func VerifySpindle(e Execer, filters ...filter) (int64, error) { +func VerifySpindle(e Execer, filters ...Filter) (int64, error) { var conditions []string var args []any for _, filter := range filters { @@ -132,7 +132,7 @@ func VerifySpindle(e Execer, filters ...filter) (int64, error) { return res.RowsAffected() } -func DeleteSpindle(e Execer, filters ...filter) error { +func DeleteSpindle(e Execer, filters ...Filter) error { var conditions []string var args []any for _, filter := range filters { @@ -162,7 +162,7 @@ func AddSpindleMember(e Execer, member SpindleMember) error { return err } -func RemoveSpindleMember(e Execer, filters ...filter) error { +func RemoveSpindleMember(e Execer, filters ...Filter) error { var conditions []string var args []any for _, filter := range filters { @@ -181,7 +181,7 @@ func RemoveSpindleMember(e Execer, filters ...filter) error { return err } -func GetSpindleMembers(e Execer, filters ...filter) ([]SpindleMember, error) { +func GetSpindleMembers(e Execer, filters ...Filter) ([]SpindleMember, error) { var members []SpindleMember var conditions []string diff --git a/appview/db/star.go b/appview/db/star.go index b7b44fc7..f9052d39 100644 --- a/appview/db/star.go +++ b/appview/db/star.go @@ -152,7 +152,7 @@ func GetStarStatus(e Execer, userDid string, repoAt syntax.ATURI) bool { func GetStarStatuses(e Execer, userDid string, repoAts []syntax.ATURI) (map[string]bool, error) { return getStarStatuses(e, userDid, repoAts) } -func GetStars(e Execer, limit int, filters ...filter) ([]Star, error) { +func GetStars(e Execer, limit int, filters ...Filter) ([]Star, error) { var conditions []string var args []any for _, filter := range filters { @@ -235,7 +235,7 @@ func GetStars(e Execer, limit int, filters ...filter) ([]Star, error) { return stars, nil } -func CountStars(e Execer, filters ...filter) (int64, error) { +func CountStars(e Execer, filters ...Filter) (int64, error) { var conditions []string var args []any for _, filter := range filters { diff --git a/appview/db/strings.go b/appview/db/strings.go index 5f47ecff..847473f1 100644 --- a/appview/db/strings.go +++ b/appview/db/strings.go @@ -123,7 +123,7 @@ func AddString(e Execer, s String) error { return err } -func GetStrings(e Execer, limit int, filters ...filter) ([]String, error) { +func GetStrings(e Execer, limit int, filters ...Filter) ([]String, error) { var all []String var conditions []string @@ -206,7 +206,7 @@ func GetStrings(e Execer, limit int, filters ...filter) ([]String, error) { return all, nil } -func CountStrings(e Execer, filters ...filter) (int64, error) { +func CountStrings(e Execer, filters ...Filter) (int64, error) { var conditions []string var args []any for _, filter := range filters { @@ -230,7 +230,7 @@ func CountStrings(e Execer, filters ...filter) (int64, error) { return count, nil } -func DeleteString(e Execer, filters ...filter) error { +func DeleteString(e Execer, filters ...Filter) error { var conditions []string var args []any for _, filter := range filters { diff --git a/appview/pages/pages.go b/appview/pages/pages.go index 7b165d3e..ba857dca 100644 --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -1212,6 +1212,7 @@ type PipelinesParams struct { LoggedInUser *oauth.User RepoInfo repoinfo.RepoInfo Pipelines []db.Pipeline + Pagination pagination.Pagination Active string } diff --git a/appview/pages/templates/repo/pipelines/pipelines.html b/appview/pages/templates/repo/pipelines/pipelines.html index 457c1897..06cf268f 100644 --- a/appview/pages/templates/repo/pipelines/pipelines.html +++ b/appview/pages/templates/repo/pipelines/pipelines.html @@ -7,7 +7,7 @@ {{ end }} {{ define "repoContent" }} -
+
{{ range .Pipelines }} {{ block "pipeline" (list $ .) }} {{ end }} @@ -17,6 +17,7 @@

{{ end }}
+ {{ block "pagination" . }} {{ end }}
{{ end }} @@ -100,3 +101,34 @@
{{ end }} {{ end }} + +{{ define "pagination" }} +{{ $currentPage := .Pagination.CurrentPage }} +
+ {{ if gt $currentPage.No 0 }} + {{ $prev := .Pagination.PreviousPage }} + + {{ i "chevron-left" "w-4 h-4" }} + previous + + {{ else }} +
+ {{ end }} + + {{ if lt (add $currentPage.No 1) .Pagination.TotalPageCount }} + {{ $next := .Pagination.NextPage }} + + next + {{ i "chevron-right" "w-4 h-4" }} + + {{ end }} +
+{{ end }} diff --git a/appview/pipelines/pipelines.go b/appview/pipelines/pipelines.go index c15d5270..e3b8af52 100644 --- a/appview/pipelines/pipelines.go +++ b/appview/pipelines/pipelines.go @@ -13,6 +13,7 @@ import ( "tangled.sh/tangled.sh/core/appview/db" "tangled.sh/tangled.sh/core/appview/oauth" "tangled.sh/tangled.sh/core/appview/pages" + "tangled.sh/tangled.sh/core/appview/pagination" "tangled.sh/tangled.sh/core/appview/reporesolver" "tangled.sh/tangled.sh/core/eventconsumer" "tangled.sh/tangled.sh/core/idresolver" @@ -71,21 +72,46 @@ func (p *Pipelines) Index(w http.ResponseWriter, r *http.Request) { } repoInfo := f.RepoInfo(user) - - ps, err := db.GetPipelineStatuses( - p.db, + filters := []db.Filter{ db.FilterEq("repo_owner", repoInfo.OwnerDid), db.FilterEq("repo_name", repoInfo.Name), db.FilterEq("knot", repoInfo.Knot), + } + + page, ok := r.Context().Value("page").(pagination.Page) + if !ok { + l.Error("failed to get page") + page = pagination.Page{No: 0, Count: 16} + } + pipelinesCount, err := db.GetPipelineCount(p.db, filters...) + if err != nil { + l.Error("failed to get pipeline count", "err", err) + p.pages.Notice(w, "pipelines", "Failed to load pipelines. Try again later.") + return + } + paginate := pagination.NewFromPage(page, pipelinesCount) + + currentPage := paginate.CurrentPage() + filters = append(filters, db.FilterBetween( + "row_num", + currentPage.Count*currentPage.No+1, + currentPage.Count*(currentPage.No+1), + )) + + ps, err := db.GetPipelineStatuses( + p.db, + filters..., ) if err != nil { l.Error("failed to query db", "err", err) + p.pages.Notice(w, "pipelines", "Failed to load pipelines. Try again later.") return } p.pages.Pipelines(w, pages.PipelinesParams{ LoggedInUser: user, RepoInfo: repoInfo, + Pagination: paginate, Pipelines: ps, }) } diff --git a/appview/pipelines/router.go b/appview/pipelines/router.go index a54e7117..2c096017 100644 --- a/appview/pipelines/router.go +++ b/appview/pipelines/router.go @@ -9,7 +9,7 @@ import ( func (p *Pipelines) Router(mw *middleware.Middleware) http.Handler { r := chi.NewRouter() - r.Get("/", p.Index) + r.With(middleware.Paginate(16)).Get("/", p.Index) r.Get("/{pipeline}/workflow/{workflow}", p.Workflow) r.Get("/{pipeline}/workflow/{workflow}/logs", p.Logs) -- 2.43.0