(refs #2)Implementing pull request list.

This commit is contained in:
takezoe
2013-08-05 03:31:27 +09:00
parent ebf5d00fd2
commit 4ea23a96ae
10 changed files with 202 additions and 49 deletions

View File

@@ -40,14 +40,14 @@ trait DashboardControllerBase extends ControllerBase {
// //
dashboard.html.issues( dashboard.html.issues(
issues.html.listparts( issues.html.listparts(
searchIssue(condition, filterUser, (page - 1) * IssueLimit, IssueLimit, repositories: _*), searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, repositories: _*),
page, page,
countIssue(condition.copy(state = "open"), filterUser, repositories: _*), countIssue(condition.copy(state = "open"), filterUser, false, repositories: _*),
countIssue(condition.copy(state = "closed"), filterUser, repositories: _*), countIssue(condition.copy(state = "closed"), filterUser, false, repositories: _*),
condition), condition),
countIssue(condition, Map.empty, repositories: _*), countIssue(condition, Map.empty, false, repositories: _*),
countIssue(condition, Map("assigned" -> userName), repositories: _*), countIssue(condition, Map("assigned" -> userName), false, repositories: _*),
countIssue(condition, Map("created_by" -> userName), repositories: _*), countIssue(condition, Map("created_by" -> userName), false, repositories: _*),
countIssueGroupByRepository(condition, filterUser, repositories: _*), countIssueGroupByRepository(condition, filterUser, repositories: _*),
condition, condition,
filter) filter)

View File

@@ -313,16 +313,16 @@ trait IssuesControllerBase extends ControllerBase {
session.put(sessionKey, condition) session.put(sessionKey, condition)
issues.html.list( issues.html.list(
searchIssue(condition, filterUser, (page - 1) * IssueLimit, IssueLimit, owner -> repoName), searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
page, page,
(getCollaborators(owner, repoName) :+ owner).sorted, (getCollaborators(owner, repoName) :+ owner).sorted,
getMilestones(owner, repoName), getMilestones(owner, repoName),
getLabels(owner, repoName), getLabels(owner, repoName),
countIssue(condition.copy(state = "open"), filterUser, owner -> repoName), countIssue(condition.copy(state = "open"), filterUser, false, owner -> repoName),
countIssue(condition.copy(state = "closed"), filterUser, owner -> repoName), countIssue(condition.copy(state = "closed"), filterUser, false, owner -> repoName),
countIssue(condition, Map.empty, owner -> repoName), countIssue(condition, Map.empty, false, owner -> repoName),
context.loginAccount.map(x => countIssue(condition, Map("assigned" -> x.userName), owner -> repoName)), context.loginAccount.map(x => countIssue(condition, Map("assigned" -> x.userName), false, owner -> repoName)),
context.loginAccount.map(x => countIssue(condition, Map("created_by" -> x.userName), owner -> repoName)), context.loginAccount.map(x => countIssue(condition, Map("created_by" -> x.userName), false, owner -> repoName)),
countIssueGroupByLabels(owner, repoName, condition, filterUser), countIssueGroupByLabels(owner, repoName, condition, filterUser),
condition, condition,
filter, filter,

View File

@@ -3,16 +3,19 @@ package app
import util.{LockUtil, CollaboratorsAuthenticator, JGitUtil, ReferrerAuthenticator} import util.{LockUtil, CollaboratorsAuthenticator, JGitUtil, ReferrerAuthenticator}
import util.Directory._ import util.Directory._
import util.Implicits._ import util.Implicits._
import util.JGitUtil.{DiffInfo, CommitInfo}
import service._ import service._
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import jp.sf.amateras.scalatra.forms._ import jp.sf.amateras.scalatra.forms._
import org.eclipse.jgit.transport.RefSpec import org.eclipse.jgit.transport.RefSpec
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import service.RepositoryService.RepositoryTreeNode
import org.eclipse.jgit.lib.PersonIdent import org.eclipse.jgit.lib.PersonIdent
import org.eclipse.jgit.api.MergeCommand.FastForwardMode import org.eclipse.jgit.api.MergeCommand.FastForwardMode
import service.IssuesService._
import util.JGitUtil.DiffInfo
import scala.Some
import service.RepositoryService.RepositoryTreeNode
import util.JGitUtil.CommitInfo
class PullRequestsController extends PullRequestsControllerBase class PullRequestsController extends PullRequestsControllerBase
with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with ActivityService with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with ActivityService
@@ -50,10 +53,14 @@ trait PullRequestsControllerBase extends ControllerBase {
case class MergeForm(message: String) case class MergeForm(message: String)
get("/:owner/:repository/pulls")(referrersOnly { repository => get("/:owner/:repository/pulls")(referrersOnly { repository =>
pulls.html.list(repository) searchPullRequests(None, repository)
}) })
get("/:owner/:repository/pulls/:id")(referrersOnly { repository => get("/:owner/:repository/pulls/:userName")(referrersOnly { repository =>
searchPullRequests(Some(params("userName")), repository)
})
get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
val owner = repository.owner val owner = repository.owner
val name = repository.name val name = repository.name
val issueId = params("id").toInt val issueId = params("id").toInt
@@ -86,7 +93,7 @@ trait PullRequestsControllerBase extends ControllerBase {
} getOrElse NotFound } getOrElse NotFound
}) })
post("/:owner/:repository/pulls/:id/merge", mergeForm)(collaboratorsOnly { (form, repository) => post("/:owner/:repository/pull/:id/merge", mergeForm)(collaboratorsOnly { (form, repository) =>
LockUtil.lock(s"${repository.owner}/${repository.name}/merge"){ LockUtil.lock(s"${repository.owner}/${repository.name}/merge"){
val issueId = params("id").toInt val issueId = params("id").toInt
@@ -143,7 +150,7 @@ trait PullRequestsControllerBase extends ControllerBase {
} }
} }
redirect(s"/${repository.owner}/${repository.name}/pulls/${issueId}") redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
} finally { } finally {
git.getRepository.close git.getRepository.close
@@ -188,7 +195,7 @@ trait PullRequestsControllerBase extends ControllerBase {
} }
} }
get("/:owner/:repository/pulls/compare")(collaboratorsOnly { forkedRepository => get("/:owner/:repository/compare")(collaboratorsOnly { forkedRepository =>
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match { (forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
case (Some(originUserName), Some(originRepositoryName)) => { case (Some(originUserName), Some(originRepositoryName)) => {
getRepository(originUserName, originRepositoryName, baseUrl).map { originRepository => getRepository(originUserName, originRepositoryName, baseUrl).map { originRepository =>
@@ -199,20 +206,20 @@ trait PullRequestsControllerBase extends ControllerBase {
val oldBranch = JGitUtil.getDefaultBranch(oldGit, originRepository).get._2 val oldBranch = JGitUtil.getDefaultBranch(oldGit, originRepository).get._2
val newBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository).get._2 val newBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository).get._2
redirect(s"${context.path}/${forkedRepository.owner}/${forkedRepository.name}/pulls/compare/${originUserName}:${oldBranch}...${newBranch}") redirect(s"${context.path}/${forkedRepository.owner}/${forkedRepository.name}/compare/${originUserName}:${oldBranch}...${newBranch}")
} }
} getOrElse NotFound } getOrElse NotFound
} }
case _ => { case _ => {
JGitUtil.withGit(getRepositoryDir(forkedRepository.owner, forkedRepository.name)){ git => JGitUtil.withGit(getRepositoryDir(forkedRepository.owner, forkedRepository.name)){ git =>
val defaultBranch = JGitUtil.getDefaultBranch(git, forkedRepository).get._2 val defaultBranch = JGitUtil.getDefaultBranch(git, forkedRepository).get._2
redirect(s"${context.path}/${forkedRepository.owner}/${forkedRepository.name}/pulls/compare/${defaultBranch}...${defaultBranch}") redirect(s"${context.path}/${forkedRepository.owner}/${forkedRepository.name}/compare/${defaultBranch}...${defaultBranch}")
} }
} }
} }
}) })
get("/:owner/:repository/pulls/compare/*...*")(collaboratorsOnly { repository => get("/:owner/:repository/compare/*...*")(collaboratorsOnly { repository =>
val Seq(origin, forked) = multiParams("splat") val Seq(origin, forked) = multiParams("splat")
val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, repository.owner) val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, repository.owner)
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, repository.owner) val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, repository.owner)
@@ -361,4 +368,29 @@ trait PullRequestsControllerBase extends ControllerBase {
} }
} }
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) = {
val owner = repository.owner
val repoName = repository.name
val filterUser = userName.map { x => Map("created_by" -> x) } getOrElse Map("all" -> "")
val page = IssueSearchCondition.page(request)
val sessionKey = s"${owner}/${repoName}/pulls"
// retrieve search condition
val condition = if(request.getQueryString == null){
session.get(sessionKey).getOrElse(IssueSearchCondition()).asInstanceOf[IssueSearchCondition]
} else IssueSearchCondition(request)
session.put(sessionKey, condition)
pulls.html.list(
searchIssue(condition, filterUser, true, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
userName,
page,
countIssue(condition.copy(state = "open"), filterUser, true, owner -> repoName),
countIssue(condition.copy(state = "closed"), filterUser, true, owner -> repoName),
condition,
repository,
hasWritePermission(owner, repoName, context.loginAccount))
}
} }

