diff --git a/src/main/scala/gitbucket/core/api/ApiMilestone.scala b/src/main/scala/gitbucket/core/api/ApiMilestone.scala index d4f332c..77c9339 100644 --- a/src/main/scala/gitbucket/core/api/ApiMilestone.scala +++ b/src/main/scala/gitbucket/core/api/ApiMilestone.scala @@ -34,7 +34,7 @@ ): 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 diff --git a/src/main/scala/gitbucket/core/controller/DashboardController.scala b/src/main/scala/gitbucket/core/controller/DashboardController.scala index bae642d..24d6786 100644 --- a/src/main/scala/gitbucket/core/controller/DashboardController.scala +++ b/src/main/scala/gitbucket/core/controller/DashboardController.scala @@ -88,10 +88,10 @@ 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 @@ 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)) diff --git a/src/main/scala/gitbucket/core/controller/IssuesController.scala b/src/main/scala/gitbucket/core/controller/IssuesController.scala index 1e19051..4f68c6d 100644 --- a/src/main/scala/gitbucket/core/controller/IssuesController.scala +++ b/src/main/scala/gitbucket/core/controller/IssuesController.scala @@ -369,6 +369,9 @@ } case _ => BadRequest() } + if (params("uri").nonEmpty) { + redirect(params("uri")) + } } }) @@ -377,6 +380,9 @@ 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 @@ executeBatch(repository) { updateAssignedUserName(repository.owner, repository.name, _, value, true) } + if (params("uri").nonEmpty) { + redirect(params("uri")) + } } }) @@ -449,6 +458,7 @@ 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 @@ 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), diff --git a/src/main/scala/gitbucket/core/controller/MilestonesController.scala b/src/main/scala/gitbucket/core/controller/MilestonesController.scala index 874ebec..de445c4 100644 --- a/src/main/scala/gitbucket/core/controller/MilestonesController.scala +++ b/src/main/scala/gitbucket/core/controller/MilestonesController.scala @@ -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 @@ ) }) + 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, _) }) diff --git a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala index d0f9814..c93c864 100644 --- a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala +++ b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala @@ -643,14 +643,20 @@ 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), diff --git a/src/main/scala/gitbucket/core/service/IssuesService.scala b/src/main/scala/gitbucket/core/service/IssuesService.scala index a728055..94240ed 100644 --- a/src/main/scala/gitbucket/core/service/IssuesService.scala +++ b/src/main/scala/gitbucket/core/service/IssuesService.scala @@ -107,14 +107,14 @@ * 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 @@ 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 @@ 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 @@ * 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 @@ */ 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 @@ 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 @@ 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 @@ 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 @@ /** * 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 @@ (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 @@ 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 @@ ) } + +sealed trait IssueSearchOption + +object IssueSearchOption { + case object Issues extends IssueSearchOption + case object PullRequests extends IssueSearchOption + case object Both extends IssueSearchOption +} diff --git a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala index 4972295..d8e1196 100644 --- a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala +++ b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala @@ -318,8 +318,8 @@ // 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 diff --git a/src/main/twirl/gitbucket/core/issues/list.scala.html b/src/main/twirl/gitbucket/core/issues/list.scala.html index b84f98f..6b57594 100644 --- a/src/main/twirl/gitbucket/core/issues/list.scala.html +++ b/src/main/twirl/gitbucket/core/issues/list.scala.html @@ -46,6 +46,7 @@
} diff --git a/src/main/twirl/gitbucket/core/issues/milestones/list.scala.html b/src/main/twirl/gitbucket/core/issues/milestones/list.scala.html index ecd4648..b5c3a16 100644 --- a/src/main/twirl/gitbucket/core/issues/milestones/list.scala.html +++ b/src/main/twirl/gitbucket/core/issues/milestones/list.scala.html @@ -32,7 +32,7 @@
+ @if(isManageable){
+
+
+ @gitbucket.core.helper.html.dropdown("Mark as") {
+ |
+
---|
+ No issues and pull requests to show. + | +
+ @if(isManageable){
+
+ }
+ @*
+
+ *@
+ @if(repository.isEmpty){
+ @issue.repositoryName ・
+ }
+ @if(issue.isPullRequest){
+
+ @issue.title
+
+ } else {
+
+ @if(issue.closed){}else{}@issue.title@issue.title
+
+ }
+ @gitbucket.core.issues.html.commitstatus(issue, commitStatus)
+ @labels.map { label =>
+ @label.labelName
+ }
+
+ @issue.assignedUserName.map { userName =>
+ @helpers.avatar(userName, 20, tooltip = true)
+ }
+ @if(commentCount > 0){
+
+ @commentCount
+
+ } else {
+
+ @commentCount
+
+ }
+
+
+ #@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 =>
+
+ @priority.priorityName
+ }
+ @milestone.map { milestone =>
+ @milestone
+ }
+
+ |
+
+
+
+ @milestone.description.map { description =>
+
+ @milestone.title
+
+
+ @if(milestone.closedDate.isDefined){
+ Closed @gitbucket.core.helper.html.datetimeago(milestone.closedDate.get)
+ } else {
+ @milestone.dueDate.map { dueDate =>
+ @if(helpers.isPast(dueDate)){
+
+ Due by @helpers.date(dueDate)
+ } else {
+ Due by @helpers.date(dueDate)
+ }
+ }.getOrElse {
+ No due date
+ }
+ }
+
+
+ @gitbucket.core.issues.milestones.html.progress(openCount + closedCount, closedCount)
+
+
+
+
+ @if(closedCount == 0){
+ 0%
+ } else {
+ @((closedCount.toDouble / (openCount + closedCount).toDouble * 100).toInt)%
+ } complete
+ @openCount open
+ @closedCount closed
+
+
+
+ @helpers.markdown(
+ markdown = description,
+ repository = repository,
+ branch = repository.repository.defaultBranch,
+ enableWikiLink = false,
+ enableRefsLink = false,
+ enableLineBreaks = true
+ )
+
+ }
+ |
+