diff --git a/README.md b/README.md index f4fef24..4a44fce 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,16 @@ Release Notes -------- +### 2.4.1 - 6 Oct 2014 +- Bug fix + +### 2.4 - 6 Oct 2014 +- New UI is applied to Issues and Pull requests +- Side-by-side diff is available +- Fix relative path problem in Markdown links and images +- Plugin System is disabled in default +- Some bug fix and improvements + ### 2.3 - 1 Sep 2014 - Scala based plugin system - Embedded Jetty war extraction directory moved to `GITBUCKET_HOME/tmp` diff --git a/etc/icons.svg b/etc/icons.svg index a1849e3..1d50b97 100644 --- a/etc/icons.svg +++ b/etc/icons.svg @@ -34,9 +34,9 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="0.7" - inkscape:cx="482.58197" - inkscape:cy="-83.92636" + inkscape:zoom="0.98994949" + inkscape:cx="174.78739" + inkscape:cy="-195.96338" inkscape:document-units="px" inkscape:current-layer="layer1-9" showgrid="false" @@ -1583,7 +1583,7 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/project/build.scala b/project/build.scala index bbef2e5..8586d34 100644 --- a/project/build.scala +++ b/project/build.scala @@ -42,7 +42,7 @@ "org.apache.commons" % "commons-email" % "1.3.1", "org.apache.httpcomponents" % "httpclient" % "4.3", "org.apache.sshd" % "apache-sshd" % "0.11.0", - "com.typesafe.slick" %% "slick" % "2.1.0-RC3", + "com.typesafe.slick" %% "slick" % "2.1.0", "com.novell.ldap" % "jldap" % "2009-10-07", "org.quartz-scheduler" % "quartz" % "2.2.1", "com.h2database" % "h2" % "1.4.180", diff --git a/src/main/scala/app/DashboardController.scala b/src/main/scala/app/DashboardController.scala index d0a40f0..9ac588b 100644 --- a/src/main/scala/app/DashboardController.scala +++ b/src/main/scala/app/DashboardController.scala @@ -50,20 +50,20 @@ val userName = context.loginAccount.get.userName val userRepos = getUserRepositories(userName, context.baseUrl, true).map(repo => repo.owner -> repo.name) - val filterUser = Map(filter -> userName) + //val filterUser = Map(filter -> userName) val page = IssueSearchCondition.page(request) dashboard.html.issues( - issues.html.listparts( - searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*), + dashboard.html.issueslist( + searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*), page, - countIssue(condition.copy(state = "open" ), filterUser, false, userRepos: _*), - countIssue(condition.copy(state = "closed"), filterUser, false, userRepos: _*), + countIssue(condition.copy(state = "open" ), false, userRepos: _*), + countIssue(condition.copy(state = "closed"), false, userRepos: _*), condition), - countIssue(condition, Map.empty, false, userRepos: _*), - countIssue(condition, Map("assigned" -> userName), false, userRepos: _*), - countIssue(condition, Map("created_by" -> userName), false, userRepos: _*), - countIssueGroupByRepository(condition, filterUser, false, userRepos: _*), + countIssue(condition.copy(assigned = None, author = None), false, userRepos: _*), + countIssue(condition.copy(assigned = Some(userName), author = None), false, userRepos: _*), + countIssue(condition.copy(assigned = None, author = Some(userName)), false, userRepos: _*), + countIssueGroupByRepository(condition, false, userRepos: _*), condition, filter) @@ -80,24 +80,24 @@ }.copy(repo = repository)) val userName = context.loginAccount.get.userName - val allRepos = getAllRepositories() + val allRepos = getAllRepositories(userName) val userRepos = getUserRepositories(userName, context.baseUrl, true).map(repo => repo.owner -> repo.name) val filterUser = Map(filter -> userName) val page = IssueSearchCondition.page(request) val counts = countIssueGroupByRepository( - IssueSearchCondition().copy(state = condition.state), Map.empty, true, userRepos: _*) + IssueSearchCondition().copy(state = condition.state), true, userRepos: _*) dashboard.html.pulls( - pulls.html.listparts( - searchIssue(condition, filterUser, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*), + dashboard.html.pullslist( + searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*), page, - countIssue(condition.copy(state = "open" ), filterUser, true, allRepos: _*), - countIssue(condition.copy(state = "closed"), filterUser, true, allRepos: _*), + countIssue(condition.copy(state = "open" ), true, allRepos: _*), + countIssue(condition.copy(state = "closed"), true, allRepos: _*), condition, None, false), - getPullRequestCountGroupByUser(condition.state == "closed", None, None), + getAllPullRequestCountGroupByUser(condition.state == "closed", userName), userRepos.map { case (userName, repoName) => (userName, repoName, counts.find { x => x._1 == userName && x._2 == repoName }.map(_._3).getOrElse(0)) }.sortBy(_._3).reverse, diff --git a/src/main/scala/app/IssuesController.scala b/src/main/scala/app/IssuesController.scala index 4fe478e..ef178ff 100644 --- a/src/main/scala/app/IssuesController.scala +++ b/src/main/scala/app/IssuesController.scala @@ -21,7 +21,6 @@ case class IssueCreateForm(title: String, content: Option[String], assignedUserName: Option[String], milestoneId: Option[Int], labelNames: Option[String]) - case class IssueEditForm(title: String, content: Option[String]) case class CommentForm(issueId: Int, content: String) case class IssueStateForm(issueId: Int, content: Option[String]) @@ -33,10 +32,12 @@ "labelNames" -> trim(optional(text())) )(IssueCreateForm.apply) + val issueTitleEditForm = mapping( + "title" -> trim(label("Title", text(required))) + )(x => x) val issueEditForm = mapping( - "title" -> trim(label("Title", text(required))), - "content" -> trim(optional(text())) - )(IssueEditForm.apply) + "content" -> trim(optional(text())) + )(x => x) val commentForm = mapping( "issueId" -> label("Issue Id", number()), @@ -48,16 +49,8 @@ "content" -> trim(optional(text())) )(IssueStateForm.apply) - get("/:owner/:repository/issues")(referrersOnly { - searchIssues("all", _) - }) - - get("/:owner/:repository/issues/assigned/:userName")(referrersOnly { - searchIssues("assigned", _) - }) - - get("/:owner/:repository/issues/created_by/:userName")(referrersOnly { - searchIssues("created_by", _) + get("/:owner/:repository/issues")(referrersOnly { repository => + searchIssues(repository) }) get("/:owner/:repository/issues/:id")(referrersOnly { repository => @@ -126,14 +119,29 @@ } }) - ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (form, repository) => + ajaxPost("/:owner/:repository/issues/edit_title/:id", issueTitleEditForm)(readableUsersOnly { (title, repository) => defining(repository.owner, repository.name){ case (owner, name) => getIssue(owner, name, params("id")).map { issue => if(isEditable(owner, name, issue.openedUserName)){ // update issue - updateIssue(owner, name, issue.issueId, form.title, form.content) + updateIssue(owner, name, issue.issueId, title, issue.content) // extract references and create refer comment - createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse("")) + createReferComment(owner, name, issue.copy(title = title), title) + + redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}") + } else Unauthorized + } getOrElse NotFound + } + }) + + ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (content, repository) => + defining(repository.owner, repository.name){ case (owner, name) => + getIssue(owner, name, params("id")).map { issue => + if(isEditable(owner, name, issue.openedUserName)){ + // update issue + updateIssue(owner, name, issue.issueId, issue.title, content) + // extract references and create refer comment + createReferComment(owner, name, issue, content.getOrElse("")) redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}") } else Unauthorized @@ -181,7 +189,7 @@ if(isEditable(x.userName, x.repositoryName, x.openedUserName)){ params.get("dataType") collect { case t if t == "html" => issues.html.editissue( - x.title, x.content, x.issueId, x.userName, x.repositoryName) + x.content, x.issueId, x.userName, x.repositoryName) } getOrElse { contentType = formats("json") org.json4s.jackson.Serialization.write( @@ -235,15 +243,17 @@ milestoneId("milestoneId").map { milestoneId => getMilestonesWithIssueCount(repository.owner, repository.name) .find(_._1.milestoneId == milestoneId).map { case (_, openCount, closeCount) => - issues.milestones.html.progress(openCount + closeCount, closeCount, false) + issues.milestones.html.progress(openCount + closeCount, closeCount) } getOrElse NotFound } getOrElse Ok() }) post("/:owner/:repository/issues/batchedit/state")(collaboratorsOnly { repository => defining(params.get("value")){ action => - executeBatch(repository) { - handleComment(_, None, repository)( _ => action) + action match { + case Some("open") => executeBatch(repository) { handleComment(_, None, repository)( _ => Some("reopen")) } + case Some("close") => executeBatch(repository) { handleComment(_, None, repository)( _ => Some("close")) } + case _ => // TODO BadRequest } } }) @@ -293,7 +303,10 @@ private def executeBatch(repository: RepositoryService.RepositoryInfo)(execute: Int => Unit) = { params("checked").split(',') map(_.toInt) foreach execute - redirect(s"/${repository.owner}/${repository.name}/issues") + params("from") match { + case "issues" => redirect(s"/${repository.owner}/${repository.name}/issues") + case "pulls" => redirect(s"/${repository.owner}/${repository.name}/pulls") + } } private def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String) = { @@ -319,15 +332,15 @@ val (action, recordActivity) = getAction(issue) .collect { - case "close" => true -> (Some("close") -> - Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _)) - case "reopen" => false -> (Some("reopen") -> - Some(recordReopenIssueActivity _)) - } + case "close" if(!issue.closed) => true -> + (Some("close") -> Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _)) + case "reopen" if(issue.closed) => false -> + (Some("reopen") -> Some(recordReopenIssueActivity _)) + } .map { case (closed, t) => - updateClosed(owner, name, issueId, closed) - t - } + updateClosed(owner, name, issueId, closed) + t + } .getOrElse(None -> None) val commentId = content @@ -337,7 +350,7 @@ case (content, action) => createComment(owner, name, userName, issueId, content, action) } - // record activity + // record comment activity if comment is entered content foreach { (if(issue.isPullRequest) recordCommentPullRequestActivity _ else recordCommentIssueActivity _) (owner, name, userName, issueId, _) @@ -370,9 +383,8 @@ } } - private def searchIssues(filter: String, repository: RepositoryService.RepositoryInfo) = { + private def searchIssues(repository: RepositoryService.RepositoryInfo) = { defining(repository.owner, repository.name){ case (owner, repoName) => - val filterUser = Map(filter -> params.getOrElse("userName", "")) val page = IssueSearchCondition.page(request) val sessionKey = Keys.Session.Issues(owner, repoName) @@ -383,19 +395,15 @@ ) issues.html.list( - searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName), + "issues", + searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName), page, (getCollaborators(owner, repoName) :+ owner).sorted, getMilestones(owner, repoName), getLabels(owner, repoName), - countIssue(condition.copy(state = "open"), filterUser, false, owner -> repoName), - countIssue(condition.copy(state = "closed"), filterUser, false, owner -> repoName), - countIssue(condition, Map.empty, false, 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), false, owner -> repoName)), - countIssueGroupByLabels(owner, repoName, condition, filterUser), + countIssue(condition.copy(state = "open" ), false, owner -> repoName), + countIssue(condition.copy(state = "closed"), false, owner -> repoName), condition, - filter, repository, hasWritePermission(owner, repoName, context.loginAccount)) } diff --git a/src/main/scala/app/LabelsController.scala b/src/main/scala/app/LabelsController.scala index 2ac47fc..f84012d 100644 --- a/src/main/scala/app/LabelsController.scala +++ b/src/main/scala/app/LabelsController.scala @@ -2,51 +2,67 @@ import jp.sf.amateras.scalatra.forms._ import service._ -import util.CollaboratorsAuthenticator +import util.{ReferrerAuthenticator, CollaboratorsAuthenticator} import util.Implicits._ import org.scalatra.i18n.Messages +import org.scalatra.Ok class LabelsController extends LabelsControllerBase - with LabelsService with RepositoryService with AccountService with CollaboratorsAuthenticator + with LabelsService with IssuesService with RepositoryService with AccountService +with ReferrerAuthenticator with CollaboratorsAuthenticator trait LabelsControllerBase extends ControllerBase { - self: LabelsService with RepositoryService with CollaboratorsAuthenticator => + self: LabelsService with IssuesService with RepositoryService + with ReferrerAuthenticator with CollaboratorsAuthenticator => case class LabelForm(labelName: String, color: String) - val newForm = mapping( - "newLabelName" -> trim(label("Label name", text(required, labelName, maxlength(100)))), - "newColor" -> trim(label("Color", text(required, color))) + val labelForm = mapping( + "labelName" -> trim(label("Label name", text(required, labelName, maxlength(100)))), + "labelColor" -> trim(label("Color", text(required, color))) )(LabelForm.apply) - val editForm = mapping( - "editLabelName" -> trim(label("Label name", text(required, labelName, maxlength(100)))), - "editColor" -> trim(label("Color", text(required, color))) - )(LabelForm.apply) - - post("/:owner/:repository/issues/label/new", newForm)(collaboratorsOnly { (form, repository) => - createLabel(repository.owner, repository.name, form.labelName, form.color.substring(1)) - redirect(s"/${repository.owner}/${repository.name}/issues") + get("/:owner/:repository/issues/labels")(referrersOnly { repository => + issues.labels.html.list( + getLabels(repository.owner, repository.name), + countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition()), + repository, + hasWritePermission(repository.owner, repository.name, context.loginAccount)) }) - ajaxGet("/:owner/:repository/issues/label/edit")(collaboratorsOnly { repository => - issues.labels.html.editlist(getLabels(repository.owner, repository.name), repository) + ajaxGet("/:owner/:repository/issues/labels/new")(collaboratorsOnly { repository => + issues.labels.html.edit(None, repository) }) - ajaxGet("/:owner/:repository/issues/label/:labelId/edit")(collaboratorsOnly { repository => + ajaxPost("/:owner/:repository/issues/labels/new", labelForm)(collaboratorsOnly { (form, repository) => + val labelId = createLabel(repository.owner, repository.name, form.labelName, form.color.substring(1)) + issues.labels.html.label( + getLabel(repository.owner, repository.name, labelId).get, + // TODO futility + countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition()), + repository, + hasWritePermission(repository.owner, repository.name, context.loginAccount)) + }) + + ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(collaboratorsOnly { repository => getLabel(repository.owner, repository.name, params("labelId").toInt).map { label => issues.labels.html.edit(Some(label), repository) } getOrElse NotFound() }) - ajaxPost("/:owner/:repository/issues/label/:labelId/edit", editForm)(collaboratorsOnly { (form, repository) => + ajaxPost("/:owner/:repository/issues/labels/:labelId/edit", labelForm)(collaboratorsOnly { (form, repository) => updateLabel(repository.owner, repository.name, params("labelId").toInt, form.labelName, form.color.substring(1)) - issues.labels.html.editlist(getLabels(repository.owner, repository.name), repository) + issues.labels.html.label( + getLabel(repository.owner, repository.name, params("labelId").toInt).get, + // TODO futility + countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition()), + repository, + hasWritePermission(repository.owner, repository.name, context.loginAccount)) }) - ajaxGet("/:owner/:repository/issues/label/:labelId/delete")(collaboratorsOnly { repository => + ajaxPost("/:owner/:repository/issues/labels/:labelId/delete")(collaboratorsOnly { repository => deleteLabel(repository.owner, repository.name, params("labelId").toInt) - issues.labels.html.editlist(getLabels(repository.owner, repository.name), repository) + Ok() }) /** diff --git a/src/main/scala/app/PullRequestsController.scala b/src/main/scala/app/PullRequestsController.scala index 8fd4672..2f9aba5 100644 --- a/src/main/scala/app/PullRequestsController.scala +++ b/src/main/scala/app/PullRequestsController.scala @@ -62,10 +62,6 @@ searchPullRequests(None, repository) }) - get("/:owner/:repository/pulls/:userName")(referrersOnly { repository => - searchPullRequests(Some(params("userName")), repository) - }) - get("/:owner/:repository/pull/:id")(referrersOnly { repository => params("id").toIntOpt.flatMap{ issueId => val owner = repository.owner @@ -453,7 +449,6 @@ private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) = defining(repository.owner, repository.name){ case (owner, repoName) => - val filterUser = userName.map { x => Map("created_by" -> x) } getOrElse Map("all" -> "") val page = IssueSearchCondition.page(request) val sessionKey = Keys.Session.Pulls(owner, repoName) @@ -463,14 +458,15 @@ else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition()) ) - pulls.html.list( - searchIssue(condition, filterUser, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName), - getPullRequestCountGroupByUser(condition.state == "closed", Some(owner), Some(repoName)), - userName, + issues.html.list( + "pulls", + searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName), page, - countIssue(condition.copy(state = "open" ), filterUser, true, owner -> repoName), - countIssue(condition.copy(state = "closed"), filterUser, true, owner -> repoName), - countIssue(condition, Map.empty, true, owner -> repoName), + (getCollaborators(owner, repoName) :+ owner).sorted, + getMilestones(owner, repoName), + getLabels(owner, repoName), + countIssue(condition.copy(state = "open" ), true, owner -> repoName), + countIssue(condition.copy(state = "closed"), true, owner -> repoName), condition, repository, hasWritePermission(owner, repoName, context.loginAccount)) diff --git a/src/main/scala/app/SystemSettingsController.scala b/src/main/scala/app/SystemSettingsController.scala index c81e6d9..27b18fa 100644 --- a/src/main/scala/app/SystemSettingsController.scala +++ b/src/main/scala/app/SystemSettingsController.scala @@ -85,41 +85,55 @@ }) get("/admin/plugins")(adminOnly { - val installedPlugins = plugin.PluginSystem.plugins - val updatablePlugins = getAvailablePlugins(installedPlugins).filter(_.status == "updatable") - admin.plugins.html.installed(installedPlugins, updatablePlugins) + if(enablePluginSystem){ + val installedPlugins = plugin.PluginSystem.plugins + val updatablePlugins = getAvailablePlugins(installedPlugins).filter(_.status == "updatable") + admin.plugins.html.installed(installedPlugins, updatablePlugins) + } else NotFound }) post("/admin/plugins/_update", pluginForm)(adminOnly { form => - deletePlugins(form.pluginIds) - installPlugins(form.pluginIds) - redirect("/admin/plugins") + if(enablePluginSystem){ + deletePlugins(form.pluginIds) + installPlugins(form.pluginIds) + redirect("/admin/plugins") + } else NotFound }) post("/admin/plugins/_delete", pluginForm)(adminOnly { form => - deletePlugins(form.pluginIds) - redirect("/admin/plugins") + if(enablePluginSystem){ + deletePlugins(form.pluginIds) + redirect("/admin/plugins") + } else NotFound }) get("/admin/plugins/available")(adminOnly { - val installedPlugins = plugin.PluginSystem.plugins - val availablePlugins = getAvailablePlugins(installedPlugins).filter(_.status == "available") - admin.plugins.html.available(availablePlugins) + if(enablePluginSystem){ + val installedPlugins = plugin.PluginSystem.plugins + val availablePlugins = getAvailablePlugins(installedPlugins).filter(_.status == "available") + admin.plugins.html.available(availablePlugins) + } else NotFound }) post("/admin/plugins/_install", pluginForm)(adminOnly { form => - installPlugins(form.pluginIds) - redirect("/admin/plugins") + if(enablePluginSystem){ + installPlugins(form.pluginIds) + redirect("/admin/plugins") + } else NotFound }) get("/admin/plugins/console")(adminOnly { - admin.plugins.html.console() + if(enablePluginSystem){ + admin.plugins.html.console() + } else NotFound }) post("/admin/plugins/console")(adminOnly { - val script = request.getParameter("script") - val result = plugin.ScalaPlugin.eval(script) - Ok() + if(enablePluginSystem){ + val script = request.getParameter("script") + val result = plugin.ScalaPlugin.eval(script) + Ok() + } else NotFound }) // TODO Move these methods to PluginSystem or Service? diff --git a/src/main/scala/model/Labels.scala b/src/main/scala/model/Labels.scala index e36746a..47c6a2b 100644 --- a/src/main/scala/model/Labels.scala +++ b/src/main/scala/model/Labels.scala @@ -31,7 +31,7 @@ if(Integer.parseInt(r, 16) + Integer.parseInt(g, 16) + Integer.parseInt(b, 16) > 408){ "000000" } else { - "FFFFFF" + "ffffff" } } } diff --git a/src/main/scala/service/IssuesService.scala b/src/main/scala/service/IssuesService.scala index 292b93a..9e7bfa6 100644 --- a/src/main/scala/service/IssuesService.scala +++ b/src/main/scala/service/IssuesService.scala @@ -43,14 +43,13 @@ * Returns the count of the search result against issues. * * @param condition the search condition - * @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 * @return the count of the search result */ - def countIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean, - repos: (String, String)*)(implicit s: Session): Int = - Query(searchIssueQuery(repos, condition, filterUser, onlyPullRequest).length).first + def countIssue(condition: IssueSearchCondition, onlyPullRequest: Boolean, repos: (String, String)*) + (implicit s: Session): Int = + Query(searchIssueQuery(repos, condition, onlyPullRequest).length).first /** * Returns the Map which contains issue count for each labels. @@ -58,13 +57,12 @@ * @param owner the repository owner * @param repository the repository name * @param condition the search condition - * @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name) * @return the Map which contains issue count for each labels (key is label name, value is issue count) */ - def countIssueGroupByLabels(owner: String, repository: String, condition: IssueSearchCondition, - filterUser: Map[String, String])(implicit s: Session): Map[String, Int] = { + def countIssueGroupByLabels(owner: String, repository: String, + condition: IssueSearchCondition)(implicit s: Session): Map[String, Int] = { - searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), filterUser, false) + searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), false) .innerJoin(IssueLabels).on { (t1, t2) => t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) } @@ -84,15 +82,14 @@ * If the issue does not exist, its repository is not included in the result. * * @param condition the search condition - * @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 repos Tuple of the repository owner and the repository name * @return list which contains issue count for each repository */ def countIssueGroupByRepository( - condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean, + condition: IssueSearchCondition, onlyPullRequest: Boolean, repos: (String, String)*)(implicit s: Session): List[(String, String, Int)] = { - searchIssueQuery(repos, condition.copy(repo = None), filterUser, onlyPullRequest) + searchIssueQuery(repos, condition.copy(repo = None), onlyPullRequest) .groupBy { t => t.userName -> t.repositoryName } @@ -107,19 +104,18 @@ * Returns the search result against issues. * * @param condition the search condition - * @param filterUser the filter user name (key is "all", "assigned", "created_by" or "not_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 pullRequest 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 * @return the search result (list of tuples which contain issue, labels and comment count) */ - def searchIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean, + def searchIssue(condition: IssueSearchCondition, pullRequest: Boolean, offset: Int, limit: Int, repos: (String, String)*) - (implicit s: Session): List[(Issue, List[Label], Int)] = { + (implicit s: Session): List[IssueInfo] = { // get issues and comment count and labels - searchIssueQuery(repos, condition, filterUser, onlyPullRequest) + searchIssueQuery(repos, condition, pullRequest) .innerJoin(IssueOutline).on { (t1, t2) => t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) } .sortBy { case (t1, t2) => (condition.sort match { @@ -136,21 +132,23 @@ .drop(offset).take(limit) .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) } - .map { case (((t1, t2), t3), t4) => - (t1, t2.commentCount, t4.labelId.?, t4.labelName.?, t4.color.?) + .leftJoin (Milestones) .on { case ((((t1, t2), t3), t4), t5) => t1.byMilestone(t5.userName, t5.repositoryName, t5.milestoneId) } + .map { case ((((t1, t2), t3), t4), t5) => + (t1, t2.commentCount, t4.labelId.?, t4.labelName.?, t4.color.?, t5.title.?) } .list .splitWith { (c1, c2) => - c1._1.userName == c2._1.userName && + c1._1.userName == c2._1.userName && c1._1.repositoryName == c2._1.repositoryName && - c1._1.issueId == c2._1.issueId + c1._1.issueId == c2._1.issueId } .map { issues => issues.head match { - case (issue, commentCount, _,_,_) => - (issue, + case (issue, commentCount, _, _, _, milestone) => + IssueInfo(issue, issues.flatMap { t => t._3.map ( Label(issue.userName, issue.repositoryName, _, t._4.get, t._5.get) )} toList, + milestone, commentCount) }} toList } @@ -159,7 +157,7 @@ * Assembles query for conditional issue searching. */ private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition, - filterUser: Map[String, String], onlyPullRequest: Boolean)(implicit s: Session) = + pullRequest: Boolean)(implicit s: Session) = Issues filter { t1 => condition.repo .map { _.split('/') match { case array => Seq(array(0) -> array(1)) } } @@ -169,10 +167,9 @@ (t1.closed === (condition.state == "closed").bind) && (t1.milestoneId === condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) && (t1.milestoneId.? isEmpty, condition.milestoneId == Some(None)) && - (t1.assignedUserName === filterUser("assigned").bind, filterUser.get("assigned").isDefined) && - (t1.openedUserName === filterUser("created_by").bind, filterUser.get("created_by").isDefined) && - (t1.openedUserName =!= filterUser("not_created_by").bind, filterUser.get("not_created_by").isDefined) && - (t1.pullRequest === true.bind, onlyPullRequest) && + (t1.assignedUserName === condition.assigned.get.bind, condition.assigned.isDefined) && + (t1.openedUserName === condition.author.get.bind, condition.author.isDefined) && + (t1.pullRequest === pullRequest.bind) && (IssueLabels filter { t2 => (t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) && (t2.labelId in @@ -337,11 +334,20 @@ case class IssueSearchCondition( labels: Set[String] = Set.empty, milestoneId: Option[Option[Int]] = None, + author: Option[String] = None, + assigned: Option[String] = None, repo: Option[String] = None, state: String = "open", sort: String = "created", direction: String = "desc"){ + def isEmpty: Boolean = { + labels.isEmpty && milestoneId.isEmpty && author.isEmpty && assigned.isEmpty && + state == "open" && sort == "created" && direction == "desc" + } + + def nonEmpty: Boolean = !isEmpty + def toURL: String = "?" + List( if(labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))), @@ -349,6 +355,8 @@ case Some(x) => x.toString case None => "none" })}, + author .map(x => "author=" + urlEncode(x)), + assigned.map(x => "assigned=" + urlEncode(x)), repo.map("for=" + urlEncode(_)), Some("state=" + urlEncode(state)), Some("sort=" + urlEncode(sort)), @@ -370,6 +378,8 @@ case "none" => None case x => x.toIntOpt }, + param(request, "author"), + param(request, "assigned"), param(request, "for"), param(request, "state", Seq("open", "closed")).getOrElse("open"), param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"), @@ -383,4 +393,6 @@ } } + case class IssueInfo(issue: Issue, labels: List[Label], milestone: Option[String], commentCount: Int) + } diff --git a/src/main/scala/service/LabelsService.scala b/src/main/scala/service/LabelsService.scala index 37c3d35..de1dcb8 100644 --- a/src/main/scala/service/LabelsService.scala +++ b/src/main/scala/service/LabelsService.scala @@ -12,8 +12,8 @@ def getLabel(owner: String, repository: String, labelId: Int)(implicit s: Session): Option[Label] = Labels.filter(_.byPrimaryKey(owner, repository, labelId)).firstOption - def createLabel(owner: String, repository: String, labelName: String, color: String)(implicit s: Session): Unit = - Labels insert Label( + def createLabel(owner: String, repository: String, labelName: String, color: String)(implicit s: Session): Int = + Labels returning Labels.map(_.labelId) += Label( userName = owner, repositoryName = repository, labelName = labelName, diff --git a/src/main/scala/service/PullRequestService.scala b/src/main/scala/service/PullRequestService.scala index 2129d07..d5bcb0b 100644 --- a/src/main/scala/service/PullRequestService.scala +++ b/src/main/scala/service/PullRequestService.scala @@ -36,6 +36,24 @@ .list .map { x => PullRequestCount(x._1, x._2) } + def getAllPullRequestCountGroupByUser(closed: Boolean, userName: String)(implicit s: Session): List[PullRequestCount] = + PullRequests + .innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) } + .innerJoin(Repositories).on { case ((t1, t2), t3) => t2.byRepository(t3.userName, t3.repositoryName) } + .filter { case ((t1, t2), t3) => + (t2.closed === closed.bind) && + ( + (t3.isPrivate === false.bind) || + (t3.userName === userName.bind) || + (Collaborators.filter { t4 => t4.byRepository(t3.userName, t3.repositoryName) && (t4.collaboratorName === userName.bind)} exists) + ) + } + .groupBy { case ((t1, t2), t3) => t2.openedUserName } + .map { case (userName, t) => userName -> t.length } + .sortBy(_._2 desc) + .list + .map { x => PullRequestCount(x._1, x._2) } + def createPullRequest(originUserName: String, originRepositoryName: String, issueId: Int, originBranch: String, requestUserName: String, requestRepositoryName: String, requestBranch: String, commitIdFrom: String, commitIdTo: String)(implicit s: Session): Unit = diff --git a/src/main/scala/service/RepositoryService.scala b/src/main/scala/service/RepositoryService.scala index 8e204a8..31e3637 100644 --- a/src/main/scala/service/RepositoryService.scala +++ b/src/main/scala/service/RepositoryService.scala @@ -166,8 +166,19 @@ } } - def getAllRepositories()(implicit s: Session): List[(String, String)] = { - Repositories.sortBy(_.lastActivityDate desc).map{ t => + /** + * Returns the repositories without private repository that user does not have access right. + * Include public repository, private own repository and private but collaborator repository. + * + * @param userName the user name of collaborator + * @return the repository infomation list + */ + def getAllRepositories(userName: String)(implicit s: Session): List[(String, String)] = { + Repositories.filter { t1 => + (t1.isPrivate === false.bind) || + (t1.userName === userName.bind) || + (Collaborators.filter { t2 => t2.byRepository(t1.userName, t1.repositoryName) && (t2.collaboratorName === userName.bind)} exists) + }.sortBy(_.lastActivityDate desc).map{ t => (t.userName, t.repositoryName) }.list } diff --git a/src/main/scala/service/SystemSettingsService.scala b/src/main/scala/service/SystemSettingsService.scala index 2d8f1d3..9fbd78f 100644 --- a/src/main/scala/service/SystemSettingsService.scala +++ b/src/main/scala/service/SystemSettingsService.scala @@ -191,4 +191,7 @@ else value } + // TODO temporary flag + val enablePluginSystem = Option(System.getProperty("enable.plugin")).getOrElse("false").toBoolean + } diff --git a/src/main/scala/servlet/AutoUpdateListener.scala b/src/main/scala/servlet/AutoUpdateListener.scala index 2670181..aff3010 100644 --- a/src/main/scala/servlet/AutoUpdateListener.scala +++ b/src/main/scala/servlet/AutoUpdateListener.scala @@ -11,6 +11,7 @@ import org.eclipse.jgit.api.Git import util.Directory import plugin.PluginUpdateJob +import service.SystemSettingsService object AutoUpdate { @@ -52,6 +53,7 @@ * The history of versions. A head of this sequence is the current BitBucket version. */ val versions = Seq( + new Version(2, 4), new Version(2, 3) { override def update(conn: Connection): Unit = { super.update(conn) @@ -168,18 +170,15 @@ */ class AutoUpdateListener extends ServletContextListener { import org.quartz.impl.StdSchedulerFactory - import org.quartz.JobBuilder._ - import org.quartz.TriggerBuilder._ - import org.quartz.SimpleScheduleBuilder._ import AutoUpdate._ private val logger = LoggerFactory.getLogger(classOf[AutoUpdateListener]) private val scheduler = StdSchedulerFactory.getDefaultScheduler override def contextInitialized(event: ServletContextEvent): Unit = { - val datadir = event.getServletContext.getInitParameter("gitbucket.home") - if(datadir != null){ - System.setProperty("gitbucket.home", datadir) + val dataDir = event.getServletContext.getInitParameter("gitbucket.home") + if(dataDir != null){ + System.setProperty("gitbucket.home", dataDir) } org.h2.Driver.load() @@ -210,21 +209,23 @@ logger.debug("End schema update") } - getDatabase(context).withSession { implicit session => - logger.debug("Starting plugin system...") - try { - plugin.PluginSystem.init() + if(SystemSettingsService.enablePluginSystem){ + getDatabase(context).withSession { implicit session => + logger.debug("Starting plugin system...") + try { + plugin.PluginSystem.init() - scheduler.start() - PluginUpdateJob.schedule(scheduler) - logger.debug("PluginUpdateJob is started.") + scheduler.start() + PluginUpdateJob.schedule(scheduler) + logger.debug("PluginUpdateJob is started.") - logger.debug("Plugin system is initialized.") - } catch { - case ex: Throwable => { - logger.error("Failed to initialize plugin system", ex) - ex.printStackTrace() - throw ex + logger.debug("Plugin system is initialized.") + } catch { + case ex: Throwable => { + logger.error("Failed to initialize plugin system", ex) + ex.printStackTrace() + throw ex + } } } } diff --git a/src/main/scala/servlet/GitRepositoryServlet.scala b/src/main/scala/servlet/GitRepositoryServlet.scala index 012c6ea..df55d46 100644 --- a/src/main/scala/servlet/GitRepositoryServlet.scala +++ b/src/main/scala/servlet/GitRepositoryServlet.scala @@ -134,8 +134,8 @@ // Retrieve all issue count in the repository val issueCount = - countIssue(IssueSearchCondition(state = "open"), Map.empty, false, owner -> repository) + - countIssue(IssueSearchCondition(state = "closed"), Map.empty, false, owner -> repository) + countIssue(IssueSearchCondition(state = "open"), false, owner -> repository) + + countIssue(IssueSearchCondition(state = "closed"), false, owner -> repository) // Extract new commit and apply issue comment val defaultBranch = getRepository(owner, repository, baseUrl).get.repository.defaultBranch diff --git a/src/main/scala/view/Markdown.scala b/src/main/scala/view/Markdown.scala index d06dc1f..08c3dd4 100644 --- a/src/main/scala/view/Markdown.scala +++ b/src/main/scala/view/Markdown.scala @@ -97,9 +97,10 @@ Map[String, VerbatimSerializer](VerbatimSerializer.DEFAULT -> new GitBucketVerbatimSerializer).asJava ) with LinkConverter with RequestCache { - override protected def printImageTag(imageNode: SuperNode, url: String): Unit = - printer.print("") - .print("\"").printEncoded(printChildrenToString(imageNode)).print("\"/") + override protected def printImageTag(imageNode: SuperNode, url: String): Unit = { + printer.print("") + .print("\"").printEncoded(printChildrenToString(imageNode)).print("\"/") + } override protected def printLink(rendering: LinkRenderer.Rendering): Unit = { printer.print('<').print('a') @@ -110,15 +111,20 @@ printer.print('>').print(rendering.text).print("") } - private def fixUrl(url: String): String = { - if(!enableWikiLink){ - if(url.startsWith("http://") || url.startsWith("https://") || url.startsWith("#") || url.startsWith("/") || - context.currentPath.contains("/blob/")){ - url + private def fixUrl(url: String, isImage: Boolean = false): String = { + if(url.startsWith("http://") || url.startsWith("https://") || url.startsWith("#") || url.startsWith("/")){ + url + } else if(!enableWikiLink){ + if(context.currentPath.contains("/blob/")){ + url + (if(isImage) "?raw=true" else "") + } else if(context.currentPath.contains("/tree/")){ + val paths = context.currentPath.split("/") + val branch = if(paths.length > 3) paths.drop(4).mkString("/") else repository.repository.defaultBranch + repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "") } else { val paths = context.currentPath.split("/") val branch = if(paths.length > 3) paths.last else repository.repository.defaultBranch - repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "") } } else { repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/_blob/" + url diff --git a/src/main/twirl/admin/menu.scala.html b/src/main/twirl/admin/menu.scala.html index 25cda19..1e48858 100644 --- a/src/main/twirl/admin/menu.scala.html +++ b/src/main/twirl/admin/menu.scala.html @@ -11,9 +11,11 @@ System Settings - - Plugins - + @if(service.SystemSettingsService.enablePluginSystem){ + + Plugins + + }
  • H2 Console
  • diff --git a/src/main/twirl/dashboard/issueslist.scala.html b/src/main/twirl/dashboard/issueslist.scala.html new file mode 100644 index 0000000..4f27ea8 --- /dev/null +++ b/src/main/twirl/dashboard/issueslist.scala.html @@ -0,0 +1,184 @@ +@(issues: List[service.IssuesService.IssueInfo], + page: Int, + openCount: Int, + closedCount: Int, + condition: service.IssuesService.IssueSearchCondition, + collaborators: List[String] = Nil, + milestones: List[model.Milestone] = Nil, + labels: List[model.Label] = Nil, + repository: Option[service.RepositoryService.RepositoryInfo] = None, + hasWritePermission: Boolean = false)(implicit context: app.Context) +@import context._ +@import view.helpers._ +@import service.IssuesService.IssueInfo +
    + @if(condition.labels.nonEmpty || condition.milestoneId.isDefined){ + + Clear milestone and label filters + + } + @if(condition.repo.isDefined){ + + Clear filter on @condition.repo + + } +
    + @helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), service.IssuesService.IssueLimit, 7, condition.toURL) +
    + + @helper.html.dropdown( + value = (condition.sort, condition.direction) match { + case ("created" , "desc") => "Newest" + case ("created" , "asc" ) => "Oldest" + case ("comments", "desc") => "Most commented" + case ("comments", "asc" ) => "Least commented" + case ("updated" , "desc") => "Recently updated" + case ("updated" , "asc" ) => "Least recently updated" + }, + prefix = "Sort", + mini = false + ){ +
  • + + @helper.html.checkicon(condition.sort == "created" && condition.direction == "desc") Newest + +
  • +
  • + + @helper.html.checkicon(condition.sort == "created" && condition.direction == "asc") Oldest + +
  • +
  • + + @helper.html.checkicon(condition.sort == "comments" && condition.direction == "desc") Most commented + +
  • +
  • + + @helper.html.checkicon(condition.sort == "comments" && condition.direction == "asc") Least commented + +
  • +
  • + + @helper.html.checkicon(condition.sort == "updated" && condition.direction == "desc") Recently updated + +
  • +
  • + + @helper.html.checkicon(condition.sort == "updated" && condition.direction == "asc") Least recently updated + +
  • + } + + @if(issues.isEmpty){ + + + + } else { + @if(hasWritePermission){ + + + + } + } + @issues.map { case IssueInfo(issue, labels, milestone, commentCount) => + + + + } +
    + No issues to show. + @if(condition.labels.nonEmpty || condition.milestoneId.isDefined){ + Clear active filters. + } else { + @if(repository.isDefined){ + Create a new issue. + } + } +
    +
    + +
    + @helper.html.dropdown("Label") { + @labels.map { label => +
  • + + +   + @label.labelName + +
  • + } + } + @helper.html.dropdown("Assignee") { +
  • Clear assignee
  • + @collaborators.map { collaborator => +
  • @avatar(collaborator, 20) @collaborator
  • + } + } + @helper.html.dropdown("Milestone") { +
  • Clear this milestone
  • + @milestones.map { milestone => +
  • + + @milestone.title +
    + @milestone.dueDate.map { dueDate => + @if(isPast(dueDate)){ + Due by @date(dueDate) + } else { + Due by @date(dueDate) + } + }.getOrElse { + No due date + } +
    +
    +
  • + } + } +
    + @if(hasWritePermission){ + + } +
    +
    + @helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), service.IssuesService.IssueLimit, 10, condition.toURL) +
    +
    + diff --git a/src/main/twirl/dashboard/pullslist.scala.html b/src/main/twirl/dashboard/pullslist.scala.html new file mode 100644 index 0000000..46343b4 --- /dev/null +++ b/src/main/twirl/dashboard/pullslist.scala.html @@ -0,0 +1,101 @@ +@(issues: List[service.IssuesService.IssueInfo], + page: Int, + openCount: Int, + closedCount: Int, + condition: service.IssuesService.IssueSearchCondition, + repository: Option[service.RepositoryService.RepositoryInfo], + hasWritePermission: Boolean)(implicit context: app.Context) +@import context._ +@import view.helpers._ +@import service.IssuesService.IssueInfo +
    + @repository.map { repository => + @if(hasWritePermission){ +
    + @helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), service.PullRequestService.PullRequestLimit, 7, condition.toURL) + New pull request +
    + } + } + + @helper.html.dropdown( + value = (condition.sort, condition.direction) match { + case ("created" , "desc") => "Newest" + case ("created" , "asc" ) => "Oldest" + case ("comments", "desc") => "Most commented" + case ("comments", "asc" ) => "Least commented" + case ("updated" , "desc") => "Recently updated" + case ("updated" , "asc" ) => "Least recently updated" + }, + prefix = "Sort", + mini = false + ){ +
  • + + @helper.html.checkicon(condition.sort == "created" && condition.direction == "desc") Newest + +
  • +
  • + + @helper.html.checkicon(condition.sort == "created" && condition.direction == "asc") Oldest + +
  • +
  • + + @helper.html.checkicon(condition.sort == "comments" && condition.direction == "desc") Most commented + +
  • +
  • + + @helper.html.checkicon(condition.sort == "comments" && condition.direction == "asc") Least commented + +
  • +
  • + + @helper.html.checkicon(condition.sort == "updated" && condition.direction == "desc") Recently updated + +
  • +
  • + + @helper.html.checkicon(condition.sort == "updated" && condition.direction == "asc") Least recently updated + +
  • + } + + @if(issues.isEmpty){ + + + + } + @issues.map { case IssueInfo(issue, labels, milestone, commentCount) => + + + + } +
    + No pull requests to show. +
    + + @issue.title + #@issue.issueId +
    + @issue.content.map { content => + @cut(content, 90) + }.getOrElse { + No description available + } +
    +
    + @avatarLink(issue.openedUserName, 20) by @user(issue.openedUserName, styleClass="username") @datetime(issue.registeredDate)  + @if(commentCount > 0){ + @commentCount @plural(commentCount, "comment") + } +
    +
    +
    + @helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), service.PullRequestService.PullRequestLimit, 10, condition.toURL) +
    +
    \ No newline at end of file diff --git a/src/main/twirl/helper/diff.scala.html b/src/main/twirl/helper/diff.scala.html index 598841c..8d0cd88 100644 --- a/src/main/twirl/helper/diff.scala.html +++ b/src/main/twirl/helper/diff.scala.html @@ -41,7 +41,7 @@ - - + + diff --git a/src/main/twirl/issues/labels/list.scala.html b/src/main/twirl/issues/labels/list.scala.html new file mode 100644 index 0000000..135f77c --- /dev/null +++ b/src/main/twirl/issues/labels/list.scala.html @@ -0,0 +1,67 @@ +@(labels: List[model.Label], + counts: Map[String, Int], + repository: service.RepositoryService.RepositoryInfo, + hasWritePermission: Boolean)(implicit context: app.Context) +@import context._ +@import view.helpers._ +@html.main(s"Labels - ${repository.owner}/${repository.name}"){ + @html.menu("issues", repository){ + @issues.html.tab("labels", hasWritePermission, repository) +   +
    + @if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){ @diff.oldPath -> @diff.newPath @if(newCommitId.isDefined){ @@ -69,7 +69,7 @@
    + @if(diff.newContent != None || diff.oldContent != None){
    diff --git a/src/main/twirl/helper/dropdown.scala.html b/src/main/twirl/helper/dropdown.scala.html index 70d1dce..20e2f4a 100644 --- a/src/main/twirl/helper/dropdown.scala.html +++ b/src/main/twirl/helper/dropdown.scala.html @@ -1,6 +1,13 @@ -@(value: String = "", prefix: String = "", mini: Boolean = true, style: String = "", right: Boolean = false)(body: Html) -
    -
    +
    + +
    +
    + @counts.get(label.labelName).getOrElse(0) open issues +
    +
    + @if(hasWritePermission){ +
    +
    + Edit +    + Delete +
    +
    + } +
    +
    + + + + + + + @labels.map { label => + @_root_.issues.labels.html.label(label, counts, repository, hasWritePermission) + } + @if(labels.isEmpty){ + + + + } +
    + @labels.size labels +
    + No labels to show. + @if(hasWritePermission){ + Create a new label. + } +
    + } +} + diff --git a/src/main/twirl/issues/list.scala.html b/src/main/twirl/issues/list.scala.html index 0e41fba..cd00ac3 100644 --- a/src/main/twirl/issues/list.scala.html +++ b/src/main/twirl/issues/list.scala.html @@ -1,143 +1,25 @@ -@(issues: List[(model.Issue, List[model.Label], Int)], +@(target: String, + issues: List[service.IssuesService.IssueInfo], page: Int, collaborators: List[String], milestones: List[model.Milestone], labels: List[model.Label], openCount: Int, closedCount: Int, - allCount: Int, - assignedCount: Option[Int], - createdByCount: Option[Int], - labelCounts: Map[String, Int], condition: service.IssuesService.IssueSearchCondition, - filter: String, repository: service.RepositoryService.RepositoryInfo, hasWritePermission: Boolean)(implicit context: app.Context) @import context._ @import view.helpers._ -@html.main(s"Issues - ${repository.owner}/${repository.name}", Some(repository)){ - @html.menu("issues", repository){ - @tab("issues", false, repository) -
    -
    - -
    - @if(condition.milestoneId.isEmpty){ - No milestone selected - } else { - @if(condition.milestoneId.get.isEmpty){ - Issues with no milestone - } else { - Milestone: @milestones.find(_.milestoneId == condition.milestoneId.get.get).map(_.title) - } - } - @helper.html.dropdown() { - @if(condition.milestoneId.isDefined){ -
  • - - Clear milestone filter - -
  • - } -
  • - - @helper.html.checkicon(condition.milestoneId == Some(None)) Issues with no milestone - -
  • - @milestones.filter(_.closedDate.isEmpty).map { milestone => -
  • - - @helper.html.checkicon(condition.milestoneId == Some(Some(milestone.milestoneId))) @milestone.title -
    - @milestone.dueDate.map { dueDate => - @if(isPast(dueDate)){ - Due in @date(dueDate) - } else { - Due in @date(dueDate) - } - }.getOrElse { - No due date - } -
    -
    -
  • - } - } - @if(condition.milestoneId.isDefined && condition.milestoneId.get.isDefined){ - @milestones.find(_.milestoneId == condition.milestoneId.get.get).map { milestone => -
    - @_root_.issues.milestones.html.progress(openCount + closedCount, closedCount, false) -
    - @openCount open issues - @if(milestone.closedDate.isDefined){ - @milestone.closedDate.map { closedDate => - Closed in @date(closedDate) - } - } else { - @milestone.dueDate.map { dueDate => - @if(isPast(dueDate)){ - Due in @date(dueDate) - } else { - Due in @date(dueDate) - } - } - } - } - } -
    - Labels - - @if(hasWritePermission){ -
    - -
    - New label - @_root_.issues.labels.html.edit(None, repository) - } -
    - @***** show issue list *****@ - @listparts(issues, page, openCount, closedCount, condition, collaborators, milestones, labels, Some(repository), hasWritePermission) -
    +@html.main((if(target == "issues") "Issues" else "Pull requests") + s" - ${repository.owner}/${repository.name}", Some(repository)){ + @html.menu(target, repository){ + @tab(target, true, repository) + @listparts(target, issues, page, openCount, closedCount, condition, collaborators, milestones, labels, Some(repository), hasWritePermission) @if(hasWritePermission){
    +
    } } @@ -145,20 +27,34 @@ @if(hasWritePermission){