Fix template error when comment review doesn't exist (#29888)

Fix #29885
This commit is contained in:
wxiaoguang 2024-03-19 12:19:48 +08:00 committed by GitHub
parent 0e183d81fc
commit 828701ff2d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 264 additions and 202 deletions

View File

@ -75,3 +75,11 @@
content: "comment in private pository" content: "comment in private pository"
created_unix: 946684811 created_unix: 946684811
updated_unix: 946684811 updated_unix: 946684811
-
id: 9
type: 22 # review
poster_id: 2
issue_id: 2 # in repo_id 1
review_id: 20
created_unix: 946684810

View File

@ -170,3 +170,12 @@
content: "review request for user15" content: "review request for user15"
updated_unix: 946684835 updated_unix: 946684835
created_unix: 946684835 created_unix: 946684835
-
id: 20
type: 22
reviewer_id: 1
issue_id: 2
content: "Review Comment"
updated_unix: 946684810
created_unix: 946684810

View File

@ -4,6 +4,7 @@
package repo package repo
import ( import (
"net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
@ -73,4 +74,20 @@ func TestRenderConversation(t *testing.T) {
renderConversation(ctx, preparedComment, "timeline") renderConversation(ctx, preparedComment, "timeline")
assert.Contains(t, resp.Body.String(), `<div id="code-comments-`) assert.Contains(t, resp.Body.String(), `<div id="code-comments-`)
}) })
run("diff non-existing review", func(t *testing.T, ctx *context.Context, resp *httptest.ResponseRecorder) {
err := db.TruncateBeans(db.DefaultContext, &issues_model.Review{})
assert.NoError(t, err)
ctx.Data["ShowOutdatedComments"] = true
renderConversation(ctx, preparedComment, "diff")
assert.Equal(t, http.StatusOK, resp.Code)
assert.NotContains(t, resp.Body.String(), `status-page-500`)
})
run("timeline non-existing review", func(t *testing.T, ctx *context.Context, resp *httptest.ResponseRecorder) {
err := db.TruncateBeans(db.DefaultContext, &issues_model.Review{})
assert.NoError(t, err)
ctx.Data["ShowOutdatedComments"] = true
renderConversation(ctx, preparedComment, "timeline")
assert.Equal(t, http.StatusOK, resp.Code)
assert.NotContains(t, resp.Body.String(), `status-page-500`)
})
} }

View File

@ -37,7 +37,7 @@
{{ctx.Locale.Tr "repo.issues.review.outdated"}} {{ctx.Locale.Tr "repo.issues.review.outdated"}}
</a> </a>
{{end}} {{end}}
{{if and .Review}} {{if .Review}}
{{if eq .Review.Type 0}} {{if eq .Review.Type 0}}
<div class="ui label basic small yellow pending-label" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.review.pending.tooltip" (ctx.Locale.Tr "repo.diff.review") (ctx.Locale.Tr "repo.diff.review.approve") (ctx.Locale.Tr "repo.diff.review.comment") (ctx.Locale.Tr "repo.diff.review.reject")}}"> <div class="ui label basic small yellow pending-label" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.review.pending.tooltip" (ctx.Locale.Tr "repo.diff.review") (ctx.Locale.Tr "repo.diff.review.approve") (ctx.Locale.Tr "repo.diff.review.comment") (ctx.Locale.Tr "repo.diff.review.reject")}}">
{{ctx.Locale.Tr "repo.issues.review.pending"}} {{ctx.Locale.Tr "repo.issues.review.pending"}}

View File

