appview: paginate pipelines index #518

closed
opened by ptr.pet targeting master from ptr.pet/core: pipeline-paginated
Changed files
+115 -41
appview
db
issues
middleware
pages
templates
repo
pipelines
pagination
pipelines
+7
appview/db/db.go
···
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 {
+34 -12
appview/db/pipeline.go
···
}
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,
···
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)
+1 -1
appview/issues/issues.go
···
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)
+2 -1
appview/issues/router.go
···
"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) {
+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(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 {
+1
appview/pages/pages.go
···
LoggedInUser *oauth.User
RepoInfo repoinfo.RepoInfo
Pipelines []db.Pipeline
+
Page pagination.Page
Active string
+32 -1
appview/pages/templates/repo/pipelines/pipelines.html
···
{{ end }}
{{ define "repoContent" }}
-
<div class="flex justify-between items-center gap-4">
+
<div class="flex flex-col justify-between items-center gap-4">
<div class="w-full flex flex-col gap-2">
{{ range .Pipelines }}
{{ block "pipeline" (list $ .) }} {{ end }}
···
</p>
{{ end }}
</div>
+
{{ block "pagination" . }} {{ end }}
</div>
{{ end }}
···
</div>
{{ end }}
{{ end }}
+
+
{{ define "pagination" }}
+
<div class="flex place-self-end mt-4 gap-2">
+
{{ if gt .Page.Offset 0 }}
+
{{ $prev := .Page.Previous }}
+
<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 }}/pipelines?&offset={{ $prev.Offset }}&limit={{ $prev.Limit }}"
+
>
+
{{ i "chevron-left" "w-4 h-4" }}
+
previous
+
</a>
+
{{ else }}
+
<div></div>
+
{{ end }}
+
+
{{ if eq (len .Pipelines) .Page.Limit }}
+
{{ $next := .Page.Next }}
+
<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 }}/pipelines?offset={{ $next.Offset }}&limit={{ $next.Limit }}"
+
>
+
next
+
{{ i "chevron-right" "w-4 h-4" }}
+
</a>
+
{{ end }}
+
</div>
+
{{ end }}
+3 -3
appview/pagination/page.go
···
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,
+9
appview/pipelines/pipelines.go
···
"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"
···
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(
···
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)
···
p.pages.Pipelines(w, pages.PipelinesParams{
LoggedInUser: user,
RepoInfo: repoInfo,
+
Page: page,
Pipelines: ps,
})
}
+2 -1
appview/pipelines/router.go
···
"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)