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 @@
- @milestone.title + @milestone.title
@if(milestone.closedDate.isDefined){ Closed @gitbucket.core.helper.html.datetimeago(milestone.closedDate.get) diff --git a/src/main/twirl/gitbucket/core/issues/milestones/listparts.scala.html b/src/main/twirl/gitbucket/core/issues/milestones/listparts.scala.html new file mode 100644 index 0000000..8a0a3f5 --- /dev/null +++ b/src/main/twirl/gitbucket/core/issues/milestones/listparts.scala.html @@ -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){ +
+ + + Clear current search query, filters, and sorts + +
+} +*@ + + + + + + + + @if(issues.isEmpty){ + + + + } + @issues.map { case IssueInfo(issue, labels, milestone, priority, commentCount, commitStatus) => { + + + + }} + +
+ @if(isManageable){ + + + @gitbucket.core.helper.html.dropdown("Mark as") { +
  • Open
  • +
  • Close
  • + } + @gitbucket.core.helper.html.dropdown("Label", filter = ("label", "Find Label...")) { + @labels.map { label => +
  • + + +   + @label.labelName + +
  • + } + } + @gitbucket.core.helper.html.dropdown("Assignee", filter = ("assignee", "Find Assignee...")) { +
  • Clear assignee
  • + @collaborators.map { collaborator => +
  • @helpers.avatar(collaborator, 20) @collaborator
  • + } + } +
    + } +
    + 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 + } +
    +
    +
    + @gitbucket.core.helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), gitbucket.core.service.IssuesService.IssueLimit, 10, condition.toURL) +
    diff --git a/src/main/twirl/gitbucket/core/issues/milestones/milestone.scala.html b/src/main/twirl/gitbucket/core/issues/milestones/milestone.scala.html new file mode 100644 index 0000000..2abb8aa --- /dev/null +++ b/src/main/twirl/gitbucket/core/issues/milestones/milestone.scala.html @@ -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) => + + + + + + +
    +
    +
    + @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 +
    +
    + @if(hasWritePermission){ + Edit    + @if(milestone.closedDate.isDefined){ + Open    + } else { + Close    + } + Delete + } +
    +
    +
    +
    + @milestone.description.map { description => +
    + @helpers.markdown( + markdown = description, + repository = repository, + branch = repository.repository.defaultBranch, + enableWikiLink = false, + enableRefsLink = false, + enableLineBreaks = true + ) +
    + } +
    + + @gitbucket.core.issues.milestones.html.listparts(issues, page, openCount, closedCount, condition, collaborators, milestones.map(p => p._1), priorities, labels, Some(repository), hasWritePermission) + @if(hasWritePermission){ +
    + + + + +
    + } + } + } +} +@if(hasWritePermission){ + +} +