From 5ec085201e67ab19f5761f955886799ae8b93f5d Mon Sep 17 00:00:00 2001 From: dusk Date: Wed, 20 Aug 2025 23:10:51 +0300 Subject: [PATCH] appview: paginate pipelines index Signed-off-by: dusk --- appview/db/db.go | 7 +++ appview/db/pipeline.go | 46 ++++++++++++++----- appview/issues/issues.go | 2 +- appview/issues/router.go | 3 +- appview/middleware/middleware.go | 46 ++++++++++--------- appview/pages/pages.go | 1 + .../templates/repo/pipelines/pipelines.html | 33 ++++++++++++- appview/pagination/page.go | 6 +-- appview/pipelines/pipelines.go | 9 ++++ appview/pipelines/router.go | 3 +- 10 files changed, 115 insertions(+), 41 deletions(-) diff --git a/appview/db/db.go b/appview/db/db.go index afb4fe9..d986562 100644 --- a/appview/db/db.go +++ b/appview/db/db.go @@ -741,11 +741,18 @@ 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 { rv := reflect.ValueOf(f.arg) kind := rv.Kind() + if f.cmp == "between" { + return fmt.Sprintf("%s %s ? and ?", f.key, f.cmp) + } + // if we have `FilterIn(k, [1, 2, 3])`, compile it down to `k in (?, ?, ?)` if (kind == reflect.Slice && rv.Type().Elem().Kind() != reflect.Uint8) || kind == reflect.Array { if rv.Len() == 0 { diff --git a/appview/db/pipeline.go b/appview/db/pipeline.go index b13647f..249842a 100644 --- a/appview/db/pipeline.go +++ b/appview/db/pipeline.go @@ -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) diff --git a/appview/issues/issues.go b/appview/issues/issues.go index b46045f..f8e06f3 100644 --- a/appview/issues/issues.go +++ b/appview/issues/issues.go @@ -594,7 +594,7 @@ func (rp *Issues) RepoIssues(w http.ResponseWriter, r *http.Request) { page, ok := r.Context().Value("page").(pagination.Page) if !ok { log.Println("failed to get page") - page = pagination.FirstPage() + page = pagination.FirstPage(10) } user := rp.oauth.GetUser(r) diff --git a/appview/issues/router.go b/appview/issues/router.go index f2420da..804e847 100644 --- a/appview/issues/router.go +++ b/appview/issues/router.go @@ -5,13 +5,14 @@ import ( "github.com/go-chi/chi/v5" "tangled.sh/tangled.sh/core/appview/middleware" + "tangled.sh/tangled.sh/core/appview/pagination" ) func (i *Issues) Router(mw *middleware.Middleware) http.Handler { r := chi.NewRouter() r.Route("/", func(r chi.Router) { - r.With(middleware.Paginate).Get("/", i.RepoIssues) + r.With(middleware.Paginate(pagination.FirstPage(10))).Get("/", i.RepoIssues) r.Get("/{issue}", i.RepoSingleIssue) r.Group(func(r chi.Router) { diff --git a/appview/middleware/middleware.go b/appview/middleware/middleware.go index 94b1696..6c9d5f4 100644 --- a/appview/middleware/middleware.go +++ b/appview/middleware/middleware.go @@ -81,33 +81,35 @@ func AuthMiddleware(a *oauth.OAuth) middlewareFunc { } } -func Paginate(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - page := pagination.FirstPage() +func Paginate(firstPage pagination.Page) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + page := firstPage - offsetVal := r.URL.Query().Get("offset") - if offsetVal != "" { - offset, err := strconv.Atoi(offsetVal) - if err != nil { - log.Println("invalid offset") - } else { - page.Offset = offset + offsetVal := r.URL.Query().Get("offset") + if offsetVal != "" { + offset, err := strconv.Atoi(offsetVal) + if err != nil { + log.Println("invalid offset") + } else { + page.Offset = offset + } } - } - limitVal := r.URL.Query().Get("limit") - if limitVal != "" { - limit, err := strconv.Atoi(limitVal) - if err != nil { - log.Println("invalid limit") - } else { - page.Limit = limit + limitVal := r.URL.Query().Get("limit") + if limitVal != "" { + limit, err := strconv.Atoi(limitVal) + if err != nil { + log.Println("invalid limit") + } else { + page.Limit = limit + } } - } - 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 { diff --git a/appview/pages/pages.go b/appview/pages/pages.go index e12e3ee..d99dc1d 100644 --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -1094,6 +1094,7 @@ type PipelinesParams struct { LoggedInUser *oauth.User RepoInfo repoinfo.RepoInfo Pipelines []db.Pipeline + Page pagination.Page Active string } diff --git a/appview/pages/templates/repo/pipelines/pipelines.html b/appview/pages/templates/repo/pipelines/pipelines.html index 457c189..6484d5d 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,33 @@
{{ end }} {{ end }} + +{{ define "pagination" }} +
+ {{ if gt .Page.Offset 0 }} + {{ $prev := .Page.Previous }} + + {{ i "chevron-left" "w-4 h-4" }} + previous + + {{ else }} +
+ {{ end }} + + {{ if eq (len .Pipelines) .Page.Limit }} + {{ $next := .Page.Next }} + + next + {{ i "chevron-right" "w-4 h-4" }} + + {{ end }} +
+{{ end }} diff --git a/appview/pagination/page.go b/appview/pagination/page.go index e9f47fa..386bf5b 100644 --- a/appview/pagination/page.go +++ b/appview/pagination/page.go @@ -5,16 +5,16 @@ type Page struct { Limit int // number of items in a page } -func FirstPage() Page { +func FirstPage(limit int) Page { return Page{ Offset: 0, - Limit: 10, + Limit: limit, } } func (p Page) Previous() Page { if p.Offset-p.Limit < 0 { - return FirstPage() + return FirstPage(p.Limit) } else { return Page{ Offset: p.Offset - p.Limit, diff --git a/appview/pipelines/pipelines.go b/appview/pipelines/pipelines.go index c15d527..ce5dc38 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" @@ -70,6 +71,12 @@ func (p *Pipelines) Index(w http.ResponseWriter, r *http.Request) { return } + page, ok := r.Context().Value("page").(pagination.Page) + if !ok { + l.Error("failed to get page") + page = pagination.FirstPage(16) + } + repoInfo := f.RepoInfo(user) ps, err := db.GetPipelineStatuses( @@ -77,6 +84,7 @@ func (p *Pipelines) Index(w http.ResponseWriter, r *http.Request) { db.FilterEq("repo_owner", repoInfo.OwnerDid), db.FilterEq("repo_name", repoInfo.Name), db.FilterEq("knot", repoInfo.Knot), + db.FilterBetween("row_num", page.Offset+1, page.Offset+page.Limit), ) if err != nil { l.Error("failed to query db", "err", err) @@ -86,6 +94,7 @@ func (p *Pipelines) Index(w http.ResponseWriter, r *http.Request) { p.pages.Pipelines(w, pages.PipelinesParams{ LoggedInUser: user, RepoInfo: repoInfo, + Page: page, Pipelines: ps, }) } diff --git a/appview/pipelines/router.go b/appview/pipelines/router.go index a54e711..4b2e8e3 100644 --- a/appview/pipelines/router.go +++ b/appview/pipelines/router.go @@ -5,11 +5,12 @@ import ( "github.com/go-chi/chi/v5" "tangled.sh/tangled.sh/core/appview/middleware" + "tangled.sh/tangled.sh/core/appview/pagination" ) func (p *Pipelines) Router(mw *middleware.Middleware) http.Handler { r := chi.NewRouter() - r.Get("/", p.Index) + r.With(middleware.Paginate(pagination.FirstPage(16))).Get("/", p.Index) r.Get("/{pipeline}/workflow/{workflow}", p.Workflow) r.Get("/{pipeline}/workflow/{workflow}/logs", p.Logs) -- 2.43.0