Implement Review form

Show Review comments on comment stream

Signed-off-by: Jonas Franz <info@jonasfranz.software>
This commit is contained in:
Jonas Franz 2018-05-11 12:50:44 +02:00
parent e252d3bdb5
commit 3e5f3c349e
No known key found for this signature in database
GPG Key ID: 506AEEBE80BEDECD
11 changed files with 179 additions and 14 deletions

View File

@ -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
}

View File

@ -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
}
}
// __________ .__
// \______ \ ____ | | ____ _____ ______ ____
// | _// __ \| | _/ __ \\__ \ / ___// __ \

View File

@ -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

View File

@ -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;

View File

@ -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
}
}
}

View File

@ -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
}
}

View File

@ -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)

View File

@ -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}}

View File

@ -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">
{{.CsrfTokenHtml}}
<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"}}
{{$.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>

View File

@ -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}}