mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 04:14:01 +01:00 
			
		
		
		
	Implement Review form
Show Review comments on comment stream Signed-off-by: Jonas Franz <info@jonasfranz.software>
This commit is contained in:
		
							parent
							
								
									e252d3bdb5
								
							
						
					
					
						commit
						3e5f3c349e
					
				@ -27,6 +27,21 @@ const (
 | 
			
		||||
	ReviewTypeReject
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Icon returns the corresponding icon for the review type
 | 
			
		||||
func (rt ReviewType) Icon() string {
 | 
			
		||||
	switch rt {
 | 
			
		||||
	case ReviewTypeApprove:
 | 
			
		||||
		return "eye"
 | 
			
		||||
	case ReviewTypeReject:
 | 
			
		||||
		return "x"
 | 
			
		||||
	default:
 | 
			
		||||
	case ReviewTypeComment:
 | 
			
		||||
	case ReviewTypeUnknown:
 | 
			
		||||
		return "comment"
 | 
			
		||||
	}
 | 
			
		||||
	return "comment"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Review represents collection of code comments giving feedback for a PR
 | 
			
		||||
type Review struct {
 | 
			
		||||
	ID         int64 `xorm:"pk autoincr"`
 | 
			
		||||
@ -177,3 +192,11 @@ func getCurrentReview(e Engine, reviewer *User, issue *Issue) (*Review, error) {
 | 
			
		||||
func GetCurrentReview(reviewer *User, issue *Issue) (*Review, error) {
 | 
			
		||||
	return getCurrentReview(x, reviewer, issue)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateReview will update all cols of the given review in db
 | 
			
		||||
func UpdateReview(r *Review) error {
 | 
			
		||||
	if _, err := x.ID(r.ID).AllCols().Update(r); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -371,6 +371,31 @@ func (f *CodeCommentForm) Validate(ctx *macaron.Context, errs binding.Errors) bi
 | 
			
		||||
	return validate(errs, ctx.Data, f, ctx.Locale)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SubmitReviewForm for submitting a finished code review
 | 
			
		||||
type SubmitReviewForm struct {
 | 
			
		||||
	Content string
 | 
			
		||||
	Type    string `binding:"Required;In(approve,comment,reject)"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate validates the fields
 | 
			
		||||
func (f *SubmitReviewForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
 | 
			
		||||
	return validate(errs, ctx.Data, f, ctx.Locale)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReviewType will return the corresponding reviewtype for type
 | 
			
		||||
func (f SubmitReviewForm) ReviewType() models.ReviewType {
 | 
			
		||||
	switch f.Type {
 | 
			
		||||
	case "approve":
 | 
			
		||||
		return models.ReviewTypeApprove
 | 
			
		||||
	case "comment":
 | 
			
		||||
		return models.ReviewTypeComment
 | 
			
		||||
	case "reject":
 | 
			
		||||
		return models.ReviewTypeReject
 | 
			
		||||
	default:
 | 
			
		||||
		return models.ReviewTypeUnknown
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// __________       .__
 | 
			
		||||
// \______   \ ____ |  |   ____ _____    ______ ____
 | 
			
		||||
//  |       _// __ \|  | _/ __ \\__  \  /  ___// __ \
 | 
			
		||||
 | 
			
		||||
@ -757,6 +757,9 @@ issues.due_date_added = "added the due date %s %s"
 | 
			
		||||
issues.due_date_modified = "modified the due date to %s from %s %s"
 | 
			
		||||
issues.due_date_remove = "removed the due date %s %s"
 | 
			
		||||
issues.due_date_overdue = "Overdue"
 | 
			
		||||
issues.review.approve = "approved these changes %s"
 | 
			
		||||
issues.review.comment = "left review comments %s"
 | 
			
		||||
issues.review.reject = "rejected these changes %s"
 | 
			
		||||
 | 
			
		||||
pulls.desc = Enable merge requests and code reviews.
 | 
			
		||||
pulls.new = New Pull Request
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -684,6 +684,21 @@
 | 
			
		||||
                        margin-right: -1px;
 | 
			
		||||
                        font-size: 25px;
 | 
			
		||||
                    }
 | 
			
		||||
                    &.octicon-comment {
 | 
			
		||||
                        margin-top: 4px;
 | 
			
		||||
                        margin-left: -35px;
 | 
			
		||||
                        font-size: 24px;
 | 
			
		||||
                    }
 | 
			
		||||
                    &.octicon-eye {
 | 
			
		||||
                        margin-top: 3px;
 | 
			
		||||
                        margin-left: -35px;
 | 
			
		||||
                        margin-right: 0px;
 | 
			
		||||
                        font-size: 22px;
 | 
			
		||||
                    }
 | 
			
		||||
                    &.octicon-x {
 | 
			
		||||
                        margin-left: -33px;
 | 
			
		||||
                        font-size: 25px;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                .detail {
 | 
			
		||||
                    font-size: 0.9rem;
 | 
			
		||||
 | 
			
		||||
@ -706,6 +706,11 @@ func ViewIssue(ctx *context.Context) {
 | 
			
		||||
				ctx.ServerError("LoadAssignees", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		} else if comment.Type == models.CommentTypeCode || comment.Type == models.CommentTypeReview {
 | 
			
		||||
			if err = comment.LoadReview(); err != nil {
 | 
			
		||||
				ctx.ServerError("LoadReview", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,6 @@ import (
 | 
			
		||||
// CreateCodeComment will create a code comment including an pending review if required
 | 
			
		||||
func CreateCodeComment(ctx *context.Context, form auth.CodeCommentForm) {
 | 
			
		||||
	issue := GetActionIssue(ctx)
 | 
			
		||||
 | 
			
		||||
	if !issue.IsPull {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@ -88,3 +87,69 @@ func CreateCodeComment(ctx *context.Context, form auth.CodeCommentForm) {
 | 
			
		||||
 | 
			
		||||
	log.Trace("Comment created: %d/%d/%d", ctx.Repo.Repository.ID, issue.ID, comment.ID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SubmitReview creates a review out of the existing pending review or creates a new one if no pending review exist
 | 
			
		||||
func SubmitReview(ctx *context.Context, form auth.SubmitReviewForm) {
 | 
			
		||||
	issue := GetActionIssue(ctx)
 | 
			
		||||
	if !issue.IsPull {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if ctx.Written() {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if ctx.HasError() {
 | 
			
		||||
		ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
 | 
			
		||||
		ctx.Redirect(fmt.Sprintf("%s/pulls/%d/files", ctx.Repo.RepoLink, issue.Index))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	var review *models.Review
 | 
			
		||||
	var err error
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if review != nil {
 | 
			
		||||
			comm, err := models.CreateComment(&models.CreateCommentOptions{
 | 
			
		||||
				Type:     models.CommentTypeReview,
 | 
			
		||||
				Doer:     ctx.User,
 | 
			
		||||
				Content:  review.Content,
 | 
			
		||||
				Issue:    issue,
 | 
			
		||||
				Repo:     issue.Repo,
 | 
			
		||||
				ReviewID: review.ID,
 | 
			
		||||
			})
 | 
			
		||||
			if err != nil || comm == nil {
 | 
			
		||||
				ctx.Redirect(fmt.Sprintf("%s/pulls/%d/files", ctx.Repo.RepoLink, issue.Index))
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Redirect(fmt.Sprintf("%s/pulls/%d#%s", ctx.Repo.RepoLink, issue.Index, comm.HashTag()))
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.Redirect(fmt.Sprintf("%s/pulls/%d/files", ctx.Repo.RepoLink, issue.Index))
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	reviewType := form.ReviewType()
 | 
			
		||||
	if reviewType == models.ReviewTypeUnknown {
 | 
			
		||||
		ctx.ServerError("GetCurrentReview", fmt.Errorf("unknown ReviewType: %s", form.Type))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	review, err = models.GetCurrentReview(ctx.User, issue)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if !models.IsErrReviewNotExist(err) {
 | 
			
		||||
			ctx.ServerError("GetCurrentReview", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		// No current review. Create a new one!
 | 
			
		||||
		if review, err = models.CreateReview(models.CreateReviewOptions{
 | 
			
		||||
			Type:     reviewType,
 | 
			
		||||
			Issue:    issue,
 | 
			
		||||
			Reviewer: ctx.User,
 | 
			
		||||
			Content:  form.Content,
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			ctx.ServerError("CreateReview", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	review.Content = form.Content
 | 
			
		||||
	review.Type = reviewType
 | 
			
		||||
	if err = models.UpdateReview(review); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -639,6 +639,7 @@ func RegisterRoutes(m *macaron.Macaron) {
 | 
			
		||||
				m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ViewPullFiles)
 | 
			
		||||
				m.Group("/reviews", func() {
 | 
			
		||||
					m.Post("/comments", bindIgnErr(auth.CodeCommentForm{}), repo.CreateCodeComment)
 | 
			
		||||
					m.Post("/submit", bindIgnErr(auth.SubmitReviewForm{}), repo.SubmitReview)
 | 
			
		||||
				})
 | 
			
		||||
			})
 | 
			
		||||
		}, repo.MustAllowPulls)
 | 
			
		||||
 | 
			
		||||
@ -122,7 +122,7 @@
 | 
			
		||||
																	    {{ template "repo/diff/comments" dict "root" $ "comments" (index $.CodeComments $file.Name (mul $line.LeftIdx -1))}}
 | 
			
		||||
																	    </ui>
 | 
			
		||||
																    </div>
 | 
			
		||||
															    {{template "repo/diff/comment_form" .}}
 | 
			
		||||
															    {{template "repo/diff/comment_form" $}}
 | 
			
		||||
															    </div>
 | 
			
		||||
														    </td>
 | 
			
		||||
													    {{end}}
 | 
			
		||||
 | 
			
		||||
@ -1,25 +1,29 @@
 | 
			
		||||
<div class="ui top right pointing dropdown custom">
 | 
			
		||||
<div class="ui top right pointing dropdown custom" id="review-box">
 | 
			
		||||
	<div class="ui tiny green button btn-review">
 | 
			
		||||
		<span class="text">{{.i18n.Tr "repo.diff.review"}}</span>
 | 
			
		||||
		<i class="dropdown icon"></i>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="menu">
 | 
			
		||||
		<div class="ui clearing segment">
 | 
			
		||||
			<form class="ui form" action="{{.Link}}" method="post">
 | 
			
		||||
			<form class="ui form" action="{{.Link}}/reviews/submit" method="post">
 | 
			
		||||
			{{.CsrfTokenHtml}}
 | 
			
		||||
				<div class="ui right floated">
 | 
			
		||||
					<a href="#" class="close"><i class="icon close"></i></a>
 | 
			
		||||
					<button onclick="$('.btn-review').click()" type="button" class="ui tiny icon button"><i class="icon close"></i></button>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="header">
 | 
			
		||||
				{{$.i18n.Tr "repo.diff.review.header"}}
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="ui field">
 | 
			
		||||
					<textarea name="comment" tabindex="0" rows="2" placeholder="{{$.i18n.Tr "repo.diff.review.placeholder"}}"></textarea>
 | 
			
		||||
					<textarea name="content" tabindex="0" rows="2"
 | 
			
		||||
							  placeholder="{{$.i18n.Tr "repo.diff.review.placeholder"}}"></textarea>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="ui divider"></div>
 | 
			
		||||
				<div class="ui submit green tiny button btn-submit">{{$.i18n.Tr "repo.diff.review.approve"}}</div>
 | 
			
		||||
				<div class="ui submit tiny basic button btn-submit">{{$.i18n.Tr "repo.diff.review.comment"}}</div>
 | 
			
		||||
				<div class="ui submit red tiny button btn-submit">{{$.i18n.Tr "repo.diff.review.reject"}}</div>
 | 
			
		||||
				<button type="submit" name="type" value="approve"
 | 
			
		||||
						class="ui submit green tiny button btn-submit">{{$.i18n.Tr "repo.diff.review.approve"}}</button>
 | 
			
		||||
				<button type="submit" name="type" value="comment"
 | 
			
		||||
						class="ui submit tiny basic button btn-submit">{{$.i18n.Tr "repo.diff.review.comment"}}</button>
 | 
			
		||||
				<button type="submit" name="type" value="reject"
 | 
			
		||||
						class="ui submit red tiny button btn-submit">{{$.i18n.Tr "repo.diff.review.reject"}}</button>
 | 
			
		||||
			</form>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
{{range .Issue.Comments}}
 | 
			
		||||
	{{ $createdStr:= TimeSinceUnix .CreatedUnix $.Lang }}
 | 
			
		||||
 | 
			
		||||
	<!-- 0 = COMMENT, 1 = REOPEN, 2 = CLOSE, 3 = ISSUE_REF, 4 = COMMIT_REF, 5 = COMMENT_REF, 6 = PULL_REF, 7 = COMMENT_LABEL, 12 = START_TRACKING, 13 = STOP_TRACKING, 14 = ADD_TIME_MANUAL, 16 = ADDED_DEADLINE, 17 = MODIFIED_DEADLINE, 18 = REMOVED_DEADLINE -->
 | 
			
		||||
	<!-- 0 = COMMENT, 1 = REOPEN, 2 = CLOSE, 3 = ISSUE_REF, 4 = COMMIT_REF, 5 = COMMENT_REF, 6 = PULL_REF, 7 = COMMENT_LABEL, 12 = START_TRACKING, 13 = STOP_TRACKING, 14 = ADD_TIME_MANUAL, 16 = ADDED_DEADLINE, 17 = MODIFIED_DEADLINE, 18 = REMOVED_DEADLINE, 19 = CODE, 20 = REVIEW -->
 | 
			
		||||
	{{if eq .Type 0}}
 | 
			
		||||
		<div class="comment" id="{{.HashTag}}">
 | 
			
		||||
			<a class="avatar" {{if gt .Poster.ID 0}}href="{{.Poster.HomeLink}}"{{end}}>
 | 
			
		||||
@ -219,5 +219,29 @@
 | 
			
		||||
			{{$.i18n.Tr "repo.issues.due_date_remove" .Content $createdStr | Safe}}
 | 
			
		||||
			</span>
 | 
			
		||||
		</div>
 | 
			
		||||
	{{else if eq .Type 20}}
 | 
			
		||||
	    <div class="event" id="{{.HashTag}}">
 | 
			
		||||
	    	<span class="octicon octicon-{{.Review.Type.Icon}}"></span>
 | 
			
		||||
	    	<a class="ui avatar image" href="{{.Poster.HomeLink}}">
 | 
			
		||||
	    		<img src="{{.Poster.RelAvatarLink}}">
 | 
			
		||||
	    	</a>
 | 
			
		||||
	    	<span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a>
 | 
			
		||||
	    		{{if eq .Review.Type 1}}
 | 
			
		||||
	    			{{$.i18n.Tr "repo.issues.review.approve" $createdStr | Safe}}
 | 
			
		||||
	    		{{else if eq .Review.Type 2}}
 | 
			
		||||
	    			{{$.i18n.Tr "repo.issues.review.comment" $createdStr | Safe}}
 | 
			
		||||
	    		{{else if eq .Review.Type 3}}
 | 
			
		||||
	    			{{$.i18n.Tr "repo.issues.review.reject" $createdStr | Safe}}
 | 
			
		||||
	    		{{else}}
 | 
			
		||||
	    			{{$.i18n.Tr "repo.issues.review.comment" $createdStr | Safe}}
 | 
			
		||||
	    		{{end}}
 | 
			
		||||
	    	</span>
 | 
			
		||||
	    	{{if .Content}}
 | 
			
		||||
	    		<div class="detail">
 | 
			
		||||
					<span class="octicon octicon-quote"></span>
 | 
			
		||||
	    			<span class="text grey">{{.Content}}</span>
 | 
			
		||||
	    		</div>
 | 
			
		||||
	    	{{end}}
 | 
			
		||||
	    </div>
 | 
			
		||||
	{{end}}
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user