@ -1,66 +1,72 @@
{{$resolved := (index .comments 0).IsResolved}} {{if len .comments}}
{{$invalid := (index .comments 0).Invalidated}} {{$comment := index .comments 0}}
{{$resolveDoer := (index .comments 0).ResolveDoer}} {{$resolved := $comment.IsResolved}}
{{$isNotPending := (not (eq (index .comments 0).Review.Type 0))}} {{$invalid := $comment.Invalidated}}
{{$referenceUrl := printf "%s#%s" $.Issue.Link (index .comments 0).HashTag}} {{$resolveDoer := $comment.ResolveDoer}}
<div class="conversation-holder" data-path="{{(index .comments 0).TreePath}}" data-side="{{if lt (index .comments 0).Line 0}}left{{else}}right{{end}}" data-idx="{{(index .comments 0).UnsignedLine}}"> {{$hasReview := and $comment.Review}}
{{if $resolved}} {{$isReviewPending := and $hasReview (eq $comment.Review.Type 0)}}
<div class="ui attached header resolved-placeholder gt-df gt-ac gt-sb"> {{$referenceUrl := printf "%s#%s" $.Issue.Link $comment.HashTag}}
<div class="ui grey text gt-df gt-ac gt-fw gt-gap-2"> <div class="conversation-holder" data-path="{{$comment.TreePath}}" data-side="{{if lt $comment.Line 0}}left{{else}}right{{end}}" data-idx="{{$comment.UnsignedLine}}">
{{svg "octicon-check" 16 "icon gt-mr-2"}} {{if $resolved}}
<b>{{$resolveDoer.Name}}</b> {{ctx.Locale.Tr "repo.issues.review.resolved_by"}} <div class="ui attached header resolved-placeholder gt-df gt-ac gt-sb">
{{if $invalid}} <div class="ui grey text gt-df gt-ac gt-fw gt-gap-2">
<!-- {{svg "octicon-check" 16 "icon gt-mr-2"}}
We only handle the case $resolved=true and $invalid=true in this template because if the comment is not resolved it has the outdated label in the comments area (not the header above). <b>{{$resolveDoer.Name}}</b> {{ctx.Locale.Tr "repo.issues.review.resolved_by"}}
The case $resolved=false and $invalid=true is handled in repo/diff/comments.tmpl {{if $invalid}}
--> <!--
<a href="{{$referenceUrl}}" class="ui label basic small gt-ml-3" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.review.outdated_description"}}"> We only handle the case $resolved=true and $invalid=true in this template because if the comment is not resolved it has the outdated label in the comments area (not the header above).
{{ctx.Locale.Tr "repo.issues.review.outdated"}} The case $resolved=false and $invalid=true is handled in repo/diff/comments.tmpl
</a> -->
<a href="{{$referenceUrl}}" class="ui label basic small gt-ml-3" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.review.outdated_description"}}">
{{ctx.Locale.Tr "repo.issues.review.outdated"}}
</a>
{{end}}
</div>
<div class="gt-df gt-ac gt-gap-3">
<button id="show-outdated-{{$comment.ID}}" data-comment="{{$comment.ID}}" class="ui tiny labeled button show-outdated gt-df gt-ac">
{{svg "octicon-unfold" 16 "gt-mr-3"}}
{{ctx.Locale.Tr "repo.issues.review.show_resolved"}}
</button>
<button id="hide-outdated-{{$comment.ID}}" data-comment="{{$comment.ID}}" class="ui tiny labeled button hide-outdated gt-df gt-ac gt-hidden">
{{svg "octicon-fold" 16 "gt-mr-3"}}
{{ctx.Locale.Tr "repo.issues.review.hide_resolved"}}
</button>
</div>
</div>
{{end}}
<div id="code-comments-{{$comment.ID}}" class="field comment-code-cloud {{if $resolved}}gt-hidden{{end}}">
<div class="comment-list">
<ui class="ui comments">
{{template "repo/diff/comments" dict "root" $ "comments" .comments}}
</ui>
</div>
<div class="gt-df gt-je gt-ac gt-fw gt-mt-3">
<div class="ui buttons gt-mr-2">
<button class="ui icon tiny basic button previous-conversation">
{{svg "octicon-arrow-up" 12 "icon"}} {{ctx.Locale.Tr "repo.issues.previous"}}
</button>
<button class="ui icon tiny basic button next-conversation">
{{svg "octicon-arrow-down" 12 "icon"}} {{ctx.Locale.Tr "repo.issues.next"}}
</button>
</div>
{{if and $.CanMarkConversation $hasReview (not $isReviewPending)}}
<button class="ui icon tiny basic button resolve-conversation" data-origin="diff" data-action="{{if not $resolved}}Resolve{{else}}UnResolve{{end}}" data-comment-id="{{$comment.ID}}" data-update-url="{{$.RepoLink}}/issues/resolve_conversation">
{{if $resolved}}
{{ctx.Locale.Tr "repo.issues.review.un_resolve_conversation"}}
{{else}}
{{ctx.Locale.Tr "repo.issues.review.resolve_conversation"}}
{{end}}
</button>
{{end}}
{{if and $.SignedUserID (not $.Repository.IsArchived)}}
<button class="comment-form-reply ui primary tiny labeled icon button gt-ml-2 gt-mr-0">
{{svg "octicon-reply" 16 "reply icon gt-mr-2"}}{{ctx.Locale.Tr "repo.diff.comment.reply"}}
</button>
{{end}} {{end}}
</div> </div>
<div class="gt-df gt-ac gt-gap-3"> {{template "repo/diff/comment_form_datahandler" dict "hidden" true "reply" $comment.ReviewID "root" $ "comment" $comment}}
<button id="show-outdated-{{(index .comments 0).ID}}" data-comment="{{(index .comments 0).ID}}" class="ui tiny labeled button show-outdated gt-df gt-ac">
{{svg "octicon-unfold" 16 "gt-mr-3"}}
{{ctx.Locale.Tr "repo.issues.review.show_resolved"}}
</button>
<button id="hide-outdated-{{(index .comments 0).ID}}" data-comment="{{(index .comments 0).ID}}" class="ui tiny labeled button hide-outdated gt-df gt-ac gt-hidden">
{{svg "octicon-fold" 16 "gt-mr-3"}}
{{ctx.Locale.Tr "repo.issues.review.hide_resolved"}}
</button>
</div>
</div> </div>
{{end}}
<div id="code-comments-{{(index .comments 0).ID}}" class="field comment-code-cloud {{if $resolved}}gt-hidden{{end}}">
<div class="comment-list">
<ui class="ui comments">
{{template "repo/diff/comments" dict "root" $ "comments" .comments}}
</ui>
</div>
<div class="gt-df gt-je gt-ac gt-fw gt-mt-3">
<div class="ui buttons gt-mr-2">
<button class="ui icon tiny basic button previous-conversation">
{{svg "octicon-arrow-up" 12 "icon"}} {{ctx.Locale.Tr "repo.issues.previous"}}
</button>
<button class="ui icon tiny basic button next-conversation">
{{svg "octicon-arrow-down" 12 "icon"}} {{ctx.Locale.Tr "repo.issues.next"}}
</button>
</div>
{{if and $.CanMarkConversation $isNotPending}}
<button class="ui icon tiny basic button resolve-conversation" data-origin="diff" data-action="{{if not $resolved}}Resolve{{else}}UnResolve{{end}}" data-comment-id="{{(index .comments 0).ID}}" data-update-url="{{$.RepoLink}}/issues/resolve_conversation">
{{if $resolved}}
{{ctx.Locale.Tr "repo.issues.review.un_resolve_conversation"}}
{{else}}
{{ctx.Locale.Tr "repo.issues.review.resolve_conversation"}}
{{end}}
</button>
{{end}}
{{if and $.SignedUserID (not $.Repository.IsArchived)}}
<button class="comment-form-reply ui primary tiny labeled icon button gt-ml-2 gt-mr-0">
{{svg "octicon-reply" 16 "reply icon gt-mr-2"}}{{ctx.Locale.Tr "repo.diff.comment.reply"}}
</button>
{{end}}
</div>
{{template "repo/diff/comment_form_datahandler" dict "hidden" true "reply" (index .comments 0).ReviewID "root" $ "comment" (index .comments 0)}}
</div> </div>
</div> {{else}}
{{template "repo/diff/conversation_outdated"}}
{{end}}

