Merge pull request #1864 from kounoike/pr-more-action-issuecomments

Save more actions as issue comments.
This commit is contained in:
Naoki Takezoe
2018-02-27 18:17:46 +09:00
committed by GitHub
14 changed files with 321 additions and 153 deletions

View File

@@ -7,7 +7,7 @@ To determine if it was any operation, you see the `ACTION` column.
And in the case of some actions, `CONTENT` column value contains additional information.
|ACTION |CONTENT |
|---------------|-----------------|
|----------------|----------------------|
|comment |comment |
|close_comment |comment |
|reopen_comment |comment |
@@ -17,6 +17,11 @@ And in the case of some actions, `CONTENT` column value contains additional info
|merge |comment |
|delete_branch |branchName |
|refer |issueId:title |
|add_label |labelName |
|delete_label |labelName |
|change_priority |oldPriority:priority |
|change_milestone|oldMilestone:milestone|
|assign |oldAssigned:assigned |
### comment
@@ -54,3 +59,23 @@ Therefore, this comment is not displayed, and not counted as a comment.
This value is saved when other issue or issue comment contains reference to the issue like `#issueId`.
At the same time, store id and title of the referrer issue as `id:title`.
### add_label
This value is saved when users have added the label.
### delete_label
This value is saved when users have deleted the label.
### change_priority
This value is saved when users have changed the priority.
### change_milestone
This value is saved when users have changed the milestone.
### assign
This value is saved when users have assign issue/PR to user or remove the assign.

View File

