drone/app/store/database/pullreq_reviewers.go

316 lines
9.3 KiB
Go

// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package database
import (
"context"
"fmt"
"time"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/store/database"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/guregu/null"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
var _ store.PullReqReviewerStore = (*PullReqReviewerStore)(nil)
const maxPullRequestReviewers = 100
// NewPullReqReviewerStore returns a new PullReqReviewerStore.
func NewPullReqReviewerStore(db *sqlx.DB,
pCache store.PrincipalInfoCache) *PullReqReviewerStore {
return &PullReqReviewerStore{
db: db,
pCache: pCache,
}
}
// PullReqReviewerStore implements store.PullReqReviewerStore backed by a relational database.
type PullReqReviewerStore struct {
db *sqlx.DB
pCache store.PrincipalInfoCache
}
// pullReqReviewer is used to fetch pull request reviewer data from the database.
type pullReqReviewer struct {
PullReqID int64 `db:"pullreq_reviewer_pullreq_id"`
PrincipalID int64 `db:"pullreq_reviewer_principal_id"`
CreatedBy int64 `db:"pullreq_reviewer_created_by"`
Created int64 `db:"pullreq_reviewer_created"`
Updated int64 `db:"pullreq_reviewer_updated"`
RepoID int64 `db:"pullreq_reviewer_repo_id"`
Type enum.PullReqReviewerType `db:"pullreq_reviewer_type"`
LatestReviewID null.Int `db:"pullreq_reviewer_latest_review_id"`
ReviewDecision enum.PullReqReviewDecision `db:"pullreq_reviewer_review_decision"`
SHA string `db:"pullreq_reviewer_sha"`
}
const (
pullreqReviewerColumns = `
pullreq_reviewer_pullreq_id
,pullreq_reviewer_principal_id
,pullreq_reviewer_created_by
,pullreq_reviewer_created
,pullreq_reviewer_updated
,pullreq_reviewer_repo_id
,pullreq_reviewer_type
,pullreq_reviewer_latest_review_id
,pullreq_reviewer_review_decision
,pullreq_reviewer_sha`
pullreqReviewerSelectBase = `
SELECT` + pullreqReviewerColumns + `
FROM pullreq_reviewers`
)
// Find finds the pull request reviewer by pull request id and principal id.
func (s *PullReqReviewerStore) Find(ctx context.Context, prID, principalID int64) (*types.PullReqReviewer, error) {
const sqlQuery = pullreqReviewerSelectBase + `
WHERE pullreq_reviewer_pullreq_id = $1 AND pullreq_reviewer_principal_id = $2`
db := dbtx.GetAccessor(ctx, s.db)
dst := &pullReqReviewer{}
if err := db.GetContext(ctx, dst, sqlQuery, prID, principalID); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to find pull request reviewer")
}
return s.mapPullReqReviewer(ctx, dst), nil
}
// Create creates a new pull request reviewer.
func (s *PullReqReviewerStore) Create(ctx context.Context, v *types.PullReqReviewer) error {
const sqlQuery = `
INSERT INTO pullreq_reviewers (
pullreq_reviewer_pullreq_id
,pullreq_reviewer_principal_id
,pullreq_reviewer_created_by
,pullreq_reviewer_created
,pullreq_reviewer_updated
,pullreq_reviewer_repo_id
,pullreq_reviewer_type
,pullreq_reviewer_latest_review_id
,pullreq_reviewer_review_decision
,pullreq_reviewer_sha
) values (
:pullreq_reviewer_pullreq_id
,:pullreq_reviewer_principal_id
,:pullreq_reviewer_created_by
,:pullreq_reviewer_created
,:pullreq_reviewer_updated
,:pullreq_reviewer_repo_id
,:pullreq_reviewer_type
,:pullreq_reviewer_latest_review_id
,:pullreq_reviewer_review_decision
,:pullreq_reviewer_sha
)`
db := dbtx.GetAccessor(ctx, s.db)
query, arg, err := db.BindNamed(sqlQuery, mapInternalPullReqReviewer(v))
if err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to bind pull request reviewer object")
}
if _, err = db.ExecContext(ctx, query, arg...); err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to insert pull request reviewer")
}
return nil
}
// Update updates the pull request reviewer.
func (s *PullReqReviewerStore) Update(ctx context.Context, v *types.PullReqReviewer) error {
const sqlQuery = `
UPDATE pullreq_reviewers
SET
pullreq_reviewer_updated = :pullreq_reviewer_updated
,pullreq_reviewer_latest_review_id = :pullreq_reviewer_latest_review_id
,pullreq_reviewer_review_decision = :pullreq_reviewer_review_decision
,pullreq_reviewer_sha = :pullreq_reviewer_sha
WHERE pullreq_reviewer_pullreq_id = :pullreq_reviewer_pullreq_id AND
pullreq_reviewer_principal_id = :pullreq_reviewer_principal_id`
db := dbtx.GetAccessor(ctx, s.db)
updatedAt := time.Now()
dbv := mapInternalPullReqReviewer(v)
dbv.Updated = updatedAt.UnixMilli()
query, arg, err := db.BindNamed(sqlQuery, dbv)
if err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to bind pull request activity object")
}
_, err = db.ExecContext(ctx, query, arg...)
if err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to update pull request activity")
}
v.Updated = dbv.Updated
return nil
}
// Delete deletes the pull request reviewer.
func (s *PullReqReviewerStore) Delete(ctx context.Context, prID, reviewerID int64) error {
const sqlQuery = `
DELETE from pullreq_reviewers
WHERE pullreq_reviewer_pullreq_id = $1 AND
pullreq_reviewer_principal_id = $2`
db := dbtx.GetAccessor(ctx, s.db)
if _, err := db.ExecContext(ctx, sqlQuery, prID, reviewerID); err != nil {
return database.ProcessSQLErrorf(ctx, err, "delete reviewer query failed")
}
return nil
}
// List returns a list of pull reviewers for a pull request.
func (s *PullReqReviewerStore) List(ctx context.Context, prID int64) ([]*types.PullReqReviewer, error) {
stmt := database.Builder.
Select(pullreqReviewerColumns).
From("pullreq_reviewers").
Where("pullreq_reviewer_pullreq_id = ?", prID).
OrderBy("pullreq_reviewer_created asc").
Limit(maxPullRequestReviewers) // memory safety limit
sql, args, err := stmt.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert pull request reviewer list query to sql")
}
dst := make([]*pullReqReviewer, 0)
db := dbtx.GetAccessor(ctx, s.db)
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed executing pull request reviewer list query")
}
result, err := s.mapSlicePullReqReviewer(ctx, dst)
if err != nil {
return nil, err
}
return result, nil
}
func mapPullReqReviewer(v *pullReqReviewer) *types.PullReqReviewer {
m := &types.PullReqReviewer{
PullReqID: v.PullReqID,
PrincipalID: v.PrincipalID,
CreatedBy: v.CreatedBy,
Created: v.Created,
Updated: v.Updated,
RepoID: v.RepoID,
Type: v.Type,
LatestReviewID: v.LatestReviewID.Ptr(),
ReviewDecision: v.ReviewDecision,
SHA: v.SHA,
}
return m
}
func mapInternalPullReqReviewer(v *types.PullReqReviewer) *pullReqReviewer {
m := &pullReqReviewer{
PullReqID: v.PullReqID,
PrincipalID: v.PrincipalID,
CreatedBy: v.CreatedBy,
Created: v.Created,
Updated: v.Updated,
RepoID: v.RepoID,
Type: v.Type,
LatestReviewID: null.IntFromPtr(v.LatestReviewID),
ReviewDecision: v.ReviewDecision,
SHA: v.SHA,
}
return m
}
func (s *PullReqReviewerStore) mapPullReqReviewer(ctx context.Context, v *pullReqReviewer) *types.PullReqReviewer {
m := &types.PullReqReviewer{
PullReqID: v.PullReqID,
PrincipalID: v.PrincipalID,
CreatedBy: v.CreatedBy,
Created: v.Created,
Updated: v.Updated,
RepoID: v.RepoID,
Type: v.Type,
LatestReviewID: v.LatestReviewID.Ptr(),
ReviewDecision: v.ReviewDecision,
SHA: v.SHA,
}
addedBy, err := s.pCache.Get(ctx, v.CreatedBy)
if err != nil {
log.Ctx(ctx).Err(err).Msg("failed to load PR reviewer addedBy")
}
if addedBy != nil {
m.AddedBy = *addedBy
}
reviewer, err := s.pCache.Get(ctx, v.PrincipalID)
if err != nil {
log.Ctx(ctx).Err(err).Msg("failed to load PR reviewer principal")
}
if reviewer != nil {
m.Reviewer = *reviewer
}
return m
}
func (s *PullReqReviewerStore) mapSlicePullReqReviewer(ctx context.Context,
reviewers []*pullReqReviewer) ([]*types.PullReqReviewer, error) {
// collect all principal IDs
ids := make([]int64, 0, 2*len(reviewers))
for _, v := range reviewers {
ids = append(ids, v.CreatedBy)
ids = append(ids, v.PrincipalID)
}
// pull principal infos from cache
infoMap, err := s.pCache.Map(ctx, ids)
if err != nil {
return nil, fmt.Errorf("failed to load PR principal infos: %w", err)
}
// attach the principal infos back to the slice items
m := make([]*types.PullReqReviewer, len(reviewers))
for i, v := range reviewers {
m[i] = mapPullReqReviewer(v)
if addedBy, ok := infoMap[v.CreatedBy]; ok {
m[i].AddedBy = *addedBy
}
if reviewer, ok := infoMap[v.PrincipalID]; ok {
m[i].Reviewer = *reviewer
}
}
return m, nil
}