mirror of https://github.com/harness/drone.git
316 lines
9.3 KiB
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
|
|
}
|