Priorities now integrated with issues.

This commit is contained in:
Istvan Meszaros
2017-06-06 22:43:33 +02:00
parent c908b5e642
commit 889e94a494
21 changed files with 268 additions and 37 deletions

View File

@@ -20,13 +20,13 @@ import org.scalatra.BadRequest
class AccountController extends AccountControllerBase
with AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
with AccessTokenService with WebHookService with RepositoryCreationService
with AccessTokenService with WebHookService with PrioritiesService with RepositoryCreationService
trait AccountControllerBase extends AccountManagementControllerBase {
self: AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
with AccessTokenService with WebHookService with RepositoryCreationService =>
with AccessTokenService with WebHookService with PrioritiesService with RepositoryCreationService =>
case class AccountNewForm(userName: String, password: String, fullName: String, mailAddress: String,
description: Option[String], url: Option[String], fileId: Option[String])

View File

@@ -33,6 +33,7 @@ class ApiController extends ApiControllerBase
with WebHookIssueCommentService
with WikiService
with ActivityService
with PrioritiesService
with OwnerAuthenticator
with UsersAuthenticator
with GroupManagerAuthenticator
@@ -52,6 +53,7 @@ trait ApiControllerBase extends ControllerBase {
with RepositoryCreationService
with IssueCreationService
with HandleCommentService
with PrioritiesService
with OwnerAuthenticator
with UsersAuthenticator
with GroupManagerAuthenticator
@@ -365,6 +367,7 @@ trait ApiControllerBase extends ControllerBase {
data.body,
data.assignees.headOption,
milestone.map(_.milestoneId),
None,
data.labels,
loginAccount)
JsonFormat(ApiIssue(issue, RepositoryName(repository), ApiUser(loginAccount)))

View File

@@ -27,6 +27,7 @@ class IssuesController extends IssuesControllerBase
with PullRequestService
with WebHookIssueCommentService
with CommitsService
with PrioritiesService
trait IssuesControllerBase extends ControllerBase {
self: IssuesService
@@ -41,10 +42,11 @@ trait IssuesControllerBase extends ControllerBase {
with ReferrerAuthenticator
with WritableUsersAuthenticator
with PullRequestService
with WebHookIssueCommentService =>
with WebHookIssueCommentService
with PrioritiesService =>
case class IssueCreateForm(title: String, content: Option[String],
assignedUserName: Option[String], milestoneId: Option[Int], labelNames: Option[String])
assignedUserName: Option[String], milestoneId: Option[Int], priorityId: Option[Int], labelNames: Option[String])
case class CommentForm(issueId: Int, content: String)
case class IssueStateForm(issueId: Int, content: Option[String])
@@ -53,6 +55,7 @@ trait IssuesControllerBase extends ControllerBase {
"content" -> trim(optional(text())),
"assignedUserName" -> trim(optional(text())),
"milestoneId" -> trim(optional(number())),
"priorityId" -> trim(optional(number())),
"labelNames" -> trim(optional(text()))
)(IssueCreateForm.apply)
@@ -94,6 +97,7 @@ trait IssuesControllerBase extends ControllerBase {
getIssueLabels(owner, name, issueId.toInt),
getAssignableUserNames(owner, name),
getMilestonesWithIssueCount(owner, name),
getPriorities(owner, name),
getLabels(owner, name),
isIssueEditable(repository),
isIssueManageable(repository),
@@ -109,6 +113,7 @@ trait IssuesControllerBase extends ControllerBase {
html.create(
getAssignableUserNames(owner, name),
getMilestones(owner, name),
getPriorities(owner, name),
getLabels(owner, name),
isIssueManageable(repository),
getContentTemplate(repository, "ISSUE_TEMPLATE"),
@@ -125,6 +130,7 @@ trait IssuesControllerBase extends ControllerBase {
form.content,
form.assignedUserName,
form.milestoneId,
form.priorityId,
form.labelNames.toArray.flatMap(_.split(",")),
context.loginAccount.get)
@@ -291,6 +297,11 @@ trait IssuesControllerBase extends ControllerBase {
} getOrElse Ok()
})
ajaxPost("/:owner/:repository/issues/:id/priority")(writableUsersOnly { repository =>
updatePriorityId(repository.owner, repository.name, params("id").toInt, priorityId("priorityId"))
Ok("updated")
})
post("/:owner/:repository/issues/batchedit/state")(writableUsersOnly { repository =>
defining(params.get("value")){ action =>
action match {
@@ -335,6 +346,14 @@ trait IssuesControllerBase extends ControllerBase {
}
})
post("/:owner/:repository/issues/batchedit/priority")(writableUsersOnly { repository =>
defining(priorityId("value")){ value =>
executeBatch(repository) {
updatePriorityId(repository.owner, repository.name, _, value)
}
}
})
get("/:owner/:repository/_attached/:file")(referrersOnly { repository =>
(Directory.getAttachedDir(repository.owner, repository.name) match {
case dir if(dir.exists && dir.isDirectory) =>
@@ -348,6 +367,7 @@ trait IssuesControllerBase extends ControllerBase {
val assignedUserName = (key: String) => params.get(key) filter (_.trim != "")
val milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
val priorityId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
private def executeBatch(repository: RepositoryService.RepositoryInfo)(execute: Int => Unit) = {
params("checked").split(',') map(_.toInt) foreach execute
@@ -370,6 +390,7 @@ trait IssuesControllerBase extends ControllerBase {
page,
getAssignableUserNames(owner, repoName),
getMilestones(owner, repoName),
getPriorities(owner, repoName),
getLabels(owner, repoName),
countIssue(condition.copy(state = "open" ), false, owner -> repoName),
countIssue(condition.copy(state = "closed"), false, owner -> repoName),

View File

@@ -27,7 +27,7 @@ trait PrioritiesControllerBase extends ControllerBase {
get("/:owner/:repository/issues/priorities")(referrersOnly { repository =>
html.list(
getPriorities(repository.owner, repository.name),
Map.empty, // TODO
countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
repository,
hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
})
@@ -40,7 +40,7 @@ trait PrioritiesControllerBase extends ControllerBase {
val priorityId = createPriority(repository.owner, repository.name, form.priorityName, form.color.substring(1))
html.priority(
getPriority(repository.owner, repository.name, priorityId).get,
Map.empty, // TODO,
countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
repository,
hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
})
@@ -55,7 +55,7 @@ trait PrioritiesControllerBase extends ControllerBase {
updatePriority(repository.owner, repository.name, params("priorityId").toInt, form.priorityName, form.color.substring(1))
html.priority(
getPriority(repository.owner, repository.name, params("priorityId").toInt).get,
Map.empty, // TODO,
countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
repository,
hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
})

View File

@@ -24,14 +24,14 @@ class PullRequestsController extends PullRequestsControllerBase
with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService
with CommitsService with ActivityService with WebHookPullRequestService
with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator
with CommitStatusService with MergeService with ProtectedBranchService
with CommitStatusService with MergeService with ProtectedBranchService with PrioritiesService
trait PullRequestsControllerBase extends ControllerBase {
self: RepositoryService with AccountService with IssuesService with MilestonesService with LabelsService
with CommitsService with ActivityService with PullRequestService with WebHookPullRequestService
with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator
with CommitStatusService with MergeService with ProtectedBranchService =>
with CommitStatusService with MergeService with ProtectedBranchService with PrioritiesService =>
val pullRequestForm = mapping(
"title" -> trim(label("Title" , text(required, maxlength(100)))),
@@ -45,6 +45,7 @@ trait PullRequestsControllerBase extends ControllerBase {
"commitIdTo" -> trim(text(required, maxlength(40))),
"assignedUserName" -> trim(optional(text())),
"milestoneId" -> trim(optional(number())),
"priorityId" -> trim(optional(number())),
"labelNames" -> trim(optional(text()))
)(PullRequestForm.apply)
@@ -64,6 +65,7 @@ trait PullRequestsControllerBase extends ControllerBase {
commitIdTo: String,
assignedUserName: Option[String],
milestoneId: Option[Int],
priorityId: Option[Int],
labelNames: Option[String]
)
@@ -93,6 +95,7 @@ trait PullRequestsControllerBase extends ControllerBase {
getIssueLabels(owner, name, issueId),
getAssignableUserNames(owner, name),
getMilestonesWithIssueCount(owner, name),
getPriorities(owner, name),
getLabels(owner, name),
commits,
diffs,
@@ -390,6 +393,7 @@ trait PullRequestsControllerBase extends ControllerBase {
hasDeveloperRole(originRepository.owner, originRepository.name, context.loginAccount),
getAssignableUserNames(originRepository.owner, originRepository.name),
getMilestones(originRepository.owner, originRepository.name),
getPriorities(originRepository.owner, originRepository.name),
getLabels(originRepository.owner, originRepository.name)
)
}
@@ -445,6 +449,7 @@ trait PullRequestsControllerBase extends ControllerBase {
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)
createPullRequest(
@@ -518,6 +523,7 @@ trait PullRequestsControllerBase extends ControllerBase {
page,
getAssignableUserNames(owner, repoName),
getMilestones(owner, repoName),
getPriorities(owner, repoName),
getLabels(owner, repoName),
countIssue(condition.copy(state = "open" ), true, owner -> repoName),
countIssue(condition.copy(state = "closed"), true, owner -> repoName),

View File

@@ -13,12 +13,13 @@ trait IssueComponent extends TemplateComponent { self: Profile =>
def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
}
class IssueOutline(tag: Tag) extends Table[(String, String, Int, Int)](tag, "ISSUE_OUTLINE_VIEW") with IssueTemplate {
class IssueOutline(tag: Tag) extends Table[(String, String, Int, Int, Int)](tag, "ISSUE_OUTLINE_VIEW") with IssueTemplate {
val commentCount = column[Int]("COMMENT_COUNT")
def * = (userName, repositoryName, issueId, commentCount)
val priority = column[Int]("PRIORITY")
def * = (userName, repositoryName, issueId, commentCount, priority)
}
class Issues(tag: Tag) extends Table[Issue](tag, "ISSUE") with IssueTemplate with MilestoneTemplate {
class Issues(tag: Tag) extends Table[Issue](tag, "ISSUE") with IssueTemplate with MilestoneTemplate with PriorityTemplate {
val openedUserName = column[String]("OPENED_USER_NAME")
val assignedUserName = column[String]("ASSIGNED_USER_NAME")
val title = column[String]("TITLE")
@@ -27,7 +28,7 @@ trait IssueComponent extends TemplateComponent { self: Profile =>
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
val updatedDate = column[java.util.Date]("UPDATED_DATE")
val pullRequest = column[Boolean]("PULL_REQUEST")
def * = (userName, repositoryName, issueId, openedUserName, milestoneId.?, assignedUserName.?, title, content.?, closed, registeredDate, updatedDate, pullRequest) <> (Issue.tupled, Issue.unapply)
def * = (userName, repositoryName, issueId, openedUserName, milestoneId.?, priorityId.?, assignedUserName.?, title, content.?, closed, registeredDate, updatedDate, pullRequest) <> (Issue.tupled, Issue.unapply)
def byPrimaryKey(owner: String, repository: String, issueId: Int) = byIssue(owner, repository, issueId)
}
@@ -39,6 +40,7 @@ case class Issue(
issueId: Int,
openedUserName: String,
milestoneId: Option[Int],
priorityId: Option[Int],
assignedUserName: Option[String],
title: String,
content: Option[String],

View File

@@ -12,7 +12,7 @@ trait IssueCreationService {
self: RepositoryService with WebHookIssueCommentService with LabelsService with IssuesService with ActivityService =>
def createIssue(repository: RepositoryInfo, title:String, body:Option[String],
assignee: Option[String], milestoneId: Option[Int], labelNames: Seq[String],
assignee: Option[String], milestoneId: Option[Int], priorityId: Option[Int], labelNames: Seq[String],
loginAccount: Account)(implicit context: Context, s: Session) : Issue = {
val owner = repository.owner
@@ -23,7 +23,8 @@ trait IssueCreationService {
// insert issue
val issueId = insertIssue(owner, name, userName, title, body,
if (manageable) assignee else None,
if (manageable) milestoneId else None)
if (manageable) milestoneId else None,
if (manageable) priorityId else None)
val issue: Issue = getIssue(owner, name, issueId.toString).get
// insert labels

View File

@@ -97,6 +97,30 @@ trait IssuesService {
.list.toMap
}
/**
* Returns the Map which contains issue count for each priority.
*
* @param owner the repository owner
* @param repository the repository name
* @param condition the search condition
* @return the Map which contains issue count for each priority (key is priority name, value is issue count)
*/
def countIssueGroupByPriorities(owner: String, repository: String, condition: IssueSearchCondition,
filterUser: Map[String, String])(implicit s: Session): Map[String, Int] = {
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), false)
.join(Priorities).on { case t1 ~ t2 =>
t1.byPriority(t2.userName, t2.repositoryName, t2.priorityId)
}
.groupBy { case t1 ~ t2 =>
t2.priorityName
}
.map { case priorityName ~ t =>
priorityName -> t.length
}
.list.toMap
}
def getCommitStatues(userName: String, repositoryName: String, issueId: Int)(implicit s: Session): Option[CommitStatusInfo] = {
val status = PullRequests
.filter { pr =>
@@ -139,18 +163,20 @@ trait IssuesService {
.joinLeft (IssueLabels) .on { case t1 ~ t2 ~ i ~ t3 => t1.byIssue(t3.userName, t3.repositoryName, t3.issueId) }
.joinLeft (Labels) .on { case t1 ~ t2 ~ i ~ t3 ~ t4 => t3.map(_.byLabel(t4.userName, t4.repositoryName, t4.labelId)) }
.joinLeft (Milestones) .on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 => t1.byMilestone(t5.userName, t5.repositoryName, t5.milestoneId) }
.sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 => i asc }
.map { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 =>
(t1, t2.commentCount, t4.map(_.labelId), t4.map(_.labelName), t4.map(_.color), t5.map(_.title))
.joinLeft (Priorities) .on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 => t1.byPriority(t6.userName, t6.repositoryName, t6.priorityId) }
.sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 => i asc }
.map { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 =>
(t1, t2.commentCount, t4.map(_.labelId), t4.map(_.labelName), t4.map(_.color), t5.map(_.title), t6.map(_.priorityName))
}
.list
.splitWith { (c1, c2) => c1._1.userName == c2._1.userName && c1._1.repositoryName == c2._1.repositoryName && c1._1.issueId == c2._1.issueId }
result.map { issues => issues.head match {
case (issue, commentCount, _, _, _, milestone) =>
case (issue, commentCount, _, _, _, milestone, priority) =>
IssueInfo(issue,
issues.flatMap { t => t._3.map (Label(issue.userName, issue.repositoryName, _, t._4.get, t._5.get))} toList,
milestone,
priority,
commentCount,
getCommitStatues(issue.userName, issue.repositoryName, issue.issueId))
}} toList
@@ -204,6 +230,10 @@ trait IssuesService {
case "asc" => t1.updatedDate asc
case "desc" => t1.updatedDate desc
}
case "priority" => condition.direction match {
case "asc" => t2.priority asc
case "desc" => t2.priority desc
}
}
}
.drop(offset).take(limit).zipWithIndex
@@ -219,6 +249,7 @@ trait IssuesService {
.foldLeft[Rep[Boolean]](false) ( _ || _ ) &&
(t1.closed === (condition.state == "closed").bind) &&
(t1.milestoneId.? isEmpty, condition.milestone == Some(None)) &&
(t1.priorityId.? isEmpty, condition.priority == Some(None)) &&
(t1.assignedUserName.? isEmpty, condition.assigned == Some(None)) &&
(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
(t1.pullRequest === pullRequest.bind) &&
@@ -227,6 +258,11 @@ trait IssuesService {
(t2.byPrimaryKey(t1.userName, t1.repositoryName, t1.milestoneId)) &&
(t2.title === condition.milestone.get.get.bind)
} exists, condition.milestone.flatten.isDefined) &&
// Priority filter
(Priorities filter { t2 =>
(t2.byPrimaryKey(t1.userName, t1.repositoryName, t1.priorityId)) &&
(t2.priorityName === condition.priority.get.get.bind)
} exists, condition.priority.flatten.isDefined) &&
// Assignee filter
(t1.assignedUserName === condition.assigned.get.get.bind, condition.assigned.flatten.isDefined) &&
// Label filter
@@ -253,7 +289,7 @@ trait IssuesService {
}
def insertIssue(owner: String, repository: String, loginUser: String, title: String, content: Option[String],
assignedUserName: Option[String], milestoneId: Option[Int],
assignedUserName: Option[String], milestoneId: Option[Int], priorityId: Option[Int],
isPullRequest: Boolean = false)(implicit s: Session): Int = {
// next id number
sql"SELECT ISSUE_ID + 1 FROM ISSUE_ID WHERE USER_NAME = $owner AND REPOSITORY_NAME = $repository FOR UPDATE".as[Int]
@@ -264,6 +300,7 @@ trait IssuesService {
id,
loginUser,
milestoneId,
priorityId,
assignedUserName,
title,
content,
@@ -316,6 +353,10 @@ trait IssuesService {
Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.milestoneId?).update (milestoneId)
}
def updatePriorityId(owner: String, repository: String, issueId: Int, priorityId: Option[Int])(implicit s: Session): Int = {
Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.priorityId?).update (priorityId)
}
def updateComment(commentId: Int, content: String)(implicit s: Session): Int = {
IssueComments.filter (_.byPrimaryKey(commentId)).map(t => (t.content, t.updatedDate)).update(content, currentDate)
}
@@ -430,6 +471,7 @@ object IssuesService {
case class IssueSearchCondition(
labels: Set[String] = Set.empty,
milestone: Option[Option[String]] = None,
priority: Option[Option[String]] = None,
author: Option[String] = None,
assigned: Option[Option[String]] = None,
mentioned: Option[String] = None,
@@ -459,6 +501,10 @@ object IssuesService {
case Some(x) => s"milestone:${x}"
case None => "no:milestone"
}},
priority.map { _ match {
case Some(x) => s"priority:${x}"
case None => "no:priority"
}},
(sort, direction) match {
case ("created" , "desc") => None
case ("created" , "asc" ) => Some("sort:created-asc")
@@ -466,6 +512,8 @@ object IssuesService {
case ("comments", "asc" ) => Some("sort:comments-asc")
case ("updated" , "desc") => Some("sort:updated-desc")
case ("updated" , "asc" ) => Some("sort:updated-asc")
case ("priority", "desc") => Some("sort:priority-desc")
case ("priority", "asc" ) => Some("sort:priority-asc")
case x => throw new MatchError(x)
},
visibility.map(visibility => s"visibility:${visibility}")
@@ -480,6 +528,10 @@ object IssuesService {
case Some(x) => "milestone=" + urlEncode(x)
case None => "milestone=none"
},
priority.map {
case Some(x) => "priority=" + urlEncode(x)
case None => "priority=none"
},
author .map(x => "author=" + urlEncode(x)),
assigned.map {
case Some(x) => "assigned=" + urlEncode(x)
@@ -512,6 +564,10 @@ object IssuesService {
case "none" => None
case x => Some(x)
},
param(request, "priority").map {
case "none" => None
case x => Some(x)
},
param(request, "author"),
param(request, "assigned").map {
case "none" => None
@@ -519,7 +575,7 @@ object IssuesService {
},
param(request, "mentioned"),
param(request, "state", Seq("open", "closed")).getOrElse("open"),
param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"),
param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
param(request, "visibility"),
param(request, "groups").map(_.split(",").toSet).getOrElse(Set.empty)
@@ -535,6 +591,6 @@ object IssuesService {
case class CommitStatusInfo(count: Int, successCount: Int, context: Option[String], state: Option[CommitState], targetUrl: Option[String], description: Option[String])
case class IssueInfo(issue: Issue, labels: List[Label], milestone: Option[String], commentCount: Int, status:Option[CommitStatusInfo])
case class IssueInfo(issue: Issue, labels: List[Label], milestone: Option[String], priority: Option[String], commentCount: Int, status:Option[CommitStatusInfo])
}

View File

@@ -50,7 +50,11 @@ trait PrioritiesService {
}
def deletePriority(owner: String, repository: String, priorityId: Int)(implicit s: Session): Unit = {
// TODO update affected issues
Issues.filter(_.byRepository(owner, repository))
.filter(_.priorityId === priorityId)
.map(_.priorityId?)
.update(None)
Priorities.filter(_.byPrimaryKey(owner, repository, priorityId)).delete
}
}

View File

@@ -10,7 +10,7 @@ import org.eclipse.jgit.dircache.DirCache
import org.eclipse.jgit.lib.{FileMode, Constants}
trait RepositoryCreationService {
self: AccountService with RepositoryService with LabelsService with WikiService with ActivityService =>
self: AccountService with RepositoryService with LabelsService with WikiService with ActivityService with PrioritiesService =>
def createRepository(loginAccount: Account, owner: String, name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean)
(implicit s: Session) {
@@ -30,6 +30,9 @@ trait RepositoryCreationService {
// Insert default labels
insertDefaultLabels(owner, name)
// Insert default priorities
insertDefaultPriorities(owner, name)
// Create the actual repository
val gitdir = getRepositoryDir(owner, name)
JGitUtil.initRepository(gitdir)
@@ -74,5 +77,9 @@ trait RepositoryCreationService {
createLabel(userName, repositoryName, "wontfix", "ffffff")
}
def insertDefaultPriorities(userName: String, repositoryName: String)(implicit s: Session): Unit = {
createPriority(userName, repositoryName, "high", "fc2929")
createPriority(userName, repositoryName, "medium", "fcc629")
createPriority(userName, repositoryName, "low", "acacac")
}
}

View File

@@ -17,7 +17,7 @@
</tr>
</thead>
<tbody>
@issues.map { case IssueInfo(issue, labels, milestone, commentCount, commitStatus) =>
@issues.map { case IssueInfo(issue, labels, milestone, priority, commentCount, commitStatus) =>
<tr>
<td style="padding-top: 12px; padding-bottom: 12px;">
<a href="@context.path/@issue.userName/@issue.repositoryName">@issue.userName/@issue.repositoryName</a>&nbsp;&#xFF65;

View File

@@ -1,5 +1,6 @@
@(collaborators: List[String],
milestones: List[gitbucket.core.model.Milestone],
priorities: List[gitbucket.core.model.Priority],
labels: List[gitbucket.core.model.Label],
isManageable: Boolean,
content: String,
@@ -29,7 +30,7 @@
</div>
</div>
<div class="col-md-3">
@gitbucket.core.issues.html.issueinfo(None, Nil, Nil, collaborators, milestones.map(x => (x, 0, 0)), labels, isManageable, repository)
@gitbucket.core.issues.html.issueinfo(None, Nil, Nil, collaborators, milestones.map(x => (x, 0, 0)), priorities, labels, isManageable, repository)
</div>
</div>
</form>

View File

@@ -3,6 +3,7 @@
issueLabels: List[gitbucket.core.model.Label],
collaborators: List[String],
milestones: List[(gitbucket.core.model.Milestone, Int, Int)],
priorities: List[gitbucket.core.model.Priority],
labels: List[gitbucket.core.model.Label],
isEditable: Boolean,
isManageable: Boolean,
@@ -54,7 +55,7 @@
@gitbucket.core.issues.html.commentform(issue, true, isEditable, isManageable, repository)
</div>
<div class="col-md-3">
@gitbucket.core.issues.html.issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, labels, isManageable, repository)
@gitbucket.core.issues.html.issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, priorities, labels, isManageable, repository)
</div>
</div>
}

View File

@@ -3,6 +3,7 @@
issueLabels: List[gitbucket.core.model.Label],
collaborators: List[String],
milestones: List[(gitbucket.core.model.Milestone, Int, Int)],
priorities: List[gitbucket.core.model.Priority],
labels: List[gitbucket.core.model.Label],
isManageable: Boolean,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@@ -32,6 +33,40 @@
@gitbucket.core.issues.html.labellist(issueLabels)
</ul>
<hr/>
<div style="margin-bottom: 14px;">
<span class="muted small strong">Priority</span>
@if(isManageable){
<div class="pull-right">
@gitbucket.core.helper.html.dropdown("Edit", right = true, filter = ("priority", "Filter Priority")) {
<li><a href="javascript:void(0);" class="priority" data-id=""><i class="octicon octicon-x"></i> Clear priority</a></li>
@priorities.map { priority =>
<li>
<a href="javascript:void(0);" class="priority" data-id="@priority.priorityId" data-name="@priority.priorityName" data-color="#@priority.color" data-font-color="#@priority.fontColor">
@gitbucket.core.helper.html.checkicon(issue.flatMap(is => is.priorityId).map(id => id == priority.priorityId).getOrElse(false))
<span class="label" style="background-color: #@priority.color;">&nbsp;</span>
@priority.priorityName
</a>
</li>
}
}
</div>
}
</div>
<span id="label-priority">
@issue.flatMap(_.priorityId).map { priorityId =>
@priorities.collect { case priority if(priority.priorityId == priorityId) =>
<a class="issue-priority" style="background-color: #@priority.color; color: #@priority.fontColor;" href="@helpers.url(repository)/issues?priority=@helpers.urlEncode(priority.priorityName)&state=open">@priority.priorityName</a>
}
}.getOrElse {
<span class="muted small">No priority</span>
}
</span>
@if(issue.isEmpty){
<input type="hidden" name="priorityId" value=""/>
}
<hr/>
<div style="margin-bottom: 14px;">
<span class="muted small strong">Milestone</span>
@if(isManageable){
@@ -152,6 +187,19 @@ $(function(){
);
});
$('a.priority').click(function(){
var priorityName = $(this).data('name');
var priorityId = $(this).data('id');
var color = $(this).data('color');
var fontColor = $(this).data('font-color');
$.post('@helpers.url(repository)/issues/@issue.issueId/priority',
{ priorityId: priorityId },
function(data){
displayPriority(priorityName, priorityId, color, fontColor);
}
);
});
$('a.assign').click(function(){
var $this = $(this);
var userName = $this.data('name');
@@ -188,6 +236,15 @@ $(function(){
$('input[name=milestoneId]').val(milestoneId);
});
$('a.priority').click(function(){
var priorityName = $(this).data('name');
var priorityId = $(this).data('id');
var color = $(this).data('color');
var fontColor = $(this).data('font-color');
displayPriority(priorityName, priorityId, color, fontColor);
$('input[name=priorityId]').val(priorityId);
});
$('a.assign').click(function(){
var $this = $(this);
var userName = $this.data('name');
@@ -222,6 +279,22 @@ $(function(){
}
}
function displayPriority(priorityName, priorityId, color, fontColor){
$('a.priority i.octicon-check').removeClass('octicon-check');
if(priorityId == ''){
$('#label-priority').html($('<span class="muted small">').text('No priority'));
} else {
$('#label-priority').html($('<a class="issue-priority">').text(priorityName)
.attr('href', '@helpers.url(repository)/issues?priority=' + encodeURIComponent(priorityName) + '&state=open')
.css({
"background-color": color,
"color": fontColor
}));
$('a.priority[data-id=' + priorityId + '] i').addClass('octicon-check');
}
}
function displayAssignee($this, userName){
$('a.assign i.octicon-check').removeClass('octicon-check');
if(userName == ''){

View File

@@ -3,6 +3,7 @@
page: Int,
collaborators: List[String],
milestones: List[gitbucket.core.model.Milestone],
priorities: List[gitbucket.core.model.Priority],
labels: List[gitbucket.core.model.Label],
openCount: Int,
closedCount: Int,
@@ -38,7 +39,7 @@
}
}
</form>
@gitbucket.core.issues.html.listparts(target, issues, page, openCount, closedCount, condition, collaborators, milestones, labels, Some(repository), isManageable)
@gitbucket.core.issues.html.listparts(target, issues, page, openCount, closedCount, condition, collaborators, milestones, priorities, labels, Some(repository), isManageable)
@if(isManageable){
<form id="batcheditForm" method="POST">
<input type="hidden" name="value"/>
@@ -119,6 +120,9 @@ $(function(){
$('a.toggle-milestone').click(function(){
submitBatchEdit('@helpers.url(repository)/issues/batchedit/milestone', $(this).data('id'));
});
$('a.toggle-priority').click(function(){
submitBatchEdit('@helpers.url(repository)/issues/batchedit/priority', $(this).data('id'));
});
});
</script>
}

View File

@@ -6,6 +6,7 @@
condition: gitbucket.core.service.IssuesService.IssueSearchCondition,
collaborators: List[String] = Nil,
milestones: List[gitbucket.core.model.Milestone] = Nil,
priorities: List[gitbucket.core.model.Priority] = Nil,
labels: List[gitbucket.core.model.Label] = Nil,
repository: Option[gitbucket.core.service.RepositoryService.RepositoryInfo] = None,
isManageable: Boolean = false)(implicit context: gitbucket.core.controller.Context)
@@ -48,6 +49,22 @@
</li>
}
}
@gitbucket.core.helper.html.dropdown("Priority", filter = ("priority", "Find Priority...")) {
<li>
<a href="@condition.copy(priority = (if(condition.priority == Some(None)) None else Some(None))).toURL">
@gitbucket.core.helper.html.checkicon(condition.priority == Some(None)) Issues with no priority
</a>
</li>
@priorities.map { priority =>
<li>
<a href="@condition.copy(priority = (if(condition.priority == Some(Some(priority.priorityName))) None else Some(Some(priority.priorityName)))).toURL">
@gitbucket.core.helper.html.checkicon(condition.priority == Some(Some(priority.priorityName)))
<span style="background-color: #@priority.color;" class="label-color">&nbsp;&nbsp;</span>
@priority.priorityName
</a>
</li>
}
}
@gitbucket.core.helper.html.dropdown("Milestone", filter = ("milestone", "Find Milestone...")) {
<li>
<a href="@condition.copy(milestone = (if(condition.milestone == Some(None)) None else Some(None))).toURL">
@@ -88,6 +105,16 @@
@gitbucket.core.helper.html.checkicon(condition.sort == "created" && condition.direction == "asc") Oldest
</a>
</li>
<li>
<a href="@condition.copy(sort="priority", direction="asc").toURL">
@gitbucket.core.helper.html.checkicon(condition.sort == "priority" && condition.direction == "asc") Highest priority
</a>
</li>
<li>
<a href="@condition.copy(sort="priority", direction="desc" ).toURL">
@gitbucket.core.helper.html.checkicon(condition.sort == "priority" && condition.direction == "desc") Lowest priority
</a>
</li>
<li>
<a href="@condition.copy(sort="comments", direction="desc").toURL">
@gitbucket.core.helper.html.checkicon(condition.sort == "comments" && condition.direction == "desc") Most commented
@@ -127,6 +154,14 @@
</li>
}
}
@gitbucket.core.helper.html.dropdown("Priority", filter = ("priority", "Find Priority...")) {
<li><a href="javascript:void(0);" class="toggle-priority" data-id="">No priority</a></li>
@priorities.map { priority =>
<li><a href="javascript:void(0);" class="toggle-priority" data-id="@priority.priorityId">
<span style="background-color: #@priority.color;" class="label">&nbsp;</span>
@priority.priorityName</a></li>
}
}
@gitbucket.core.helper.html.dropdown("Milestone", filter = ("milestone", "Find Milestone...")) {
<li><a href="javascript:void(0);" class="toggle-milestone" data-id="">No milestone</a></li>
@milestones.filter(_.closedDate.isEmpty).map { milestone =>
@@ -171,7 +206,7 @@
</td>
</tr>
}
@issues.map { case IssueInfo(issue, labels, milestone, commentCount, commitStatus) =>
@issues.map { case IssueInfo(issue, labels, milestone, priority, commentCount, commitStatus) =>
<tr>
<td style="padding-top: 12px; padding-bottom: 12px;">
@if(isManageable){
@@ -208,6 +243,10 @@
</span>
<div class="small muted" style="margin-left: 12px; margin-top: 2px;">
#@issue.issueId opened @gitbucket.core.helper.html.datetimeago(issue.registeredDate) by @helpers.user(issue.openedUserName, styleClass="username")
@priority.map(priority => priorities.filter(p => p.priorityName == priority).head).map { priority =>
<span style="margin: 20px;"><a href="@condition.copy(priority = Some(Some(priority.priorityName))).toURL" class="username"><i class="octicon octicon-flame"></i>
<span class="issue-priority issue-priority-inline" style="background-color: #@priority.color; color: #@priority.fontColor;">@priority.priorityName</span></a></span>
}
@milestone.map { milestone =>
<span style="margin: 20px;"><a href="@condition.copy(milestone = Some(Some(milestone))).toURL" class="username"><i class="octicon octicon-milestone"></i> @milestone</a></span>
}

View File

@@ -14,6 +14,7 @@
hasOriginWritePermission: Boolean,
collaborators: List[String],
milestones: List[gitbucket.core.model.Milestone],
priorities: List[gitbucket.core.model.Priority],
labels: List[gitbucket.core.model.Label])(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@gitbucket.core.html.main(s"Pull requests - ${repository.owner}/${repository.name}", Some(repository)){
@@ -81,7 +82,7 @@
</div>
</div>
<div class="col-md-3">
@gitbucket.core.issues.html.issueinfo(None, Nil, Nil, collaborators, milestones.map((_, 0, 0)), labels, hasOriginWritePermission, repository)
@gitbucket.core.issues.html.issueinfo(None, Nil, Nil, collaborators, milestones.map((_, 0, 0)), priorities, labels, hasOriginWritePermission, repository)
</div>
</div>
</form>

View File

@@ -5,6 +5,7 @@
issueLabels: List[gitbucket.core.model.Label],
collaborators: List[String],
milestones: List[(gitbucket.core.model.Milestone, Int, Int)],
priorities: List[gitbucket.core.model.Priority],
labels: List[gitbucket.core.model.Label],
isEditable: Boolean,
isManageable: Boolean,
@@ -45,7 +46,7 @@
}
</div>
<div class="col-md-3">
@gitbucket.core.issues.html.issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, labels, isManageable, repository)
@gitbucket.core.issues.html.issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, priorities, labels, isManageable, repository)
</div>
<script>
$(function(){

View File

@@ -4,6 +4,7 @@
issueLabels: List[gitbucket.core.model.Label],
collaborators: List[String],
milestones: List[(gitbucket.core.model.Milestone, Int, Int)],
priorities: List[gitbucket.core.model.Priority],
labels: List[gitbucket.core.model.Label],
dayByDayCommits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]],
diffs: Seq[gitbucket.core.util.JGitUtil.DiffInfo],
@@ -85,7 +86,7 @@
@flash.get("info").map{ info =>
<div class="alert alert-info">@info</div>
}
@gitbucket.core.pulls.html.conversation(issue, pullreq, commits, comments, issueLabels, collaborators, milestones, labels, isEditable, isManageable, isManageableForkedRepository, repository, forkedRepository)
@gitbucket.core.pulls.html.conversation(issue, pullreq, commits, comments, issueLabels, collaborators, milestones, priorities, labels, isEditable, isManageable, isManageableForkedRepository, repository, forkedRepository)
</div>
<div class="tab-pane" id="commits">
@if(commits.nonEmpty){

View File

@@ -823,7 +823,7 @@ div.issue-comment-action .octicon.danger {
color: #fff;
}
.nav-pills > li > span.issue-label {
.nav-pills > li > span.issue-label, .issue-priority {
display: block;
padding: 0px 8px 2px 8px;
margin-top: 2px;
@@ -832,6 +832,15 @@ div.issue-comment-action .octicon.danger {
border-radius: 5px;
}
.issue-priority {
font-size: 90%;
}
.issue-priority-inline {
font-size: 100%;
display: inline;
}
div.attachable {
margin-bottom: 10px;
}

View File

@@ -62,6 +62,7 @@ trait ServiceSpecBase {
content = None,
assignedUserName = None,
milestoneId = None,
priorityId = None,
isPullRequest = true)
}