View File

@ -371,27 +371,31 @@
{{else if eq .Type 22}} {{else if eq .Type 22}}
<div class="timeline-item-group" id="{{.HashTag}}"> <div class="timeline-item-group" id="{{.HashTag}}">
<div class="timeline-item event"> <div class="timeline-item event">
{{$reviewType := -1}}
{{if .Review}}{{$reviewType = .Review.Type}}{{end}}
{{if not .OriginalAuthor}} {{if not .OriginalAuthor}}
{{/* Some timeline avatars need a offset to correctly align with their speech {{/* Some timeline avatars need a offset to correctly align with their speech
bubble. The condition depends on review type and for positive reviews whether bubble. The condition depends on review type and for positive reviews whether
there is a comment element or not */}} there is a comment element or not */}}
<a class="timeline-avatar{{if or (and (eq .Review.Type 1) (or .Content .Attachments)) (and (eq .Review.Type 2) (or .Content .Attachments)) (eq .Review.Type 3)}} timeline-avatar-offset{{end}}"{{if gt .Poster.ID 0}} href="{{.Poster.HomeLink}}"{{end}}> <a class="timeline-avatar{{if or (and (eq $reviewType 1) (or .Content .Attachments)) (and (eq $reviewType 2) (or .Content .Attachments)) (eq $reviewType 3)}} timeline-avatar-offset{{end}}"{{if gt .Poster.ID 0}} href="{{.Poster.HomeLink}}"{{end}}>
{{ctx.AvatarUtils.Avatar .Poster 40}} {{ctx.AvatarUtils.Avatar .Poster 40}}
</a> </a>
{{end}} {{end}}
<span class="badge{{if eq .Review.Type 1}} tw-bg-green tw-text-white{{else if eq .Review.Type 3}} tw-bg-red tw-text-white{{end}}">{{svg (printf "octicon-%s" .Review.Type.Icon)}}</span> <span class="badge{{if eq $reviewType 1}} tw-bg-green tw-text-white{{else if eq $reviewType 3}} tw-bg-red tw-text-white{{end}}">
{{if .Review}}{{svg (printf "octicon-%s" .Review.Type.Icon)}}{{end}}
</span>
<span class="text grey muted-links"> <span class="text grey muted-links">
{{template "repo/issue/view_content/comments_authorlink" dict "ctxData" $ "comment" .}} {{template "repo/issue/view_content/comments_authorlink" dict "ctxData" $ "comment" .}}
{{if eq .Review.Type 1}} {{if eq $reviewType 1}}
{{ctx.Locale.Tr "repo.issues.review.approve" $createdStr}} {{ctx.Locale.Tr "repo.issues.review.approve" $createdStr}}
{{else if eq .Review.Type 2}} {{else if eq $reviewType 2}}
{{ctx.Locale.Tr "repo.issues.review.comment" $createdStr}} {{ctx.Locale.Tr "repo.issues.review.comment" $createdStr}}
{{else if eq .Review.Type 3}} {{else if eq $reviewType 3}}
{{ctx.Locale.Tr "repo.issues.review.reject" $createdStr}} {{ctx.Locale.Tr "repo.issues.review.reject" $createdStr}}
{{else}} {{else}}
{{ctx.Locale.Tr "repo.issues.review.comment" $createdStr}} {{ctx.Locale.Tr "repo.issues.review.comment" $createdStr}}
{{end}} {{end}}
{{if .Review.Dismissed}} {{if and .Review .Review.Dismissed}}
<div class="ui small label">{{ctx.Locale.Tr "repo.issues.review.dismissed_label"}}</div> <div class="ui small label">{{ctx.Locale.Tr "repo.issues.review.dismissed_label"}}</div>
{{end}} {{end}}
</span> </span>
@ -451,7 +455,7 @@
</div> </div>
{{end}} {{end}}
{{if .Review.CodeComments}} {{if and .Review .Review.CodeComments}}
<div class="timeline-item event"> <div class="timeline-item event">
{{range $filename, $lines := .Review.CodeComments}} {{range $filename, $lines := .Review.CodeComments}}
{{range $line, $comms := $lines}} {{range $line, $comms := $lines}}
@ -607,10 +611,12 @@
<span class="text grey muted-links"> <span class="text grey muted-links">
{{template "shared/user/authorlink" .Poster}} {{template "shared/user/authorlink" .Poster}}
{{$reviewerName := ""}} {{$reviewerName := ""}}
{{if eq .Review.OriginalAuthor ""}} {{if .Review}}
{{$reviewerName = .Review.Reviewer.Name}} {{if eq .Review.OriginalAuthor ""}}
{{else}} {{$reviewerName = .Review.Reviewer.Name}}
{{$reviewerName = .Review.OriginalAuthor}} {{else}}
{{$reviewerName = .Review.OriginalAuthor}}
{{end}}
{{end}} {{end}}
{{ctx.Locale.Tr "repo.issues.review.dismissed" $reviewerName $createdStr}} {{ctx.Locale.Tr "repo.issues.review.dismissed" $reviewerName $createdStr}}
</span> </span>