@@ -8,7 +8,7 @@ import gitbucket.core.service.IssuesService._
class DashboardController extends DashboardControllerBase
with IssuesService with PullRequestService with RepositoryService with AccountService with CommitsService
with UsersAuthenticator
with LabelsService with PrioritiesService with MilestonesService with UsersAuthenticator
trait DashboardControllerBase extends ControllerBase {
self: IssuesService with PullRequestService with RepositoryService with AccountService

View File

@@ -20,6 +20,9 @@ class IndexController extends IndexControllerBase
with AccountService
with RepositorySearchService
with IssuesService
with LabelsService
with MilestonesService
with PrioritiesService
with UsersAuthenticator
with ReferrerAuthenticator
with AccountFederationService

View File

@@ -271,25 +271,25 @@ trait IssuesControllerBase extends ControllerBase {
ajaxPost("/:owner/:repository/issues/:id/label/new")(writableUsersOnly { repository =>
defining(params("id").toInt){ issueId =>
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true)
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
}
})
ajaxPost("/:owner/:repository/issues/:id/label/delete")(writableUsersOnly { repository =>
defining(params("id").toInt){ issueId =>
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true)
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
}
})
ajaxPost("/:owner/:repository/issues/:id/assign")(writableUsersOnly { repository =>
updateAssignedUserName(repository.owner, repository.name, params("id").toInt, assignedUserName("assignedUserName"))
updateAssignedUserName(repository.owner, repository.name, params("id").toInt, assignedUserName("assignedUserName"), true)
Ok("updated")
})
ajaxPost("/:owner/:repository/issues/:id/milestone")(writableUsersOnly { repository =>
updateMilestoneId(repository.owner, repository.name, params("id").toInt, milestoneId("milestoneId"))
updateMilestoneId(repository.owner, repository.name, params("id").toInt, milestoneId("milestoneId"), true)
milestoneId("milestoneId").map { milestoneId =>
getMilestonesWithIssueCount(repository.owner, repository.name)
.find(_._1.milestoneId == milestoneId).map { case (_, openCount, closeCount) =>
@@ -299,7 +299,8 @@ trait IssuesControllerBase extends ControllerBase {
})
ajaxPost("/:owner/:repository/issues/:id/priority")(writableUsersOnly { repository =>
updatePriorityId(repository.owner, repository.name, params("id").toInt, priorityId("priorityId"))
val priority = priorityId("priorityId")
updatePriorityId(repository.owner, repository.name, params("id").toInt, priority, true)
Ok("updated")
})
@@ -325,7 +326,7 @@ trait IssuesControllerBase extends ControllerBase {
params("value").toIntOpt.map{ labelId =>
executeBatch(repository) { issueId =>
getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse {
registerIssueLabel(repository.owner, repository.name, issueId, labelId)
registerIssueLabel(repository.owner, repository.name, issueId, labelId, true)
}
}
} getOrElse NotFound()
@@ -334,7 +335,7 @@ trait IssuesControllerBase extends ControllerBase {
post("/:owner/:repository/issues/batchedit/assign")(writableUsersOnly { repository =>
defining(assignedUserName("value")){ value =>
executeBatch(repository) {
updateAssignedUserName(repository.owner, repository.name, _, value)
updateAssignedUserName(repository.owner, repository.name, _, value, true)
}
}
})
@@ -342,7 +343,7 @@ trait IssuesControllerBase extends ControllerBase {
post("/:owner/:repository/issues/batchedit/milestone")(writableUsersOnly { repository =>
defining(milestoneId("value")){ value =>
executeBatch(repository) {
updateMilestoneId(repository.owner, repository.name, _, value)
updateMilestoneId(repository.owner, repository.name, _, value, true)
}
}
})
@@ -350,7 +351,7 @@ trait IssuesControllerBase extends ControllerBase {
post("/:owner/:repository/issues/batchedit/priority")(writableUsersOnly { repository =>
defining(priorityId("value")){ value =>
executeBatch(repository) {
updatePriorityId(repository.owner, repository.name, _, value)
updatePriorityId(repository.owner, repository.name, _, value, true)
}
}
})

View File

@@ -1,7 +1,7 @@
package gitbucket.core.controller
import gitbucket.core.issues.labels.html
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService}
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService, MilestonesService, PrioritiesService}
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
import gitbucket.core.util.Implicits._
import gitbucket.core.util.SyntaxSugars._
@@ -10,7 +10,8 @@ import org.scalatra.i18n.Messages
import org.scalatra.Ok
class LabelsController extends LabelsControllerBase
with LabelsService with IssuesService with RepositoryService with AccountService
with IssuesService with RepositoryService with AccountService
with LabelsService with PrioritiesService with MilestonesService
with ReferrerAuthenticator with WritableUsersAuthenticator
trait LabelsControllerBase extends ControllerBase {

View File

@@ -1,7 +1,7 @@
package gitbucket.core.controller
import gitbucket.core.issues.priorities.html
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, PrioritiesService}
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService, MilestonesService, PrioritiesService}
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
import gitbucket.core.util.Implicits._
import gitbucket.core.util.SyntaxSugars._
@@ -10,7 +10,8 @@ import org.scalatra.i18n.Messages
import org.scalatra.Ok
class PrioritiesController extends PrioritiesControllerBase
with PrioritiesService with IssuesService with RepositoryService with AccountService
with IssuesService with RepositoryService with AccountService
with LabelsService with PrioritiesService with MilestonesService
with ReferrerAuthenticator with WritableUsersAuthenticator
trait PrioritiesControllerBase extends ControllerBase {

View File

@@ -32,6 +32,7 @@ import org.scalatra.i18n.Messages
class RepositoryViewerController extends RepositoryViewerControllerBase
with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
with LabelsService with MilestonesService with PrioritiesService
with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator with PullRequestService with CommitStatusService
with WebHookPullRequestService with WebHookPullRequestReviewCommentService with ProtectedBranchService

View File

@@ -4,6 +4,7 @@ import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.util.StringUtil._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.controller.Context
import gitbucket.core.model.{Issue, PullRequest, IssueComment, IssueLabel, Label, Account, Repository, CommitState, Role}
import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile._
@@ -11,7 +12,7 @@ import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.model.Profile.dateColumnType
trait IssuesService {
self: AccountService with RepositoryService =>
self: AccountService with RepositoryService with LabelsService with PrioritiesService with MilestonesService =>
import IssuesService._
def getIssue(owner: String, repository: String, issueId: String)(implicit s: Session) =
@@ -321,11 +322,35 @@ trait IssuesService {
} get
}
def registerIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int)(implicit s: Session): Int = {
def registerIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int, insertComment: Boolean = false)(implicit context: Context, s: Session): Int = {
if (insertComment) {
IssueComments insert IssueComment(
userName = owner,
repositoryName = repository,
issueId = issueId,
action = "add_label",
commentedUserName = context.loginAccount.map(_.userName).getOrElse("Unknown user"),
content = getLabel(owner, repository, labelId).map(_.labelName).getOrElse("Unknown label"),
registeredDate = currentDate,
updatedDate = currentDate
)
}
IssueLabels insert IssueLabel(owner, repository, issueId, labelId)
}
def deleteIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int)(implicit s: Session): Int = {
def deleteIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int, insertComment: Boolean = false)(implicit context: Context, s: Session): Int = {
if (insertComment) {
IssueComments insert IssueComment(
userName = owner,
repositoryName = repository,
issueId = issueId,
action = "delete_label",
commentedUserName = context.loginAccount.map(_.userName).getOrElse("Unknown user"),
content = getLabel(owner, repository, labelId).map(_.labelName).getOrElse("Unknown label"),
registeredDate = currentDate,
updatedDate = currentDate
)
}
IssueLabels filter(_.byPrimaryKey(owner, repository, issueId, labelId)) delete
}
@@ -350,15 +375,57 @@ trait IssuesService {
.update(title, content, currentDate)
}
def updateAssignedUserName(owner: String, repository: String, issueId: Int, assignedUserName: Option[String])(implicit s: Session): Int = {
def updateAssignedUserName(owner: String, repository: String, issueId: Int, assignedUserName: Option[String], insertComment: Boolean = false)(implicit context: Context, s: Session): Int = {
if (insertComment) {
val oldAssigned = getIssue(owner, repository, s"${issueId}").get.assignedUserName.getOrElse("Not assigned")
val assigned = assignedUserName.getOrElse("Not assigned")
IssueComments insert IssueComment(
userName = owner,
repositoryName = repository,
issueId = issueId,
action = "assign",
commentedUserName = context.loginAccount.map(_.userName).getOrElse("Unknown user"),
content = s"${oldAssigned}:${assigned}",
registeredDate = currentDate,
updatedDate = currentDate
)
}
Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(t => (t.assignedUserName?, t.updatedDate)).update(assignedUserName, currentDate)
}
def updateMilestoneId(owner: String, repository: String, issueId: Int, milestoneId: Option[Int])(implicit s: Session): Int = {
def updateMilestoneId(owner: String, repository: String, issueId: Int, milestoneId: Option[Int], insertComment: Boolean = false)(implicit context: Context, s: Session): Int = {
if (insertComment) {
val oldMilestoneName = getIssue(owner, repository, s"${issueId}").get.milestoneId.map(getMilestone(owner, repository, _).map(_.title).getOrElse("Unknown milestone")).getOrElse("No milestone")
val milestoneName = milestoneId.map(getMilestone(owner, repository, _).map(_.title).getOrElse("Unknown milestone")).getOrElse("No milestone")
IssueComments insert IssueComment(
userName = owner,
repositoryName = repository,
issueId = issueId,
action = "change_milestone",
commentedUserName = context.loginAccount.map(_.userName).getOrElse("Unknown user"),
content = s"${oldMilestoneName}:${milestoneName}",
registeredDate = currentDate,
updatedDate = currentDate
)
}
Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(t => (t.milestoneId?, t.updatedDate)).update(milestoneId, currentDate)
}
def updatePriorityId(owner: String, repository: String, issueId: Int, priorityId: Option[Int])(implicit s: Session): Int = {
def updatePriorityId(owner: String, repository: String, issueId: Int, priorityId: Option[Int], insertComment: Boolean = false)(implicit context: Context, s: Session): Int = {
if (insertComment) {
val oldPriorityName = getIssue(owner, repository, s"${issueId}").get.priorityId.map(getPriority(owner, repository, _).map(_.priorityName).getOrElse("Unknown priority")).getOrElse("No priority")
val priorityName = priorityId.map(getPriority(owner, repository, _).map(_.priorityName).getOrElse("Unknown priority")).getOrElse("No priority")
IssueComments insert IssueComment(
userName = owner,
repositoryName = repository,
issueId = issueId,
action = "change_priority",
commentedUserName = context.loginAccount.map(_.userName).getOrElse("Unknown user"),
content = s"${oldPriorityName}:${priorityName}",
registeredDate = currentDate,
updatedDate = currentDate
)
}
Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(t => (t.priorityId?, t.updatedDate)).update(priorityId, currentDate)
}

View File

@@ -11,7 +11,8 @@ import Implicits.request2Session
* It may be called many times in one request, so each method stores
* its result into the cache which available during a request.
*/
trait RequestCache extends SystemSettingsService with AccountService with IssuesService with RepositoryService {
trait RequestCache extends SystemSettingsService with AccountService with IssuesService with RepositoryService
with LabelsService with MilestonesService with PrioritiesService {
private implicit def context2Session(implicit context: Context): Session =
request2Session(context.request)

View File

@@ -185,6 +185,7 @@ import scala.collection.JavaConverters._
class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String, sshUrl: Option[String])
extends PostReceiveHook with PreReceiveHook
with RepositoryService with AccountService with IssuesService with ActivityService with PullRequestService with WebHookService
with LabelsService with PrioritiesService with MilestonesService
with WebHookPullRequestService with CommitsService {
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])

View File

@@ -5,37 +5,8 @@
pullreq: Option[gitbucket.core.model.PullRequest] = None)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@import gitbucket.core.model.CommitComment
@if(issue.isDefined){
<div class="panel panel-default issue-comment-box">
<div class="panel-heading">
@helpers.avatar(issue.get.openedUserName, 20)
@helpers.user(issue.get.openedUserName, styleClass="username strong")
<span class="muted">commented @gitbucket.core.helper.html.datetimeago(issue.get.registeredDate)</span>
<span class="pull-right">
@if(isManageable || context.loginAccount.map(_.userName == issue.get.openedUserName).getOrElse(false)){
<a href="#" data-issue-id="@issue.get.issueId"><i class="octicon octicon-pencil" aria-label="Edit"></i></a>
}
</span>
</div>
<div class="panel-body issue-content markdown-body" id="issueContent">
@helpers.markdown(
markdown = issue.get.content getOrElse "No description provided.",
repository = repository,
enableWikiLink = false,
enableRefsLink = true,
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = isManageable
)
</div>
</div>
}
@issueOrPullRequest()={ @if(issue.exists(_.isPullRequest))( "pull request" )else( "issue" ) }
@comments.map {
case comment: gitbucket.core.model.IssueComment => {
@if(comment.action != "close" && comment.action != "reopen" && comment.action != "delete_branch"
&& comment.action != "commit" && comment.action != "refer"){
@showFormatedComment(comment: gitbucket.core.model.IssueComment)={
<div class="panel panel-default issue-comment-box" id="comment-@comment.commentId">
<div class="panel-heading">
@helpers.avatar(comment.commentedUserName, 20)
@@ -71,7 +42,35 @@
</div>
</div>
}
@if(comment.action == "commit"){
@if(issue.isDefined){
<div class="panel panel-default issue-comment-box">
<div class="panel-heading">
@helpers.avatar(issue.get.openedUserName, 20)
@helpers.user(issue.get.openedUserName, styleClass="username strong")
<span class="muted">commented @gitbucket.core.helper.html.datetimeago(issue.get.registeredDate)</span>
<span class="pull-right">
@if(isManageable || context.loginAccount.map(_.userName == issue.get.openedUserName).getOrElse(false)){
<a href="#" data-issue-id="@issue.get.issueId"><i class="octicon octicon-pencil" aria-label="Edit"></i></a>
}
</span>
</div>
<div class="panel-body issue-content markdown-body" id="issueContent">
@helpers.markdown(
markdown = issue.get.content getOrElse "No description provided.",
repository = repository,
enableWikiLink = false,
enableRefsLink = true,
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = isManageable
)
</div>
</div>
}
@comments.map {
case comment: gitbucket.core.model.IssueComment => {
@comment.action match {
case "commit" => {
@defining({
val (content, id) = " ([a-f0-9]{40})$".r.findFirstMatchIn(comment.content)
.map(m => (m.before.toString -> Some(m.group(1))))
@@ -101,7 +100,7 @@
</div>
}
}
@if(comment.action == "refer"){
case "refer" => {
<div class="discussion-item discussion-item-refer">
<div class="discussion-item-header">
<span class="discussion-item-icon"><i class="octicon octicon-bookmark"></i></span>
@@ -117,7 +116,8 @@
</div>
</div>
}
@if(comment.action == "merge"){
case "merge" => {
@showFormatedComment(comment)
<div class="discussion-item discussion-item-merge">
<div class="discussion-item-header">
<span class="discussion-item-icon"><i class="octicon octicon-git-merge"></i></span>
@@ -134,7 +134,10 @@
</div>
</div>
}
@if(comment.action == "close" || comment.action == "close_comment"){
case "close" | "close_comment" => {
@if(comment.action == "close_comment"){
@showFormatedComment(comment)
}
<div class="discussion-item discussion-item-close">
<div class="discussion-item-header">
<span class="discussion-item-icon"><i class="octicon octicon-circle-slash"></i></span>
@@ -145,7 +148,10 @@
</div>
</div>
}
@if(comment.action == "reopen" || comment.action == "reopen_comment"){
case "reopen" | "reopen_comment" => {
@if(comment.action == "reopen_comment"){
@showFormatedComment(comment)
}
<div class="discussion-item discussion-item-reopen">
<div class="discussion-item-header">
<span class="discussion-item-icon"><i class="octicon octicon-primitive-dot"></i></span>
@@ -156,7 +162,7 @@
</div>
</div>
}
@if(comment.action == "delete_branch"){
case "delete_branch" => {
<div class="discussion-item discussion-item-delete_branch">
<div class="discussion-item-header">
<span class="discussion-item-icon"><i class="octicon octicon-git-branch"></i></span>
@@ -167,6 +173,65 @@
</div>
</div>
}
case "add_label" => {
<div class="discussion-item discussion-item-add-label">
<div class="discussion-item-header">
<span class="discussion-item-icon"><i class="octicon octicon-tag"></i></span>
@helpers.avatar(comment.commentedUserName, 16)
@helpers.user(comment.commentedUserName, styleClass="username strong")
add the <code>@comment.content</code> label
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
</div>
</div>
}
case "delete_label" => {
<div class="discussion-item discussion-item-delete-label">
<div class="discussion-item-header">
<span class="discussion-item-icon"><i class="octicon octicon-tag"></i></span>
@helpers.avatar(comment.commentedUserName, 16)
@helpers.user(comment.commentedUserName, styleClass="username strong")
removed the <code>@comment.content</code> label
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
</div>
</div>
}
case "change_priority" => {
<div class="discussion-item discussion-item-change-priority">
<div class="discussion-item-header">
<span class="discussion-item-icon"><i class="octicon octicon-flame"></i></span>
@helpers.avatar(comment.commentedUserName, 16)
@helpers.user(comment.commentedUserName, styleClass="username strong")
change priority from <code>@comment.content.split(":")(0)</code> to <code>@comment.content.split(":")(1)</code>
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
</div>
</div>
}
case "change_milestone" => {
<div class="discussion-item discussion-item-change-milestone">
<div class="discussion-item-header">
<span class="discussion-item-icon"><i class="octicon octicon-milestone"></i></span>
@helpers.avatar(comment.commentedUserName, 16)
@helpers.user(comment.commentedUserName, styleClass="username strong")
change milestone from <code>@comment.content.split(":")(0)</code> to <code>@comment.content.split(":")(1)</code>
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
</div>
</div>
}
case "assign" => {
<div class="discussion-item discussion-item-assign">
<div class="discussion-item-header">
<span class="discussion-item-icon"><i class="octicon octicon-person"></i></span>
@helpers.avatar(comment.commentedUserName, 16)
@helpers.user(comment.commentedUserName, styleClass="username strong")
change assignee from <code>@comment.content.split(":")(0)</code> to <code>@comment.content.split(":")(1)</code>
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
</div>
</div>
}
case _ => {
@showFormatedComment(comment)
}
}
}
case comment: CommitComment => {
@gitbucket.core.helper.html.commitcomment(comment, isManageable, repository, pullreq.map(_.commitIdTo))

View File

@@ -4,7 +4,8 @@ import gitbucket.core.model._
import org.scalatest.FunSpec
class PullRequestServiceSpec extends FunSpec with ServiceSpecBase
with PullRequestService with IssuesService with AccountService with RepositoryService with CommitsService {
with PullRequestService with IssuesService with AccountService with RepositoryService with CommitsService
with LabelsService with MilestonesService with PrioritiesService {
def swap(r: (Issue, PullRequest)) = (r._2 -> r._1)

View File

@@ -45,7 +45,7 @@ trait ServiceSpecBase {
def user(name:String)(implicit s:Session):Account = AccountService.getAccountByUserName(name).get
lazy val dummyService = new RepositoryService with AccountService with IssuesService with PullRequestService
with CommitsService with CommitStatusService with LabelsService (){}
with CommitsService with CommitStatusService with LabelsService with MilestonesService with PrioritiesService (){}
def generateNewUserWithDBRepository(userName:String, repositoryName:String)(implicit s:Session):Account = {
val ac = AccountService.getAccountByUserName(userName).getOrElse(generateNewAccount(userName))

View File

@@ -6,7 +6,8 @@ import gitbucket.core.model.WebHookContentType
class WebHookServiceSpec extends FunSuite with ServiceSpecBase {
lazy val service = new WebHookPullRequestService with AccountService with RepositoryService with PullRequestService with IssuesService with CommitsService
lazy val service = new WebHookPullRequestService with AccountService with RepositoryService with PullRequestService
with IssuesService with CommitsService with LabelsService with MilestonesService with PrioritiesService
test("WebHookPullRequestService.getPullRequestsByRequestForWebhook") { withTestDB { implicit session =>
val user1 = generateNewUserWithDBRepository("user1","repo1")