mirror of
https://github.com/harness/drone.git
synced 2025-05-18 18:09:56 +08:00
Pull request timeline feature: DB and the list API (#136)
This commit is contained in:
parent
23792ab2f5
commit
7fc77396a9
@ -98,7 +98,8 @@ func initSystem(ctx context.Context, config *types.Config) (*system, error) {
|
|||||||
}
|
}
|
||||||
repoController := repo.ProvideController(config, checkRepo, authorizer, spaceStore, repoStore, serviceAccountStore, gitrpcInterface)
|
repoController := repo.ProvideController(config, checkRepo, authorizer, spaceStore, repoStore, serviceAccountStore, gitrpcInterface)
|
||||||
pullReqStore := database.ProvidePullReqStore(db)
|
pullReqStore := database.ProvidePullReqStore(db)
|
||||||
pullreqController := pullreq.ProvideController(db, authorizer, pullReqStore, repoStore, serviceAccountStore, gitrpcInterface)
|
pullReqActivityStore := database.ProvidePullReqActivityStore(db)
|
||||||
|
pullreqController := pullreq.ProvideController(db, authorizer, pullReqStore, pullReqActivityStore, repoStore, serviceAccountStore, gitrpcInterface)
|
||||||
apiHandler := router.ProvideAPIHandler(config, authenticator, accountClient, spaceController, repoController, pullreqController)
|
apiHandler := router.ProvideAPIHandler(config, authenticator, accountClient, spaceController, repoController, pullreqController)
|
||||||
gitHandler := router.ProvideGitHandler(config, repoStore, authenticator, authorizer, gitrpcInterface)
|
gitHandler := router.ProvideGitHandler(config, repoStore, authenticator, authorizer, gitrpcInterface)
|
||||||
webHandler := router2.ProvideWebHandler(config)
|
webHandler := router2.ProvideWebHandler(config)
|
||||||
|
@ -58,7 +58,8 @@ func initSystem(ctx context.Context, config *types.Config) (*system, error) {
|
|||||||
checkSpace := check.ProvideSpaceCheck()
|
checkSpace := check.ProvideSpaceCheck()
|
||||||
spaceController := space.ProvideController(config, checkSpace, authorizer, spaceStore, repoStore, serviceAccountStore)
|
spaceController := space.ProvideController(config, checkSpace, authorizer, spaceStore, repoStore, serviceAccountStore)
|
||||||
pullReqStore := database.ProvidePullReqStore(db)
|
pullReqStore := database.ProvidePullReqStore(db)
|
||||||
pullreqController := pullreq.ProvideController(db, authorizer, pullReqStore, repoStore, serviceAccountStore, gitrpcInterface)
|
pullReqActivityStore := database.ProvidePullReqActivityStore(db)
|
||||||
|
pullreqController := pullreq.ProvideController(db, authorizer, pullReqStore, pullReqActivityStore, repoStore, serviceAccountStore, gitrpcInterface)
|
||||||
serviceAccount := check.ProvideServiceAccountCheck()
|
serviceAccount := check.ProvideServiceAccountCheck()
|
||||||
serviceaccountController := serviceaccount.NewController(serviceAccount, authorizer, serviceAccountStore, spaceStore, repoStore, tokenStore)
|
serviceaccountController := serviceaccount.NewController(serviceAccount, authorizer, serviceAccountStore, spaceStore, repoStore, tokenStore)
|
||||||
apiHandler := router.ProvideAPIHandler(config, authenticator, repoController, spaceController, pullreqController, serviceaccountController, controller)
|
apiHandler := router.ProvideAPIHandler(config, authenticator, repoController, spaceController, pullreqController, serviceaccountController, controller)
|
||||||
|
@ -19,32 +19,36 @@ import (
|
|||||||
"github.com/harness/gitness/types/enum"
|
"github.com/harness/gitness/types/enum"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
db *sqlx.DB
|
db *sqlx.DB
|
||||||
authorizer authz.Authorizer
|
authorizer authz.Authorizer
|
||||||
pullreqStore store.PullReqStore
|
pullreqStore store.PullReqStore
|
||||||
repoStore store.RepoStore
|
pullreqActivityStore store.PullReqActivityStore
|
||||||
saStore store.ServiceAccountStore
|
repoStore store.RepoStore
|
||||||
gitRPCClient gitrpc.Interface
|
saStore store.ServiceAccountStore
|
||||||
|
gitRPCClient gitrpc.Interface
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewController(
|
func NewController(
|
||||||
db *sqlx.DB,
|
db *sqlx.DB,
|
||||||
authorizer authz.Authorizer,
|
authorizer authz.Authorizer,
|
||||||
pullreqStore store.PullReqStore,
|
pullreqStore store.PullReqStore,
|
||||||
|
pullreqActivityStore store.PullReqActivityStore,
|
||||||
repoStore store.RepoStore,
|
repoStore store.RepoStore,
|
||||||
saStore store.ServiceAccountStore,
|
saStore store.ServiceAccountStore,
|
||||||
gitRPCClient gitrpc.Interface,
|
gitRPCClient gitrpc.Interface,
|
||||||
) *Controller {
|
) *Controller {
|
||||||
return &Controller{
|
return &Controller{
|
||||||
db: db,
|
db: db,
|
||||||
authorizer: authorizer,
|
authorizer: authorizer,
|
||||||
pullreqStore: pullreqStore,
|
pullreqStore: pullreqStore,
|
||||||
repoStore: repoStore,
|
pullreqActivityStore: pullreqActivityStore,
|
||||||
saStore: saStore,
|
repoStore: repoStore,
|
||||||
gitRPCClient: gitRPCClient,
|
saStore: saStore,
|
||||||
|
gitRPCClient: gitRPCClient,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,3 +89,24 @@ func (c *Controller) getRepoCheckAccess(ctx context.Context,
|
|||||||
|
|
||||||
return repo, nil
|
return repo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Controller) writeActivity(ctx context.Context,
|
||||||
|
pr *types.PullReq, act *types.PullReqActivity) (*types.PullReq, *types.PullReqActivity) {
|
||||||
|
prUpd, err := c.pullreqStore.UpdateActivitySeq(ctx, pr)
|
||||||
|
if err != nil {
|
||||||
|
// non-critical error
|
||||||
|
log.Err(err).Msg("failed to get pull request activity number")
|
||||||
|
return pr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
act.Order = prUpd.ActivitySeq
|
||||||
|
|
||||||
|
err = c.pullreqActivityStore.Create(ctx, act)
|
||||||
|
if err != nil {
|
||||||
|
// non-critical error
|
||||||
|
log.Err(err).Msg("failed to create pull request activity")
|
||||||
|
return prUpd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return prUpd, act
|
||||||
|
}
|
||||||
|
@ -103,24 +103,24 @@ func newPullReq(session *auth.Session, number int64,
|
|||||||
sourceRepo, targetRepo *types.Repository, in *CreateInput) *types.PullReq {
|
sourceRepo, targetRepo *types.Repository, in *CreateInput) *types.PullReq {
|
||||||
now := time.Now().UnixMilli()
|
now := time.Now().UnixMilli()
|
||||||
return &types.PullReq{
|
return &types.PullReq{
|
||||||
ID: 0, // the ID will be populated in the data layer
|
ID: 0, // the ID will be populated in the data layer
|
||||||
Version: 0,
|
Version: 0,
|
||||||
Number: number,
|
Number: number,
|
||||||
CreatedBy: session.Principal.ID,
|
CreatedBy: session.Principal.ID,
|
||||||
Created: now,
|
Created: now,
|
||||||
Updated: now,
|
Updated: now,
|
||||||
Edited: now,
|
Edited: now,
|
||||||
State: enum.PullReqStateOpen,
|
State: enum.PullReqStateOpen,
|
||||||
Title: in.Title,
|
Title: in.Title,
|
||||||
Description: in.Description,
|
Description: in.Description,
|
||||||
SourceRepoID: sourceRepo.ID,
|
SourceRepoID: sourceRepo.ID,
|
||||||
SourceBranch: in.SourceBranch,
|
SourceBranch: in.SourceBranch,
|
||||||
TargetRepoID: targetRepo.ID,
|
TargetRepoID: targetRepo.ID,
|
||||||
TargetBranch: in.TargetBranch,
|
TargetBranch: in.TargetBranch,
|
||||||
PullReqActivitySeq: 0,
|
ActivitySeq: 0,
|
||||||
MergedBy: nil,
|
MergedBy: nil,
|
||||||
Merged: nil,
|
Merged: nil,
|
||||||
MergeStrategy: nil,
|
MergeStrategy: nil,
|
||||||
Author: types.PrincipalInfo{
|
Author: types.PrincipalInfo{
|
||||||
ID: session.Principal.ID,
|
ID: session.Principal.ID,
|
||||||
UID: session.Principal.UID,
|
UID: session.Principal.UID,
|
||||||
|
50
internal/api/controller/pullreq/list_activities.go
Normal file
50
internal/api/controller/pullreq/list_activities.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// 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 pullreq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/internal/auth"
|
||||||
|
"github.com/harness/gitness/types"
|
||||||
|
"github.com/harness/gitness/types/enum"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListActivities returns a list of pull request activities
|
||||||
|
// from the provided repository and pull request number.
|
||||||
|
func (c *Controller) ListActivities(
|
||||||
|
ctx context.Context,
|
||||||
|
session *auth.Session,
|
||||||
|
repoRef string,
|
||||||
|
prNum int64,
|
||||||
|
filter *types.PullReqActivityFilter,
|
||||||
|
) ([]*types.PullReqActivity, int64, error) {
|
||||||
|
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("failed to acquire access to repo: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pr, err := c.pullreqStore.FindByNumber(ctx, repo.ID, prNum)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("failed to find pull request by number: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := c.pullreqActivityStore.List(ctx, pr.ID, filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("failed to list pull requests activities: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.Limit == 0 {
|
||||||
|
return list, int64(len(list)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := c.pullreqActivityStore.Count(ctx, pr.ID, filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("failed to count pull request activities: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return list, count, nil
|
||||||
|
}
|
@ -24,6 +24,8 @@ type UpdateInput struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update updates an pull request.
|
// Update updates an pull request.
|
||||||
|
//
|
||||||
|
//nolint:gocognit
|
||||||
func (c *Controller) Update(ctx context.Context,
|
func (c *Controller) Update(ctx context.Context,
|
||||||
session *auth.Session, repoRef string, pullreqNum int64, in *UpdateInput) (*types.PullReq, error) {
|
session *auth.Session, repoRef string, pullreqNum int64, in *UpdateInput) (*types.PullReq, error) {
|
||||||
var pr *types.PullReq
|
var pr *types.PullReq
|
||||||
@ -38,6 +40,8 @@ func (c *Controller) Update(ctx context.Context,
|
|||||||
return nil, fmt.Errorf("failed to acquire access to target repo: %w", err)
|
return nil, fmt.Errorf("failed to acquire access to target repo: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var activity *types.PullReqActivity
|
||||||
|
|
||||||
err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) error {
|
err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) error {
|
||||||
pr, err = c.pullreqStore.FindByNumber(ctx, targetRepo.ID, pullreqNum)
|
pr, err = c.pullreqStore.FindByNumber(ctx, targetRepo.ID, pullreqNum)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -62,6 +66,8 @@ func (c *Controller) Update(ctx context.Context,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
activity = getUpdateActivity(session, pr, in)
|
||||||
|
|
||||||
pr.Title = in.Title
|
pr.Title = in.Title
|
||||||
pr.Description = in.Description
|
pr.Description = in.Description
|
||||||
pr.Edited = time.Now().UnixMilli()
|
pr.Edited = time.Now().UnixMilli()
|
||||||
@ -77,7 +83,45 @@ func (c *Controller) Update(ctx context.Context,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Write a row to the pull request activity
|
// Write a row to the pull request activity
|
||||||
|
if activity != nil {
|
||||||
|
pr, activity = c.writeActivity(ctx, pr, activity)
|
||||||
|
}
|
||||||
|
|
||||||
return pr, nil
|
return pr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getUpdateActivity(session *auth.Session, pr *types.PullReq, in *UpdateInput) *types.PullReqActivity {
|
||||||
|
if pr.Title == in.Title {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().UnixMilli()
|
||||||
|
|
||||||
|
act := &types.PullReqActivity{
|
||||||
|
ID: 0, // Will be populated in the data layer
|
||||||
|
Version: 0,
|
||||||
|
CreatedBy: session.Principal.ID,
|
||||||
|
Created: now,
|
||||||
|
Updated: now,
|
||||||
|
Edited: now,
|
||||||
|
Deleted: nil,
|
||||||
|
RepoID: pr.TargetRepoID,
|
||||||
|
PullReqID: pr.ID,
|
||||||
|
Order: 0, // Will be filled in writeActivity
|
||||||
|
SubOrder: 0,
|
||||||
|
ReplySeq: 0,
|
||||||
|
Type: enum.PullReqActivityTypeTitleChange,
|
||||||
|
Kind: enum.PullReqActivityKindSystem,
|
||||||
|
Text: "",
|
||||||
|
Payload: map[string]interface{}{
|
||||||
|
"old": pr.Title,
|
||||||
|
"new": in.Title,
|
||||||
|
},
|
||||||
|
Metadata: nil,
|
||||||
|
ResolvedBy: nil,
|
||||||
|
Resolved: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
return act
|
||||||
|
}
|
||||||
|
@ -19,9 +19,10 @@ var WireSet = wire.NewSet(
|
|||||||
)
|
)
|
||||||
|
|
||||||
func ProvideController(db *sqlx.DB, authorizer authz.Authorizer,
|
func ProvideController(db *sqlx.DB, authorizer authz.Authorizer,
|
||||||
pullreqStore store.PullReqStore, repoStore store.RepoStore, saStore store.ServiceAccountStore,
|
pullReqStore store.PullReqStore, pullReqActivityStore store.PullReqActivityStore,
|
||||||
|
repoStore store.RepoStore, saStore store.ServiceAccountStore,
|
||||||
rpcClient gitrpc.Interface) *Controller {
|
rpcClient gitrpc.Interface) *Controller {
|
||||||
return NewController(db, authorizer,
|
return NewController(db, authorizer,
|
||||||
pullreqStore, repoStore, saStore,
|
pullReqStore, pullReqActivityStore, repoStore, saStore,
|
||||||
rpcClient)
|
rpcClient)
|
||||||
}
|
}
|
||||||
|
48
internal/api/handler/pullreq/list_activities.go
Normal file
48
internal/api/handler/pullreq/list_activities.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// 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 pullreq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/internal/api/controller/pullreq"
|
||||||
|
"github.com/harness/gitness/internal/api/render"
|
||||||
|
"github.com/harness/gitness/internal/api/request"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandleListActivities returns a http.HandlerFunc that lists pull request activities for a pull request.
|
||||||
|
func HandleListActivities(pullreqCtrl *pullreq.Controller) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
session, _ := request.AuthSessionFrom(ctx)
|
||||||
|
|
||||||
|
repoRef, err := request.GetRepoRefFromPath(r)
|
||||||
|
if err != nil {
|
||||||
|
render.TranslatedUserError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pullreqNumber, err := request.GetPullReqNumberFromPath(r)
|
||||||
|
if err != nil {
|
||||||
|
render.TranslatedUserError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filter, err := request.ParsePullReqActivityFilter(r)
|
||||||
|
if err != nil {
|
||||||
|
render.TranslatedUserError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
list, total, err := pullreqCtrl.ListActivities(ctx, session, repoRef, pullreqNumber, filter)
|
||||||
|
if err != nil {
|
||||||
|
render.TranslatedUserError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
render.PaginationLimit(r, w, int(total))
|
||||||
|
render.JSON(w, http.StatusOK, list)
|
||||||
|
}
|
||||||
|
}
|
@ -40,6 +40,10 @@ type updatePullReqRequest struct {
|
|||||||
pullreq.UpdateInput
|
pullreq.UpdateInput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type listPullReqActivitiesRequest struct {
|
||||||
|
pullReqRequest
|
||||||
|
}
|
||||||
|
|
||||||
var queryParameterQueryPullRequest = openapi3.ParameterOrRef{
|
var queryParameterQueryPullRequest = openapi3.ParameterOrRef{
|
||||||
Parameter: &openapi3.Parameter{
|
Parameter: &openapi3.Parameter{
|
||||||
Name: request.QueryParamQuery,
|
Name: request.QueryParamQuery,
|
||||||
@ -156,6 +160,55 @@ var queryParameterSortPullRequest = openapi3.ParameterOrRef{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var queryParameterKindPullRequestActivity = openapi3.ParameterOrRef{
|
||||||
|
Parameter: &openapi3.Parameter{
|
||||||
|
Name: request.QueryParamKind,
|
||||||
|
In: openapi3.ParameterInQuery,
|
||||||
|
Description: ptr.String("The kind of the pull request activity to include in the result."),
|
||||||
|
Required: ptr.Bool(false),
|
||||||
|
Schema: &openapi3.SchemaOrRef{
|
||||||
|
Schema: &openapi3.Schema{
|
||||||
|
Type: ptrSchemaType(openapi3.SchemaTypeArray),
|
||||||
|
Items: &openapi3.SchemaOrRef{
|
||||||
|
Schema: &openapi3.Schema{
|
||||||
|
Type: ptrSchemaType(openapi3.SchemaTypeString),
|
||||||
|
Enum: []interface{}{
|
||||||
|
ptr.String(string(enum.PullReqActivityKindSystem)),
|
||||||
|
ptr.String(string(enum.PullReqActivityKindComment)),
|
||||||
|
ptr.String(string(enum.PullReqActivityKindCodeComment)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var queryParameterTypePullRequestActivity = openapi3.ParameterOrRef{
|
||||||
|
Parameter: &openapi3.Parameter{
|
||||||
|
Name: request.QueryParamType,
|
||||||
|
In: openapi3.ParameterInQuery,
|
||||||
|
Description: ptr.String("The type of the pull request activity to include in the result."),
|
||||||
|
Required: ptr.Bool(false),
|
||||||
|
Schema: &openapi3.SchemaOrRef{
|
||||||
|
Schema: &openapi3.Schema{
|
||||||
|
Type: ptrSchemaType(openapi3.SchemaTypeArray),
|
||||||
|
Items: &openapi3.SchemaOrRef{
|
||||||
|
Schema: &openapi3.Schema{
|
||||||
|
Type: ptrSchemaType(openapi3.SchemaTypeString),
|
||||||
|
Enum: []interface{}{
|
||||||
|
ptr.String(string(enum.PullReqActivityTypeComment)),
|
||||||
|
ptr.String(string(enum.PullReqActivityTypeCodeComment)),
|
||||||
|
ptr.String(string(enum.PullReqActivityTypeTitleChange)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:funlen
|
||||||
func pullReqOperations(reflector *openapi3.Reflector) {
|
func pullReqOperations(reflector *openapi3.Reflector) {
|
||||||
createPullReq := openapi3.Operation{}
|
createPullReq := openapi3.Operation{}
|
||||||
createPullReq.WithTags("pullreq")
|
createPullReq.WithTags("pullreq")
|
||||||
@ -206,4 +259,19 @@ func pullReqOperations(reflector *openapi3.Reflector) {
|
|||||||
_ = reflector.SetJSONResponse(&putPullReq, new(usererror.Error), http.StatusUnauthorized)
|
_ = reflector.SetJSONResponse(&putPullReq, new(usererror.Error), http.StatusUnauthorized)
|
||||||
_ = reflector.SetJSONResponse(&putPullReq, new(usererror.Error), http.StatusForbidden)
|
_ = reflector.SetJSONResponse(&putPullReq, new(usererror.Error), http.StatusForbidden)
|
||||||
_ = reflector.Spec.AddOperation(http.MethodPut, "/repos/{repoRef}/pullreq/{pullreq_number}", putPullReq)
|
_ = reflector.Spec.AddOperation(http.MethodPut, "/repos/{repoRef}/pullreq/{pullreq_number}", putPullReq)
|
||||||
|
|
||||||
|
listPullReqActivities := openapi3.Operation{}
|
||||||
|
listPullReqActivities.WithTags("pullreq")
|
||||||
|
listPullReqActivities.WithMapOfAnything(map[string]interface{}{"operationId": "listPullReqActivities"})
|
||||||
|
listPullReqActivities.WithParameters(
|
||||||
|
queryParameterKindPullRequestActivity, queryParameterTypePullRequestActivity,
|
||||||
|
queryParameterSince, queryParameterUntil, queryParameterLimit)
|
||||||
|
_ = reflector.SetRequest(&listPullReqActivities, new(listPullReqActivitiesRequest), http.MethodGet)
|
||||||
|
_ = reflector.SetJSONResponse(&listPullReqActivities, new([]types.PullReqActivity), http.StatusOK)
|
||||||
|
_ = reflector.SetJSONResponse(&listPullReqActivities, new(usererror.Error), http.StatusBadRequest)
|
||||||
|
_ = reflector.SetJSONResponse(&listPullReqActivities, new(usererror.Error), http.StatusInternalServerError)
|
||||||
|
_ = reflector.SetJSONResponse(&listPullReqActivities, new(usererror.Error), http.StatusUnauthorized)
|
||||||
|
_ = reflector.SetJSONResponse(&listPullReqActivities, new(usererror.Error), http.StatusForbidden)
|
||||||
|
_ = reflector.Spec.AddOperation(http.MethodGet,
|
||||||
|
"/repos/{repoRef}/pullreq/{pullreq_number}/activities", listPullReqActivities)
|
||||||
}
|
}
|
||||||
|
@ -71,3 +71,48 @@ var queryParameterDirection = openapi3.ParameterOrRef{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var queryParameterLimit = openapi3.ParameterOrRef{
|
||||||
|
Parameter: &openapi3.Parameter{
|
||||||
|
Name: request.QueryParamLimit,
|
||||||
|
In: openapi3.ParameterInQuery,
|
||||||
|
Description: ptr.String("The maximum number of results to return."),
|
||||||
|
Required: ptr.Bool(false),
|
||||||
|
Schema: &openapi3.SchemaOrRef{
|
||||||
|
Schema: &openapi3.Schema{
|
||||||
|
Type: ptrSchemaType(openapi3.SchemaTypeInteger),
|
||||||
|
Minimum: ptr.Float64(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var queryParameterSince = openapi3.ParameterOrRef{
|
||||||
|
Parameter: &openapi3.Parameter{
|
||||||
|
Name: request.QueryParamSince,
|
||||||
|
In: openapi3.ParameterInQuery,
|
||||||
|
Description: ptr.String("The result should contain only entries created at and after this timestamp (unix millis)."),
|
||||||
|
Required: ptr.Bool(false),
|
||||||
|
Schema: &openapi3.SchemaOrRef{
|
||||||
|
Schema: &openapi3.Schema{
|
||||||
|
Type: ptrSchemaType(openapi3.SchemaTypeInteger),
|
||||||
|
Minimum: ptr.Float64(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var queryParameterUntil = openapi3.ParameterOrRef{
|
||||||
|
Parameter: &openapi3.Parameter{
|
||||||
|
Name: request.QueryParamUntil,
|
||||||
|
In: openapi3.ParameterInQuery,
|
||||||
|
Description: ptr.String("The result should contain only entries created before this timestamp (unix millis)."),
|
||||||
|
Required: ptr.Bool(false),
|
||||||
|
Schema: &openapi3.SchemaOrRef{
|
||||||
|
Schema: &openapi3.Schema{
|
||||||
|
Type: ptrSchemaType(openapi3.SchemaTypeInteger),
|
||||||
|
Minimum: ptr.Float64(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
@ -73,6 +73,11 @@ func PaginationNoTotal(r *http.Request, w http.ResponseWriter, page int, size in
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PaginationLimit writes the x-total header.
|
||||||
|
func PaginationLimit(r *http.Request, w http.ResponseWriter, total int) {
|
||||||
|
w.Header().Set("x-total", strconv.Itoa(total))
|
||||||
|
}
|
||||||
|
|
||||||
func getPaginationBaseURL(r *http.Request, page int, size int) url.URL {
|
func getPaginationBaseURL(r *http.Request, page int, size int) url.URL {
|
||||||
uri := *r.URL
|
uri := *r.URL
|
||||||
|
|
||||||
|
@ -26,8 +26,8 @@ func ParseSortPullReq(r *http.Request) enum.PullReqSort {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParsePullReqStates extracts the pull request states the url.
|
// parsePullReqStates extracts the pull request states from the url.
|
||||||
func ParsePullReqStates(r *http.Request) []enum.PullReqState {
|
func parsePullReqStates(r *http.Request) []enum.PullReqState {
|
||||||
strStates := r.Form[QueryParamState]
|
strStates := r.Form[QueryParamState]
|
||||||
m := make(map[enum.PullReqState]struct{}) // use map to eliminate duplicates
|
m := make(map[enum.PullReqState]struct{}) // use map to eliminate duplicates
|
||||||
for _, s := range strStates {
|
for _, s := range strStates {
|
||||||
@ -65,8 +65,79 @@ func ParsePullReqFilter(r *http.Request) (*types.PullReqFilter, error) {
|
|||||||
SourceRepoRef: r.FormValue("source_repo_ref"),
|
SourceRepoRef: r.FormValue("source_repo_ref"),
|
||||||
SourceBranch: r.FormValue("source_branch"),
|
SourceBranch: r.FormValue("source_branch"),
|
||||||
TargetBranch: r.FormValue("target_branch"),
|
TargetBranch: r.FormValue("target_branch"),
|
||||||
States: ParsePullReqStates(r),
|
States: parsePullReqStates(r),
|
||||||
Sort: ParseSortPullReq(r),
|
Sort: ParseSortPullReq(r),
|
||||||
Order: ParseOrder(r),
|
Order: ParseOrder(r),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParsePullReqActivityFilter extracts the pull request activity query parameter from the url.
|
||||||
|
func ParsePullReqActivityFilter(r *http.Request) (*types.PullReqActivityFilter, error) {
|
||||||
|
since, err := QueryParamAsPositiveInt64(r, QueryParamSince)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
until, err := QueryParamAsPositiveInt64(r, QueryParamUntil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
limit, err := QueryParamAsPositiveInt64(r, QueryParamLimit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &types.PullReqActivityFilter{
|
||||||
|
Since: since,
|
||||||
|
Until: until,
|
||||||
|
Limit: int(limit),
|
||||||
|
Types: parsePullReqActivityTypes(r),
|
||||||
|
Kinds: parsePullReqActivityKinds(r),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parsePullReqActivityKinds extracts the pull request activity kinds from the url.
|
||||||
|
func parsePullReqActivityKinds(r *http.Request) []enum.PullReqActivityKind {
|
||||||
|
strKinds := r.Form[QueryParamKind]
|
||||||
|
m := make(map[enum.PullReqActivityKind]struct{}) // use map to eliminate duplicates
|
||||||
|
for _, s := range strKinds {
|
||||||
|
kind, ok := enum.ParsePullReqActivityKind(s)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m[kind] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
kinds := make([]enum.PullReqActivityKind, 0, len(m))
|
||||||
|
for k := range m {
|
||||||
|
kinds = append(kinds, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
return kinds
|
||||||
|
}
|
||||||
|
|
||||||
|
// parsePullReqActivityTypes extracts the pull request activity types from the url.
|
||||||
|
func parsePullReqActivityTypes(r *http.Request) []enum.PullReqActivityType {
|
||||||
|
strType := r.Form[QueryParamType]
|
||||||
|
m := make(map[enum.PullReqActivityType]struct{}) // use map to eliminate duplicates
|
||||||
|
for _, s := range strType {
|
||||||
|
t, ok := enum.ParsePullReqActivityType(s)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m[t] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
activityTypes := make([]enum.PullReqActivityType, 0, len(m))
|
||||||
|
for t := range m {
|
||||||
|
activityTypes = append(activityTypes, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
return activityTypes
|
||||||
|
}
|
||||||
|
@ -26,7 +26,13 @@ const (
|
|||||||
QueryParamCreatedBy = "created_by"
|
QueryParamCreatedBy = "created_by"
|
||||||
|
|
||||||
QueryParamState = "state"
|
QueryParamState = "state"
|
||||||
|
QueryParamKind = "kind"
|
||||||
|
QueryParamType = "type"
|
||||||
|
|
||||||
|
QueryParamSince = "since"
|
||||||
|
QueryParamUntil = "until"
|
||||||
|
|
||||||
|
QueryParamLimit = "limit"
|
||||||
QueryParamPage = "page"
|
QueryParamPage = "page"
|
||||||
QueryParamPerPage = "per_page"
|
QueryParamPerPage = "per_page"
|
||||||
PerPageDefault = 50
|
PerPageDefault = 50
|
||||||
@ -83,6 +89,26 @@ func QueryParamOrError(r *http.Request, paramName string) (string, error) {
|
|||||||
|
|
||||||
// QueryParamAsID extracts an ID parameter from the url.
|
// QueryParamAsID extracts an ID parameter from the url.
|
||||||
func QueryParamAsID(r *http.Request, paramName string) (int64, error) {
|
func QueryParamAsID(r *http.Request, paramName string) (int64, error) {
|
||||||
|
return QueryParamAsPositiveInt64(r, paramName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryParamAsInt64 extracts an integer parameter from the url.
|
||||||
|
func QueryParamAsInt64(r *http.Request, paramName string) (int64, error) {
|
||||||
|
value, ok := QueryParam(r, paramName)
|
||||||
|
if !ok {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
valueInt, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, usererror.BadRequest(fmt.Sprintf("Parameter '%s' must be an integer.", paramName))
|
||||||
|
}
|
||||||
|
|
||||||
|
return valueInt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryParamAsPositiveInt64 extracts an integer parameter from the url.
|
||||||
|
func QueryParamAsPositiveInt64(r *http.Request, paramName string) (int64, error) {
|
||||||
value, ok := QueryParam(r, paramName)
|
value, ok := QueryParam(r, paramName)
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
|
@ -208,6 +208,7 @@ func setupPullReq(r chi.Router, pullreqCtrl *pullreq.Controller) {
|
|||||||
r.Route(fmt.Sprintf("/{%s}", request.PathParamPullReqNumber), func(r chi.Router) {
|
r.Route(fmt.Sprintf("/{%s}", request.PathParamPullReqNumber), func(r chi.Router) {
|
||||||
r.Get("/", handlerpullreq.HandleFind(pullreqCtrl))
|
r.Get("/", handlerpullreq.HandleFind(pullreqCtrl))
|
||||||
r.Put("/", handlerpullreq.HandleUpdate(pullreqCtrl))
|
r.Put("/", handlerpullreq.HandleUpdate(pullreqCtrl))
|
||||||
|
r.Get("/activities", handlerpullreq.HandleListActivities(pullreqCtrl))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
CREATE TABLE pullreq_activities (
|
||||||
|
pullreq_activity_id SERIAL PRIMARY KEY
|
||||||
|
,pullreq_activity_version BIGINT NOT NULL
|
||||||
|
,pullreq_activity_created_by INTEGER
|
||||||
|
,pullreq_activity_created BIGINT NOT NULL
|
||||||
|
,pullreq_activity_updated BIGINT NOT NULL
|
||||||
|
,pullreq_activity_edited BIGINT NOT NULL
|
||||||
|
,pullreq_activity_deleted BIGINT
|
||||||
|
,pullreq_activity_repo_id INTEGER NOT NULL
|
||||||
|
,pullreq_activity_pullreq_id INTEGER NOT NULL
|
||||||
|
,pullreq_activity_order INTEGER NOT NULL
|
||||||
|
,pullreq_activity_sub_order INTEGER NOT NULL
|
||||||
|
,pullreq_activity_reply_seq INTEGER NOT NULL
|
||||||
|
,pullreq_activity_type TEXT NOT NULL
|
||||||
|
,pullreq_activity_kind TEXT NOT NULL
|
||||||
|
,pullreq_activity_text TEXT NOT NULL
|
||||||
|
,pullreq_activity_payload JSONB NOT NULL DEFAULT '{}'
|
||||||
|
,pullreq_activity_metadata JSONB NOT NULL DEFAULT '{}'
|
||||||
|
,pullreq_activity_resolved_by INTEGER DEFAULT 0
|
||||||
|
,pullreq_activity_resolved BIGINT NULL
|
||||||
|
,CONSTRAINT fk_pullreq_activities_created_by FOREIGN KEY (pullreq_activity_created_by)
|
||||||
|
REFERENCES principals (principal_id) MATCH SIMPLE
|
||||||
|
ON UPDATE NO ACTION
|
||||||
|
ON DELETE NO ACTION
|
||||||
|
,CONSTRAINT fk_pullreq_activities_repo_id FOREIGN KEY (pullreq_activity_repo_id)
|
||||||
|
REFERENCES repositories (repo_id) MATCH SIMPLE
|
||||||
|
ON UPDATE NO ACTION
|
||||||
|
ON DELETE CASCADE
|
||||||
|
,CONSTRAINT fk_pullreq_activities_pullreq_id FOREIGN KEY (pullreq_activity_pullreq_id)
|
||||||
|
REFERENCES pullreq (pullreq_id) MATCH SIMPLE
|
||||||
|
ON UPDATE NO ACTION
|
||||||
|
ON DELETE CASCADE
|
||||||
|
,CONSTRAINT fk_pullreq_activities_resolved_by FOREIGN KEY (pullreq_activity_resolved_by)
|
||||||
|
REFERENCES principals (principal_id) MATCH SIMPLE
|
||||||
|
ON UPDATE NO ACTION
|
||||||
|
ON DELETE NO ACTION
|
||||||
|
);
|
@ -0,0 +1,2 @@
|
|||||||
|
CREATE UNIQUE INDEX index_pullreq_activities_pullreq_id_order_sub_order
|
||||||
|
ON pullreq_activities(pullreq_activity_pullreq_id, pullreq_activity_order, pullreq_activity_sub_order);
|
@ -0,0 +1,37 @@
|
|||||||
|
CREATE TABLE pullreq_activities (
|
||||||
|
pullreq_activity_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||||
|
,pullreq_activity_version BIGINT NOT NULL
|
||||||
|
,pullreq_activity_created_by INTEGER
|
||||||
|
,pullreq_activity_created BIGINT NOT NULL
|
||||||
|
,pullreq_activity_updated BIGINT NOT NULL
|
||||||
|
,pullreq_activity_edited BIGINT NOT NULL
|
||||||
|
,pullreq_activity_deleted BIGINT
|
||||||
|
,pullreq_activity_repo_id INTEGER NOT NULL
|
||||||
|
,pullreq_activity_pullreq_id INTEGER NOT NULL
|
||||||
|
,pullreq_activity_order INTEGER NOT NULL
|
||||||
|
,pullreq_activity_sub_order INTEGER NOT NULL
|
||||||
|
,pullreq_activity_reply_seq INTEGER NOT NULL
|
||||||
|
,pullreq_activity_type TEXT NOT NULL
|
||||||
|
,pullreq_activity_kind TEXT NOT NULL
|
||||||
|
,pullreq_activity_text TEXT NOT NULL
|
||||||
|
,pullreq_activity_payload TEXT NOT NULL DEFAULT '{}'
|
||||||
|
,pullreq_activity_metadata TEXT NOT NULL DEFAULT '{}'
|
||||||
|
,pullreq_activity_resolved_by INTEGER DEFAULT 0
|
||||||
|
,pullreq_activity_resolved BIGINT NULL
|
||||||
|
,CONSTRAINT fk_pullreq_activities_created_by FOREIGN KEY (pullreq_activity_created_by)
|
||||||
|
REFERENCES principals (principal_id) MATCH SIMPLE
|
||||||
|
ON UPDATE NO ACTION
|
||||||
|
ON DELETE NO ACTION
|
||||||
|
,CONSTRAINT fk_pullreq_activities_repo_id FOREIGN KEY (pullreq_activity_repo_id)
|
||||||
|
REFERENCES repositories (repo_id) MATCH SIMPLE
|
||||||
|
ON UPDATE NO ACTION
|
||||||
|
ON DELETE CASCADE
|
||||||
|
,CONSTRAINT fk_pullreq_activities_pullreq_id FOREIGN KEY (pullreq_activity_pullreq_id)
|
||||||
|
REFERENCES pullreq (pullreq_id) MATCH SIMPLE
|
||||||
|
ON UPDATE NO ACTION
|
||||||
|
ON DELETE CASCADE
|
||||||
|
,CONSTRAINT fk_pullreq_activities_resolved_by FOREIGN KEY (pullreq_activity_resolved_by)
|
||||||
|
REFERENCES principals (principal_id) MATCH SIMPLE
|
||||||
|
ON UPDATE NO ACTION
|
||||||
|
ON DELETE NO ACTION
|
||||||
|
);
|
@ -0,0 +1,2 @@
|
|||||||
|
CREATE UNIQUE INDEX index_pullreq_activities_pullreq_id_order_sub_order
|
||||||
|
ON pullreq_activities(pullreq_activity_pullreq_id, pullreq_activity_order, pullreq_activity_sub_order);
|
@ -55,7 +55,7 @@ type pullReq struct {
|
|||||||
TargetRepoID int64 `db:"pullreq_target_repo_id"`
|
TargetRepoID int64 `db:"pullreq_target_repo_id"`
|
||||||
TargetBranch string `db:"pullreq_target_branch"`
|
TargetBranch string `db:"pullreq_target_branch"`
|
||||||
|
|
||||||
PullReqActivitySeq int64 `db:"pullreq_activity_seq"`
|
ActivitySeq int64 `db:"pullreq_activity_seq"`
|
||||||
|
|
||||||
MergedBy null.Int `db:"pullreq_merged_by"`
|
MergedBy null.Int `db:"pullreq_merged_by"`
|
||||||
Merged null.Int `db:"pullreq_merged"`
|
Merged null.Int `db:"pullreq_merged"`
|
||||||
@ -238,6 +238,27 @@ func (s *PullReqStore) Update(ctx context.Context, pr *types.PullReq) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateActivitySeq updates the pull request's activity sequence.
|
||||||
|
func (s *PullReqStore) UpdateActivitySeq(ctx context.Context, pr *types.PullReq) (*types.PullReq, error) {
|
||||||
|
for {
|
||||||
|
dup := *pr
|
||||||
|
|
||||||
|
dup.ActivitySeq++
|
||||||
|
err := s.Update(ctx, &dup)
|
||||||
|
if err == nil {
|
||||||
|
return &dup, nil
|
||||||
|
}
|
||||||
|
if !errors.Is(err, store.ErrConflict) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pr, err = s.Find(ctx, pr.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Delete the pull request.
|
// Delete the pull request.
|
||||||
func (s *PullReqStore) Delete(ctx context.Context, id int64) error {
|
func (s *PullReqStore) Delete(ctx context.Context, id int64) error {
|
||||||
const pullReqDelete = `DELETE FROM pullreqs WHERE pullreq_id = $1`
|
const pullReqDelete = `DELETE FROM pullreqs WHERE pullreq_id = $1`
|
||||||
@ -383,26 +404,26 @@ func (s *PullReqStore) List(ctx context.Context, repoID int64, opts *types.PullR
|
|||||||
|
|
||||||
func mapPullReq(pr *pullReq) *types.PullReq {
|
func mapPullReq(pr *pullReq) *types.PullReq {
|
||||||
m := &types.PullReq{
|
m := &types.PullReq{
|
||||||
ID: pr.ID,
|
ID: pr.ID,
|
||||||
Version: pr.Version,
|
Version: pr.Version,
|
||||||
Number: pr.Number,
|
Number: pr.Number,
|
||||||
CreatedBy: pr.CreatedBy,
|
CreatedBy: pr.CreatedBy,
|
||||||
Created: pr.Created,
|
Created: pr.Created,
|
||||||
Updated: pr.Updated,
|
Updated: pr.Updated,
|
||||||
Edited: pr.Edited,
|
Edited: pr.Edited,
|
||||||
State: pr.State,
|
State: pr.State,
|
||||||
Title: pr.Title,
|
Title: pr.Title,
|
||||||
Description: pr.Description,
|
Description: pr.Description,
|
||||||
SourceRepoID: pr.SourceRepoID,
|
SourceRepoID: pr.SourceRepoID,
|
||||||
SourceBranch: pr.SourceBranch,
|
SourceBranch: pr.SourceBranch,
|
||||||
TargetRepoID: pr.TargetRepoID,
|
TargetRepoID: pr.TargetRepoID,
|
||||||
TargetBranch: pr.TargetBranch,
|
TargetBranch: pr.TargetBranch,
|
||||||
PullReqActivitySeq: pr.PullReqActivitySeq,
|
ActivitySeq: pr.ActivitySeq,
|
||||||
MergedBy: pr.MergedBy.Ptr(),
|
MergedBy: pr.MergedBy.Ptr(),
|
||||||
Merged: pr.Merged.Ptr(),
|
Merged: pr.Merged.Ptr(),
|
||||||
MergeStrategy: pr.MergeStrategy.Ptr(),
|
MergeStrategy: pr.MergeStrategy.Ptr(),
|
||||||
Author: types.PrincipalInfo{},
|
Author: types.PrincipalInfo{},
|
||||||
Merger: nil,
|
Merger: nil,
|
||||||
}
|
}
|
||||||
m.Author = types.PrincipalInfo{
|
m.Author = types.PrincipalInfo{
|
||||||
ID: pr.CreatedBy,
|
ID: pr.CreatedBy,
|
||||||
@ -424,24 +445,24 @@ func mapPullReq(pr *pullReq) *types.PullReq {
|
|||||||
|
|
||||||
func mapInternalPullReq(pr *types.PullReq) *pullReq {
|
func mapInternalPullReq(pr *types.PullReq) *pullReq {
|
||||||
m := &pullReq{
|
m := &pullReq{
|
||||||
ID: pr.ID,
|
ID: pr.ID,
|
||||||
Version: pr.Version,
|
Version: pr.Version,
|
||||||
Number: pr.Number,
|
Number: pr.Number,
|
||||||
CreatedBy: pr.CreatedBy,
|
CreatedBy: pr.CreatedBy,
|
||||||
Created: pr.Created,
|
Created: pr.Created,
|
||||||
Updated: pr.Updated,
|
Updated: pr.Updated,
|
||||||
Edited: pr.Edited,
|
Edited: pr.Edited,
|
||||||
State: pr.State,
|
State: pr.State,
|
||||||
Title: pr.Title,
|
Title: pr.Title,
|
||||||
Description: pr.Description,
|
Description: pr.Description,
|
||||||
SourceRepoID: pr.SourceRepoID,
|
SourceRepoID: pr.SourceRepoID,
|
||||||
SourceBranch: pr.SourceBranch,
|
SourceBranch: pr.SourceBranch,
|
||||||
TargetRepoID: pr.TargetRepoID,
|
TargetRepoID: pr.TargetRepoID,
|
||||||
TargetBranch: pr.TargetBranch,
|
TargetBranch: pr.TargetBranch,
|
||||||
PullReqActivitySeq: pr.PullReqActivitySeq,
|
ActivitySeq: pr.ActivitySeq,
|
||||||
MergedBy: null.IntFromPtr(pr.MergedBy),
|
MergedBy: null.IntFromPtr(pr.MergedBy),
|
||||||
Merged: null.IntFromPtr(pr.Merged),
|
Merged: null.IntFromPtr(pr.Merged),
|
||||||
MergeStrategy: null.StringFromPtr(pr.MergeStrategy),
|
MergeStrategy: null.StringFromPtr(pr.MergeStrategy),
|
||||||
}
|
}
|
||||||
|
|
||||||
return m
|
return m
|
||||||
|
429
internal/store/database/pullreq_activity.go
Normal file
429
internal/store/database/pullreq_activity.go
Normal file
@ -0,0 +1,429 @@
|
|||||||
|
// 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 database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/internal/store"
|
||||||
|
"github.com/harness/gitness/internal/store/database/dbtx"
|
||||||
|
"github.com/harness/gitness/types"
|
||||||
|
"github.com/harness/gitness/types/enum"
|
||||||
|
|
||||||
|
"github.com/Masterminds/squirrel"
|
||||||
|
"github.com/guregu/null"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ store.PullReqActivityStore = (*PullReqActivityStore)(nil)
|
||||||
|
|
||||||
|
// NewPullReqActivityStore returns a new PullReqJournalStore.
|
||||||
|
func NewPullReqActivityStore(db *sqlx.DB) *PullReqActivityStore {
|
||||||
|
return &PullReqActivityStore{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PullReqActivityStore implements store.PullReqActivityStore backed by a relational database.
|
||||||
|
type PullReqActivityStore struct {
|
||||||
|
db *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// journal is used to fetch pull request data from the database.
|
||||||
|
// The object should be later re-packed into a different struct to return it as an API response.
|
||||||
|
type pullReqActivity struct {
|
||||||
|
ID int64 `db:"pullreq_activity_id"`
|
||||||
|
Version int64 `db:"pullreq_activity_version"`
|
||||||
|
|
||||||
|
CreatedBy int64 `db:"pullreq_activity_created_by"`
|
||||||
|
Created int64 `db:"pullreq_activity_created"`
|
||||||
|
Updated int64 `db:"pullreq_activity_updated"`
|
||||||
|
Edited int64 `db:"pullreq_activity_edited"`
|
||||||
|
Deleted null.Int `db:"pullreq_activity_deleted"`
|
||||||
|
|
||||||
|
RepoID int64 `db:"pullreq_activity_repo_id"`
|
||||||
|
PullReqID int64 `db:"pullreq_activity_pullreq_id"`
|
||||||
|
|
||||||
|
Order int64 `db:"pullreq_activity_order"`
|
||||||
|
SubOrder int64 `db:"pullreq_activity_sub_order"`
|
||||||
|
ReplySeq int64 `db:"pullreq_activity_reply_seq"`
|
||||||
|
|
||||||
|
Type enum.PullReqActivityType `db:"pullreq_activity_type"`
|
||||||
|
Kind enum.PullReqActivityKind `db:"pullreq_activity_kind"`
|
||||||
|
|
||||||
|
Text string `db:"pullreq_activity_text"`
|
||||||
|
Payload json.RawMessage `db:"pullreq_activity_payload"`
|
||||||
|
Metadata json.RawMessage `db:"pullreq_activity_metadata"`
|
||||||
|
|
||||||
|
ResolvedBy null.Int `db:"pullreq_activity_resolved_by"`
|
||||||
|
Resolved null.Int `db:"pullreq_activity_resolved"`
|
||||||
|
|
||||||
|
AuthorUID string `db:"author_uid"`
|
||||||
|
AuthorName string `db:"author_name"`
|
||||||
|
AuthorEmail string `db:"author_email"`
|
||||||
|
ResolverUID null.String `db:"resolver_uid"`
|
||||||
|
ResolverName null.String `db:"resolver_name"`
|
||||||
|
ResolverEmail null.String `db:"resolver_email"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
pullreqActivityColumns = `
|
||||||
|
pullreq_activity_id
|
||||||
|
,pullreq_activity_version
|
||||||
|
,pullreq_activity_created_by
|
||||||
|
,pullreq_activity_created
|
||||||
|
,pullreq_activity_updated
|
||||||
|
,pullreq_activity_edited
|
||||||
|
,pullreq_activity_deleted
|
||||||
|
,pullreq_activity_repo_id
|
||||||
|
,pullreq_activity_pullreq_id
|
||||||
|
,pullreq_activity_order
|
||||||
|
,pullreq_activity_sub_order
|
||||||
|
,pullreq_activity_reply_seq
|
||||||
|
,pullreq_activity_type
|
||||||
|
,pullreq_activity_kind
|
||||||
|
,pullreq_activity_text
|
||||||
|
,pullreq_activity_payload
|
||||||
|
,pullreq_activity_metadata
|
||||||
|
,pullreq_activity_resolved_by
|
||||||
|
,pullreq_activity_resolved
|
||||||
|
,author.principal_uid as "author_uid"
|
||||||
|
,author.principal_displayName as "author_name"
|
||||||
|
,author.principal_email as "author_email"
|
||||||
|
,resolver.principal_uid as "resolver_uid"
|
||||||
|
,resolver.principal_displayName as "resolver_name"
|
||||||
|
,resolver.principal_email as "resolver_email"`
|
||||||
|
|
||||||
|
pullreqActivitySelectBase = `
|
||||||
|
SELECT` + pullreqActivityColumns + `
|
||||||
|
FROM pullreq_activities
|
||||||
|
INNER JOIN principals author on author.principal_id = pullreq_created_by
|
||||||
|
LEFT JOIN principals resolver on resolver.principal_id = journal_pullreq_merged_by`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Find finds the pull request activity by id.
|
||||||
|
func (s *PullReqActivityStore) Find(ctx context.Context, id int64) (*types.PullReqActivity, error) {
|
||||||
|
const sqlQuery = pullreqActivitySelectBase + `
|
||||||
|
WHERE pullreq_activity_id = $1`
|
||||||
|
|
||||||
|
db := dbtx.GetAccessor(ctx, s.db)
|
||||||
|
|
||||||
|
dst := &pullReqActivity{}
|
||||||
|
if err := db.GetContext(ctx, dst, sqlQuery, id); err != nil {
|
||||||
|
return nil, processSQLErrorf(err, "Select query failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapPullReqActivity(dst), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create creates a new pull request.
|
||||||
|
func (s *PullReqActivityStore) Create(ctx context.Context, act *types.PullReqActivity) error {
|
||||||
|
const sqlQuery = `
|
||||||
|
INSERT INTO pullreq_activities (
|
||||||
|
pullreq_activity_version
|
||||||
|
,pullreq_activity_created_by
|
||||||
|
,pullreq_activity_created
|
||||||
|
,pullreq_activity_updated
|
||||||
|
,pullreq_activity_edited
|
||||||
|
,pullreq_activity_deleted
|
||||||
|
,pullreq_activity_repo_id
|
||||||
|
,pullreq_activity_pullreq_id
|
||||||
|
,pullreq_activity_order
|
||||||
|
,pullreq_activity_sub_order
|
||||||
|
,pullreq_activity_reply_seq
|
||||||
|
,pullreq_activity_type
|
||||||
|
,pullreq_activity_kind
|
||||||
|
,pullreq_activity_text
|
||||||
|
,pullreq_activity_payload
|
||||||
|
,pullreq_activity_metadata
|
||||||
|
,pullreq_activity_resolved_by
|
||||||
|
,pullreq_activity_resolved
|
||||||
|
) values (
|
||||||
|
:pullreq_activity_version
|
||||||
|
,:pullreq_activity_created_by
|
||||||
|
,:pullreq_activity_created
|
||||||
|
,:pullreq_activity_updated
|
||||||
|
,:pullreq_activity_edited
|
||||||
|
,:pullreq_activity_deleted
|
||||||
|
,:pullreq_activity_repo_id
|
||||||
|
,:pullreq_activity_pullreq_id
|
||||||
|
,:pullreq_activity_order
|
||||||
|
,:pullreq_activity_sub_order
|
||||||
|
,:pullreq_activity_reply_seq
|
||||||
|
,:pullreq_activity_type
|
||||||
|
,:pullreq_activity_kind
|
||||||
|
,:pullreq_activity_text
|
||||||
|
,:pullreq_activity_payload
|
||||||
|
,:pullreq_activity_metadata
|
||||||
|
,:pullreq_activity_resolved_by
|
||||||
|
,:pullreq_activity_resolved
|
||||||
|
) RETURNING pullreq_activity_id`
|
||||||
|
|
||||||
|
db := dbtx.GetAccessor(ctx, s.db)
|
||||||
|
|
||||||
|
query, arg, err := db.BindNamed(sqlQuery, mapInternalPullReqActivity(act))
|
||||||
|
if err != nil {
|
||||||
|
return processSQLErrorf(err, "Failed to bind pull request activity object")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = db.QueryRowContext(ctx, query, arg...).Scan(&act.ID); err != nil {
|
||||||
|
return processSQLErrorf(err, "Failed to insert pull request activity")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates the pull request.
|
||||||
|
func (s *PullReqActivityStore) Update(ctx context.Context, act *types.PullReqActivity) error {
|
||||||
|
const sqlQuery = `
|
||||||
|
UPDATE pullreq_activities
|
||||||
|
SET
|
||||||
|
pullreq_activity_version = :pullreq_activity_version
|
||||||
|
,pullreq_activity_updated = :pullreq_activity_updated
|
||||||
|
,pullreq_activity_edited = :pullreq_activity_edited
|
||||||
|
,pullreq_activity_reply_seq = :pullreq_activity_reply_seq
|
||||||
|
,pullreq_activity_text = :pullreq_activity_text
|
||||||
|
,pullreq_activity_payload = :pullreq_activity_payload
|
||||||
|
,pullreq_activity_metadata = :pullreq_activity_metadata
|
||||||
|
,pullreq_activity_resolved_by = :pullreq_activity_resolved_by
|
||||||
|
,pullreq_activity_resolved = :pullreq_activity_resolved
|
||||||
|
WHERE pullreq_activity_id = :pullreq_activity_id AND pullreq_activity_version = :pullreq_activity_version - 1`
|
||||||
|
|
||||||
|
db := dbtx.GetAccessor(ctx, s.db)
|
||||||
|
|
||||||
|
updatedAt := time.Now()
|
||||||
|
|
||||||
|
dbAct := mapInternalPullReqActivity(act)
|
||||||
|
dbAct.Version++
|
||||||
|
dbAct.Updated = updatedAt.UnixMilli()
|
||||||
|
|
||||||
|
query, arg, err := db.BindNamed(sqlQuery, dbAct)
|
||||||
|
if err != nil {
|
||||||
|
return processSQLErrorf(err, "Failed to bind pull request activity object")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := db.ExecContext(ctx, query, arg...)
|
||||||
|
if err != nil {
|
||||||
|
return processSQLErrorf(err, "Failed to update pull request activity")
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := result.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return processSQLErrorf(err, "Failed to get number of updated rows")
|
||||||
|
}
|
||||||
|
|
||||||
|
if count == 0 {
|
||||||
|
return store.ErrConflict
|
||||||
|
}
|
||||||
|
|
||||||
|
act.Version = dbAct.Version
|
||||||
|
act.Updated = dbAct.Updated
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateReplySeq updates the pull request activity's reply sequence.
|
||||||
|
func (s *PullReqActivityStore) UpdateReplySeq(ctx context.Context,
|
||||||
|
act *types.PullReqActivity) (*types.PullReqActivity, error) {
|
||||||
|
for {
|
||||||
|
dup := *act
|
||||||
|
|
||||||
|
dup.ReplySeq++
|
||||||
|
err := s.Update(ctx, &dup)
|
||||||
|
if err == nil {
|
||||||
|
return &dup, nil
|
||||||
|
}
|
||||||
|
if !errors.Is(err, store.ErrConflict) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
act, err = s.Find(ctx, act.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count of pull requests for a repo.
|
||||||
|
func (s *PullReqActivityStore) Count(ctx context.Context, prID int64,
|
||||||
|
opts *types.PullReqActivityFilter) (int64, error) {
|
||||||
|
stmt := builder.
|
||||||
|
Select("count(*)").
|
||||||
|
From("pullreq_activities").
|
||||||
|
Where("pullreq_activity_pullreq_id = ?", prID)
|
||||||
|
|
||||||
|
if len(opts.Types) == 1 {
|
||||||
|
stmt = stmt.Where("pullreq_activity_type = ?", opts.Types[0])
|
||||||
|
} else if len(opts.Types) > 1 {
|
||||||
|
stmt = stmt.Where(squirrel.Eq{"pullreq_activity_type": opts.Types})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(opts.Kinds) == 1 {
|
||||||
|
stmt = stmt.Where("pullreq_activity_kind = ?", opts.Kinds[0])
|
||||||
|
} else if len(opts.Kinds) > 1 {
|
||||||
|
stmt = stmt.Where(squirrel.Eq{"pullreq_activity_kind": opts.Kinds})
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Since != 0 {
|
||||||
|
stmt = stmt.Where("pullreq_created >= ?", opts.Since)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Until != 0 {
|
||||||
|
stmt = stmt.Where("pullreq_created < ?", opts.Until)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 int64
|
||||||
|
err = db.QueryRowContext(ctx, sql, args...).Scan(&count)
|
||||||
|
if err != nil {
|
||||||
|
return 0, processSQLErrorf(err, "Failed executing count query")
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns a list of pull requests for a repo.
|
||||||
|
func (s *PullReqActivityStore) List(ctx context.Context, prID int64,
|
||||||
|
opts *types.PullReqActivityFilter) ([]*types.PullReqActivity, error) {
|
||||||
|
stmt := builder.
|
||||||
|
Select(pullreqActivityColumns).
|
||||||
|
From("pullreq_activities").
|
||||||
|
InnerJoin("principals author on author.principal_id = pullreq_activity_created_by").
|
||||||
|
LeftJoin("principals resolver on resolver.principal_id = pullreq_activity_resolved_by").
|
||||||
|
Where("pullreq_activity_pullreq_id = ?", prID)
|
||||||
|
|
||||||
|
if len(opts.Types) == 1 {
|
||||||
|
stmt = stmt.Where("pullreq_activity_type = ?", opts.Types[0])
|
||||||
|
} else if len(opts.Types) > 1 {
|
||||||
|
stmt = stmt.Where(squirrel.Eq{"pullreq_activity_type": opts.Types})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(opts.Kinds) == 1 {
|
||||||
|
stmt = stmt.Where("pullreq_activity_kind = ?", opts.Kinds[0])
|
||||||
|
} else if len(opts.Kinds) > 1 {
|
||||||
|
stmt = stmt.Where(squirrel.Eq{"pullreq_activity_kind": opts.Kinds})
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Since != 0 {
|
||||||
|
stmt = stmt.Where("pullreq_created >= ?", opts.Since)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Until != 0 {
|
||||||
|
stmt = stmt.Where("pullreq_created < ?", opts.Until)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Limit > 0 {
|
||||||
|
stmt = stmt.Limit(uint64(limit(opts.Limit)))
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt = stmt.OrderBy("pullreq_activity_order asc", "pullreq_activity_sub_order asc")
|
||||||
|
|
||||||
|
sql, args, err := stmt.ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "Failed to convert pull request activity query to sql")
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := make([]*pullReqActivity, 0)
|
||||||
|
|
||||||
|
db := dbtx.GetAccessor(ctx, s.db)
|
||||||
|
|
||||||
|
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
|
||||||
|
return nil, processSQLErrorf(err, "Failed executing pull request activity list query")
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapSlicePullReqActivity(dst), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapPullReqActivity(act *pullReqActivity) *types.PullReqActivity {
|
||||||
|
m := &types.PullReqActivity{
|
||||||
|
ID: act.ID,
|
||||||
|
Version: act.Version,
|
||||||
|
CreatedBy: act.CreatedBy,
|
||||||
|
Created: act.Created,
|
||||||
|
Updated: act.Updated,
|
||||||
|
Edited: act.Edited,
|
||||||
|
Deleted: act.Deleted.Ptr(),
|
||||||
|
RepoID: act.RepoID,
|
||||||
|
PullReqID: act.PullReqID,
|
||||||
|
Order: act.Order,
|
||||||
|
SubOrder: act.SubOrder,
|
||||||
|
ReplySeq: act.ReplySeq,
|
||||||
|
Type: act.Type,
|
||||||
|
Kind: act.Kind,
|
||||||
|
Text: act.Text,
|
||||||
|
Payload: make(map[string]interface{}),
|
||||||
|
Metadata: make(map[string]interface{}),
|
||||||
|
ResolvedBy: act.ResolvedBy.Ptr(),
|
||||||
|
Resolved: act.Resolved.Ptr(),
|
||||||
|
Author: types.PrincipalInfo{},
|
||||||
|
Resolver: nil,
|
||||||
|
}
|
||||||
|
m.Author = types.PrincipalInfo{
|
||||||
|
ID: act.CreatedBy,
|
||||||
|
UID: act.AuthorUID,
|
||||||
|
Name: act.AuthorName,
|
||||||
|
Email: act.AuthorEmail,
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = json.Unmarshal(act.Payload, &m.Payload)
|
||||||
|
_ = json.Unmarshal(act.Metadata, &m.Metadata)
|
||||||
|
|
||||||
|
if act.ResolvedBy.Valid {
|
||||||
|
m.Resolver = &types.PrincipalInfo{
|
||||||
|
ID: act.ResolvedBy.Int64,
|
||||||
|
UID: act.ResolverUID.String,
|
||||||
|
Name: act.ResolverName.String,
|
||||||
|
Email: act.ResolverEmail.String,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapInternalPullReqActivity(act *types.PullReqActivity) *pullReqActivity {
|
||||||
|
m := &pullReqActivity{
|
||||||
|
ID: act.ID,
|
||||||
|
Version: act.Version,
|
||||||
|
CreatedBy: act.CreatedBy,
|
||||||
|
Created: act.Created,
|
||||||
|
Updated: act.Updated,
|
||||||
|
Edited: act.Edited,
|
||||||
|
Deleted: null.IntFromPtr(act.Deleted),
|
||||||
|
RepoID: act.RepoID,
|
||||||
|
PullReqID: act.PullReqID,
|
||||||
|
Order: act.Order,
|
||||||
|
SubOrder: act.SubOrder,
|
||||||
|
ReplySeq: act.ReplySeq,
|
||||||
|
Type: act.Type,
|
||||||
|
Kind: act.Kind,
|
||||||
|
Text: act.Text,
|
||||||
|
Payload: nil,
|
||||||
|
Metadata: nil,
|
||||||
|
ResolvedBy: null.IntFromPtr(act.ResolvedBy),
|
||||||
|
Resolved: null.IntFromPtr(act.Resolved),
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Payload, _ = json.Marshal(act.Payload)
|
||||||
|
m.Metadata, _ = json.Marshal(act.Metadata)
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapSlicePullReqActivity(a []*pullReqActivity) []*types.PullReqActivity {
|
||||||
|
m := make([]*types.PullReqActivity, len(a))
|
||||||
|
for i, act := range a {
|
||||||
|
m[i] = mapPullReqActivity(act)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
@ -1,89 +0,0 @@
|
|||||||
// 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 database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/harness/gitness/internal/store"
|
|
||||||
"github.com/harness/gitness/internal/store/database/mutex"
|
|
||||||
"github.com/harness/gitness/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ store.PullReqStore = (*PullReqStoreSync)(nil)
|
|
||||||
|
|
||||||
// NewPullReqStoreSync returns a new PullReqStoreSync.
|
|
||||||
func NewPullReqStoreSync(base *PullReqStore) *PullReqStoreSync {
|
|
||||||
return &PullReqStoreSync{
|
|
||||||
base: base,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PullReqStoreSync synchronizes read and write access to the
|
|
||||||
// pull request store. This prevents race conditions when the database
|
|
||||||
// type is sqlite3.
|
|
||||||
type PullReqStoreSync struct {
|
|
||||||
base *PullReqStore
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find finds the pull request by id.
|
|
||||||
func (s *PullReqStoreSync) Find(ctx context.Context, id int64) (*types.PullReq, error) {
|
|
||||||
mutex.RLock()
|
|
||||||
defer mutex.RUnlock()
|
|
||||||
return s.base.Find(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindByNumber finds the pull request by repo ID and pull request number.
|
|
||||||
func (s *PullReqStoreSync) FindByNumber(ctx context.Context, repoID, number int64) (*types.PullReq, error) {
|
|
||||||
mutex.RLock()
|
|
||||||
defer mutex.RUnlock()
|
|
||||||
return s.base.FindByNumber(ctx, repoID, number)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create creates a new pull request.
|
|
||||||
func (s *PullReqStoreSync) Create(ctx context.Context, pullReq *types.PullReq) error {
|
|
||||||
mutex.Lock()
|
|
||||||
defer mutex.Unlock()
|
|
||||||
return s.base.Create(ctx, pullReq)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update updates the pull request.
|
|
||||||
func (s *PullReqStoreSync) Update(ctx context.Context, pullReq *types.PullReq) error {
|
|
||||||
mutex.Lock()
|
|
||||||
defer mutex.Unlock()
|
|
||||||
return s.base.Update(ctx, pullReq)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the pull request.
|
|
||||||
func (s *PullReqStoreSync) Delete(ctx context.Context, id int64) error {
|
|
||||||
mutex.Lock()
|
|
||||||
defer mutex.Unlock()
|
|
||||||
return s.base.Delete(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LastNumber return the number of the most recent pull request.
|
|
||||||
func (s *PullReqStoreSync) LastNumber(ctx context.Context, repoID int64) (int64, error) {
|
|
||||||
mutex.RLock()
|
|
||||||
defer mutex.RUnlock()
|
|
||||||
return s.base.LastNumber(ctx, repoID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count of pull requests for a repo.
|
|
||||||
func (s *PullReqStoreSync) Count(ctx context.Context, repoID int64, opts *types.PullReqFilter) (int64, error) {
|
|
||||||
mutex.RLock()
|
|
||||||
defer mutex.RUnlock()
|
|
||||||
return s.base.Count(ctx, repoID, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// List returns a list of pull requests for a repo.
|
|
||||||
func (s *PullReqStoreSync) List(
|
|
||||||
ctx context.Context,
|
|
||||||
repoID int64,
|
|
||||||
opts *types.PullReqFilter,
|
|
||||||
) ([]*types.PullReq, error) {
|
|
||||||
mutex.RLock()
|
|
||||||
defer mutex.RUnlock()
|
|
||||||
return s.base.List(ctx, repoID, opts)
|
|
||||||
}
|
|
@ -26,8 +26,9 @@ var WireSet = wire.NewSet(
|
|||||||
ProvideServiceStore,
|
ProvideServiceStore,
|
||||||
ProvideSpaceStore,
|
ProvideSpaceStore,
|
||||||
ProvideRepoStore,
|
ProvideRepoStore,
|
||||||
ProvidePullReqStore,
|
|
||||||
ProvideTokenStore,
|
ProvideTokenStore,
|
||||||
|
ProvidePullReqStore,
|
||||||
|
ProvidePullReqActivityStore,
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProvideDatabase provides a database connection.
|
// ProvideDatabase provides a database connection.
|
||||||
@ -114,12 +115,10 @@ func ProvideTokenStore(db *sqlx.DB) store.TokenStore {
|
|||||||
|
|
||||||
// ProvidePullReqStore provides a pull request store.
|
// ProvidePullReqStore provides a pull request store.
|
||||||
func ProvidePullReqStore(db *sqlx.DB) store.PullReqStore {
|
func ProvidePullReqStore(db *sqlx.DB) store.PullReqStore {
|
||||||
switch db.DriverName() {
|
return NewPullReqStore(db)
|
||||||
case postgres:
|
}
|
||||||
return NewPullReqStore(db)
|
|
||||||
default:
|
// ProvidePullReqActivityStore provides a pull request activity store.
|
||||||
return NewPullReqStoreSync(
|
func ProvidePullReqActivityStore(db *sqlx.DB) store.PullReqActivityStore {
|
||||||
NewPullReqStore(db),
|
return NewPullReqActivityStore(db)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -212,6 +212,10 @@ type (
|
|||||||
// Update the pull request. It will set new values to the Version and Updated fields.
|
// Update the pull request. It will set new values to the Version and Updated fields.
|
||||||
Update(ctx context.Context, repo *types.PullReq) error
|
Update(ctx context.Context, repo *types.PullReq) error
|
||||||
|
|
||||||
|
// UpdateActivitySeq the pull request's activity sequence number.
|
||||||
|
// It will set new values to the ActivitySeq, Version and Updated fields.
|
||||||
|
UpdateActivitySeq(ctx context.Context, pr *types.PullReq) (*types.PullReq, error)
|
||||||
|
|
||||||
// Delete the pull request.
|
// Delete the pull request.
|
||||||
Delete(ctx context.Context, id int64) error
|
Delete(ctx context.Context, id int64) error
|
||||||
|
|
||||||
@ -225,6 +229,28 @@ type (
|
|||||||
List(ctx context.Context, repoID int64, opts *types.PullReqFilter) ([]*types.PullReq, error)
|
List(ctx context.Context, repoID int64, opts *types.PullReqFilter) ([]*types.PullReq, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PullReqActivityStore interface {
|
||||||
|
// Find the pull request activity by id.
|
||||||
|
Find(ctx context.Context, id int64) (*types.PullReqActivity, error)
|
||||||
|
|
||||||
|
// Create a new pull request activity. Value of the Order field should be fetched with UpdateActivitySeq.
|
||||||
|
// Value of the SubOrder field (for replies) should be fetched with UpdateReplySeq (non-replies have 0).
|
||||||
|
Create(ctx context.Context, act *types.PullReqActivity) error
|
||||||
|
|
||||||
|
// Update the pull request activity. It will set new values to the Version and Updated fields.
|
||||||
|
Update(ctx context.Context, act *types.PullReqActivity) error
|
||||||
|
|
||||||
|
// UpdateReplySeq the pull request activity's reply sequence number.
|
||||||
|
// It will set new values to the ReplySeq, Version and Updated fields.
|
||||||
|
UpdateReplySeq(ctx context.Context, act *types.PullReqActivity) (*types.PullReqActivity, error)
|
||||||
|
|
||||||
|
// Count returns number of pull request activities in a pull request.
|
||||||
|
Count(ctx context.Context, prID int64, opts *types.PullReqActivityFilter) (int64, error)
|
||||||
|
|
||||||
|
// List returns a list of pull request activities in a pull request (a timeline).
|
||||||
|
List(ctx context.Context, prID int64, opts *types.PullReqActivityFilter) ([]*types.PullReqActivity, error)
|
||||||
|
}
|
||||||
|
|
||||||
// SystemStore defines internal system metadata storage.
|
// SystemStore defines internal system metadata storage.
|
||||||
SystemStore interface {
|
SystemStore interface {
|
||||||
// Config returns the system configuration.
|
// Config returns the system configuration.
|
||||||
|
@ -4,7 +4,10 @@
|
|||||||
|
|
||||||
package enum
|
package enum
|
||||||
|
|
||||||
import "strings"
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
// PullReqState defines pull request state.
|
// PullReqState defines pull request state.
|
||||||
type PullReqState string
|
type PullReqState string
|
||||||
@ -58,3 +61,67 @@ func (a PullReqSort) String() string {
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PullReqActivityType defines pull request activity message type.
|
||||||
|
// Essentially, the Type determines the structure of the pull request activity's Payload structure.
|
||||||
|
type PullReqActivityType string
|
||||||
|
|
||||||
|
// PullReqActivityType enumeration.
|
||||||
|
const (
|
||||||
|
PullReqActivityTypeComment PullReqActivityType = "comment"
|
||||||
|
PullReqActivityTypeCodeComment PullReqActivityType = "code-comment"
|
||||||
|
PullReqActivityTypeTitleChange PullReqActivityType = "title-change"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pullReqActivityTypes = []string{
|
||||||
|
string(PullReqActivityTypeComment),
|
||||||
|
string(PullReqActivityTypeCodeComment),
|
||||||
|
string(PullReqActivityTypeTitleChange),
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
sort.Strings(pullReqActivityTypes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePullReqActivityType parses the pull request activity type.
|
||||||
|
func ParsePullReqActivityType(s string) (PullReqActivityType, bool) {
|
||||||
|
if existsInSortedSlice(pullReqActivityTypes, s) {
|
||||||
|
return PullReqActivityType(s), true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// PullReqActivityKind defines kind of pull request activity system message.
|
||||||
|
// Kind defines the source of the pull request activity entry:
|
||||||
|
// Whether it's generated by the system, it's a user comment or a part of code review.
|
||||||
|
type PullReqActivityKind string
|
||||||
|
|
||||||
|
// PullReqActivityKind enumeration.
|
||||||
|
const (
|
||||||
|
PullReqActivityKindSystem PullReqActivityKind = "system"
|
||||||
|
PullReqActivityKindComment PullReqActivityKind = "comment"
|
||||||
|
PullReqActivityKindCodeComment PullReqActivityKind = "code"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pullReqActivityKinds = []string{
|
||||||
|
string(PullReqActivityKindSystem),
|
||||||
|
string(PullReqActivityKindComment),
|
||||||
|
string(PullReqActivityTypeCodeComment),
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
sort.Strings(pullReqActivityKinds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePullReqActivityKind parses the pull request activity type.
|
||||||
|
func ParsePullReqActivityKind(s string) (PullReqActivityKind, bool) {
|
||||||
|
if existsInSortedSlice(pullReqActivityKinds, s) {
|
||||||
|
return PullReqActivityKind(s), true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func existsInSortedSlice(strs []string, s string) bool {
|
||||||
|
idx := sort.SearchStrings(strs, s)
|
||||||
|
return idx >= 0 && idx < len(strs) && strs[idx] == s
|
||||||
|
}
|
||||||
|
@ -19,4 +19,7 @@ const (
|
|||||||
date = "date"
|
date = "date"
|
||||||
defaultString = "default"
|
defaultString = "default"
|
||||||
undefined = "undefined"
|
undefined = "undefined"
|
||||||
|
system = "system"
|
||||||
|
comment = "comment"
|
||||||
|
code = "code"
|
||||||
)
|
)
|
||||||
|
@ -29,7 +29,7 @@ type PullReq struct {
|
|||||||
TargetRepoID int64 `json:"target_repo_id"`
|
TargetRepoID int64 `json:"target_repo_id"`
|
||||||
TargetBranch string `json:"target_branch"`
|
TargetBranch string `json:"target_branch"`
|
||||||
|
|
||||||
PullReqActivitySeq int64 `json:"-"` // not returned, because it's a server internal field
|
ActivitySeq int64 `json:"-"` // not returned, because it's a server's internal field
|
||||||
|
|
||||||
MergedBy *int64 `json:"-"` // not returned, because the merger info is in the Merger field
|
MergedBy *int64 `json:"-"` // not returned, because the merger info is in the Merger field
|
||||||
Merged *int64 `json:"merged"`
|
Merged *int64 `json:"merged"`
|
||||||
@ -59,23 +59,25 @@ type PullReqActivity struct {
|
|||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Version int64 `json:"version"`
|
Version int64 `json:"version"`
|
||||||
|
|
||||||
CreatedBy int64 `json:"-"` // not returned, because the author info is in the Author field
|
CreatedBy int64 `json:"-"` // not returned, because the author info is in the Author field
|
||||||
Created int64 `json:"created"`
|
Created int64 `json:"created"`
|
||||||
Updated int64 `json:"updated"`
|
Updated int64 `json:"updated"`
|
||||||
Edited int64 `json:"edited"`
|
Edited int64 `json:"edited"`
|
||||||
Deleted int64 `json:"deleted"`
|
Deleted *int64 `json:"deleted"`
|
||||||
|
|
||||||
RepoID int64 `json:"repo_id"`
|
RepoID int64 `json:"repo_id"`
|
||||||
PullReqID int64 `json:"pullreq_id"`
|
PullReqID int64 `json:"pullreq_id"`
|
||||||
|
|
||||||
Seq int64 `json:"seq"`
|
Order int64 `json:"order"`
|
||||||
SubSeq int64 `json:"subseq"`
|
SubOrder int64 `json:"sub_order"`
|
||||||
|
ReplySeq int64 `json:"-"` // not returned, because it's a server's internal field
|
||||||
|
|
||||||
Type int64 `json:"type"`
|
Type enum.PullReqActivityType `json:"type"`
|
||||||
Kind int64 `json:"kind"`
|
Kind enum.PullReqActivityKind `json:"kind"`
|
||||||
|
|
||||||
Text string `json:"title"`
|
Text string `json:"title"`
|
||||||
Payload map[string]interface{} `json:"payload"`
|
Payload map[string]interface{} `json:"payload"`
|
||||||
|
Metadata map[string]interface{} `json:"metadata"`
|
||||||
|
|
||||||
ResolvedBy *int64 `json:"-"` // not returned, because the resolver info is in the Resolver field
|
ResolvedBy *int64 `json:"-"` // not returned, because the resolver info is in the Resolver field
|
||||||
Resolved *int64 `json:"resolved"`
|
Resolved *int64 `json:"resolved"`
|
||||||
@ -83,3 +85,13 @@ type PullReqActivity struct {
|
|||||||
Author PrincipalInfo `json:"author"`
|
Author PrincipalInfo `json:"author"`
|
||||||
Resolver *PrincipalInfo `json:"resolver"`
|
Resolver *PrincipalInfo `json:"resolver"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PullReqActivityFilter stores pull request activity query parameters.
|
||||||
|
type PullReqActivityFilter struct {
|
||||||
|
Since int64 `json:"since"`
|
||||||
|
Until int64 `json:"until"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
|
||||||
|
Types []enum.PullReqActivityType `json:"type"`
|
||||||
|
Kinds []enum.PullReqActivityKind `json:"kind"`
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user