View File

@ -1,137 +1,143 @@
{{$invalid := (index .comments 0).Invalidated}} {{if len .comments}}
{{$resolved := (index .comments 0).IsResolved}} {{$comment := index .comments 0}}
{{$resolveDoer := (index .comments 0).ResolveDoer}} {{$invalid := $comment.Invalidated}}
{{$isNotPending := (not (eq (index .comments 0).Review.Type 0))}} {{$resolved := $comment.IsResolved}}
<div class="ui segments conversation-holder"> {{$resolveDoer := $comment.ResolveDoer}}
<div class="ui segment collapsible-comment-box gt-py-3 gt-df gt-ac gt-sb"> {{$hasReview := and $comment.Review}}
<div class="gt-df gt-ac"> {{$isReviewPending := and $hasReview (eq $comment.Review.Type 0)}}
<a href="{{(index .comments 0).CodeCommentLink ctx}}" class="file-comment gt-ml-3 gt-word-break">{{(index .comments 0).TreePath}}</a> <div class="ui segments conversation-holder">
{{if $invalid}} <div class="ui segment collapsible-comment-box gt-py-3 gt-df gt-ac gt-sb">
<span class="ui label basic small gt-ml-3" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.review.outdated_description"}}"> <div class="gt-df gt-ac">
{{ctx.Locale.Tr "repo.issues.review.outdated"}} <a href="{{$comment.CodeCommentLink ctx}}" class="file-comment gt-ml-3 gt-word-break">{{$comment.TreePath}}</a>
</span> {{if $invalid}}
{{end}} <span class="ui label basic small gt-ml-3" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.review.outdated_description"}}">
</div> {{ctx.Locale.Tr "repo.issues.review.outdated"}}
<div> </span>
{{if or $invalid $resolved}}
<button id="show-outdated-{{(index .comments 0).ID}}" data-comment="{{(index .comments 0).ID}}" class="{{if not $resolved}}gt-hidden {{end}}ui compact labeled button show-outdated gt-df gt-ac">
{{svg "octicon-unfold" 16 "gt-mr-3"}}
{{if $resolved}}
{{ctx.Locale.Tr "repo.issues.review.show_resolved"}}
{{else}}
{{ctx.Locale.Tr "repo.issues.review.show_outdated"}}
{{end}}
</button>
<button id="hide-outdated-{{(index .comments 0).ID}}" data-comment="{{(index .comments 0).ID}}" class="{{if $resolved}}gt-hidden {{end}}ui compact labeled button hide-outdated gt-df gt-ac">
{{svg "octicon-fold" 16 "gt-mr-3"}}
{{if $resolved}}
{{ctx.Locale.Tr "repo.issues.review.hide_resolved"}}
{{else}}
{{ctx.Locale.Tr "repo.issues.review.hide_outdated"}}
{{end}}
</button>
{{end}}
</div>
</div>
{{$diff := (CommentMustAsDiff ctx (index .comments 0))}}
{{if $diff}}
{{$file := (index $diff.Files 0)}}
<div id="code-preview-{{(index .comments 0).ID}}" class="ui table segment{{if $resolved}} gt-hidden{{end}}">
<div class="diff-file-box diff-box file-content {{TabSizeClass $.Editorconfig $file.Name}}">
<div class="file-body file-code code-view code-diff code-diff-unified unicode-escaped">
<table>
<tbody>
{{template "repo/diff/section_unified" dict "file" $file "root" $}}
</tbody>
</table>
</div>
</div>
</div>
{{end}}
<div id="code-comments-{{(index .comments 0).ID}}" class="comment-code-cloud ui segment{{if $resolved}} gt-hidden{{end}}">
<div class="ui comments gt-mb-0">
{{range .comments}}
{{$createdSubStr:= TimeSinceUnix .CreatedUnix ctx.Locale}}
<div class="comment code-comment gt-pb-4" id="{{.HashTag}}">
<div class="content">
<div class="header comment-header">
<div class="comment-header-left gt-df gt-ac">
{{if not .OriginalAuthor}}
<a class="avatar">
{{ctx.AvatarUtils.Avatar .Poster 20}}
</a>
{{end}}
<span class="text grey muted-links">
{{if .OriginalAuthor}}
<span class="text black">
{{svg (MigrationIcon $.Repository.GetOriginalURLHostname)}}
{{.OriginalAuthor}}
</span>
{{if $.Repository.OriginalURL}}
<span class="migrate">({{ctx.Locale.Tr "repo.migrated_from" $.Repository.OriginalURL $.Repository.GetOriginalURLHostname}})</span>
{{end}}
{{else}}
{{template "shared/user/authorlink" .Poster}}
{{end}}
{{ctx.Locale.Tr "repo.issues.commented_at" .HashTag $createdSubStr}}
</span>
</div>
<div class="comment-header-right actions gt-df gt-ac">
{{template "repo/issue/view_content/show_role" dict "ShowRole" .ShowRole}}
{{if not $.Repository.IsArchived}}
{{template "repo/issue/view_content/add_reaction" dict "ctxData" $ "ActionURL" (printf "%s/comments/%d/reactions" $.RepoLink .ID)}}
{{template "repo/issue/view_content/context_menu" dict "ctxData" $ "item" . "delete" true "issue" true "diff" true "IsCommentPoster" (and $.IsSigned (eq $.SignedUserID .PosterID))}}
{{end}}
</div>
</div>
<div class="text comment-content">
<div class="render-content markup" {{if or $.Permission.IsAdmin $.HasIssuesOrPullsWritePermission (and $.IsSigned (eq $.SignedUserID .PosterID))}}data-can-edit="true"{{end}}>
{{if .RenderedContent}}
{{.RenderedContent}}
{{else}}
<span class="no-content">{{ctx.Locale.Tr "repo.issues.no_content"}}</span>
{{end}}
</div>
<div id="issuecomment-{{.ID}}-raw" class="raw-content gt-hidden">{{.Content}}</div>
<div class="edit-content-zone gt-hidden" data-update-url="{{$.RepoLink}}/comments/{{.ID}}" data-context="{{$.RepoLink}}" data-attachment-url="{{$.RepoLink}}/comments/{{.ID}}/attachments"></div>
{{if .Attachments}}
{{template "repo/issue/view_content/attachments" dict "Attachments" .Attachments "RenderedContent" .RenderedContent}}
{{end}}
</div>
{{$reactions := .Reactions.GroupByType}}
{{if $reactions}}
{{template "repo/issue/view_content/reactions" dict "ctxData" $ "ActionURL" (printf "%s/comments/%d/reactions" $.RepoLink .ID) "Reactions" $reactions}}
{{end}}
</div>
</div>
{{end}}
</div>
<div class="code-comment-buttons gt-df gt-ac gt-fw gt-mt-3 gt-mb-2 gt-mx-3">
<div class="gt-f1">
{{if $resolved}}
<div class="ui grey text">
{{svg "octicon-check" 16 "gt-mr-2"}}
<b>{{$resolveDoer.Name}}</b> {{ctx.Locale.Tr "repo.issues.review.resolved_by"}}
</div>
{{end}} {{end}}
</div> </div>
<div class="code-comment-buttons-buttons"> <div>
{{if and $.CanMarkConversation $isNotPending}} {{if or $invalid $resolved}}
<button class="ui tiny basic button resolve-conversation" data-origin="timeline" data-action="{{if not $resolved}}Resolve{{else}}UnResolve{{end}}" data-comment-id="{{(index .comments 0).ID}}" data-update-url="{{$.RepoLink}}/issues/resolve_conversation"> <button id="show-outdated-{{$comment.ID}}" data-comment="{{$comment.ID}}" class="{{if not $resolved}}gt-hidden {{end}}ui compact labeled button show-outdated gt-df gt-ac">
{{svg "octicon-unfold" 16 "gt-mr-3"}}
{{if $resolved}} {{if $resolved}}
{{ctx.Locale.Tr "repo.issues.review.un_resolve_conversation"}} {{ctx.Locale.Tr "repo.issues.review.show_resolved"}}
{{else}} {{else}}
{{ctx.Locale.Tr "repo.issues.review.resolve_conversation"}} {{ctx.Locale.Tr "repo.issues.review.show_outdated"}}
{{end}} {{end}}
</button> </button>
{{end}} <button id="hide-outdated-{{$comment.ID}}" data-comment="{{$comment.ID}}" class="{{if $resolved}}gt-hidden {{end}}ui compact labeled button hide-outdated gt-df gt-ac">
{{if and $.SignedUserID (not $.Repository.IsArchived)}} {{svg "octicon-fold" 16 "gt-mr-3"}}
<button class="comment-form-reply ui primary tiny labeled icon button gt-ml-2 gt-mr-0"> {{if $resolved}}
{{svg "octicon-reply" 16 "reply icon gt-mr-2"}}{{ctx.Locale.Tr "repo.diff.comment.reply"}} {{ctx.Locale.Tr "repo.issues.review.hide_resolved"}}
{{else}}
{{ctx.Locale.Tr "repo.issues.review.hide_outdated"}}
{{end}}
</button> </button>
{{end}} {{end}}
</div> </div>
</div> </div>
{{template "repo/diff/comment_form_datahandler" dict "hidden" true "reply" (index .comments 0).ReviewID "root" $ "comment" (index .comments 0)}} {{$diff := (CommentMustAsDiff ctx $comment)}}
{{if $diff}}
{{$file := (index $diff.Files 0)}}
<div id="code-preview-{{$comment.ID}}" class="ui table segment{{if $resolved}} gt-hidden{{end}}">
<div class="diff-file-box diff-box file-content {{TabSizeClass $.Editorconfig $file.Name}}">
<div class="file-body file-code code-view code-diff code-diff-unified unicode-escaped">
<table>
<tbody>
{{template "repo/diff/section_unified" dict "file" $file "root" $}}
</tbody>
</table>
</div>
</div>
</div>
{{end}}
<div id="code-comments-{{$comment.ID}}" class="comment-code-cloud ui segment{{if $resolved}} gt-hidden{{end}}">
<div class="ui comments gt-mb-0">
{{range .comments}}
{{$createdSubStr:= TimeSinceUnix .CreatedUnix ctx.Locale}}
<div class="comment code-comment gt-pb-4" id="{{.HashTag}}">
<div class="content">
<div class="header comment-header">
<div class="comment-header-left gt-df gt-ac">
{{if not .OriginalAuthor}}
<a class="avatar">
{{ctx.AvatarUtils.Avatar .Poster 20}}
</a>
{{end}}
<span class="text grey muted-links">
{{if .OriginalAuthor}}
<span class="text black">
{{svg (MigrationIcon $.Repository.GetOriginalURLHostname)}}
{{.OriginalAuthor}}
</span>
{{if $.Repository.OriginalURL}}
<span class="migrate">({{ctx.Locale.Tr "repo.migrated_from" $.Repository.OriginalURL $.Repository.GetOriginalURLHostname}})</span>
{{end}}
{{else}}
{{template "shared/user/authorlink" .Poster}}
{{end}}
{{ctx.Locale.Tr "repo.issues.commented_at" .HashTag $createdSubStr}}
</span>
</div>
<div class="comment-header-right actions gt-df gt-ac">
{{template "repo/issue/view_content/show_role" dict "ShowRole" .ShowRole}}
{{if not $.Repository.IsArchived}}
{{template "repo/issue/view_content/add_reaction" dict "ctxData" $ "ActionURL" (printf "%s/comments/%d/reactions" $.RepoLink .ID)}}
{{template "repo/issue/view_content/context_menu" dict "ctxData" $ "item" . "delete" true "issue" true "diff" true "IsCommentPoster" (and $.IsSigned (eq $.SignedUserID .PosterID))}}
{{end}}
</div>
</div>
<div class="text comment-content">
<div class="render-content markup" {{if or $.Permission.IsAdmin $.HasIssuesOrPullsWritePermission (and $.IsSigned (eq $.SignedUserID .PosterID))}}data-can-edit="true"{{end}}>
{{if .RenderedContent}}
{{.RenderedContent}}
{{else}}
<span class="no-content">{{ctx.Locale.Tr "repo.issues.no_content"}}</span>
{{end}}
</div>
<div id="issuecomment-{{.ID}}-raw" class="raw-content gt-hidden">{{.Content}}</div>
<div class="edit-content-zone gt-hidden" data-update-url="{{$.RepoLink}}/comments/{{.ID}}" data-context="{{$.RepoLink}}" data-attachment-url="{{$.RepoLink}}/comments/{{.ID}}/attachments"></div>
{{if .Attachments}}
{{template "repo/issue/view_content/attachments" dict "Attachments" .Attachments "RenderedContent" .RenderedContent}}
{{end}}
</div>
{{$reactions := .Reactions.GroupByType}}
{{if $reactions}}
{{template "repo/issue/view_content/reactions" dict "ctxData" $ "ActionURL" (printf "%s/comments/%d/reactions" $.RepoLink .ID) "Reactions" $reactions}}
{{end}}
</div>
</div>
{{end}}
</div>
<div class="code-comment-buttons gt-df gt-ac gt-fw gt-mt-3 gt-mb-2 gt-mx-3">
<div class="gt-f1">
{{if $resolved}}
<div class="ui grey text">
{{svg "octicon-check" 16 "gt-mr-2"}}
<b>{{$resolveDoer.Name}}</b> {{ctx.Locale.Tr "repo.issues.review.resolved_by"}}
</div>
{{end}}
</div>
<div class="code-comment-buttons-buttons">
{{if and $.CanMarkConversation $hasReview (not $isReviewPending)}}
<button class="ui tiny basic button resolve-conversation" data-origin="timeline" data-action="{{if not $resolved}}Resolve{{else}}UnResolve{{end}}" data-comment-id="{{$comment.ID}}" data-update-url="{{$.RepoLink}}/issues/resolve_conversation">
{{if $resolved}}
{{ctx.Locale.Tr "repo.issues.review.un_resolve_conversation"}}
{{else}}
{{ctx.Locale.Tr "repo.issues.review.resolve_conversation"}}
{{end}}
</button>
{{end}}
{{if and $.SignedUserID (not $.Repository.IsArchived)}}
<button class="comment-form-reply ui primary tiny labeled icon button gt-ml-2 gt-mr-0">
{{svg "octicon-reply" 16 "reply icon gt-mr-2"}}{{ctx.Locale.Tr "repo.diff.comment.reply"}}
</button>
{{end}}
</div>
</div>
{{template "repo/diff/comment_form_datahandler" dict "hidden" true "reply" $comment.ReviewID "root" $ "comment" $comment}}
</div>
</div> </div>
</div> {{else}}
{{template "repo/diff/conversation_outdated"}}
{{end}}

