mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-01-07 16:12:17 +01:00
Merge pull request #1616 from imeszaros/issue-priorities
Issue priorities
This commit is contained in:
26
src/main/resources/update/gitbucket-core_4.14.sql
Normal file
26
src/main/resources/update/gitbucket-core_4.14.sql
Normal file
@@ -0,0 +1,26 @@
|
||||
CREATE OR REPLACE VIEW ISSUE_OUTLINE_VIEW AS
|
||||
|
||||
SELECT
|
||||
A.USER_NAME,
|
||||
A.REPOSITORY_NAME,
|
||||
A.ISSUE_ID,
|
||||
COALESCE(B.COMMENT_COUNT, 0) + COALESCE(C.COMMENT_COUNT, 0) AS COMMENT_COUNT,
|
||||
COALESCE(D.ORDERING, 9999) AS PRIORITY
|
||||
|
||||
FROM ISSUE A
|
||||
|
||||
LEFT OUTER JOIN (
|
||||
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM ISSUE_COMMENT
|
||||
WHERE ACTION IN ('comment', 'close_comment', 'reopen_comment')
|
||||
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
|
||||
) B
|
||||
ON (A.USER_NAME = B.USER_NAME AND A.REPOSITORY_NAME = B.REPOSITORY_NAME AND A.ISSUE_ID = B.ISSUE_ID)
|
||||
|
||||
LEFT OUTER JOIN (
|
||||
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM COMMIT_COMMENT
|
||||
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
|
||||
) C
|
||||
ON (A.USER_NAME = C.USER_NAME AND A.REPOSITORY_NAME = C.REPOSITORY_NAME AND A.ISSUE_ID = C.ISSUE_ID)
|
||||
|
||||
LEFT OUTER JOIN PRIORITY D
|
||||
ON (A.PRIORITY_ID = D.PRIORITY_ID);
|
||||
22
src/main/resources/update/gitbucket-core_4.14.xml
Normal file
22
src/main/resources/update/gitbucket-core_4.14.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<createTable tableName="PRIORITY">
|
||||
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="PRIORITY_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||
<column name="PRIORITY_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="DESCRIPTION" type="varchar(255)" nullable="true"/>
|
||||
<column name="ORDERING" type="int" nullable="false"/>
|
||||
<column name="IS_DEFAULT" type="boolean" nullable="false"/>
|
||||
<column name="COLOR" type="char(6)" nullable="false"/>
|
||||
</createTable>
|
||||
|
||||
<addPrimaryKey constraintName="IDX_PRIORITY_PK" tableName="PRIORITY" columnNames="USER_NAME, REPOSITORY_NAME, PRIORITY_ID"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_PRIORITY_FK0" baseTableName="PRIORITY" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||
|
||||
<addColumn tableName="ISSUE">
|
||||
<column name="PRIORITY_ID" type="int" nullable="true" />
|
||||
</addColumn>
|
||||
|
||||
<addForeignKeyConstraint constraintName="IDX_ISSUE_FK3" baseTableName="ISSUE" baseColumnNames="PRIORITY_ID" referencedTableName="PRIORITY" referencedColumnNames="PRIORITY_ID"/>
|
||||
</changeSet>
|
||||
@@ -44,6 +44,7 @@ class ScalatraBootstrap extends LifeCycle with SystemSettingsService {
|
||||
context.mount(new RepositoryViewerController, "/*")
|
||||
context.mount(new WikiController, "/*")
|
||||
context.mount(new LabelsController, "/*")
|
||||
context.mount(new PrioritiesController, "/*")
|
||||
context.mount(new MilestonesController, "/*")
|
||||
context.mount(new IssuesController, "/*")
|
||||
context.mount(new PullRequestsController, "/*")
|
||||
|
||||
@@ -34,5 +34,9 @@ object GitBucketCoreModule extends Module("gitbucket-core",
|
||||
),
|
||||
new Version("4.12.0"),
|
||||
new Version("4.12.1"),
|
||||
new Version("4.13.0")
|
||||
new Version("4.13.0"),
|
||||
new Version("4.14.0",
|
||||
new LiquibaseMigration("update/gitbucket-core_4.14.xml"),
|
||||
new SqlMigration("update/gitbucket-core_4.14.sql")
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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,8 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
html.create(
|
||||
getAssignableUserNames(owner, name),
|
||||
getMilestones(owner, name),
|
||||
getPriorities(owner, name),
|
||||
getDefaultPriority(owner, name),
|
||||
getLabels(owner, name),
|
||||
isIssueManageable(repository),
|
||||
getContentTemplate(repository, "ISSUE_TEMPLATE"),
|
||||
@@ -125,6 +131,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
form.content,
|
||||
form.assignedUserName,
|
||||
form.milestoneId,
|
||||
form.priorityId,
|
||||
form.labelNames.toArray.flatMap(_.split(",")),
|
||||
context.loginAccount.get)
|
||||
|
||||
@@ -291,6 +298,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 +347,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 +368,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 +391,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),
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.issues.priorities.html
|
||||
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, PrioritiesService}
|
||||
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import io.github.gitbucket.scalatra.forms._
|
||||
import org.scalatra.i18n.Messages
|
||||
import org.scalatra.Ok
|
||||
|
||||
class PrioritiesController extends PrioritiesControllerBase
|
||||
with PrioritiesService with IssuesService with RepositoryService with AccountService
|
||||
with ReferrerAuthenticator with WritableUsersAuthenticator
|
||||
|
||||
trait PrioritiesControllerBase extends ControllerBase {
|
||||
self: PrioritiesService with IssuesService with RepositoryService
|
||||
with ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||
|
||||
case class PriorityForm(priorityName: String, description: Option[String], color: String)
|
||||
|
||||
val priorityForm = mapping(
|
||||
"priorityName" -> trim(label("Priority name", text(required, priorityName, uniquePriorityName, maxlength(100)))),
|
||||
"description" -> trim(label("Description", optional(text(maxlength(255))))),
|
||||
"priorityColor" -> trim(label("Color", text(required, color)))
|
||||
)(PriorityForm.apply)
|
||||
|
||||
|
||||
get("/:owner/:repository/issues/priorities")(referrersOnly { repository =>
|
||||
html.list(
|
||||
getPriorities(repository.owner, repository.name),
|
||||
countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||
repository,
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/priorities/new")(writableUsersOnly { repository =>
|
||||
html.edit(None, repository)
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/priorities/new", priorityForm)(writableUsersOnly { (form, repository) =>
|
||||
val priorityId = createPriority(repository.owner, repository.name, form.priorityName, form.description, form.color.substring(1))
|
||||
html.priority(
|
||||
getPriority(repository.owner, repository.name, priorityId).get,
|
||||
countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||
repository,
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/priorities/:priorityId/edit")(writableUsersOnly { repository =>
|
||||
getPriority(repository.owner, repository.name, params("priorityId").toInt).map { priority =>
|
||||
html.edit(Some(priority), repository)
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/priorities/:priorityId/edit", priorityForm)(writableUsersOnly { (form, repository) =>
|
||||
updatePriority(repository.owner, repository.name, params("priorityId").toInt, form.priorityName, form.description, form.color.substring(1))
|
||||
html.priority(
|
||||
getPriority(repository.owner, repository.name, params("priorityId").toInt).get,
|
||||
countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||
repository,
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/priorities/reorder")(writableUsersOnly { (repository) =>
|
||||
reorderPriorities(repository.owner, repository.name, params("order")
|
||||
.split(",")
|
||||
.map(id => id.toInt)
|
||||
.zipWithIndex
|
||||
.toMap)
|
||||
|
||||
Ok()
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/priorities/default")(writableUsersOnly { (repository) =>
|
||||
setDefaultPriority(repository.owner, repository.name, priorityId("priorityId"))
|
||||
Ok()
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/priorities/:priorityId/delete")(writableUsersOnly { repository =>
|
||||
deletePriority(repository.owner, repository.name, params("priorityId").toInt)
|
||||
Ok()
|
||||
})
|
||||
|
||||
val priorityId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
|
||||
|
||||
/**
|
||||
* Constraint for the identifier such as user name, repository name or page name.
|
||||
*/
|
||||
private def priorityName: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if(value.contains(',')){
|
||||
Some(s"${name} contains invalid character.")
|
||||
} else if(value.startsWith("_") || value.startsWith("-")){
|
||||
Some(s"${name} starts with invalid character.")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
private def uniquePriorityName: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] = {
|
||||
val owner = params("owner")
|
||||
val repository = params("repository")
|
||||
params.get("priorityId").map { priorityId =>
|
||||
getPriority(owner, repository, value).filter(_.priorityId != priorityId.toInt).map(_ => "Name has already been taken.")
|
||||
}.getOrElse {
|
||||
getPriority(owner, repository, value).map(_ => "Name has already been taken.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -38,6 +38,20 @@ protected[model] trait TemplateComponent { self: Profile =>
|
||||
byRepository(owner, repository) && (this.labelName === labelName.bind)
|
||||
}
|
||||
|
||||
trait PriorityTemplate extends BasicTemplate { self: Table[_] =>
|
||||
val priorityId = column[Int]("PRIORITY_ID")
|
||||
val priorityName = column[String]("PRIORITY_NAME")
|
||||
|
||||
def byPriority(owner: String, repository: String, priorityId: Int) =
|
||||
byRepository(owner, repository) && (this.priorityId === priorityId.bind)
|
||||
|
||||
def byPriority(userName: Rep[String], repositoryName: Rep[String], priorityId: Rep[Int]) =
|
||||
byRepository(userName, repositoryName) && (this.priorityId === priorityId)
|
||||
|
||||
def byPriority(owner: String, repository: String, priorityName: String) =
|
||||
byRepository(owner, repository) && (this.priorityName === priorityName.bind)
|
||||
}
|
||||
|
||||
trait MilestoneTemplate extends BasicTemplate { self: Table[_] =>
|
||||
val milestoneId = column[Int]("MILESTONE_ID")
|
||||
|
||||
|
||||
@@ -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],
|
||||
|
||||
43
src/main/scala/gitbucket/core/model/Priorities.scala
Normal file
43
src/main/scala/gitbucket/core/model/Priorities.scala
Normal file
@@ -0,0 +1,43 @@
|
||||
package gitbucket.core.model
|
||||
|
||||
trait PriorityComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.api._
|
||||
|
||||
lazy val Priorities = TableQuery[Priorities]
|
||||
|
||||
class Priorities(tag: Tag) extends Table[Priority](tag, "PRIORITY") with PriorityTemplate {
|
||||
override val priorityId = column[Int]("PRIORITY_ID", O AutoInc)
|
||||
override val priorityName = column[String]("PRIORITY_NAME")
|
||||
val description = column[String]("DESCRIPTION")
|
||||
val ordering = column[Int]("ORDERING")
|
||||
val isDefault = column[Boolean]("IS_DEFAULT")
|
||||
val color = column[String]("COLOR")
|
||||
def * = (userName, repositoryName, priorityId, priorityName, description.?, isDefault, ordering, color) <> (Priority.tupled, Priority.unapply)
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String, priorityId: Int) = byPriority(owner, repository, priorityId)
|
||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], priorityId: Rep[Int]) = byPriority(userName, repositoryName, priorityId)
|
||||
}
|
||||
}
|
||||
|
||||
case class Priority (
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
priorityId: Int = 0,
|
||||
priorityName: String,
|
||||
description: Option[String],
|
||||
isDefault: Boolean,
|
||||
ordering: Int = 0,
|
||||
color: String){
|
||||
|
||||
val fontColor = {
|
||||
val r = color.substring(0, 2)
|
||||
val g = color.substring(2, 4)
|
||||
val b = color.substring(4, 6)
|
||||
|
||||
if(Integer.parseInt(r, 16) + Integer.parseInt(g, 16) + Integer.parseInt(b, 16) > 408){
|
||||
"000000"
|
||||
} else {
|
||||
"ffffff"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,7 @@ trait CoreProfile extends ProfileProvider with Profile
|
||||
with IssueCommentComponent
|
||||
with IssueLabelComponent
|
||||
with LabelComponent
|
||||
with PriorityComponent
|
||||
with MilestoneComponent
|
||||
with PullRequestComponent
|
||||
with RepositoryComponent
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 =>
|
||||
@@ -136,21 +160,23 @@ trait IssuesService {
|
||||
(implicit s: Session): List[IssueInfo] = {
|
||||
// get issues and comment count and labels
|
||||
val result = searchIssueQueryBase(condition, pullRequest, offset, limit, repos)
|
||||
.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 (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) }
|
||||
.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])
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.model.Priority
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.util.StringUtil
|
||||
|
||||
trait PrioritiesService {
|
||||
|
||||
def getPriorities(owner: String, repository: String)(implicit s: Session): List[Priority] =
|
||||
Priorities.filter(_.byRepository(owner, repository)).sortBy(_.ordering asc).list
|
||||
|
||||
def getPriority(owner: String, repository: String, priorityId: Int)(implicit s: Session): Option[Priority] =
|
||||
Priorities.filter(_.byPrimaryKey(owner, repository, priorityId)).firstOption
|
||||
|
||||
def getPriority(owner: String, repository: String, priorityName: String)(implicit s: Session): Option[Priority] =
|
||||
Priorities.filter(_.byPriority(owner, repository, priorityName)).firstOption
|
||||
|
||||
def createPriority(owner: String, repository: String, priorityName: String, description: Option[String], color: String)(implicit s: Session): Int = {
|
||||
val ordering = Priorities.filter(_.byRepository(owner, repository))
|
||||
.list
|
||||
.map(p => p.ordering)
|
||||
.reduceOption(_ max _)
|
||||
.map(m => m + 1)
|
||||
.getOrElse(0)
|
||||
|
||||
Priorities returning Priorities.map(_.priorityId) insert Priority(
|
||||
userName = owner,
|
||||
repositoryName = repository,
|
||||
priorityName = priorityName,
|
||||
description = description,
|
||||
isDefault = false,
|
||||
ordering = ordering,
|
||||
color = color
|
||||
)
|
||||
}
|
||||
|
||||
def updatePriority(owner: String, repository: String, priorityId: Int, priorityName: String, description: Option[String], color: String)
|
||||
(implicit s: Session): Unit =
|
||||
Priorities.filter(_.byPrimaryKey(owner, repository, priorityId))
|
||||
.map(t => (t.priorityName, t.description.?, t.color))
|
||||
.update(priorityName, description, color)
|
||||
|
||||
def reorderPriorities(owner: String, repository: String, order: Map[Int, Int])
|
||||
(implicit s: Session): Unit = {
|
||||
|
||||
Priorities.filter(_.byRepository(owner, repository))
|
||||
.list
|
||||
.foreach(p => Priorities
|
||||
.filter(_.byPrimaryKey(owner, repository, p.priorityId))
|
||||
.map(_.ordering)
|
||||
.update(order.get(p.priorityId).get))
|
||||
}
|
||||
|
||||
def deletePriority(owner: String, repository: String, priorityId: Int)(implicit s: Session): Unit = {
|
||||
Issues.filter(_.byRepository(owner, repository))
|
||||
.filter(_.priorityId === priorityId)
|
||||
.map(_.priorityId?)
|
||||
.update(None)
|
||||
|
||||
Priorities.filter(_.byPrimaryKey(owner, repository, priorityId)).delete
|
||||
}
|
||||
|
||||
def getDefaultPriority(owner: String, repository: String)(implicit s: Session): Option[Priority] = {
|
||||
Priorities
|
||||
.filter(_.byRepository(owner, repository))
|
||||
.filter(_.isDefault)
|
||||
.list
|
||||
.headOption
|
||||
}
|
||||
|
||||
def setDefaultPriority(owner: String, repository: String, priorityId: Option[Int])(implicit s: Session): Unit = {
|
||||
Priorities
|
||||
.filter(_.byRepository(owner, repository))
|
||||
.filter(_.isDefault)
|
||||
.map(_.isDefault)
|
||||
.update(false)
|
||||
|
||||
priorityId.foreach(id => Priorities
|
||||
.filter(_.byPrimaryKey(owner, repository, id))
|
||||
.map(_.isDefault)
|
||||
.update(true))
|
||||
}
|
||||
}
|
||||
@@ -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,13 @@ trait RepositoryCreationService {
|
||||
createLabel(userName, repositoryName, "wontfix", "ffffff")
|
||||
}
|
||||
|
||||
def insertDefaultPriorities(userName: String, repositoryName: String)(implicit s: Session): Unit = {
|
||||
createPriority(userName, repositoryName, "highest", Some("All defects at this priority must be fixed before any public product is delivered."), "fc2929")
|
||||
createPriority(userName, repositoryName, "very high", Some("Issues must be addressed before a final product is delivered."), "fc5629")
|
||||
createPriority(userName, repositoryName, "high", Some("Issues should be addressed before a final product is delivered. If the issue cannot be resolved before delivery, it should be prioritized for the next release."), "fc9629")
|
||||
createPriority(userName, repositoryName, "important", Some("Issues can be shipped with a final product, but should be reviewed before the next release."), "fccd29")
|
||||
createPriority(userName, repositoryName, "default", Some("Default."), "acacac")
|
||||
|
||||
setDefaultPriority(userName, repositoryName, getPriority(userName, repositoryName, "default").map(_.priorityId))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ trait RepositoryService { self: AccountService =>
|
||||
val issues = Issues .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val pullRequests = PullRequests .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val labels = Labels .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val priorities = Priorities .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issueComments = IssueComments .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issueLabels = IssueLabels .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val commitComments = CommitComments .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
@@ -95,14 +96,19 @@ trait RepositoryService { self: AccountService =>
|
||||
WebHooks .insertAll(webHooks .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
WebHookEvents.insertAll(webHookEvents .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
Milestones .insertAll(milestones .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
Priorities .insertAll(priorities .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
IssueId .insertAll(issueId .map(_.copy(_1 = newUserName, _2 = newRepositoryName)) :_*)
|
||||
|
||||
val newMilestones = Milestones.filter(_.byRepository(newUserName, newRepositoryName)).list
|
||||
val newPriorities = Priorities.filter(_.byRepository(newUserName, newRepositoryName)).list
|
||||
Issues.insertAll(issues.map { x => x.copy(
|
||||
userName = newUserName,
|
||||
repositoryName = newRepositoryName,
|
||||
milestoneId = x.milestoneId.map { id =>
|
||||
newMilestones.find(_.title == milestones.find(_.milestoneId == id).get.title).get.milestoneId
|
||||
},
|
||||
priorityId = x.priorityId.map { id =>
|
||||
newPriorities.find(_.priorityName == priorities.find(_.priorityId == id).get.priorityName).get.priorityId
|
||||
}
|
||||
)} :_*)
|
||||
|
||||
@@ -161,6 +167,7 @@ trait RepositoryService { self: AccountService =>
|
||||
IssueComments .filter(_.byRepository(userName, repositoryName)).delete
|
||||
PullRequests .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Issues .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Priorities .filter(_.byRepository(userName, repositoryName)).delete
|
||||
IssueId .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Milestones .filter(_.byRepository(userName, repositoryName)).delete
|
||||
WebHooks .filter(_.byRepository(userName, repositoryName)).delete
|
||||
|
||||
@@ -136,6 +136,4 @@ object StringUtil {
|
||||
// }
|
||||
// b.toString
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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> ・
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
@(collaborators: List[String],
|
||||
milestones: List[gitbucket.core.model.Milestone],
|
||||
priorities: List[gitbucket.core.model.Priority],
|
||||
defaultPriority: Option[gitbucket.core.model.Priority],
|
||||
labels: List[gitbucket.core.model.Label],
|
||||
isManageable: Boolean,
|
||||
content: String,
|
||||
@@ -29,7 +31,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, defaultPriority, labels, isManageable, repository)
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -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, None, labels, isManageable, repository)
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
issueLabels: List[gitbucket.core.model.Label],
|
||||
collaborators: List[String],
|
||||
milestones: List[(gitbucket.core.model.Milestone, Int, Int)],
|
||||
priorities: List[gitbucket.core.model.Priority],
|
||||
defaultPriority: Option[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 +34,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"@if(!priority.description.isEmpty) { title="@priority.description.get" }>
|
||||
@gitbucket.core.helper.html.checkicon(issue.flatMap(_.priorityId).orElse(defaultPriority.map(_.priorityId)).map(id => id == priority.priorityId).getOrElse(false))
|
||||
<span class="label" style="background-color: #@priority.color;"> </span>
|
||||
@priority.priorityName
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<span id="label-priority">
|
||||
@issue.flatMap(_.priorityId).orElse(defaultPriority.map(_.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"@if(!priority.description.isEmpty) { title="@priority.description.get" }>@priority.priorityName</a>
|
||||
}
|
||||
}.getOrElse {
|
||||
<span class="muted small">No priority</span>
|
||||
}
|
||||
</span>
|
||||
@if(issue.isEmpty){
|
||||
<input type="hidden" name="priorityId" value="@defaultPriority.map(_.priorityId).map(_.toString).getOrElse("")"/>
|
||||
}
|
||||
<hr/>
|
||||
|
||||
<div style="margin-bottom: 14px;">
|
||||
<span class="muted small strong">Milestone</span>
|
||||
@if(isManageable){
|
||||
@@ -152,6 +188,20 @@ $(function(){
|
||||
);
|
||||
});
|
||||
|
||||
$('a.priority').click(function(){
|
||||
var priorityName = $(this).data('name');
|
||||
var priorityId = $(this).data('id');
|
||||
var description = $(this).attr('title');
|
||||
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, description, color, fontColor);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$('a.assign').click(function(){
|
||||
var $this = $(this);
|
||||
var userName = $this.data('name');
|
||||
@@ -188,6 +238,16 @@ $(function(){
|
||||
$('input[name=milestoneId]').val(milestoneId);
|
||||
});
|
||||
|
||||
$('a.priority').click(function(){
|
||||
var priorityName = $(this).data('name');
|
||||
var priorityId = $(this).data('id');
|
||||
var description = $(this).attr('title');
|
||||
var color = $(this).data('color');
|
||||
var fontColor = $(this).data('font-color');
|
||||
displayPriority(priorityName, priorityId, description, color, fontColor);
|
||||
$('input[name=priorityId]').val(priorityId);
|
||||
});
|
||||
|
||||
$('a.assign').click(function(){
|
||||
var $this = $(this);
|
||||
var userName = $this.data('name');
|
||||
@@ -222,6 +282,23 @@ $(function(){
|
||||
}
|
||||
}
|
||||
|
||||
function displayPriority(priorityName, priorityId, description, 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')
|
||||
.attr('title', description)
|
||||
.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 == ''){
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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"@if(!priority.description.isEmpty) { title="@priority.description.get" }>
|
||||
@gitbucket.core.helper.html.checkicon(condition.priority == Some(Some(priority.priorityName)))
|
||||
<span style="background-color: #@priority.color;" class="label-color"> </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"@if(!priority.description.isEmpty) { title="@priority.description.get" }>
|
||||
<span style="background-color: #@priority.color;" class="label"> </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"@if(!priority.description.isEmpty) { title="@priority.description.get" }><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>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
@(priority: Option[gitbucket.core.model.Priority],
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@defining(priority.map(_.priorityId).getOrElse("new")){ priorityId =>
|
||||
<div id="edit-priority-area-@priorityId">
|
||||
<form class="form-inline">
|
||||
<input type="text" id="priorityName-@priorityId" style="width: 300px; float: left; margin-right: 4px;" class="form-control" value="@priority.map(_.priorityName)"@if(priorityId == "new"){ placeholder="New priority name"}/>
|
||||
<div id="priority-color-@priorityId" class="input-group color bscp" data-color="#@priority.map(_.color).getOrElse("888888")" data-color-format="hex" style="width: 100px; float: left;">
|
||||
<input type="text" class="form-control" id="priorityColor-@priorityId" value="#@priority.map(_.color).getOrElse("888888")" style="width: 100px;">
|
||||
<span class="input-group-addon"><i style="background-color: #@priority.map(_.color).getOrElse("888888");"></i></span>
|
||||
</div>
|
||||
<script>
|
||||
$('div#priority-color-@priorityId').colorpicker({format: "hex"});
|
||||
</script>
|
||||
<input type="text" id="description-@priorityId" style="width: 500px; float: left; margin-left: 4px;" class="form-control" value="@priority.flatMap(_.description).getOrElse("")" placeholder="Description..." />
|
||||
<span class="pull-right">
|
||||
<span id="priority-error-@priorityId" class="error"></span>
|
||||
<input type="button" id="cancel-@priorityId" class="btn btn-default priority-edit-cancel" value="Cancel">
|
||||
<input type="button" id="submit-@priorityId" class="btn btn-success" style="margin-bottom: 0px;" value="@(if(priorityId == "new") "Create priority" else "Save changes")"/>
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
$(function(){
|
||||
$('#submit-@priorityId').click(function(e){
|
||||
$.post('@helpers.url(repository)/issues/priorities/@{if(priorityId == "new") "new" else priorityId + "/edit"}', {
|
||||
'priorityName' : $('#priorityName-@priorityId').val(),
|
||||
'description' : $('#description-@priorityId').val(),
|
||||
'priorityColor': $('#priorityColor-@priorityId').val()
|
||||
}, function(data, status){
|
||||
$('div#edit-priority-area-@priorityId').remove();
|
||||
@if(priorityId == "new"){
|
||||
$('#new-priority-table').hide();
|
||||
// Insert row into the top of table
|
||||
$('#priorities-table tbody').append(data);
|
||||
} else {
|
||||
// Replace table row
|
||||
$('#priority-row-@priorityId').after(data).remove();
|
||||
}
|
||||
$('#priority-no-priorities').hide();
|
||||
updatePriorityCount();
|
||||
}).fail(function(xhr, status, error){
|
||||
var errors = JSON.parse(xhr.responseText);
|
||||
if(errors.priorityName){
|
||||
$('span#priority-error-@priorityId').text(errors.priorityName);
|
||||
} else if(errors.description){
|
||||
$('span#priority-error-@priorityId').text(errors.description);
|
||||
} else if(errors.priorityColor){
|
||||
$('span#priority-error-@priorityId').text(errors.priorityColor);
|
||||
} else {
|
||||
$('span#priority-error-@priorityId').text('error');
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#cancel-@priorityId').click(function(e){
|
||||
$('div#edit-priority-area-@priorityId').remove();
|
||||
@if(priorityId == "new"){
|
||||
$('#new-priority-table').hide();
|
||||
} else {
|
||||
$('#priority-@priorityId').show();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
}
|
||||
124
src/main/twirl/gitbucket/core/issues/priorities/list.scala.html
Normal file
124
src/main/twirl/gitbucket/core/issues/priorities/list.scala.html
Normal file
@@ -0,0 +1,124 @@
|
||||
@(priorities: List[gitbucket.core.model.Priority],
|
||||
counts: Map[String, Int],
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
hasWritePermission: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.html.main(s"Priorities - ${repository.owner}/${repository.name}"){
|
||||
@gitbucket.core.html.menu("priorities", repository){
|
||||
@if(hasWritePermission){
|
||||
<div class="pull-right" style="margin-bottom: 10px;">
|
||||
<a class="btn btn-success" href="javascript:void(0);" id="new-priority-button">New priority</a>
|
||||
</div>
|
||||
}
|
||||
<table class="table table-bordered table-hover table-issues" id="new-priority-table" style="display: none;">
|
||||
<tr><td></td></tr>
|
||||
</table>
|
||||
<table id="priorities-table" class="table table-bordered table-hover table-issues">
|
||||
<thead>
|
||||
<tr id="priority-row-header">
|
||||
<th style="background-color: #eee;">
|
||||
<span class="small">@priorities.size priorities</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@priorities.map { priority =>
|
||||
@gitbucket.core.issues.priorities.html.priority(priority, counts, repository, hasWritePermission)
|
||||
}
|
||||
<tr id="priority-no-priorities" @if(!priorities.isEmpty){ style="display:none" }>
|
||||
<td style="padding: 20px; background-color: #eee; text-align: center;">
|
||||
No priorities to show.
|
||||
@if(hasWritePermission){
|
||||
Click on the "New priority" button above to create one.
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
}
|
||||
<script>
|
||||
$(function(){
|
||||
$('#new-priority-button').click(function(e){
|
||||
if($('#edit-priority-area-new').size() != 0){
|
||||
$('div#edit-priority-area-new').remove();
|
||||
$('#new-priority-table').hide();
|
||||
} else {
|
||||
$.get('@helpers.url(repository)/issues/priorities/new',
|
||||
function(data){
|
||||
$('#new-priority-table').show().find('tr td').append(data);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
@if(hasWritePermission){
|
||||
$('#priorities-table tbody').sortable({
|
||||
axis: 'y',
|
||||
cursor: 'move',
|
||||
helper: function (e, ui) {
|
||||
ui.children().each(function() {
|
||||
$(this).width($(this).width());
|
||||
});
|
||||
return ui;
|
||||
},
|
||||
handle: '.priority-sort-handle',
|
||||
update: function() {
|
||||
var ids = [];
|
||||
$('tr.priority-row').each(function(idx, elem) {
|
||||
ids.push($(elem).attr('id').replace('priority-row-', ''));
|
||||
});
|
||||
$.post('@helpers.url(repository)/issues/priorities/reorder', {
|
||||
'order' : ids.join(',')
|
||||
}).fail(function(xhr, status, error){
|
||||
alert('Unable to reorder priorities.');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function deletePriority(priorityId){
|
||||
if(confirm('Once you delete this priority, there is no going back.\nAre you sure?')){
|
||||
$.post('@helpers.url(repository)/issues/priorities/' + priorityId + '/delete', function(){
|
||||
$('tr#priority-row-' + priorityId).remove();
|
||||
if ($('tr.priority-row').size() == 0) {
|
||||
$('#priority-no-priorities').show();
|
||||
}
|
||||
updatePriorityCount();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function editPriority(priorityId){
|
||||
$.get('@helpers.url(repository)/issues/priorities/' + priorityId + '/edit',
|
||||
function(data){
|
||||
$('#priority-' + priorityId).hide().parent().append(data);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function updatePriorityCount() {
|
||||
var $counter = $('#priority-row-header span');
|
||||
$counter.text($counter.text().replace(/\d+/, $('tr.priority-row').size()));
|
||||
}
|
||||
|
||||
function setDefaultPriority(priorityId){
|
||||
var $a = $('a[data-id="' + priorityId + '"].priority-default');
|
||||
var isDefault = $a.data('default');
|
||||
$.post('@helpers.url(repository)/issues/priorities/default',
|
||||
{ priorityId: isDefault ? '' : priorityId },
|
||||
function(){
|
||||
$('.priority-default')
|
||||
.removeClass('priority-default-selected')
|
||||
.data('default', false)
|
||||
.attr('title', 'Set as default');
|
||||
if (!isDefault) {
|
||||
$a
|
||||
.addClass('priority-default-selected')
|
||||
.data('default', true)
|
||||
.attr('title', 'Unset as default');
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,49 @@
|
||||
@(priority: gitbucket.core.model.Priority,
|
||||
counts: Map[String, Int],
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
hasWritePermission: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
<tr id="priority-row-@priority.priorityId" class="priority-row">
|
||||
<td style="padding-top: 15px; padding-bottom: 15px;">
|
||||
<div class="milestone row" id="priority-@priority.priorityId">
|
||||
<div class="col-md-2">
|
||||
@if(hasWritePermission) {
|
||||
<div class="pull-left priority-sort-handle" style="margin-top: 3px"><i class="octicon octicon-grabber"></i></div>
|
||||
}
|
||||
<div style="margin-top: 6px">
|
||||
<a href="@helpers.url(repository)/issues?priority=@helpers.urlEncode(priority.priorityName)&state=open" id="priority-row-content-@priority.priorityId">
|
||||
<span style="background-color: #@priority.color; color: #@priority.fontColor; padding: 8px; font-size: 120%; border-radius: 4px;">
|
||||
<i class="octicon octicon-flame" style="color: #@priority.fontColor;"></i> @priority.priorityName
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="@if(hasWritePermission){col-md-6} else {col-md-8}">
|
||||
<span>@priority.description.getOrElse("")</span>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<div class="pull-right">
|
||||
@if(hasWritePermission){
|
||||
<a href="javascript:void(0);" onclick="setDefaultPriority(@priority.priorityId)" data-id="@priority.priorityId" data-default="@priority.isDefault" class="priority-default@if(priority.isDefault){ priority-default-selected}" title="@if(priority.isDefault){Unset as default} else {Set as default}"><i class="octicon octicon-star"></i></a>
|
||||
} else if(priority.isDefault) {
|
||||
<i class="octicon octicon-star priority-default priority-default-selected" title="Default"></i>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<div class="pull-right">
|
||||
<span class="muted">@counts.get(priority.priorityName).getOrElse(0) open issues</span>
|
||||
</div>
|
||||
</div>
|
||||
@if(hasWritePermission){
|
||||
<div class="col-md-2">
|
||||
<div class="pull-right">
|
||||
<a href="javascript:void(0);" onclick="editPriority(@priority.priorityId)">Edit</a>
|
||||
|
||||
<a href="javascript:void(0);" onclick="deletePriority(@priority.priorityId)">Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -18,8 +18,12 @@
|
||||
<link href="@helpers.assets("/vendors/AdminLTE-2.3.8/css/AdminLTE.min.css")" rel="stylesheet">
|
||||
<link href="@helpers.assets("/vendors/AdminLTE-2.3.8/css/skins/skin-blue.min.css")" rel="stylesheet">
|
||||
<link href="@helpers.assets("/vendors/font-awesome-4.6.3/css/font-awesome.min.css")" rel="stylesheet">
|
||||
<link href="@helpers.assets("/vendors/jquery-ui/jquery-ui.min.css")" rel="stylesheet">
|
||||
<link href="@helpers.assets("/vendors/jquery-ui/jquery-ui.structure.min.css")" rel="stylesheet">
|
||||
<link href="@helpers.assets("/vendors/jquery-ui/jquery-ui.theme.min.css")" rel="stylesheet">
|
||||
<link href="@helpers.assets("/common/css/gitbucket.css")" rel="stylesheet">
|
||||
<script src="@helpers.assets("/vendors/jquery/jquery-1.12.2.min.js")"></script>
|
||||
<script src="@helpers.assets("/vendors/jquery-ui/jquery-ui.min.js")"></script>
|
||||
<script src="@helpers.assets("/vendors/dropzone/dropzone.js")"></script>
|
||||
<script src="@helpers.assets("/common/js/validation.js")"></script>
|
||||
<script src="@helpers.assets("/common/js/gitbucket.js")"></script>
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
@menuitem("/issues", "issues", "Issues", "issue-opened", repository.issueCount)
|
||||
@menuitem("/pulls", "pulls", "Pull requests", "git-pull-request", repository.pullCount)
|
||||
@menuitem("/issues/labels", "labels", "Labels", "tag")
|
||||
@menuitem("/issues/priorities", "priorities", "Priorities", "flame")
|
||||
@menuitem("/issues/milestones", "milestones", "Milestones", "milestone")
|
||||
} else {
|
||||
@repository.repository.options.externalIssuesUrl.map { externalIssuesUrl =>
|
||||
|
||||
@@ -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, None, labels, hasOriginWritePermission, repository)
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -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, None, labels, isManageable, repository)
|
||||
</div>
|
||||
<script>
|
||||
$(function(){
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -943,6 +952,28 @@ pre.reset.discussion-item-content-text{
|
||||
color: white;
|
||||
}
|
||||
|
||||
.priority-sort-handle {
|
||||
margin-top: 3px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.priority-sort-handle i::before {
|
||||
cursor: move;
|
||||
display: block;
|
||||
width: 0.5em;
|
||||
overflow: hidden;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.priority-default .octicon {
|
||||
font-size: 1.5em;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.priority-default-selected .octicon {
|
||||
color: #3c8dbc;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
/* Pull request */
|
||||
/****************************************************************************/
|
||||
|
||||
BIN
src/main/webapp/assets/vendors/jquery-ui/images/ui-icons_444444_256x240.png
vendored
Normal file
BIN
src/main/webapp/assets/vendors/jquery-ui/images/ui-icons_444444_256x240.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
BIN
src/main/webapp/assets/vendors/jquery-ui/images/ui-icons_555555_256x240.png
vendored
Normal file
BIN
src/main/webapp/assets/vendors/jquery-ui/images/ui-icons_555555_256x240.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
BIN
src/main/webapp/assets/vendors/jquery-ui/images/ui-icons_777620_256x240.png
vendored
Normal file
BIN
src/main/webapp/assets/vendors/jquery-ui/images/ui-icons_777620_256x240.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
BIN
src/main/webapp/assets/vendors/jquery-ui/images/ui-icons_777777_256x240.png
vendored
Normal file
BIN
src/main/webapp/assets/vendors/jquery-ui/images/ui-icons_777777_256x240.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
BIN
src/main/webapp/assets/vendors/jquery-ui/images/ui-icons_cc0000_256x240.png
vendored
Normal file
BIN
src/main/webapp/assets/vendors/jquery-ui/images/ui-icons_cc0000_256x240.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
BIN
src/main/webapp/assets/vendors/jquery-ui/images/ui-icons_ffffff_256x240.png
vendored
Normal file
BIN
src/main/webapp/assets/vendors/jquery-ui/images/ui-icons_ffffff_256x240.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
7
src/main/webapp/assets/vendors/jquery-ui/jquery-ui.min.css
vendored
Normal file
7
src/main/webapp/assets/vendors/jquery-ui/jquery-ui.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
7
src/main/webapp/assets/vendors/jquery-ui/jquery-ui.min.js
vendored
Normal file
7
src/main/webapp/assets/vendors/jquery-ui/jquery-ui.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
5
src/main/webapp/assets/vendors/jquery-ui/jquery-ui.structure.min.css
vendored
Normal file
5
src/main/webapp/assets/vendors/jquery-ui/jquery-ui.structure.min.css
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/*! jQuery UI - v1.12.1 - 2017-06-03
|
||||
* http://jqueryui.com
|
||||
* Copyright jQuery Foundation and other contributors; Licensed MIT */
|
||||
|
||||
.ui-sortable-handle{-ms-touch-action:none;touch-action:none}
|
||||
5
src/main/webapp/assets/vendors/jquery-ui/jquery-ui.theme.min.css
vendored
Normal file
5
src/main/webapp/assets/vendors/jquery-ui/jquery-ui.theme.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -62,6 +62,7 @@ trait ServiceSpecBase {
|
||||
content = None,
|
||||
assignedUserName = None,
|
||||
milestoneId = None,
|
||||
priorityId = None,
|
||||
isPullRequest = true)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user