mirror of https://github.com/go-gitea/gitea.git
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">
|
||||
{{.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>
|
||||
|
|
|
@ -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…
Reference in New Issue