From 6946df193b0bfa5be04d5c2a24939b0abd079b96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Ga=C4=87e=C5=A1a?= Date: Mon, 31 Jul 2023 13:23:34 +0200 Subject: [PATCH] add pagination to the status check list API --- internal/api/controller/check/check_list.go | 27 ++++++++++--- internal/api/handler/check/check_list.go | 7 +++- internal/api/openapi/check.go | 24 ++++++----- internal/api/request/check.go | 19 +++++++++ internal/store/database.go | 5 ++- internal/store/database/check.go | 44 ++++++++++++++++++--- internal/store/database/space.go | 1 + types/check.go | 6 +++ 8 files changed, 107 insertions(+), 26 deletions(-) create mode 100644 internal/api/request/check.go diff --git a/internal/api/controller/check/check_list.go b/internal/api/controller/check/check_list.go index 1e3aae3b7..ff1e32dfe 100644 --- a/internal/api/controller/check/check_list.go +++ b/internal/api/controller/check/check_list.go @@ -9,6 +9,7 @@ import ( "fmt" "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/store/database/dbtx" "github.com/harness/gitness/types" "github.com/harness/gitness/types/enum" ) @@ -19,16 +20,32 @@ func (c *Controller) ListChecks( session *auth.Session, repoRef string, commitSHA string, -) ([]*types.Check, error) { + opts types.CheckListOptions, +) ([]types.Check, int, error) { repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView) if err != nil { - return nil, fmt.Errorf("failed to acquire access access to repo: %w", err) + return nil, 0, fmt.Errorf("failed to acquire access access to repo: %w", err) } - list, err := c.checkStore.List(ctx, repo.ID, commitSHA) + var checks []types.Check + var count int + + err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) (err error) { + count, err = c.checkStore.Count(ctx, repo.ID, commitSHA, opts) + if err != nil { + return fmt.Errorf("failed to count status check results for repo=%s: %w", repo.UID, err) + } + + checks, err = c.checkStore.List(ctx, repo.ID, commitSHA, opts) + if err != nil { + return fmt.Errorf("failed to list status check results for repo=%s: %w", repo.UID, err) + } + + return nil + }) if err != nil { - return nil, fmt.Errorf("failed to list status check results for repo=%s: %w", repo.UID, err) + return nil, 0, err } - return list, nil + return checks, count, nil } diff --git a/internal/api/handler/check/check_list.go b/internal/api/handler/check/check_list.go index 9747b140e..8e099cde3 100644 --- a/internal/api/handler/check/check_list.go +++ b/internal/api/handler/check/check_list.go @@ -30,12 +30,15 @@ func HandleCheckList(checkCtrl *check.Controller) http.HandlerFunc { return } - list, err := checkCtrl.ListChecks(ctx, session, repoRef, commitSHA) + opts := request.ParseCheckListOptions(r) + + checks, count, err := checkCtrl.ListChecks(ctx, session, repoRef, commitSHA, opts) if err != nil { render.TranslatedUserError(w, err) return } - render.JSON(w, http.StatusOK, list) + render.Pagination(r, w, opts.Page, opts.Size, count) + render.JSON(w, http.StatusOK, checks) } } diff --git a/internal/api/openapi/check.go b/internal/api/openapi/check.go index 1de65167a..00b7dd446 100644 --- a/internal/api/openapi/check.go +++ b/internal/api/openapi/check.go @@ -14,24 +14,17 @@ import ( "github.com/swaggest/openapi-go/openapi3" ) -type reportStatusCheckResultRequest struct { - repoRequest - CommitSHA string `path:"commit_sha"` - check.ReportInput -} - -type listStatusCheckResultsRequest struct { - repoRequest - CommitSHA string `path:"commit_sha"` -} - func checkOperations(reflector *openapi3.Reflector) { const tag = "status_checks" reportStatusCheckResults := openapi3.Operation{} reportStatusCheckResults.WithTags(tag) reportStatusCheckResults.WithMapOfAnything(map[string]interface{}{"operationId": "reportStatusCheckResults"}) - _ = reflector.SetRequest(&reportStatusCheckResults, new(reportStatusCheckResultRequest), http.MethodPut) + _ = reflector.SetRequest(&reportStatusCheckResults, struct { + repoRequest + CommitSHA string `path:"commit_sha"` + check.ReportInput + }{}, http.MethodPut) _ = reflector.SetJSONResponse(&reportStatusCheckResults, new(types.Check), http.StatusOK) _ = reflector.SetJSONResponse(&reportStatusCheckResults, new(usererror.Error), http.StatusBadRequest) _ = reflector.SetJSONResponse(&reportStatusCheckResults, new(usererror.Error), http.StatusInternalServerError) @@ -42,8 +35,13 @@ func checkOperations(reflector *openapi3.Reflector) { listStatusCheckResults := openapi3.Operation{} listStatusCheckResults.WithTags(tag) + listStatusCheckResults.WithParameters( + queryParameterPage, queryParameterLimit) listStatusCheckResults.WithMapOfAnything(map[string]interface{}{"operationId": "listStatusCheckResults"}) - _ = reflector.SetRequest(&listStatusCheckResults, new(listStatusCheckResultsRequest), http.MethodGet) + _ = reflector.SetRequest(&listStatusCheckResults, struct { + repoRequest + CommitSHA string `path:"commit_sha"` + }{}, http.MethodGet) _ = reflector.SetJSONResponse(&listStatusCheckResults, new([]types.Check), http.StatusOK) _ = reflector.SetJSONResponse(&listStatusCheckResults, new(usererror.Error), http.StatusBadRequest) _ = reflector.SetJSONResponse(&listStatusCheckResults, new(usererror.Error), http.StatusInternalServerError) diff --git a/internal/api/request/check.go b/internal/api/request/check.go new file mode 100644 index 000000000..4fdf89c13 --- /dev/null +++ b/internal/api/request/check.go @@ -0,0 +1,19 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package request + +import ( + "net/http" + + "github.com/harness/gitness/types" +) + +// ParseCheckListOptions extracts the status check list API options from the url. +func ParseCheckListOptions(r *http.Request) types.CheckListOptions { + return types.CheckListOptions{ + Page: ParsePage(r), + Size: ParseLimit(r), + } +} diff --git a/internal/store/database.go b/internal/store/database.go index fafd43224..5dc84cf30 100644 --- a/internal/store/database.go +++ b/internal/store/database.go @@ -419,8 +419,11 @@ type ( // Upsert creates new or updates an existing status check result. Upsert(ctx context.Context, check *types.Check) error + // Count counts status check results for a specific commit in a repo. + Count(ctx context.Context, repoID int64, commitSHA string, opts types.CheckListOptions) (int, error) + // List returns a list of status check results for a specific commit in a repo. - List(ctx context.Context, repoID int64, commitSHA string) ([]*types.Check, error) + List(ctx context.Context, repoID int64, commitSHA string, opts types.CheckListOptions) ([]types.Check, error) // ListRecent returns a list of recently executed status checks in a repository. ListRecent(ctx context.Context, repoID int64, since time.Time) ([]string, error) diff --git a/internal/store/database/check.go b/internal/store/database/check.go index 35558b741..5b17f7d69 100644 --- a/internal/store/database/check.go +++ b/internal/store/database/check.go @@ -132,13 +132,47 @@ func (s *CheckStore) Upsert(ctx context.Context, check *types.Check) error { return nil } +// Count counts status check results for a specific commit in a repo. +func (s *CheckStore) Count(ctx context.Context, + repoID int64, + commitSHA string, + _ types.CheckListOptions, +) (int, error) { + stmt := database.Builder. + Select("count(*)"). + From("checks"). + Where("check_repo_id = ?", repoID). + Where("check_commit_sha = ?", commitSHA) + + sql, args, err := stmt.ToSql() + if err != nil { + return 0, errors.Wrap(err, "Failed to convert query to sql") + } + + db := dbtx.GetAccessor(ctx, s.db) + + var count int + err = db.QueryRowContext(ctx, sql, args...).Scan(&count) + if err != nil { + return 0, database.ProcessSQLErrorf(err, "Failed to execute count status checks query") + } + + return count, nil +} + // List returns a list of status check results for a specific commit in a repo. -func (s *CheckStore) List(ctx context.Context, repoID int64, commitSHA string) ([]*types.Check, error) { +func (s *CheckStore) List(ctx context.Context, + repoID int64, + commitSHA string, + opts types.CheckListOptions, +) ([]types.Check, error) { stmt := database.Builder. Select(checkColumns). From("checks"). Where("check_repo_id = ?", repoID). Where("check_commit_sha = ?", commitSHA). + Limit(database.Limit(opts.Size)). + Offset(database.Offset(opts.Page, opts.Size)). OrderBy("check_updated desc") sql, args, err := stmt.ToSql() @@ -208,8 +242,8 @@ func mapInternalCheck(c *types.Check) *check { return m } -func mapCheck(c *check) *types.Check { - return &types.Check{ +func mapCheck(c *check) types.Check { + return types.Check{ ID: c.ID, CreatedBy: c.CreatedBy, Created: c.Created, @@ -230,7 +264,7 @@ func mapCheck(c *check) *types.Check { } } -func (s *CheckStore) mapSliceCheck(ctx context.Context, checks []*check) ([]*types.Check, error) { +func (s *CheckStore) mapSliceCheck(ctx context.Context, checks []*check) ([]types.Check, error) { // collect all principal IDs ids := make([]int64, len(checks)) for i, req := range checks { @@ -244,7 +278,7 @@ func (s *CheckStore) mapSliceCheck(ctx context.Context, checks []*check) ([]*typ } // attach the principal infos back to the slice items - m := make([]*types.Check, len(checks)) + m := make([]types.Check, len(checks)) for i, c := range checks { m[i] = mapCheck(c) if reportedBy, ok := infoMap[c.CreatedBy]; ok { diff --git a/internal/store/database/space.go b/internal/store/database/space.go index 36395aa41..d5ce5cb0e 100644 --- a/internal/store/database/space.go +++ b/internal/store/database/space.go @@ -270,6 +270,7 @@ func (s *SpaceStore) Count(ctx context.Context, id int64, opts *types.SpaceFilte if err != nil { return 0, database.ProcessSQLErrorf(err, "Failed executing count query") } + return count, nil } diff --git a/types/check.go b/types/check.go index 46763f536..23b0d8a6a 100644 --- a/types/check.go +++ b/types/check.go @@ -33,6 +33,12 @@ type CheckPayload struct { Data json.RawMessage `json:"data"` } +// CheckListOptions holds check list query parameters. +type CheckListOptions struct { + Page int `json:"page"` + Size int `json:"size"` +} + type ReqCheck struct { ID int64 `json:"id"` CreatedBy int64 `json:"-"` // clients will use "added_by"