@ -334,54 +334,51 @@ func IsOfficialReviewerTeam(ctx context.Context, issue *Issue, team *organizatio
// CreateReview creates a new review based on opts
func CreateReview ( ctx context . Context , opts CreateReviewOptions ) ( * Review , error ) {
ctx , committer , err := db . TxContext ( ctx )
if err != nil {
return nil , err
}
defer committer . Close ( )
sess := db . GetEngine ( ctx )
review := & Review {
Issue : opts . Issue ,
IssueID : opts . Issue . ID ,
Reviewer : opts . Reviewer ,
ReviewerTeam : opts . ReviewerTeam ,
Content : opts . Content ,
Official : opts . Official ,
CommitID : opts . CommitID ,
Stale : opts . Stale ,
}
return db . WithTx2 ( ctx , func ( ctx context . Context ) ( * Review , error ) {
sess := db . GetEngine ( ctx )
review := & Review {
Issue : opts . Issue ,
IssueID : opts . Issue . ID ,
Reviewer : opts . Reviewer ,
ReviewerTeam : opts . ReviewerTeam ,
Content : opts . Content ,
Official : opts . Official ,
CommitID : opts . CommitID ,
Stale : opts . Stale ,
}
if opts . Reviewer != nil {
review . Type = opts . Type
review . ReviewerID = opts . Reviewer . ID
if opts . Reviewer != nil {
review . Type = opts . Type
review . ReviewerID = opts . Reviewer . ID
reviewCond := builder . Eq { "reviewer_id" : opts . Reviewer . ID , "issue_id" : opts . Issue . ID }
// make sure user review requests are cleared
if opts . Type != ReviewTypePending {
if _ , err := sess . Where ( reviewCond . And ( builder . Eq { "type" : ReviewTypeRequest } ) ) . Delete ( new ( Review ) ) ; err != nil {
return nil , err
reviewCond := builder . Eq { "reviewer_id" : opts . Reviewer . ID , "issue_id" : opts . Issue . ID }
// make sure user review requests are cleared
if opts . Type != ReviewTypePending {
if _ , err := sess . Where ( reviewCond . And ( builder . Eq { "type" : ReviewTypeRequest } ) ) . Delete ( new ( Review ) ) ; err != nil {
return nil , err
}
}
}
// make sure if the created review gets dismissed no old review surface
// other types can be ignored, as they don't affect branch protection
if opts . Type == ReviewTypeApprove || opts . Type == ReviewTypeReject {
if _ , err := sess . Where ( reviewCond . And ( builder . In ( "type" , ReviewTypeApprove , ReviewTypeReject ) ) ) .
Cols ( "dismissed" ) . Update ( & Review { Dismissed : true } ) ; err != nil {
return nil , err
// make sure if the created review gets dismissed no old review surface
// other types can be ignored, as they don't affect branch protection
if opts . Type == ReviewTypeApprove || opts . Type == ReviewTypeReject {
if _ , err := sess . Where ( reviewCond . And ( builder . In ( "type" , ReviewTypeApprove , ReviewTypeReject ) ) ) .
Cols ( "dismissed" ) . Update ( & Review { Dismissed : true } ) ; err != nil {
return nil , err
}
}
} else if opts . ReviewerTeam != nil {
review . Type = ReviewTypeRequest
review . ReviewerTeamID = opts . ReviewerTeam . ID
} else {
return nil , errors . New ( "provide either reviewer or reviewer team" )
}
} else if opts . ReviewerTeam != nil {
review . Type = ReviewTypeRequest
review . ReviewerTeamID = opts . ReviewerTeam . ID
} else {
return nil , errors . New ( "provide either reviewer or reviewer team" )
}
if _ , err := sess . Insert ( review ) ; err != nil {
return nil , err
}
return review , committer . Commit ( )
if _ , err := sess . Insert ( review ) ; err != nil {
return nil , err
}
return review , nil
} )
}
// GetCurrentReview returns the current pending review of reviewer for given issue
@ -605,168 +602,152 @@ func DismissReview(ctx context.Context, review *Review, isDismiss bool) (err err
// InsertReviews inserts review and review comments
func InsertReviews ( ctx context . Context , reviews [ ] * Review ) error {
ctx , committer , err := db . TxContext ( ctx )
if err != nil {
return err
}
defer committer . Close ( )
sess := db . GetEngine ( ctx )
return db . WithTx ( ctx , func ( ctx context . Context ) error {
sess := db . GetEngine ( ctx )
for _ , review := range reviews {
if _ , err := sess . NoAutoTime ( ) . Insert ( review ) ; err != nil {
return err
}
for _ , review := range reviews {
if _ , err := sess . NoAutoTime ( ) . Insert ( review ) ; err != nil {
return err
}
if _ , err := sess . NoAutoTime ( ) . Insert ( & Comment {
Type : CommentTypeReview ,
Content : review . Content ,
PosterID : review . ReviewerID ,
OriginalAuthor : review . OriginalAuthor ,
OriginalAuthorID : review . OriginalAuthorID ,
IssueID : review . IssueID ,
ReviewID : review . ID ,
CreatedUnix : review . CreatedUnix ,
UpdatedUnix : review . UpdatedUnix ,
} ) ; err != nil {
return err
}
if _ , err := sess . NoAutoTime ( ) . Insert ( & Comment {
Type : CommentTypeReview ,
Content : review . Content ,
PosterID : review . ReviewerID ,
OriginalAuthor : review . OriginalAuthor ,
OriginalAuthorID : review . OriginalAuthorID ,
IssueID : review . IssueID ,
ReviewID : review . ID ,
CreatedUnix : review . CreatedUnix ,
UpdatedUnix : review . UpdatedUnix ,
} ) ; err != nil {
return err
}
for _ , c := range review . Comments {
c . ReviewID = review . ID
}
for _ , c := range review . Comments {
c . ReviewID = review . ID
}
if len ( review . Comments ) > 0 {
if _ , err := sess . NoAutoTime ( ) . Insert ( review . Comments ) ; err != nil {
return err
if len ( review . Comments ) > 0 {
if _ , err := sess . NoAutoTime ( ) . Insert ( review . Comments ) ; err != nil {
return err
}
}
}
if err := UpdateIssueNumComments ( ctx , review . IssueID ) ; err != nil {
return err
if err := UpdateIssueNumComments ( ctx , review . IssueID ) ; err != nil {
return err
}
}
}
return committer . Commit ( )
return nil
} )
}
// AddReviewRequest add a review request from one reviewer
func AddReviewRequest ( ctx context . Context , issue * Issue , reviewer , doer * user_model . User ) ( * Comment , error ) {
ctx , committer , err := db . TxContext ( ctx )
if err != nil {
return nil , err
}
defer committer . Close ( )
sess := db . GetEngine ( ctx )
return db . WithTx2 ( ctx , func ( ctx context . Context ) ( * Comment , error ) {
sess := db . GetEngine ( ctx )
review , err := GetReviewByIssueIDAndUserID ( ctx , issue . ID , reviewer . ID )
if err != nil && ! IsErrReviewNotExist ( err ) {
return nil , err
}
if review != nil {
// skip it when reviewer has been request to review
if review . Type == ReviewTypeRequest {
return nil , committer . Commit ( ) // still commit the transaction, or committer.Close() will rollback it, even if it's a reused transaction.
}
if issue . IsClosed {
return nil , ErrReviewRequestOnClosedPR { }
review , err := GetReviewByIssueIDAndUserID ( ctx , issue . ID , reviewer . ID )
if err != nil && ! IsErrReviewNotExist ( err ) {
return nil , err
}
if issue . IsPull {
if err := issue . LoadPullRequest ( ctx ) ; err != nil {
return nil , err
if review != nil {
// skip it when reviewer has been request to review
if review . Type == ReviewTypeRequest {
return nil , nil // still commit the transaction, or committer.Close() will rollback it, even if it's a reused transaction.
}
if issue . PullRequest . HasMerged {
if issue . IsClosed {
return nil , ErrReviewRequestOnClosedPR { }
}
if issue . IsPull {
if err := issue . LoadPullRequest ( ctx ) ; err != nil {
return nil , err
}
if issue . PullRequest . HasMerged {
return nil , ErrReviewRequestOnClosedPR { }
}
}
}
}
// if the reviewer is an official reviewer,
// remove the official flag in the all previous reviews
official , err := IsOfficialReviewer ( ctx , issue , reviewer )
if err != nil {
return nil , err
} else if official {
if _ , err := sess . Exec ( "UPDATE `review` SET official=? WHERE issue_id=? AND reviewer_id=?" , false , issue . ID , reviewer . ID ) ; err != nil {
// if the reviewer is an official reviewer,
// remove the official flag in the all previous reviews
official , err := IsOfficialReviewer ( ctx , issue , reviewer )
if err != nil {
return nil , err
} else if official {
if _ , err := sess . Exec ( "UPDATE `review` SET official=? WHERE issue_id=? AND reviewer_id=?" , false , issue . ID , reviewer . ID ) ; err != nil {
return nil , err
}
}
}
review , err = CreateReview ( ctx , CreateReviewOptions {
Type : ReviewTypeRequest ,
Issue : issue ,
Reviewer : reviewer ,
Official : official ,
Stale : false ,
} )
if err != nil {
return nil , err
}
review , err = CreateReview ( ctx , CreateReviewOptions {
Type : ReviewTypeRequest ,
Issue : issue ,
Reviewer : reviewer ,
Official : official ,
Stale : false ,
} )
if err != nil {
return nil , err
}
comment , err := CreateComment ( ctx , & CreateCommentOptions {
Type : CommentTypeReviewRequest ,
Doer : doer ,
Repo : issue . Repo ,
Issue : issue ,
RemovedAssignee : false , // Use RemovedAssignee as !isRequest
AssigneeID : reviewer . ID , // Use AssigneeID as reviewer ID
ReviewID : review . ID ,
} )
if err != nil {
return nil , err
}
comment , err := CreateComment ( ctx , & CreateCommentOptions {
Type : CommentTypeReviewRequest ,
Doer : doer ,
Repo : issue . Repo ,
Issue : issue ,
RemovedAssignee : false , // Use RemovedAssignee as !isRequest
AssigneeID : reviewer . ID , // Use AssigneeID as reviewer ID
ReviewID : review . ID ,
} )
if err != nil {
return nil , err
}
// func caller use the created comment to retrieve created review too.
comment . Review = review
// func caller use the created comment to retrieve created review too.
comment . Review = review
return comment , committer . Commit ( )
return comment , nil
} )
}
// RemoveReviewRequest remove a review request from one reviewer
func RemoveReviewRequest ( ctx context . Context , issue * Issue , reviewer , doer * user_model . User ) ( * Comment , error ) {
ctx , committer , err := db . TxContext ( ctx )
if err != nil {
return nil , err
}
defer committer . Close ( )
review , err := GetReviewByIssueIDAndUserID ( ctx , issue . ID , reviewer . ID )
if err != nil && ! IsErrReviewNotExist ( err ) {
return nil , err
}
return db . WithTx2 ( ctx , func ( ctx context . Context ) ( * Comment , error ) {
review , err := GetReviewByIssueIDAndUserID ( ctx , issue . ID , reviewer . ID )
if err != nil && ! IsErrReviewNotExist ( err ) {
return nil , err
}
if review == nil || review . Type != ReviewTypeRequest {
return nil , nil
}
if review == nil || review . Type != ReviewTypeRequest {
return nil , nil
}
if _ , err = db . DeleteByBean ( ctx , review ) ; err != nil {
return nil , err
}
if _ , err = db . DeleteByBean ( ctx , review ) ; err != nil {
return nil , err
}
official , err := IsOfficialReviewer ( ctx , issue , reviewer )
if err != nil {
return nil , err
} else if official {
if err := restoreLatestOfficialReview ( ctx , issue . ID , reviewer . ID ) ; err != nil {
official , err := IsOfficialReviewer ( ctx , issue , reviewer )
if err != nil {
return nil , err
} else if official {
if err := restoreLatestOfficialReview ( ctx , issue . ID , reviewer . ID ) ; err != nil {
return nil , err
}
}
}
comment , err := CreateComment ( ctx , & CreateCommentOptions {
Type : CommentTypeReviewRequest ,
Doer : doer ,
Repo : issue . Repo ,
Issue : issue ,
RemovedAssignee : true , // Use RemovedAssignee as !isRequest
AssigneeID : reviewer . ID , // Use AssigneeID as reviewer ID
return CreateComment ( ctx , & CreateCommentOptions {
Type : CommentTypeReviewRequest ,
Doer : doer ,
Repo : issue . Repo ,
Issue : issue ,
RemovedAssignee : true , // Use RemovedAssignee as !isRequest
AssigneeID : reviewer . ID , // Use AssigneeID as reviewer ID
} )
} )
if err != nil {
return nil , err
}
return comment , committer . Commit ( )
}
// Recalculate the latest official review for reviewer
@ -787,120 +768,112 @@ func restoreLatestOfficialReview(ctx context.Context, issueID, reviewerID int64)
// AddTeamReviewRequest add a review request from one team
func AddTeamReviewRequest ( ctx context . Context , issue * Issue , reviewer * organization . Team , doer * user_model . User ) ( * Comment , error ) {
ctx , committer , err := db . TxContext ( ctx )
if err != nil {
return nil , err
}
defer committer . Close ( )
return db . WithTx2 ( ctx , func ( ctx context . Context ) ( * Comment , error ) {
review , err := GetTeamReviewerByIssueIDAndTeamID ( ctx , issue . ID , reviewer . ID )
if err != nil && ! IsErrReviewNotExist ( err ) {
return nil , err
}
review , err := GetTeamReviewerByIssueIDAndTeamID ( ctx , issue . ID , reviewer . ID )
if err != nil && ! IsErrReviewNotExist ( err ) {
return nil , err
}
// This team already has been requested to review - therefore skip this.
if review != nil {
return nil , nil
}
// This team already has been requested to review - therefore skip this.
if review != nil {
return nil , nil
}
official , err := IsOfficialReviewerTeam ( ctx , issue , reviewer )
if err != nil {
return nil , fmt . Errorf ( "isOfficialReviewerTeam(): %w" , err )
} else if ! official {
if official , err = IsOfficialReviewer ( ctx , issue , doer ) ; err != nil {
return nil , fmt . Errorf ( "isOfficialReviewer(): %w" , err )
}
}
official , err := IsOfficialReviewerTeam ( ctx , issue , reviewer )
if err != nil {
return nil , fmt . Errorf ( "isOfficialReviewerTeam(): %w" , err )
} else if ! official {
if official , err = IsOfficialReviewer ( ctx , issue , doer ) ; err != nil {
return nil , fmt . Errorf ( "isOfficialReviewer(): %w" , err )
if review , err = CreateReview ( ctx , CreateReviewOptions {
Type : ReviewTypeRequest ,
Issue : issue ,
ReviewerTeam : reviewer ,
Official : official ,
Stale : false ,
} ) ; err != nil {
return nil , err
}
}
if review , err = CreateReview ( ctx , CreateReviewOptions {
Type : ReviewTypeRequest ,
Issue : issue ,
ReviewerTeam : reviewer ,
Official : official ,
Stale : false ,
} ) ; err != nil {
return nil , err
}
if official {
if _ , err := db . Exec ( ctx , "UPDATE `review` SET official=? WHERE issue_id=? AND reviewer_team_id=?" , false , issue . ID , reviewer . ID ) ; err != nil {
return nil , err
}
}
if official {
if _ , err := db . Exec ( ctx , "UPDATE `review` SET official=? WHERE issue_id=? AND reviewer_team_id=?" , false , issue . ID , reviewer . ID ) ; err != nil {
return nil , err
comment , err := CreateComment ( ctx , & CreateCommentOptions {
Type : CommentTypeReviewRequest ,
Doer : doer ,
Repo : issue . Repo ,
Issue : issue ,
RemovedAssignee : false , // Use RemovedAssignee as !isRequest
AssigneeTeamID : reviewer . ID , // Use AssigneeTeamID as reviewer team ID
ReviewID : review . ID ,
} )
if err != nil {
return nil , fmt . Errorf ( "CreateComment(): %w" , err )
}
}
comment , err := CreateComment ( ctx , & CreateCommentOptions {
Type : CommentTypeReviewRequest ,
Doer : doer ,
Repo : issue . Repo ,
Issue : issue ,
RemovedAssignee : false , // Use RemovedAssignee as !isRequest
AssigneeTeamID : reviewer . ID , // Use AssigneeTeamID as reviewer team ID
ReviewID : review . ID ,
return comment , nil
} )
if err != nil {
return nil , fmt . Errorf ( "CreateComment(): %w" , err )
}
return comment , committer . Commit ( )
}
// RemoveTeamReviewRequest remove a review request from one team
func RemoveTeamReviewRequest ( ctx context . Context , issue * Issue , reviewer * organization . Team , doer * user_model . User ) ( * Comment , error ) {
ctx , committer , err := db . TxContext ( ctx )
if err != nil {
return nil , err
}
defer committer . Close ( )
review , err := GetTeamReviewerByIssueIDAndTeamID ( ctx , issue . ID , reviewer . ID )
if err != nil && ! IsErrReviewNotExist ( err ) {
return nil , err
}
if review == nil {
return nil , nil
}
if _ , err = db . DeleteByBean ( ctx , review ) ; err != nil {
return nil , err
}
return db . WithTx2 ( ctx , func ( ctx context . Context ) ( * Comment , error ) {
review , err := GetTeamReviewerByIssueIDAndTeamID ( ctx , issue . ID , reviewer . ID )
if err != nil && ! IsErrReviewNotExist ( err ) {
return nil , err
}
official , err := IsOfficialReviewerTeam ( ctx , issue , reviewer )
if err != nil {
return nil , fmt . Errorf ( "isOfficialReviewerTeam(): %w" , err )
}
if review == nil {
return nil , nil
}
if official {
// recalculate which is the latest official review from that team
review , err := GetReviewByIssueIDAndUserID ( ctx , issue . ID , - reviewer . ID )
if err != nil && ! IsErrReviewNotExist ( err ) {
if _ , err = db . DeleteByBean ( ctx , review ) ; err != nil {
return nil , err
}
if review != nil {
if _ , err := db . Exec ( ctx , "UPDATE `review` SET official=? WHERE id=?" , true , review . ID ) ; err != nil {
official , err := IsOfficialReviewerTeam ( ctx , issue , reviewer )
if err != nil {
return nil , fmt . Errorf ( "isOfficialReviewerTeam(): %w" , err )
}
if official {
// recalculate which is the latest official review from that team
review , err := GetReviewByIssueIDAndUserID ( ctx , issue . ID , - reviewer . ID )
if err != nil && ! IsErrReviewNotExist ( err ) {
return nil , err
}
if review != nil {
if _ , err := db . Exec ( ctx , "UPDATE `review` SET official=? WHERE id=?" , true , review . ID ) ; err != nil {
return nil , err
}
}
}
}
if doer == nil {
return nil , committer . Commit ( )
}
if doer == nil {
return nil , nil
}
comment , err := CreateComment ( ctx , & CreateCommentOptions {
Type : CommentTypeReviewRequest ,
Doer : doer ,
Repo : issue . Repo ,
Issue : issue ,
RemovedAssignee : true , // Use RemovedAssignee as !isRequest
AssigneeTeamID : reviewer . ID , // Use AssigneeTeamID as reviewer team ID
} )
if err != nil {
return nil , fmt . Errorf ( "CreateComment(): %w" , err )
}
comment , err := CreateComment ( ctx , & CreateCommentOptions {
Type : CommentTypeReviewRequest ,
Doer : doer ,
Repo : issue . Repo ,
Issue : issue ,
RemovedAssignee : true , // Use RemovedAssignee as !isRequest
AssigneeTeamID : reviewer . ID , // Use AssigneeTeamID as reviewer team ID
} )
if err != nil {
return nil , fmt . Errorf ( "CreateComment(): %w" , err )
}
return comment , committer . Commit ( )
return comment , nil
} )
}
// MarkConversation Add or remove Conversation mark for a code comment
@ -966,61 +939,56 @@ func CanMarkConversation(ctx context.Context, issue *Issue, doer *user_model.Use
// DeleteReview delete a review and it's code comments
func DeleteReview ( ctx context . Context , r * Review ) error {
ctx , committer , err := db . TxContext ( ctx )
if err != nil {
return err
}
defer committer . Close ( )
if r . ID == 0 {
return errors . New ( "review is not allowed to be 0" )
}
if r . Type == ReviewTypeRequest {
return errors . New ( "review request can not be deleted using this method" )
}
return db . WithTx ( ctx , func ( ctx context . Context ) error {
if r . ID == 0 {
return errors . New ( "review is not allowed to be 0" )
}
opts := FindCommentsOptions {
Type : CommentTypeCode ,
IssueID : r . IssueID ,
ReviewID : r . ID ,
}
if r . Type == ReviewTypeRequest {
return errors . New ( "review request can not be deleted using this method" )
}
if _ , err := db . Delete [ Comment ] ( ctx , opts ) ; err != nil {
return err
}
opts := FindCommentsOptions {
Type : CommentTypeCode ,
IssueID : r . IssueID ,
ReviewID : r . ID ,
}
opts = FindCommentsOptions {
Type : CommentTypeReview ,
IssueID : r . IssueID ,
ReviewID : r . ID ,
}
if _ , err := db . Delete [ Comment ] ( ctx , opts ) ; err != nil {
return err
}
if _ , err := db . Delete [ Comment ] ( ctx , opts ) ; err != nil {
return err
}
opts = FindCommentsOptions {
Type : CommentTypeReview ,
IssueID : r . IssueID ,
ReviewID : r . ID ,
}
opts = FindCommentsOptions {
Type : CommentTypeDismissReview ,
IssueID : r . IssueID ,
ReviewID : r . ID ,
}
if _ , err := db . Delete [ Comment ] ( ctx , opts ) ; err != nil {
return err
}
if _ , err := db . Delete [ Comment ] ( ctx , opts ) ; err != nil {
return err
}
opts = FindCommentsOptions {
Type : CommentTypeDismissReview ,
IssueID : r . IssueID ,
ReviewID : r . ID ,
}
if _ , err := db . DeleteByID [ Review ] ( ctx , r . ID ) ; err != nil {
return err
}
if _ , err := db . Delete [ Comment ] ( ctx , opts ) ; err != nil {
return err
}
if r . Official {
if err := restoreLatestOfficialReview ( ctx , r . IssueID , r . ReviewerID ) ; err != nil {
if _ , err := db . DeleteByID [ Review ] ( ctx , r . ID ) ; err != nil {
return err
}
}
return committer . Commit ( )
if r . Official {
if err := restoreLatestOfficialReview ( ctx , r . IssueID , r . ReviewerID ) ; err != nil {
return err
}
}
return nil
} )
}
// GetCodeCommentsCount return count of CodeComments a Review has