mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-12-30 20:29:58 +01:00
Add milestone page (#2586)
This commit is contained in:
@@ -34,7 +34,7 @@ object ApiMilestone {
|
||||
): ApiMilestone =
|
||||
ApiMilestone(
|
||||
url = ApiPath(s"/api/v3/repos/${RepositoryName(repository).fullName}/milestones/${milestone.milestoneId}"),
|
||||
html_url = ApiPath(s"/${RepositoryName(repository).fullName}/issues?milestone=${milestone.title}&state=open"),
|
||||
html_url = ApiPath(s"/${RepositoryName(repository).fullName}/milestone/${milestone.milestoneId}"),
|
||||
// label_url = ApiPath(s"/api/v3/repos/${RepositoryName(repository).fullName}/milestones/${milestone_number}/labels"),
|
||||
id = milestone.milestoneId,
|
||||
number = milestone.milestoneId, // use milestoneId as number
|
||||
|
||||
@@ -88,10 +88,10 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
val page = IssueSearchCondition.page(request)
|
||||
|
||||
html.issues(
|
||||
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
|
||||
searchIssue(condition, IssueSearchOption.Issues, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
|
||||
page,
|
||||
countIssue(condition.copy(state = "open"), false, userRepos: _*),
|
||||
countIssue(condition.copy(state = "closed"), false, userRepos: _*),
|
||||
countIssue(condition.copy(state = "open"), IssueSearchOption.Issues, userRepos: _*),
|
||||
countIssue(condition.copy(state = "closed"), IssueSearchOption.Issues, userRepos: _*),
|
||||
filter match {
|
||||
case "assigned" => condition.copy(assigned = Some(Some(userName)))
|
||||
case "mentioned" => condition.copy(mentioned = Some(userName))
|
||||
@@ -118,10 +118,16 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
val page = IssueSearchCondition.page(request)
|
||||
|
||||
html.pulls(
|
||||
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*),
|
||||
searchIssue(
|
||||
condition,
|
||||
IssueSearchOption.PullRequests,
|
||||
(page - 1) * PullRequestLimit,
|
||||
PullRequestLimit,
|
||||
allRepos: _*
|
||||
),
|
||||
page,
|
||||
countIssue(condition.copy(state = "open"), true, allRepos: _*),
|
||||
countIssue(condition.copy(state = "closed"), true, allRepos: _*),
|
||||
countIssue(condition.copy(state = "open"), IssueSearchOption.PullRequests, allRepos: _*),
|
||||
countIssue(condition.copy(state = "closed"), IssueSearchOption.PullRequests, allRepos: _*),
|
||||
filter match {
|
||||
case "assigned" => condition.copy(assigned = Some(Some(userName)))
|
||||
case "mentioned" => condition.copy(mentioned = Some(userName))
|
||||
|
||||
@@ -369,6 +369,9 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
}
|
||||
case _ => BadRequest()
|
||||
}
|
||||
if (params("uri").nonEmpty) {
|
||||
redirect(params("uri"))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -377,6 +380,9 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
executeBatch(repository) { issueId =>
|
||||
getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse {
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, labelId, true)
|
||||
if (params("uri").nonEmpty) {
|
||||
redirect(params("uri"))
|
||||
}
|
||||
}
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
@@ -387,6 +393,9 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
executeBatch(repository) {
|
||||
updateAssignedUserName(repository.owner, repository.name, _, value, true)
|
||||
}
|
||||
if (params("uri").nonEmpty) {
|
||||
redirect(params("uri"))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -449,6 +458,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
params("from") match {
|
||||
case "issues" => redirect(s"/${repository.owner}/${repository.name}/issues")
|
||||
case "pulls" => redirect(s"/${repository.owner}/${repository.name}/pulls")
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,14 +472,14 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
|
||||
html.list(
|
||||
"issues",
|
||||
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
|
||||
searchIssue(condition, IssueSearchOption.Issues, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
|
||||
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),
|
||||
countIssue(condition.copy(state = "open"), IssueSearchOption.Issues, owner -> repoName),
|
||||
countIssue(condition.copy(state = "closed"), IssueSearchOption.Issues, owner -> repoName),
|
||||
condition,
|
||||
repository,
|
||||
isIssueEditable(repository),
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.issues.milestones.html
|
||||
import gitbucket.core.service.{AccountService, MilestonesService, RepositoryService}
|
||||
import gitbucket.core.service.IssuesService.{IssueLimit, IssueSearchCondition}
|
||||
import gitbucket.core.service.{AccountService, IssueSearchOption, MilestonesService, RepositoryService}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.view.helpers.{getAssignableUserNames, getLabels, getPriorities, searchIssue}
|
||||
import org.scalatra.forms._
|
||||
import org.scalatra.i18n.Messages
|
||||
|
||||
@@ -36,6 +38,34 @@ trait MilestonesControllerBase extends ControllerBase {
|
||||
)
|
||||
})
|
||||
|
||||
get("/:owner/:repository/milestone/:id")(referrersOnly { repository =>
|
||||
val milestone = getMilestone(repository.owner, repository.name, params("id").toInt)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
val condition = IssueSearchCondition(
|
||||
request,
|
||||
milestone.get.title
|
||||
)
|
||||
html.milestone(
|
||||
condition.state,
|
||||
searchIssue(
|
||||
condition,
|
||||
IssueSearchOption.Both,
|
||||
(page - 1) * IssueLimit,
|
||||
IssueLimit,
|
||||
repository.owner -> repository.name
|
||||
),
|
||||
page,
|
||||
getAssignableUserNames(repository.owner, repository.name),
|
||||
getPriorities(repository.owner, repository.name),
|
||||
getLabels(repository.owner, repository.name),
|
||||
condition,
|
||||
getMilestonesWithIssueCount(repository.owner, repository.name)
|
||||
.filter(p => p._1.milestoneId == milestone.get.milestoneId),
|
||||
repository,
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
)
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/milestones/new")(writableUsersOnly {
|
||||
html.edit(None, _)
|
||||
})
|
||||
|
||||
@@ -643,14 +643,20 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
|
||||
gitbucket.core.issues.html.list(
|
||||
"pulls",
|
||||
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
|
||||
searchIssue(
|
||||
condition,
|
||||
IssueSearchOption.PullRequests,
|
||||
(page - 1) * PullRequestLimit,
|
||||
PullRequestLimit,
|
||||
owner -> repoName
|
||||
),
|
||||
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),
|
||||
countIssue(condition.copy(state = "open"), IssueSearchOption.PullRequests, owner -> repoName),
|
||||
countIssue(condition.copy(state = "closed"), IssueSearchOption.PullRequests, owner -> repoName),
|
||||
condition,
|
||||
repository,
|
||||
isEditable(repository),
|
||||
|
||||
@@ -107,14 +107,14 @@ trait IssuesService {
|
||||
* Returns the count of the search result against issues.
|
||||
*
|
||||
* @param condition the search condition
|
||||
* @param onlyPullRequest if true then counts only pull request, false then counts both of issue and pull request.
|
||||
* @param searchOption if true then counts only pull request, false then counts both of issue and pull request.
|
||||
* @param repos Tuple of the repository owner and the repository name
|
||||
* @return the count of the search result
|
||||
*/
|
||||
def countIssue(condition: IssueSearchCondition, onlyPullRequest: Boolean, repos: (String, String)*)(
|
||||
def countIssue(condition: IssueSearchCondition, searchOption: IssueSearchOption, repos: (String, String)*)(
|
||||
implicit s: Session
|
||||
): Int = {
|
||||
Query(searchIssueQuery(repos, condition, onlyPullRequest).length).first
|
||||
Query(searchIssueQuery(repos, condition, searchOption).length).first
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,7 +132,7 @@ trait IssuesService {
|
||||
filterUser: Map[String, String]
|
||||
)(implicit s: Session): Map[String, Int] = {
|
||||
|
||||
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), false)
|
||||
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), IssueSearchOption.Issues)
|
||||
.join(IssueLabels)
|
||||
.on {
|
||||
case t1 ~ t2 =>
|
||||
@@ -170,7 +170,7 @@ trait IssuesService {
|
||||
filterUser: Map[String, String]
|
||||
)(implicit s: Session): Map[String, Int] = {
|
||||
|
||||
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), false)
|
||||
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), IssueSearchOption.Issues)
|
||||
.join(Priorities)
|
||||
.on {
|
||||
case t1 ~ t2 =>
|
||||
@@ -223,7 +223,7 @@ trait IssuesService {
|
||||
* Returns the search result against issues.
|
||||
*
|
||||
* @param condition the search condition
|
||||
* @param pullRequest if true then returns only pull requests, false then returns only issues.
|
||||
* @param searchOption if true then returns only pull requests, false then returns only issues.
|
||||
* @param offset the offset for pagination
|
||||
* @param limit the limit for pagination
|
||||
* @param repos Tuple of the repository owner and the repository name
|
||||
@@ -231,13 +231,13 @@ trait IssuesService {
|
||||
*/
|
||||
def searchIssue(
|
||||
condition: IssueSearchCondition,
|
||||
pullRequest: Boolean,
|
||||
searchOption: IssueSearchOption,
|
||||
offset: Int,
|
||||
limit: Int,
|
||||
repos: (String, String)*
|
||||
)(implicit s: Session): List[IssueInfo] = {
|
||||
// get issues and comment count and labels
|
||||
val result = searchIssueQueryBase(condition, pullRequest, offset, limit, repos)
|
||||
val result = searchIssueQueryBase(condition, searchOption, offset, limit, repos)
|
||||
.joinLeft(IssueLabels)
|
||||
.on { case t1 ~ t2 ~ i ~ t3 => t1.byIssue(t3.userName, t3.repositoryName, t3.issueId) }
|
||||
.joinLeft(Labels)
|
||||
@@ -288,7 +288,7 @@ trait IssuesService {
|
||||
implicit s: Session
|
||||
): List[(Issue, Account, Option[Account])] = {
|
||||
// get issues and comment count and labels
|
||||
searchIssueQueryBase(condition, false, offset, limit, repos)
|
||||
searchIssueQueryBase(condition, IssueSearchOption.Issues, offset, limit, repos)
|
||||
.join(Accounts)
|
||||
.on { case t1 ~ t2 ~ i ~ t3 => t3.userName === t1.openedUserName }
|
||||
.joinLeft(Accounts)
|
||||
@@ -305,7 +305,7 @@ trait IssuesService {
|
||||
implicit s: Session
|
||||
): List[(Issue, Account, Int, PullRequest, Repository, Account, Option[Account])] = {
|
||||
// get issues and comment count and labels
|
||||
searchIssueQueryBase(condition, true, offset, limit, repos)
|
||||
searchIssueQueryBase(condition, IssueSearchOption.PullRequests, offset, limit, repos)
|
||||
.join(PullRequests)
|
||||
.on { case t1 ~ t2 ~ i ~ t3 => t3.byPrimaryKey(t1.userName, t1.repositoryName, t1.issueId) }
|
||||
.join(Repositories)
|
||||
@@ -323,12 +323,12 @@ trait IssuesService {
|
||||
|
||||
private def searchIssueQueryBase(
|
||||
condition: IssueSearchCondition,
|
||||
pullRequest: Boolean,
|
||||
searchOption: IssueSearchOption,
|
||||
offset: Int,
|
||||
limit: Int,
|
||||
repos: Seq[(String, String)]
|
||||
)(implicit s: Session) =
|
||||
searchIssueQuery(repos, condition, pullRequest)
|
||||
searchIssueQuery(repos, condition, searchOption)
|
||||
.join(IssueOutline)
|
||||
.on { (t1, t2) =>
|
||||
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
|
||||
@@ -366,7 +366,11 @@ trait IssuesService {
|
||||
/**
|
||||
* Assembles query for conditional issue searching.
|
||||
*/
|
||||
private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition, pullRequest: Boolean)(
|
||||
private def searchIssueQuery(
|
||||
repos: Seq[(String, String)],
|
||||
condition: IssueSearchCondition,
|
||||
searchOption: IssueSearchOption
|
||||
)(
|
||||
implicit s: Session
|
||||
) =
|
||||
Issues filter { t1 =>
|
||||
@@ -380,7 +384,11 @@ trait IssuesService {
|
||||
(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) &&
|
||||
(searchOption match {
|
||||
case IssueSearchOption.Issues => t1.pullRequest === false
|
||||
case IssueSearchOption.PullRequests => t1.pullRequest === true
|
||||
case IssueSearchOption.Both => t1.pullRequest === false || t1.pullRequest === true
|
||||
}) &&
|
||||
// Milestone filter
|
||||
(Milestones filter { t2 =>
|
||||
(t2.byPrimaryKey(t1.userName, t1.repositoryName, t1.milestoneId)) &&
|
||||
@@ -927,6 +935,27 @@ object IssuesService {
|
||||
param(request, "groups").map(_.split(",").toSet).getOrElse(Set.empty)
|
||||
)
|
||||
|
||||
def apply(request: HttpServletRequest, milestone: String): IssueSearchCondition =
|
||||
IssueSearchCondition(
|
||||
param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty),
|
||||
Some(Some(milestone)),
|
||||
param(request, "priority").map {
|
||||
case "none" => None
|
||||
case x => Some(x)
|
||||
},
|
||||
param(request, "author"),
|
||||
param(request, "assigned").map {
|
||||
case "none" => None
|
||||
case x => Some(x)
|
||||
},
|
||||
param(request, "mentioned"),
|
||||
param(request, "state", Seq("open", "closed")).getOrElse("open"),
|
||||
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)
|
||||
)
|
||||
|
||||
def page(request: HttpServletRequest) = {
|
||||
PaginationHelper.page(param(request, "page"))
|
||||
}
|
||||
@@ -951,3 +980,11 @@ object IssuesService {
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
sealed trait IssueSearchOption
|
||||
|
||||
object IssueSearchOption {
|
||||
case object Issues extends IssueSearchOption
|
||||
case object PullRequests extends IssueSearchOption
|
||||
case object Both extends IssueSearchOption
|
||||
}
|
||||
|
||||
@@ -318,8 +318,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
|
||||
// Retrieve all issue count in the repository
|
||||
val issueCount =
|
||||
countIssue(IssueSearchCondition(state = "open"), false, owner -> repository) +
|
||||
countIssue(IssueSearchCondition(state = "closed"), false, owner -> repository)
|
||||
countIssue(IssueSearchCondition(state = "open"), IssueSearchOption.Issues, owner -> repository) +
|
||||
countIssue(IssueSearchCondition(state = "closed"), IssueSearchOption.Issues, owner -> repository)
|
||||
|
||||
// Extract new commit and apply issue comment
|
||||
val defaultBranch = repositoryInfo.repository.defaultBranch
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
<form id="batcheditForm" method="POST">
|
||||
<input type="hidden" name="value"/>
|
||||
<input type="hidden" name="checked"/>
|
||||
<input type="hidden" name="uri"/>
|
||||
<input type="hidden" name="from" value="@target"/>
|
||||
</form>
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<td style="padding-top: 15px; padding-bottom: 15px;">
|
||||
<div class="milestone row">
|
||||
<div class="col-md-4">
|
||||
<a href="@helpers.url(repository)/issues?milestone=@helpers.urlEncode(milestone.title)&state=open" class="milestone-title">@milestone.title</a>
|
||||
<a href="@helpers.url(repository)/milestone/@milestone.milestoneId" class="milestone-title">@milestone.title</a>
|
||||
<div>
|
||||
@if(milestone.closedDate.isDefined){
|
||||
<span class="muted">Closed @gitbucket.core.helper.html.datetimeago(milestone.closedDate.get)</span>
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
@(issues: List[gitbucket.core.service.IssuesService.IssueInfo],
|
||||
page: Int,
|
||||
openCount: Int,
|
||||
closedCount: Int,
|
||||
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)
|
||||
@import gitbucket.core.view.helpers
|
||||
@import gitbucket.core.service.IssuesService.IssueInfo
|
||||
@*
|
||||
@if(condition.nonEmpty){
|
||||
<div style="width: 100%; display: inline-block;">
|
||||
<a href="@gitbucket.core.service.IssuesService.IssueSearchCondition().toURL" class="header-link">
|
||||
<i class="octicon octicon-x" ></i>
|
||||
<span class="strong">Clear current search query, filters, and sorts</span>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
*@
|
||||
<table class="table table-bordered table-hover table-issues">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="background-color: #eee;">
|
||||
@if(isManageable){
|
||||
<input type="checkbox" aria-label="Select all issues"/>
|
||||
<span id="table-issues-batchedit">
|
||||
@gitbucket.core.helper.html.dropdown("Mark as") {
|
||||
<li><a href="javascript:void(0);" class="toggle-state" data-id="open">Open</a></li>
|
||||
<li><a href="javascript:void(0);" class="toggle-state" data-id="close">Close</a></li>
|
||||
}
|
||||
@gitbucket.core.helper.html.dropdown("Label", filter = ("label", "Find Label...")) {
|
||||
@labels.map { label =>
|
||||
<li>
|
||||
<a href="javascript:void(0);" class="toggle-label" data-id="@label.labelId">
|
||||
<i class="octicon"></i>
|
||||
<span class="label" style="background-color: #@label.color;"> </span>
|
||||
@label.labelName
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
@gitbucket.core.helper.html.dropdown("Assignee", filter = ("assignee", "Find Assignee...")) {
|
||||
<li><a href="javascript:void(0);" class="toggle-assign" data-name=""><i class="octicon octicon-x"></i> Clear assignee</a></li>
|
||||
@collaborators.map { collaborator =>
|
||||
<li><a href="javascript:void(0);" class="toggle-assign" data-name="@collaborator"><i class="octicon"></i>@helpers.avatar(collaborator, 20) @collaborator</a></li>
|
||||
}
|
||||
}
|
||||
</span>
|
||||
}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if(issues.isEmpty){
|
||||
<tr>
|
||||
<td style="padding: 20px; background-color: #eee; text-align: center;">
|
||||
No issues and pull requests to show.
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@issues.map { case IssueInfo(issue, labels, milestone, priority, commentCount, commitStatus) => {
|
||||
<tr>
|
||||
<td style="padding-top: 12px; padding-bottom: 12px;">
|
||||
@if(isManageable){
|
||||
<input type="checkbox" value="@issue.issueId" aria-labelledby="issue_@(issue.issueId)_link"/>
|
||||
}
|
||||
@*
|
||||
<i class="octicon octicon-issue-@(if(issue.closed) "closed" else "opened")" style="margin-right: 3px;"></i>
|
||||
*@
|
||||
@if(repository.isEmpty){
|
||||
<a href="@context.path/@issue.userName/@issue.repositoryName">@issue.repositoryName</a> ・
|
||||
}
|
||||
@if(issue.isPullRequest){
|
||||
<a id="issue_@(issue.issueId)_link" href="@context.path/@issue.userName/@issue.repositoryName/pull/@issue.issueId" class="issue-title">
|
||||
<i class="menu-icon octicon octicon-git-pull-request"></i>@issue.title
|
||||
</a>
|
||||
} else {
|
||||
<a id="issue_@(issue.issueId)_link" href="@context.path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-title">
|
||||
@if(issue.closed){<i class="menu-icon octicon octicon-issue-closed"></i>}else{<i class="menu-icon octicon octicon-issue-opened"></i>}@issue.title@issue.title
|
||||
</a>
|
||||
}
|
||||
@gitbucket.core.issues.html.commitstatus(issue, commitStatus)
|
||||
@labels.map { label =>
|
||||
<span class="label-color small" style="background-color: #@label.color; color: #@label.fontColor; padding-left: 4px; padding-right: 4px">@label.labelName</span>
|
||||
}
|
||||
<span class="pull-right small">
|
||||
@issue.assignedUserName.map { userName =>
|
||||
@helpers.avatar(userName, 20, tooltip = true)
|
||||
}
|
||||
@if(commentCount > 0){
|
||||
<a href="@context.path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count">
|
||||
<i class="octicon octicon-comment active"></i> @commentCount
|
||||
</a>
|
||||
} else {
|
||||
<a href="@context.path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count" style="color: silver;">
|
||||
<i class="octicon octicon-comment"></i> @commentCount
|
||||
</a>
|
||||
}
|
||||
</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>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="pull-right">
|
||||
@gitbucket.core.helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), gitbucket.core.service.IssuesService.IssueLimit, 10, condition.toURL)
|
||||
</div>
|
||||
@@ -0,0 +1,154 @@
|
||||
@(state: String,
|
||||
issues: List[gitbucket.core.service.IssuesService.IssueInfo],
|
||||
page: Int,
|
||||
collaborators: List[String],
|
||||
priorities: List[gitbucket.core.model.Priority],
|
||||
labels: List[gitbucket.core.model.Label],
|
||||
condition: gitbucket.core.service.IssuesService.IssueSearchCondition,
|
||||
milestones: List[(gitbucket.core.model.Milestone, Int, 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"Milestones - ${repository.owner}/${repository.name}"){
|
||||
@gitbucket.core.html.menu("milestones", repository){
|
||||
@milestones.map { case (milestone, openCount, closedCount) =>
|
||||
<table class="table table-issues">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding-top: 15px; padding-bottom: 15px;">
|
||||
<div class="milestone row">
|
||||
<div class="col-md-4">
|
||||
<a class="milestone-title">@milestone.title</a>
|
||||
<div>
|
||||
@if(milestone.closedDate.isDefined){
|
||||
<span class="muted">Closed @gitbucket.core.helper.html.datetimeago(milestone.closedDate.get)</span>
|
||||
} else {
|
||||
@milestone.dueDate.map { dueDate =>
|
||||
@if(helpers.isPast(dueDate)){
|
||||
<i class="octicon octicon-alert" style="color:#BD2C00;"></i>
|
||||
<span class="muted milestone-alert">Due by @helpers.date(dueDate)</span>
|
||||
} else {
|
||||
<span class="muted">Due by @helpers.date(dueDate)</span>
|
||||
}
|
||||
}.getOrElse {
|
||||
<span class="muted">No due date</span>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
@gitbucket.core.issues.milestones.html.progress(openCount + closedCount, closedCount)
|
||||
<div>
|
||||
<div>
|
||||
@if(closedCount == 0){
|
||||
0%
|
||||
} else {
|
||||
@((closedCount.toDouble / (openCount + closedCount).toDouble * 100).toInt)%
|
||||
} <span class="muted">complete</span>
|
||||
@openCount <span class="muted">open</span>
|
||||
@closedCount <span class="muted">closed</span>
|
||||
</div>
|
||||
<div class="milestone-menu">
|
||||
@if(hasWritePermission){
|
||||
<a href="@helpers.url(repository)/issues/milestones/@milestone.milestoneId/edit">Edit</a>
|
||||
@if(milestone.closedDate.isDefined){
|
||||
<a href="@helpers.url(repository)/issues/milestones/@milestone.milestoneId/open">Open</a>
|
||||
} else {
|
||||
<a href="@helpers.url(repository)/issues/milestones/@milestone.milestoneId/close">Close</a>
|
||||
}
|
||||
<a href="@helpers.url(repository)/issues/milestones/@milestone.milestoneId/delete" class="delete">Delete</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@milestone.description.map { description =>
|
||||
<div class="milestone-description markdown-body">
|
||||
@helpers.markdown(
|
||||
markdown = description,
|
||||
repository = repository,
|
||||
branch = repository.repository.defaultBranch,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = false,
|
||||
enableLineBreaks = true
|
||||
)
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<ul class="nav nav-pills pull-left" style="line-height: 14px; margin-bottom: 10px;">
|
||||
<li class="@if(condition.state == "open"){active}">
|
||||
<a href="@context.path/@repository.owner/@repository.name/milestone/@milestone.milestoneId">Open <span class="badge">@openCount</span></a>
|
||||
</li>
|
||||
<li class="@if(condition.state == "closed"){active}">
|
||||
<a href="@context.path/@repository.owner/@repository.name/milestone/@milestone.milestoneId?state=closed">Closed <span class="badge">@closedCount</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
@gitbucket.core.issues.milestones.html.listparts(issues, page, openCount, closedCount, condition, collaborators, milestones.map(p => p._1), priorities, labels, Some(repository), hasWritePermission)
|
||||
@if(hasWritePermission){
|
||||
<form id="batcheditForm" method="POST">
|
||||
<input type="hidden" name="value"/>
|
||||
<input type="hidden" name="checked"/>
|
||||
<input type="hidden" name="uri" value="@context.path/@repository.owner/@repository.name/milestone/@milestone.milestoneId@if(condition.state=="closed"){?state=closed}">
|
||||
<input type="hidden" name="from" value="milestone"/>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@if(hasWritePermission){
|
||||
<script>
|
||||
$(function(){
|
||||
$('a.delete').click(function(){
|
||||
return confirm('Once you delete this milestone, there is no going back.\nAre you sure?');
|
||||
});
|
||||
|
||||
$('.table-issues input[type=checkbox]').change(function(){
|
||||
var all = $('.table-issues input[type=checkbox][value]');
|
||||
|
||||
// check all
|
||||
if($(this).val() == 'on'){
|
||||
var flag = $(this).is(":checked");
|
||||
all.each(function(){
|
||||
$(this).prop("checked", flag);
|
||||
});
|
||||
}
|
||||
|
||||
var count = all.filter(':checked').length;
|
||||
// whether all check
|
||||
$('.table-issues input[type=checkbox]').filter(':first').prop("checked", count > 0 && count == all.length);
|
||||
|
||||
if(count == 0){
|
||||
$('#table-issues-control').show();
|
||||
$('#table-issues-batchedit').hide();
|
||||
} else {
|
||||
$('#batchedit-selected').text(count);
|
||||
$('#table-issues-control').hide();
|
||||
$('#table-issues-batchedit').show();
|
||||
}
|
||||
}).filter(':first').change();
|
||||
|
||||
var submitBatchEdit = function(action, value) {
|
||||
var checked = $('.table-issues input[type=checkbox][value]').filter(':checked').map(function(){ return this.value; }).get().join();
|
||||
var form = $('#batcheditForm');
|
||||
form.find('input[name=value]').val(value);
|
||||
form.find('input[name=checked]').val(checked);
|
||||
form.attr('action', action);
|
||||
form.submit();
|
||||
};
|
||||
|
||||
$('a.toggle-state').click(function(){
|
||||
submitBatchEdit('@helpers.url(repository)/issues/batchedit/state', $(this).data('id'));
|
||||
});
|
||||
$('a.toggle-label').click(function(){
|
||||
submitBatchEdit('@helpers.url(repository)/issues/batchedit/label', $(this).data('id'));
|
||||
});
|
||||
$('a.toggle-assign').click(function(){
|
||||
submitBatchEdit('@helpers.url(repository)/issues/batchedit/assign', $(this).data('name'));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user