View File

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/test"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
files_service "code.gitea.io/gitea/services/repository/files" files_service "code.gitea.io/gitea/services/repository/files"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
@ -26,10 +27,19 @@ func TestPullView_ReviewerMissed(t *testing.T) {
session := loginUser(t, "user1") session := loginUser(t, "user1")
req := NewRequest(t, "GET", "/pulls") req := NewRequest(t, "GET", "/pulls")
session.MakeRequest(t, req, http.StatusOK) resp := session.MakeRequest(t, req, http.StatusOK)
assert.True(t, test.IsNormalPageCompleted(resp.Body.String()))
req = NewRequest(t, "GET", "/user2/repo1/pulls/3") req = NewRequest(t, "GET", "/user2/repo1/pulls/3")
session.MakeRequest(t, req, http.StatusOK) resp = session.MakeRequest(t, req, http.StatusOK)
assert.True(t, test.IsNormalPageCompleted(resp.Body.String()))
// if some reviews are missing, the page shouldn't fail
err := db.TruncateBeans(db.DefaultContext, &issues_model.Review{})
assert.NoError(t, err)
req = NewRequest(t, "GET", "/user2/repo1/pulls/2")
resp = session.MakeRequest(t, req, http.StatusOK)
assert.True(t, test.IsNormalPageCompleted(resp.Body.String()))
} }
func TestPullView_CodeOwner(t *testing.T) { func TestPullView_CodeOwner(t *testing.T) {