View File

@@ -44,14 +44,16 @@ trait IssuesService {
* *
* @param condition the search condition * @param condition the search condition
* @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name) * @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name)
* @param onlyPullRequest 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 * @param repos Tuple of the repository owner and the repository name
* @return the count of the search result * @return the count of the search result
*/ */
def countIssue(condition: IssueSearchCondition, filterUser: Map[String, String], repos: (String, String)*): Int = { def countIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
repos: (String, String)*): Int = {
// TODO It must be _.length instead of map (_.issueId) list).length. // TODO It must be _.length instead of map (_.issueId) list).length.
// But it does not work on Slick 1.0.1 (worked on Slick 1.0.0). // But it does not work on Slick 1.0.1 (worked on Slick 1.0.0).
// https://github.com/slick/slick/issues/170 // https://github.com/slick/slick/issues/170
(searchIssueQuery(repos, condition, filterUser) map (_.issueId) list).length (searchIssueQuery(repos, condition, filterUser, onlyPullRequest) map (_.issueId) list).length
} }
/** /**
* Returns the Map which contains issue count for each labels. * Returns the Map which contains issue count for each labels.
@@ -65,7 +67,7 @@ trait IssuesService {
def countIssueGroupByLabels(owner: String, repository: String, condition: IssueSearchCondition, def countIssueGroupByLabels(owner: String, repository: String, condition: IssueSearchCondition,
filterUser: Map[String, String]): Map[String, Int] = { filterUser: Map[String, String]): Map[String, Int] = {
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), filterUser) searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), filterUser, false)
.innerJoin(IssueLabels).on { (t1, t2) => .innerJoin(IssueLabels).on { (t1, t2) =>
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
} }
@@ -89,9 +91,9 @@ trait IssuesService {
* @param repos Tuple of the repository owner and the repository name * @param repos Tuple of the repository owner and the repository name
* @return list which contains issue count for each repository * @return list which contains issue count for each repository
*/ */
def countIssueGroupByRepository(condition: IssueSearchCondition, filterUser: Map[String, String], def countIssueGroupByRepository(
repos: (String, String)*): List[(String, String, Int)] = { condition: IssueSearchCondition, filterUser: Map[String, String], repos: (String, String)*): List[(String, String, Int)] = {
searchIssueQuery(repos, condition.copy(repo = None), filterUser) searchIssueQuery(repos, condition.copy(repo = None), filterUser, false)
.groupBy { t => .groupBy { t =>
t.userName ~ t.repositoryName t.userName ~ t.repositoryName
} }
@@ -107,16 +109,17 @@ trait IssuesService {
* *
* @param condition the search condition * @param condition the search condition
* @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name) * @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name)
* @param onlyPullRequest if true then returns only pull request, false then returns both of issue and pull request.
* @param offset the offset for pagination * @param offset the offset for pagination
* @param limit the limit for pagination * @param limit the limit for pagination
* @param repos Tuple of the repository owner and the repository name * @param repos Tuple of the repository owner and the repository name
* @return the search result (list of tuples which contain issue, labels and comment count) * @return the search result (list of tuples which contain issue, labels and comment count)
*/ */
def searchIssue(condition: IssueSearchCondition, filterUser: Map[String, String], def searchIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
offset: Int, limit: Int, repos: (String, String)*): List[(Issue, List[Label], Int)] = { offset: Int, limit: Int, repos: (String, String)*): List[(Issue, List[Label], Int)] = {
// get issues and comment count and labels // get issues and comment count and labels
searchIssueQuery(repos, condition, filterUser) searchIssueQuery(repos, condition, filterUser, onlyPullRequest)
.innerJoin(IssueOutline).on { (t1, t2) => t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) } .innerJoin(IssueOutline).on { (t1, t2) => t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) }
.leftJoin (IssueLabels) .on { case ((t1, t2), t3) => t1.byIssue(t3.userName, t3.repositoryName, t3.issueId) } .leftJoin (IssueLabels) .on { case ((t1, t2), t3) => t1.byIssue(t3.userName, t3.repositoryName, t3.issueId) }
.leftJoin (Labels) .on { case (((t1, t2), t3), t4) => t3.byLabel(t4.userName, t4.repositoryName, t4.labelId) } .leftJoin (Labels) .on { case (((t1, t2), t3), t4) => t3.byLabel(t4.userName, t4.repositoryName, t4.labelId) }
@@ -156,7 +159,8 @@ trait IssuesService {
/** /**
* Assembles query for conditional issue searching. * Assembles query for conditional issue searching.
*/ */
private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition, filterUser: Map[String, String]) = private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition,
filterUser: Map[String, String], onlyPullRequest: Boolean) =
Query(Issues) filter { t1 => Query(Issues) filter { t1 =>
condition.repo condition.repo
.map { _.split('/') match { case array => Seq(array(0) -> array(1)) } } .map { _.split('/') match { case array => Seq(array(0) -> array(1)) } }
@@ -168,6 +172,7 @@ trait IssuesService {
(t1.milestoneId isNull, condition.milestoneId == Some(None)) && (t1.milestoneId isNull, condition.milestoneId == Some(None)) &&
(t1.assignedUserName is filterUser("assigned").bind, filterUser.get("assigned").isDefined) && (t1.assignedUserName is filterUser("assigned").bind, filterUser.get("assigned").isDefined) &&
(t1.openedUserName is filterUser("created_by").bind, filterUser.get("created_by").isDefined) && (t1.openedUserName is filterUser("created_by").bind, filterUser.get("created_by").isDefined) &&
(t1.pullRequest is true.bind, onlyPullRequest) &&
(IssueLabels filter { t2 => (IssueLabels filter { t2 =>
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) && (t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) &&
(t2.labelId in (t2.labelId in

View File

@@ -31,12 +31,4 @@ trait PullRequestService { self: IssuesService =>
commitIdFrom, commitIdFrom,
commitIdTo)) commitIdTo))
// def mergePullRequest(originUserName: String, originRepositoryName: String, issueId: Int,
// mergeStartId: String, mergeEndId: String): Unit = {
// Query(PullRequests)
// .filter(_.byPrimaryKey(originUserName, originRepositoryName, issueId))
// .map(t => t.mergeStartId ~ t.mergeEndId)
// .update(mergeStartId, mergeEndId)
// }
} }

View File

@@ -54,7 +54,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
def activityMessage(message: String)(implicit context: app.Context): Html = def activityMessage(message: String)(implicit context: app.Context): Html =
Html(message Html(message
.replaceAll("\\[issue:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]" , s"""<a href="${context.path}/$$1/$$2/issues/$$3">$$1/$$2#$$3</a>""") .replaceAll("\\[issue:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]" , s"""<a href="${context.path}/$$1/$$2/issues/$$3">$$1/$$2#$$3</a>""")
.replaceAll("\\[pullreq:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]" , s"""<a href="${context.path}/$$1/$$2/pulls/$$3">$$1/$$2#$$3</a>""") .replaceAll("\\[pullreq:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]" , s"""<a href="${context.path}/$$1/$$2/pull/$$3">$$1/$$2#$$3</a>""")
.replaceAll("\\[repo:([^\\s]+?)/([^\\s]+?)\\]" , s"""<a href="${context.path}/$$1/$$2\">$$1/$$2</a>""") .replaceAll("\\[repo:([^\\s]+?)/([^\\s]+?)\\]" , s"""<a href="${context.path}/$$1/$$2\">$$1/$$2</a>""")
.replaceAll("\\[branch:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]", s"""<a href="${context.path}/$$1/$$2/tree/$$3">$$3</a>""") .replaceAll("\\[branch:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]", s"""<a href="${context.path}/$$1/$$2/tree/$$3">$$3</a>""")
.replaceAll("\\[tag:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]" , s"""<a href="${context.path}/$$1/$$2/tree/$$3">$$3</a>""") .replaceAll("\\[tag:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]" , s"""<a href="${context.path}/$$1/$$2/tree/$$3">$$3</a>""")

View File

@@ -149,7 +149,7 @@
<a href="@path/@issue.userName/@issue.repositoryName">@issue.repositoryName</a>&nbsp;&#xFF65; <a href="@path/@issue.userName/@issue.repositoryName">@issue.repositoryName</a>&nbsp;&#xFF65;
} }
@if(issue.isPullRequest){ @if(issue.isPullRequest){
<a href="@path/@issue.userName/@issue.repositoryName/pulls/@issue.issueId" class="issue-title">@issue.title</a> <a href="@path/@issue.userName/@issue.repositoryName/pull/@issue.issueId" class="issue-title">@issue.title</a>
} else { } else {
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-title">@issue.title</a> <a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-title">@issue.title</a>
} }

View File

@@ -159,11 +159,11 @@ $(function(){
e.parents('div.btn-group').find('button strong').text(e.text()); e.parents('div.btn-group').find('button strong').text(e.text());
@if(members.isEmpty){ @if(members.isEmpty){
location.href = '@url(repository)/pulls/compare/' + location.href = '@url(repository)/compare/' +
$.trim($('i.icon-ok').parents('a.origin-branch').data('name')) + '...' + $.trim($('i.icon-ok').parents('a.origin-branch').data('name')) + '...' +
$.trim($('i.icon-ok').parents('a.forked-branch').data('name')); $.trim($('i.icon-ok').parents('a.forked-branch').data('name'));
} else { } else {
location.href = '@url(repository)/pulls/compare/' + location.href = '@url(repository)/compare/' +
$.trim($('i.icon-ok').parents('a.origin-owner' ).data('name')) + ':' + $.trim($('i.icon-ok').parents('a.origin-owner' ).data('name')) + ':' +
$.trim($('i.icon-ok').parents('a.origin-branch').data('name')) + '...' + $.trim($('i.icon-ok').parents('a.origin-branch').data('name')) + '...' +
$.trim($('i.icon-ok').parents('a.forked-owner' ).data('name')) + ':' + $.trim($('i.icon-ok').parents('a.forked-owner' ).data('name')) + ':' +

View File

@@ -149,7 +149,7 @@ git push origin @{pullreq.branch}</pre>
</div> </div>
</div> </div>
<div id="confirm-merge-form" style="display: none;"> <div id="confirm-merge-form" style="display: none;">
<form method="POST" action="@url(repository)/pulls/@issue.issueId/merge"> <form method="POST" action="@url(repository)/pull/@issue.issueId/merge">
<div> <div>
<strong>Merge pull request #@issue.issueId from @{pullreq.requestUserName}/@{pullreq.requestBranch}</strong> <strong>Merge pull request #@issue.issueId from @{pullreq.requestUserName}/@{pullreq.requestBranch}</strong>
</div> </div>

View File

@@ -1,8 +1,132 @@
@(repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context) @(issues: List[(model.Issue, List[model.Label], Int)],
filter: Option[String],
page: Int,
openCount: Int,
closedCount: Int,
condition: service.IssuesService.IssueSearchCondition,
repository: service.RepositoryService.RepositoryInfo,
hasWritePermission: Boolean)(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@html.main("Pull Requests - " + repository.owner + "/" + repository.name){ @html.main("Pull Requests - " + repository.owner + "/" + repository.name){
@html.header("pulls", repository) @html.header("pulls", repository)
<a href="@url(repository)/pulls/compare" class="btn btn-success">New pull request</a> <div class="row-fluid">
<div class="span3">
<ul class="nav nav-pills nav-stacked">
<li@if(filter.isEmpty){ class="active"}>
<a href="@url(repository)/pulls">
<span class="count-right">@(openCount + closedCount)</span>
All Requests
</a>
</li>
@if(loginAccount.isDefined){
<li@if(filter.isDefined && filter.get == loginAccount.get.userName){ class="active"}>
<a href="@url(repository)/pulls/@loginAccount.map(_.userName)">
<span class="count-right">0</span>
Yours
</a>
</li>
}
</ul>
</div>
<div class="span9">
@if(hasWritePermission){
<div class="pull-right">
<a href="@url(repository)/compare" class="btn btn-success">New pull request</a>
</div>
}
<div class="btn-group">
<a class="btn@if(condition.state == "open"){ active}" href="@condition.copy(state = "open").toURL">@openCount Open</a>
<a class="btn@if(condition.state == "closed"){ active}" href="@condition.copy(state = "closed").toURL">@closedCount Closed</a>
</div>
<div class="btn-group">
<button class="btn dropdown-toggle" data-toggle="dropdown">
Sort:
<strong>
@if(condition.sort == "created" && condition.direction == "desc"){ Newest }
@if(condition.sort == "created" && condition.direction == "asc" ){ Oldest }
@if(condition.sort == "comments" && condition.direction == "desc"){ Most commented }
@if(condition.sort == "comments" && condition.direction == "asc" ){ Least commented }
@if(condition.sort == "updated" && condition.direction == "desc"){ Recently updated }
@if(condition.sort == "updated" && condition.direction == "asc" ){ Least recently updated }
</strong>
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li>
<a href="@condition.copy(sort="created", direction="desc").toURL">
@helper.html.checkicon(condition.sort == "created" && condition.direction == "desc") Newest
</a>
</li>
<li>
<a href="@condition.copy(sort="created", direction="asc" ).toURL">
@helper.html.checkicon(condition.sort == "created" && condition.direction == "asc") Oldest
</a>
</li>
<li>
<a href="@condition.copy(sort="comments", direction="desc").toURL">
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "desc") Most commented
</a>
</li>
<li>
<a href="@condition.copy(sort="comments", direction="asc" ).toURL">
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "asc") Least commented
</a>
</li>
<li>
<a href="@condition.copy(sort="updated", direction="desc").toURL">
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "desc") Recently updated
</a>
</li>
<li>
<a href="@condition.copy(sort="updated", direction="asc" ).toURL">
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "asc") Least recently updated
</a>
</li>
</ul>
</div>
<table class="table table-bordered table-hover table-issues">
@if(issues.isEmpty){
<tr>
<td style="padding: 20px; background-color: #eee; text-align: center;">
No pull requests to show.
</td>
</tr>
}
@issues.map { case (issue, labels, commentCount) =>
<tr>
<td>
@if(hasWritePermission){
<label class="checkbox" style="cursor: default;">
<input type="checkbox" value="@issue.issueId"/>
}
<a href="@path/@issue.userName/@issue.repositoryName/pull/@issue.issueId" class="issue-title">@issue.title</a>
@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 muted">
@issue.assignedUserName.map { userName =>
@avatar(userName, 20, tooltip = true)
}
#@issue.issueId
</span>
<div class="small muted">
Opened by <a href="@url(issue.openedUserName)" class="username">@issue.openedUserName</a> @datetime(issue.registeredDate)&nbsp;
@if(commentCount > 0){
<i class="icon-comment"></i><a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count">@commentCount @plural(commentCount, "comment")</a>
}
</div>
@if(hasWritePermission){
</label>
}
</td>
</tr>
}
</table>
<div class="pull-right">
@helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), service.IssuesService.IssueLimit, 10, condition.toURL)
</div>
</div>
</div>
} }