diff --git a/src/main/resources/update/gitbucket-core_4.38.xml b/src/main/resources/update/gitbucket-core_4.38.xml
index 2d243ac..a6feb65 100644
--- a/src/main/resources/update/gitbucket-core_4.38.xml
+++ b/src/main/resources/update/gitbucket-core_4.38.xml
@@ -28,4 +28,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ INSERT INTO ISSUE_ASSIGNEE (USER_NAME, REPOSITORY_NAME, ISSUE_ID, ASSIGNEE_USER_NAME)
+ SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, ASSIGNED_USER_NAME FROM ISSUE WHERE ASSIGNED_USER_NAME IS NOT NULL
+
+
+
diff --git a/src/main/scala/gitbucket/core/api/ApiIssue.scala b/src/main/scala/gitbucket/core/api/ApiIssue.scala
index a686cd4..88afee1 100644
--- a/src/main/scala/gitbucket/core/api/ApiIssue.scala
+++ b/src/main/scala/gitbucket/core/api/ApiIssue.scala
@@ -12,7 +12,7 @@
number: Int,
title: String,
user: ApiUser,
- assignee: Option[ApiUser],
+ assignees: List[ApiUser],
labels: List[ApiLabel],
state: String,
created_at: Date,
@@ -21,7 +21,7 @@
milestone: Option[ApiMilestone]
)(repositoryName: RepositoryName, isPullRequest: Boolean) {
val id = 0 // dummy id
- val assignees = List(assignee).flatten
+ val assignee = assignees.headOption
val comments_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/issues/${number}/comments")
val html_url = ApiPath(s"/${repositoryName.fullName}/${if (isPullRequest) { "pull" } else { "issues" }}/${number}")
val pull_request = if (isPullRequest) {
@@ -43,7 +43,7 @@
issue: Issue,
repositoryName: RepositoryName,
user: ApiUser,
- assignee: Option[ApiUser],
+ assignees: List[ApiUser],
labels: List[ApiLabel],
milestone: Option[ApiMilestone]
): ApiIssue =
@@ -51,7 +51,7 @@
number = issue.issueId,
title = issue.title,
user = user,
- assignee = assignee,
+ assignees = assignees,
labels = labels,
milestone = milestone,
state = if (issue.closed) { "closed" } else { "open" },
diff --git a/src/main/scala/gitbucket/core/api/ApiPullRequest.scala b/src/main/scala/gitbucket/core/api/ApiPullRequest.scala
index 5777bdf..6079198 100644
--- a/src/main/scala/gitbucket/core/api/ApiPullRequest.scala
+++ b/src/main/scala/gitbucket/core/api/ApiPullRequest.scala
@@ -21,10 +21,11 @@
body: String,
user: ApiUser,
labels: List[ApiLabel],
- assignee: Option[ApiUser],
+ assignees: List[ApiUser],
draft: Option[Boolean]
) {
val id = 0 // dummy id
+ val assignee = assignees.headOption
val html_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}")
//val diff_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.diff")
//val patch_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.patch")
@@ -45,7 +46,7 @@
baseRepo: ApiRepository,
user: ApiUser,
labels: List[ApiLabel],
- assignee: Option[ApiUser],
+ assignees: List[ApiUser],
mergedComment: Option[(IssueComment, Account)]
): ApiPullRequest =
ApiPullRequest(
@@ -63,7 +64,7 @@
body = issue.content.getOrElse(""),
user = user,
labels = labels,
- assignee = assignee,
+ assignees = assignees,
draft = Some(pullRequest.isDraft)
)
diff --git a/src/main/scala/gitbucket/core/controller/IssuesController.scala b/src/main/scala/gitbucket/core/controller/IssuesController.scala
index 61ae064..d69ee6f 100644
--- a/src/main/scala/gitbucket/core/controller/IssuesController.scala
+++ b/src/main/scala/gitbucket/core/controller/IssuesController.scala
@@ -53,7 +53,7 @@
case class IssueCreateForm(
title: String,
content: Option[String],
- assignedUserName: Option[String],
+ assigneeUserNames: Option[String],
milestoneId: Option[Int],
priorityId: Option[Int],
labelNames: Option[String]
@@ -64,7 +64,7 @@
val issueCreateForm = mapping(
"title" -> trim(label("Title", text(required))),
"content" -> trim(optional(text())),
- "assignedUserName" -> trim(optional(text())),
+ "assigneeUserNames" -> trim(optional(text())),
"milestoneId" -> trim(optional(number())),
"priorityId" -> trim(optional(number())),
"labelNames" -> trim(optional(text()))
@@ -107,6 +107,7 @@
issue,
getComments(repository.owner, repository.name, issueId.toInt),
getIssueLabels(repository.owner, repository.name, issueId.toInt),
+ getIssueAssignees(repository.owner, repository.name, issueId.toInt),
getAssignableUserNames(repository.owner, repository.name),
getMilestonesWithIssueCount(repository.owner, repository.name),
getPriorities(repository.owner, repository.name),
@@ -145,7 +146,7 @@
repository,
form.title,
form.content,
- form.assignedUserName,
+ form.assigneeUserNames.toSeq.flatMap(_.split(",")),
form.milestoneId,
form.priorityId,
form.labelNames.toSeq.flatMap(_.split(",")),
@@ -356,15 +357,16 @@
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"),
- true
- )
- Ok("updated")
+ ajaxPost("/:owner/:repository/issues/:id/assignee/new")(writableUsersOnly { repository =>
+ val issueId = params("id").toInt
+ registerIssueAssignee(repository.owner, repository.name, issueId, params("assigneeUserName"), true)
+ Ok()
+ })
+
+ ajaxPost("/:owner/:repository/issues/:id/assignee/delete")(writableUsersOnly { repository =>
+ val issueId = params("id").toInt
+ deleteIssueAssignee(repository.owner, repository.name, issueId, params("assigneeUserName"), true)
+ Ok()
})
ajaxPost("/:owner/:repository/issues/:id/milestone")(writableUsersOnly { repository =>
@@ -455,7 +457,13 @@
post("/:owner/:repository/issues/batchedit/assign")(writableUsersOnly { repository =>
val value = assignedUserName("value")
executeBatch(repository) {
- updateAssignedUserName(repository.owner, repository.name, _, value, true)
+ //updateAssignedUserName(repository.owner, repository.name, _, value, true)
+ value match {
+ case Some(assignedUserName) =>
+ registerIssueAssignee(repository.owner, repository.name, _, assignedUserName, true)
+ case None =>
+ deleteAllIssueAssignees(repository.owner, repository.name, _, true)
+ }
}
if (params("uri").nonEmpty) {
redirect(params("uri"))
diff --git a/src/main/scala/gitbucket/core/controller/LabelsController.scala b/src/main/scala/gitbucket/core/controller/LabelsController.scala
index 224a571..5701538 100644
--- a/src/main/scala/gitbucket/core/controller/LabelsController.scala
+++ b/src/main/scala/gitbucket/core/controller/LabelsController.scala
@@ -44,7 +44,7 @@
get("/:owner/:repository/issues/labels")(referrersOnly { repository =>
html.list(
getLabels(repository.owner, repository.name),
- countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
+ countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition()),
repository,
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
)
@@ -59,7 +59,7 @@
html.label(
getLabel(repository.owner, repository.name, labelId).get,
// TODO futility
- countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
+ countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition()),
repository,
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
)
@@ -76,7 +76,7 @@
html.label(
getLabel(repository.owner, repository.name, params("labelId").toInt).get,
// TODO futility
- countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
+ countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition()),
repository,
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
)
diff --git a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala
index 879d534..21481cc 100644
--- a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala
+++ b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala
@@ -92,7 +92,7 @@
commitIdFrom: String,
commitIdTo: String,
isDraft: Boolean,
- assignedUserName: Option[String],
+ assignedUserNames: Option[String],
milestoneId: Option[Int],
priorityId: Option[Int],
labelNames: Option[String]
@@ -131,6 +131,7 @@
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
diffs.size,
getIssueLabels(repository.owner, repository.name, issueId),
+ getIssueAssignees(repository.owner, repository.name, issueId),
getAssignableUserNames(repository.owner, repository.name),
getMilestonesWithIssueCount(repository.owner, repository.name),
getPriorities(repository.owner, repository.name),
@@ -571,7 +572,6 @@
loginUser = loginAccount.userName,
title = form.title,
content = form.content,
- assignedUserName = if (manageable) form.assignedUserName else None,
milestoneId = if (manageable) form.milestoneId else None,
priorityId = if (manageable) form.priorityId else None,
isPullRequest = true
@@ -591,8 +591,14 @@
settings = context.settings
)
- // insert labels
if (manageable) {
+ // insert assignees
+ form.assignedUserNames.foreach { value =>
+ value.split(",").foreach { userName =>
+ registerIssueAssignee(repository.owner, repository.name, issueId, userName)
+ }
+ }
+ // insert labels
form.labelNames.foreach { value =>
val labels = getLabels(repository.owner, repository.name)
value.split(",").foreach { labelName =>
diff --git a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala
index 7443c3e..4026a53 100644
--- a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala
+++ b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala
@@ -679,7 +679,6 @@
loginUser = loginAccount.userName,
title = requestBranch,
content = commitMessage,
- assignedUserName = None,
milestoneId = None,
priorityId = None,
isPullRequest = true
diff --git a/src/main/scala/gitbucket/core/controller/api/ApiIssueControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiIssueControllerBase.scala
index 350ad44..9d90b3d 100644
--- a/src/main/scala/gitbucket/core/controller/api/ApiIssueControllerBase.scala
+++ b/src/main/scala/gitbucket/core/controller/api/ApiIssueControllerBase.scala
@@ -29,9 +29,9 @@
val page = IssueSearchCondition.page(request)
// TODO: more api spec condition
val condition = IssueSearchCondition(request)
- val baseOwner = getAccountByUserName(repository.owner).get
+ //val baseOwner = getAccountByUserName(repository.owner).get
- val issues: List[(Issue, Account, Option[Account])] =
+ val issues: List[(Issue, Account, List[Account])] =
searchIssueByApi(
condition = condition,
offset = (page - 1) * PullRequestLimit,
@@ -40,12 +40,12 @@
)
JsonFormat(issues.map {
- case (issue, issueUser, assignedUser) =>
+ case (issue, issueUser, assigneeUsers) =>
ApiIssue(
issue = issue,
repositoryName = RepositoryName(repository),
user = ApiUser(issueUser),
- assignee = assignedUser.map(ApiUser(_)),
+ assignees = assigneeUsers.map(ApiUser(_)),
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository))),
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
@@ -61,7 +61,8 @@
(for {
issueId <- params("id").toIntOpt
issue <- getIssue(repository.owner, repository.name, issueId.toString)
- users = getAccountsByUserNames(Set(issue.openedUserName) ++ issue.assignedUserName, Set())
+ assigneeUsers = getIssueAssignees(repository.owner, repository.name, issueId)
+ users = getAccountsByUserNames(Set(issue.openedUserName) ++ assigneeUsers.map(_.assigneeUserName), Set())
openedUser <- users.get(issue.openedUserName)
} yield {
JsonFormat(
@@ -69,7 +70,7 @@
issue,
RepositoryName(repository),
ApiUser(openedUser),
- issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)),
+ assigneeUsers.flatMap(x => users.get(x.assigneeUserName)).map(ApiUser(_)),
getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository))),
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
)
@@ -92,7 +93,7 @@
repository,
data.title,
data.body,
- data.assignees.headOption,
+ data.assignees,
milestone.map(_.milestoneId),
None,
data.labels,
@@ -103,7 +104,9 @@
issue,
RepositoryName(repository),
ApiUser(loginAccount),
- issue.assignedUserName.flatMap(getAccountByUserName(_)).map(ApiUser(_)),
+ getIssueAssignees(repository.owner, repository.name, issue.issueId)
+ .flatMap(x => getAccountByUserName(x.assigneeUserName, false))
+ .map(ApiUser.apply),
getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository))),
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
diff --git a/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala
index bc6c6b1..bdd6561 100644
--- a/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala
+++ b/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala
@@ -40,7 +40,7 @@
val condition = IssueSearchCondition(request)
val baseOwner = getAccountByUserName(repository.owner).get
- val issues: List[(Issue, Account, Int, PullRequest, Repository, Account, Option[Account])] =
+ val issues: List[(Issue, Account, Int, PullRequest, Repository, Account, List[Account])] =
searchPullRequestByApi(
condition = condition,
offset = (page - 1) * PullRequestLimit,
@@ -49,7 +49,7 @@
)
JsonFormat(issues.map {
- case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner, assignee) =>
+ case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner, assignees) =>
ApiPullRequest(
issue = issue,
pullRequest = pullRequest,
@@ -58,7 +58,7 @@
user = ApiUser(issueUser),
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository))),
- assignee = assignee.map(ApiUser.apply),
+ assignees = assignees.map(ApiUser.apply),
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
)
})
@@ -99,7 +99,6 @@
loginUser = context.loginAccount.get.userName,
title = createPullReq.title,
content = createPullReq.body,
- assignedUserName = None,
milestoneId = None,
priorityId = None,
isPullRequest = true
@@ -319,8 +318,8 @@
baseOwner <- users.get(repository.owner)
headOwner <- users.get(pullRequest.requestUserName)
issueUser <- users.get(issue.openedUserName)
- assignee = issue.assignedUserName.flatMap { userName =>
- getAccountByUserName(userName, false)
+ assignees = getIssueAssignees(repository.owner, repository.name, issueId).flatMap { assignedUser =>
+ getAccountByUserName(assignedUser.assigneeUserName, false)
}
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
} yield {
@@ -332,7 +331,7 @@
user = ApiUser(issueUser),
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository))),
- assignee = assignee.map(ApiUser.apply),
+ assignees = assignees.map(ApiUser.apply),
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
)
}
diff --git a/src/main/scala/gitbucket/core/model/Issue.scala b/src/main/scala/gitbucket/core/model/Issue.scala
index ccf2c81..9a84f17 100644
--- a/src/main/scala/gitbucket/core/model/Issue.scala
+++ b/src/main/scala/gitbucket/core/model/Issue.scala
@@ -27,7 +27,6 @@
with MilestoneTemplate
with PriorityTemplate {
val openedUserName = column[String]("OPENED_USER_NAME")
- val assignedUserName = column[String]("ASSIGNED_USER_NAME")
val title = column[String]("TITLE")
val content = column[String]("CONTENT")
val closed = column[Boolean]("CLOSED")
@@ -42,7 +41,6 @@
openedUserName,
milestoneId.?,
priorityId.?,
- assignedUserName.?,
title,
content.?,
closed,
@@ -62,7 +60,6 @@
openedUserName: String,
milestoneId: Option[Int],
priorityId: Option[Int],
- assignedUserName: Option[String],
title: String,
content: Option[String],
closed: Boolean,
diff --git a/src/main/scala/gitbucket/core/model/IssueAssignee.scala b/src/main/scala/gitbucket/core/model/IssueAssignee.scala
new file mode 100644
index 0000000..81ab4b0
--- /dev/null
+++ b/src/main/scala/gitbucket/core/model/IssueAssignee.scala
@@ -0,0 +1,26 @@
+package gitbucket.core.model
+
+trait IssueAssigneeComponent extends TemplateComponent { self: Profile =>
+ import profile.api._
+ import self._
+
+ lazy val IssueAssignees = TableQuery[IssueAssignees]
+
+ class IssueAssignees(tag: Tag) extends Table[IssueAssignee](tag, "ISSUE_ASSIGNEE") with IssueTemplate {
+ val assigneeUserName = column[String]("ASSIGNEE_USER_NAME")
+ def * =
+ (userName, repositoryName, issueId, assigneeUserName)
+ .<>(IssueAssignee.tupled, IssueAssignee.unapply)
+
+ def byPrimaryKey(owner: String, repository: String, issueId: Int, assigneeUserName: String) = {
+ byIssue(owner, repository, issueId) && this.assigneeUserName === assigneeUserName.bind
+ }
+ }
+}
+
+case class IssueAssignee(
+ userName: String,
+ repositoryName: String,
+ issueId: Int,
+ assigneeUserName: String
+)
diff --git a/src/main/scala/gitbucket/core/model/Profile.scala b/src/main/scala/gitbucket/core/model/Profile.scala
index c175c1f..c155de5 100644
--- a/src/main/scala/gitbucket/core/model/Profile.scala
+++ b/src/main/scala/gitbucket/core/model/Profile.scala
@@ -74,5 +74,6 @@
with AccountPreferenceComponent
with CustomFieldComponent
with IssueCustomFieldComponent
+ with IssueAssigneeComponent
object Profile extends CoreProfile
diff --git a/src/main/scala/gitbucket/core/service/IssueCreationService.scala b/src/main/scala/gitbucket/core/service/IssueCreationService.scala
index c56cf0c..467c462 100644
--- a/src/main/scala/gitbucket/core/service/IssueCreationService.scala
+++ b/src/main/scala/gitbucket/core/service/IssueCreationService.scala
@@ -16,7 +16,7 @@
repository: RepositoryInfo,
title: String,
body: Option[String],
- assignee: Option[String],
+ assignees: Seq[String],
milestoneId: Option[Int],
priorityId: Option[Int],
labelNames: Seq[String],
@@ -35,16 +35,19 @@
userName,
title,
body,
- if (manageable) assignee else None,
if (manageable) milestoneId else None,
if (manageable) priorityId else None
)
val issue: Issue = getIssue(owner, name, issueId.toString).get
- // insert labels
if (manageable) {
+ // insert assignees
+ assignees.foreach { assignee =>
+ registerIssueAssignee(owner, name, issueId, assignee)
+ }
+ // insert labels
val labels = getLabels(owner, name)
- labelNames.map { labelName =>
+ labelNames.foreach { labelName =>
labels.find(_.labelName == labelName).map { label =>
registerIssueLabel(owner, name, issueId, label.labelId)
}
diff --git a/src/main/scala/gitbucket/core/service/IssuesService.scala b/src/main/scala/gitbucket/core/service/IssuesService.scala
index 76eb8c0..cbe2802 100644
--- a/src/main/scala/gitbucket/core/service/IssuesService.scala
+++ b/src/main/scala/gitbucket/core/service/IssuesService.scala
@@ -5,7 +5,17 @@
import gitbucket.core.util.Implicits._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.controller.Context
-import gitbucket.core.model.{Account, Issue, IssueComment, IssueLabel, Label, PullRequest, Repository, Role}
+import gitbucket.core.model.{
+ Account,
+ Issue,
+ IssueAssignee,
+ IssueComment,
+ IssueLabel,
+ Label,
+ PullRequest,
+ Repository,
+ Role
+}
import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile._
import gitbucket.core.model.Profile.profile.blockingApi._
@@ -118,8 +128,7 @@
def countIssueGroupByLabels(
owner: String,
repository: String,
- condition: IssueSearchCondition,
- filterUser: Map[String, String]
+ condition: IssueSearchCondition
)(implicit s: Session): Map[String, Int] = {
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), IssueSearchOption.Issues)
@@ -244,20 +253,31 @@
}
/** for api
- * @return (issue, issueUser, assignedUser)
+ * @return (issue, issueUser, Seq(assigneeUsers))
*/
def searchIssueByApi(condition: IssueSearchCondition, offset: Int, limit: Int, repos: (String, String)*)(
implicit s: Session
- ): List[(Issue, Account, Option[Account])] = {
+ ): List[(Issue, Account, List[Account])] = {
// get issues and comment count and labels
searchIssueQueryBase(condition, IssueSearchOption.Issues, offset, limit, repos)
.join(Accounts)
.on { case t1 ~ t2 ~ i ~ t3 => t3.userName === t1.openedUserName }
+ .joinLeft(IssueAssignees)
+ .on { case t1 ~ t2 ~ i ~ t3 ~ t4 => t4.byIssue(t1.userName, t1.repositoryName, t1.issueId) }
.joinLeft(Accounts)
- .on { case t1 ~ t2 ~ i ~ t3 ~ t4 => t4.userName === t1.assignedUserName }
- .sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 => i asc }
- .map { case t1 ~ t2 ~ i ~ t3 ~ t4 => (t1, t3, t4) }
+ .on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 => t5.userName === t4.map(_.assigneeUserName) }
+ .sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 => i asc }
+ .map { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 => (t1, t3, t5) }
.list
+ .groupBy {
+ case (issue, account, assignedUsers) =>
+ (issue, account)
+ }
+ .map {
+ case (_, values) =>
+ (values.head._1, values.head._2, values.flatMap(_._3))
+ }
+ .toList
}
/** for api
@@ -265,7 +285,7 @@
*/
def searchPullRequestByApi(condition: IssueSearchCondition, offset: Int, limit: Int, repos: (String, String)*)(
implicit s: Session
- ): List[(Issue, Account, Int, PullRequest, Repository, Account, Option[Account])] = {
+ ): List[(Issue, Account, Int, PullRequest, Repository, Account, List[Account])] = {
// get issues and comment count and labels
searchIssueQueryBase(condition, IssueSearchOption.PullRequests, offset, limit, repos)
.join(PullRequests)
@@ -276,11 +296,30 @@
.on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 => t5.userName === t1.openedUserName }
.join(Accounts)
.on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 => t6.userName === t4.userName }
+ .joinLeft(IssueAssignees)
+ .on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 => t7.byIssue(t1.userName, t1.repositoryName, t1.issueId) }
.joinLeft(Accounts)
- .on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 => t7.userName === t1.assignedUserName }
- .sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 => i asc }
- .map { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 => (t1, t5, t2.commentCount, t3, t4, t6, t7) }
+ .on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 ~ t8 => t8.userName === t7.map(_.assigneeUserName) }
+ .sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 ~ t8 => i asc }
+ .map { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 ~ t8 => (t1, t5, t2.commentCount, t3, t4, t6, t8) }
.list
+ .groupBy {
+ case (issue, openedUser, commentCount, pullRequest, repository, account, assignedUser) =>
+ (issue, openedUser, commentCount, pullRequest, repository, account)
+ }
+ .map {
+ case (_, values) =>
+ (
+ values.head._1,
+ values.head._2,
+ values.head._3,
+ values.head._4,
+ values.head._5,
+ values.head._6,
+ values.flatMap(_._7)
+ )
+ }
+ .toList
}
private def searchIssueQueryBase(
@@ -347,7 +386,7 @@
case _ => t1.closed === true || t1.closed === false
}).&&(t1.milestoneId.? isEmpty, condition.milestone == Some(None))
.&&(t1.priorityId.? isEmpty, condition.priority == Some(None))
- .&&(t1.assignedUserName.? isEmpty, condition.assigned == Some(None))
+ //.&&(t1.assignedUserName.? isEmpty, condition.assigned == Some(None))
.&&(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
(searchOption match {
case IssueSearchOption.Issues => t1.pullRequest === false
@@ -371,7 +410,13 @@
condition.priority.flatten.isDefined
)
// Assignee filter
- .&&(t1.assignedUserName === condition.assigned.get.get.bind, condition.assigned.flatten.isDefined)
+ .&&(
+ IssueAssignees filter { a =>
+ a.byIssue(t1.userName, t1.repositoryName, t1.issueId) &&
+ a.assigneeUserName === condition.assigned.get.get.bind
+ } exists,
+ condition.assigned.flatten.isDefined
+ )
// Label filter
.&&(
IssueLabels filter { t2 =>
@@ -396,7 +441,9 @@
.&&(t1.userName inSetBind condition.groups, condition.groups.nonEmpty)
// Mentioned filter
.&&(
- (t1.openedUserName === condition.mentioned.get.bind) || t1.assignedUserName === condition.mentioned.get.bind ||
+ (t1.openedUserName === condition.mentioned.get.bind) || (IssueAssignees filter { t1 =>
+ t1.byIssue(t1.userName, t1.repositoryName, t1.issueId) && t1.assigneeUserName === condition.mentioned.get.bind
+ } exists) ||
(IssueComments filter { t2 =>
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) && (t2.commentedUserName === condition.mentioned.get.bind)
} exists),
@@ -410,7 +457,6 @@
loginUser: String,
title: String,
content: Option[String],
- assignedUserName: Option[String],
milestoneId: Option[Int],
priorityId: Option[Int],
isPullRequest: Boolean = false
@@ -427,7 +473,6 @@
loginUser,
milestoneId,
priorityId,
- assignedUserName,
title,
content,
false,
@@ -542,35 +587,91 @@
.update(true)
}
- def updateAssignedUserName(
+ def getIssueAssignees(owner: String, repository: String, issueId: Int)(
+ implicit s: Session
+ ): List[IssueAssignee] = {
+ IssueAssignees.filter(_.byIssue(owner, repository, issueId)).sortBy(_.assigneeUserName).list
+ }
+
+ def registerIssueAssignee(
owner: String,
repository: String,
issueId: Int,
- assignedUserName: Option[String],
+ assigneeUserName: String,
insertComment: Boolean = false
- )(implicit context: Context, s: Session): Int = {
- val oldAssigned = getIssue(owner, repository, s"${issueId}").get.assignedUserName
- val assigned = assignedUserName
+ )(
+ implicit context: Context,
+ s: Session
+ ): Int = {
val assigner = context.loginAccount.map(_.userName)
if (insertComment) {
IssueComments insert IssueComment(
userName = owner,
repositoryName = repository,
issueId = issueId,
- action = "assign",
+ action = "add_assignee",
commentedUserName = assigner.getOrElse("Unknown user"),
- content = s"""${oldAssigned.getOrElse("Not assigned")}:${assigned.getOrElse("Not assigned")}""",
+ content = assigneeUserName,
registeredDate = currentDate,
updatedDate = currentDate
)
}
for (issue <- getIssue(owner, repository, issueId.toString); repo <- getRepository(owner, repository)) {
- PluginRegistry().getIssueHooks.foreach(_.assigned(issue, repo, assigner, assigned, oldAssigned))
+ PluginRegistry().getIssueHooks.foreach(_.assigned(issue, repo, assigner, Some(assigneeUserName), None))
}
- Issues
- .filter(_.byPrimaryKey(owner, repository, issueId))
- .map(t => (t.assignedUserName ?, t.updatedDate))
- .update(assignedUserName, currentDate)
+ IssueAssignees insert IssueAssignee(owner, repository, issueId, assigneeUserName)
+ }
+
+ def deleteIssueAssignee(
+ owner: String,
+ repository: String,
+ issueId: Int,
+ assigneeUserName: String,
+ insertComment: Boolean = false
+ )(
+ implicit context: Context,
+ s: Session
+ ): Int = {
+ val assigner = context.loginAccount.map(_.userName)
+ if (insertComment) {
+ IssueComments insert IssueComment(
+ userName = owner,
+ repositoryName = repository,
+ issueId = issueId,
+ action = "delete_assignee",
+ commentedUserName = assigner.getOrElse("Unknown user"),
+ content = assigneeUserName,
+ registeredDate = currentDate,
+ updatedDate = currentDate
+ )
+ }
+
+ // TODO Notify plugins of unassignment as doing in registerIssueAssignee()?
+
+ IssueAssignees filter (_.byPrimaryKey(owner, repository, issueId, assigneeUserName)) delete
+ }
+
+ def deleteAllIssueAssignees(owner: String, repository: String, issueId: Int, insertComment: Boolean = false)(
+ implicit context: Context,
+ s: Session
+ ): Int = {
+ val assigner = context.loginAccount.map(_.userName)
+ if (insertComment) {
+ IssueComments insert IssueComment(
+ userName = owner,
+ repositoryName = repository,
+ issueId = issueId,
+ action = "delete_assign",
+ commentedUserName = assigner.getOrElse("Unknown user"),
+ content = "All assignees",
+ registeredDate = currentDate,
+ updatedDate = currentDate
+ )
+ }
+
+ // TODO Notify plugins of unassignment as doing in registerIssueAssignee()?
+
+ IssueAssignees filter (_.byIssue(owner, repository, issueId)) delete
}
def updateMilestoneId(
diff --git a/src/main/scala/gitbucket/core/service/WebHookService.scala b/src/main/scala/gitbucket/core/service/WebHookService.scala
index f064597..68c85ff 100644
--- a/src/main/scala/gitbucket/core/service/WebHookService.scala
+++ b/src/main/scala/gitbucket/core/service/WebHookService.scala
@@ -379,8 +379,12 @@
settings: SystemSettings
)(implicit s: Session, context: JsonFormat.Context): Unit = {
callWebHookOf(repository.owner, repository.name, WebHook.Issues, settings) {
+ val assigneeUsers = getIssueAssignees(repository.owner, repository.name, issue.issueId)
val users =
- getAccountsByUserNames(Set(repository.owner, issue.openedUserName) ++ issue.assignedUserName, Set(sender))
+ getAccountsByUserNames(
+ Set(repository.owner, issue.openedUserName) ++ assigneeUsers.map(_.assigneeUserName),
+ Set(sender)
+ )
for {
repoOwner <- users.get(repository.owner)
issueUser <- users.get(issue.openedUserName)
@@ -393,7 +397,7 @@
issue,
RepositoryName(repository),
ApiUser(issueUser),
- issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)),
+ assigneeUsers.flatMap(x => users.get(x.assigneeUserName)).map(ApiUser(_)),
getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository))),
getApiMilestone(repository, issue.milestoneId getOrElse (0))
@@ -415,16 +419,15 @@
callWebHookOf(repository.owner, repository.name, WebHook.PullRequest, settings) {
for {
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
+ assignees = getIssueAssignees(repository.owner, repository.name, issueId)
users = getAccountsByUserNames(
- Set(repository.owner, pullRequest.requestUserName, issue.openedUserName),
+ Set(repository.owner, pullRequest.requestUserName, issue.openedUserName) ++ assignees.map(_.assigneeUserName),
Set(sender)
)
baseOwner <- users.get(repository.owner)
headOwner <- users.get(pullRequest.requestUserName)
issueUser <- users.get(issue.openedUserName)
- assignee = issue.assignedUserName.flatMap { userName =>
- getAccountByUserName(userName, false)
- }
+ assigneeUsers = assignees.flatMap(x => users.get(x.assigneeUserName))
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository)))
@@ -433,7 +436,7 @@
action = action,
issue = issue,
issueUser = issueUser,
- assignee = assignee,
+ assignees = assigneeUsers,
pullRequest = pullRequest,
headRepository = headRepo,
headOwner = headOwner,
@@ -481,8 +484,8 @@
requestRepository.name,
requestBranch
)
- assignee = issue.assignedUserName.flatMap { userName =>
- getAccountByUserName(userName, false)
+ assignees = getIssueAssignees(requestRepository.owner, requestRepository.name, issue.issueId).flatMap { x =>
+ getAccountByUserName(x.assigneeUserName, false)
}
baseRepo <- getRepository(pullRequest.userName, pullRequest.repositoryName)
labels = getIssueLabels(pullRequest.userName, pullRequest.repositoryName, issue.issueId)
@@ -492,7 +495,7 @@
action = action,
issue = issue,
issueUser = issueUser,
- assignee = assignee,
+ assignees = assignees,
pullRequest = pullRequest,
headRepository = requestRepository,
headOwner = headOwner,
@@ -522,15 +525,17 @@
)(implicit s: Session, c: JsonFormat.Context): Unit = {
import WebHookService._
callWebHookOf(repository.owner, repository.name, WebHook.PullRequestReviewComment, settings) {
+ val assignees = getIssueAssignees(repository.owner, pullRequest.requestUserName, issue.issueId)
val users =
- getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set(sender))
+ getAccountsByUserNames(
+ Set(repository.owner, pullRequest.requestUserName, issue.openedUserName) ++ assignees.map(_.assigneeUserName),
+ Set(sender)
+ )
for {
baseOwner <- users.get(repository.owner)
headOwner <- users.get(pullRequest.requestUserName)
issueUser <- users.get(issue.openedUserName)
- assignee = issue.assignedUserName.flatMap { userName =>
- getAccountByUserName(userName, false)
- }
+ assigneeUsers = assignees.flatMap(x => users.get(x.assigneeUserName))
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
labels = getIssueLabels(pullRequest.userName, pullRequest.repositoryName, issue.issueId)
.map(ApiLabel(_, RepositoryName(pullRequest.userName, pullRequest.repositoryName)))
@@ -540,7 +545,7 @@
comment = comment,
issue = issue,
issueUser = issueUser,
- assignee = assignee,
+ assignees = assigneeUsers,
pullRequest = pullRequest,
headRepository = headRepo,
headOwner = headOwner,
@@ -569,14 +574,17 @@
callWebHookOf(repository.owner, repository.name, WebHook.IssueComment, settings) {
for {
issueComment <- getComment(repository.owner, repository.name, issueCommentId.toString())
+ assignees = getIssueAssignees(repository.owner, repository.name, issue.issueId)
users = getAccountsByUserNames(
- Set(issue.openedUserName, repository.owner, issueComment.commentedUserName) ++ issue.assignedUserName,
+ Set(issue.openedUserName, repository.owner, issueComment.commentedUserName) ++ assignees.map(
+ _.assigneeUserName
+ ),
Set(sender)
)
issueUser <- users.get(issue.openedUserName)
repoOwner <- users.get(repository.owner)
commenter <- users.get(issueComment.commentedUserName)
- assignedUser = issue.assignedUserName.flatMap(users.get(_))
+ assigneeUsers = assignees.flatMap(x => users.get(x.assigneeUserName))
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
milestone = getApiMilestone(repository, issue.milestoneId getOrElse (0))
} yield {
@@ -587,7 +595,7 @@
commentUser = commenter,
repository = repository,
repositoryUser = repoOwner,
- assignedUser = assignedUser,
+ assignees = assigneeUsers,
sender = sender,
labels = labels,
milestone = milestone
@@ -710,7 +718,7 @@
action: String,
issue: Issue,
issueUser: Account,
- assignee: Option[Account],
+ assignees: List[Account],
pullRequest: PullRequest,
headRepository: RepositoryInfo,
headOwner: Account,
@@ -731,7 +739,7 @@
baseRepo = baseRepoPayload,
user = ApiUser(issueUser),
labels = labels,
- assignee = assignee.map(ApiUser.apply),
+ assignees = assignees.map(ApiUser.apply),
mergedComment = mergedComment
)
@@ -762,7 +770,7 @@
commentUser: Account,
repository: RepositoryInfo,
repositoryUser: Account,
- assignedUser: Option[Account],
+ assignees: List[Account],
sender: Account,
labels: List[Label],
milestone: Option[ApiMilestone]
@@ -774,7 +782,7 @@
issue,
RepositoryName(repository),
ApiUser(issueUser),
- assignedUser.map(ApiUser(_)),
+ assignees.map(ApiUser(_)),
labels.map(ApiLabel(_, RepositoryName(repository))),
milestone
),
@@ -799,7 +807,7 @@
comment: CommitComment,
issue: Issue,
issueUser: Account,
- assignee: Option[Account],
+ assignees: List[Account],
pullRequest: PullRequest,
headRepository: RepositoryInfo,
headOwner: Account,
@@ -828,7 +836,7 @@
baseRepo = baseRepoPayload,
user = ApiUser(issueUser),
labels = labels,
- assignee = assignee.map(ApiUser.apply),
+ assignees = assignees.map(ApiUser.apply),
mergedComment = mergedComment
),
repository = baseRepoPayload,
diff --git a/src/main/twirl/gitbucket/core/dashboard/issueslist.scala.html b/src/main/twirl/gitbucket/core/dashboard/issueslist.scala.html
index 9105bd0..d0ef372 100644
--- a/src/main/twirl/gitbucket/core/dashboard/issueslist.scala.html
+++ b/src/main/twirl/gitbucket/core/dashboard/issueslist.scala.html
@@ -33,9 +33,11 @@
@label.labelName
}
+ @*
@issue.assignedUserName.map { userName =>
@helpers.avatar(userName, 20, tooltip = true)
}
+ *@
@if(commentCount > 0){