forked from forgejo/forgejo
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
11 changed files with 179 additions and 14 deletions
|
@ -27,6 +27,21 @@ const (
|
||||||
ReviewTypeReject
|
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
|
// Review represents collection of code comments giving feedback for a PR
|
||||||
type Review struct {
|
type Review struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
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) {
|
func GetCurrentReview(reviewer *User, issue *Issue) (*Review, error) {
|
||||||
return getCurrentReview(x, reviewer, issue)
|
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)
|
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_modified = "modified the due date to %s from %s %s"
|
||||||
issues.due_date_remove = "removed the due date %s %s"
|
issues.due_date_remove = "removed the due date %s %s"
|
||||||
issues.due_date_overdue = "Overdue"
|
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.desc = Enable merge requests and code reviews.
|
||||||
pulls.new = New Pull Request
|
pulls.new = New Pull Request
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -684,6 +684,21 @@
|
||||||
margin-right: -1px;
|
margin-right: -1px;
|
||||||
font-size: 25px;
|
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 {
|
.detail {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
|
@ -1752,4 +1767,4 @@ tbody.commit-list {
|
||||||
|
|
||||||
#repo-topic {
|
#repo-topic {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -706,6 +706,11 @@ func ViewIssue(ctx *context.Context) {
|
||||||
ctx.ServerError("LoadAssignees", err)
|
ctx.ServerError("LoadAssignees", err)
|
||||||
return
|
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
|
// CreateCodeComment will create a code comment including an pending review if required
|
||||||
func CreateCodeComment(ctx *context.Context, form auth.CodeCommentForm) {
|
func CreateCodeComment(ctx *context.Context, form auth.CodeCommentForm) {
|
||||||
issue := GetActionIssue(ctx)
|
issue := GetActionIssue(ctx)
|
||||||
|
|
||||||
if !issue.IsPull {
|
if !issue.IsPull {
|
||||||
return
|
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)
|
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.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ViewPullFiles)
|
||||||
m.Group("/reviews", func() {
|
m.Group("/reviews", func() {
|
||||||
m.Post("/comments", bindIgnErr(auth.CodeCommentForm{}), repo.CreateCodeComment)
|
m.Post("/comments", bindIgnErr(auth.CodeCommentForm{}), repo.CreateCodeComment)
|
||||||
|
m.Post("/submit", bindIgnErr(auth.SubmitReviewForm{}), repo.SubmitReview)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}, repo.MustAllowPulls)
|
}, repo.MustAllowPulls)
|
||||||
|
|
|
@ -122,7 +122,7 @@
|
||||||
{{ template "repo/diff/comments" dict "root" $ "comments" (index $.CodeComments $file.Name (mul $line.LeftIdx -1))}}
|
{{ template "repo/diff/comments" dict "root" $ "comments" (index $.CodeComments $file.Name (mul $line.LeftIdx -1))}}
|
||||||
</ui>
|
</ui>
|
||||||
</div>
|
</div>
|
||||||
{{template "repo/diff/comment_form" .}}
|
{{template "repo/diff/comment_form" $}}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
{{end}}
|
{{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">
|
<div class="ui tiny green button btn-review">
|
||||||
<span class="text">{{.i18n.Tr "repo.diff.review"}}</span>
|
<span class="text">{{.i18n.Tr "repo.diff.review"}}</span>
|
||||||
<i class="dropdown icon"></i>
|
<i class="dropdown icon"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<div class="ui clearing segment">
|
<div class="ui clearing segment">
|
||||||
<form class="ui form" action="{{.Link}}" method="post">
|
<form class="ui form" action="{{.Link}}/reviews/submit" method="post">
|
||||||
{{.CsrfTokenHtml}}
|
{{.CsrfTokenHtml}}
|
||||||
<div class="ui right floated">
|
<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>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
{{$.i18n.Tr "repo.diff.review.header"}}
|
{{$.i18n.Tr "repo.diff.review.header"}}
|
||||||
</div>
|
</div>
|
||||||
<div class="ui field">
|
<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>
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<div class="ui submit green tiny button btn-submit">{{$.i18n.Tr "repo.diff.review.approve"}}</div>
|
<button type="submit" name="type" value="approve"
|
||||||
<div class="ui submit tiny basic button btn-submit">{{$.i18n.Tr "repo.diff.review.comment"}}</div>
|
class="ui submit green tiny button btn-submit">{{$.i18n.Tr "repo.diff.review.approve"}}</button>
|
||||||
<div class="ui submit red tiny button btn-submit">{{$.i18n.Tr "repo.diff.review.reject"}}</div>
|
<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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{{range .Issue.Comments}}
|
{{range .Issue.Comments}}
|
||||||
{{ $createdStr:= TimeSinceUnix .CreatedUnix $.Lang }}
|
{{ $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}}
|
{{if eq .Type 0}}
|
||||||
<div class="comment" id="{{.HashTag}}">
|
<div class="comment" id="{{.HashTag}}">
|
||||||
<a class="avatar" {{if gt .Poster.ID 0}}href="{{.Poster.HomeLink}}"{{end}}>
|
<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}}
|
{{$.i18n.Tr "repo.issues.due_date_remove" .Content $createdStr | Safe}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
Loading…
Add table
Reference in a new issue