(refs #3)Issue search is temporary available.

This commit is contained in:
takezoe
2013-07-17 16:47:43 +09:00
parent d06a986293
commit 512e59193d
4 changed files with 95 additions and 12 deletions

View File

@@ -4,20 +4,18 @@ import util._
import util.Directory._ import util.Directory._
import service._ import service._
import jp.sf.amateras.scalatra.forms._ import jp.sf.amateras.scalatra.forms._
import org.eclipse.jgit.api.Git
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.eclipse.jgit.treewalk.TreeWalk import org.eclipse.jgit.treewalk.TreeWalk
import org.eclipse.jgit.revwalk.RevWalk import org.eclipse.jgit.revwalk.RevWalk
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
import org.eclipse.jgit.lib.FileMode import org.eclipse.jgit.lib.FileMode
import java.util.regex.Pattern
class IndexController extends IndexControllerBase class IndexController extends IndexControllerBase
with RepositoryService with AccountService with SystemSettingsService with ActivityService with RepositoryService with AccountService with SystemSettingsService with ActivityService with IssuesService
with ReferrerAuthenticator with ReferrerAuthenticator
trait IndexControllerBase extends ControllerBase { self: RepositoryService trait IndexControllerBase extends ControllerBase { self: RepositoryService
with SystemSettingsService with ActivityService with SystemSettingsService with ActivityService with IssuesService
with ReferrerAuthenticator => with ReferrerAuthenticator =>
val searchForm = mapping( val searchForm = mapping(
@@ -44,21 +42,36 @@ trait IndexControllerBase extends ControllerBase { self: RepositoryService
// TODO readable only // TODO readable only
get("/:owner/:repository/search")(referrersOnly { repository => get("/:owner/:repository/search")(referrersOnly { repository =>
val owner = params("owner")
val name = params("repository")
val query = params("q") val query = params("q")
val target = params.getOrElse("type", "Code") val target = params.getOrElse("type", "Code")
target.toLowerCase match { target.toLowerCase match {
case "issue" => { case "issue" => {
// TODO search issue // TODO search issue
val lowerQueries = query.toLowerCase.split("[ \\t ]+")
search.html.issues(queryIssues(repository.owner, repository.name, query).map { case (issue, commentCount, content) =>
val lowerText = content.toLowerCase
val indices = lowerQueries.map { lowerQuery =>
lowerText.indexOf(lowerQuery)
}
val highlightText = if(!indices.exists(_ < 0)){
val lineNumber = content.substring(0, indices.min).split("\n").size - 1
StringUtil.escapeHtml(content.split("\n").drop(lineNumber).take(5).mkString("\n"))
.replaceAll("(?i)(" + lowerQueries.map("\\Q" + _ + "\\E").mkString("|") + ")",
"<span style=\"background-color: yellow;\">$1</span>")
} else content.split("\n").take(5).mkString("\n")
IssueSearchResult(issue.issueId, issue.title, issue.openedUserName, issue.registeredDate, commentCount, highlightText)
}, query, repository)
} }
case _ => { case _ => {
JGitUtil.withGit(getRepositoryDir(owner, name)){ git => JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
val revWalk = new RevWalk(git.getRepository) val revWalk = new RevWalk(git.getRepository)
val objectId = git.getRepository.resolve("HEAD") val objectId = git.getRepository.resolve("HEAD")
val revCommit = revWalk.parseCommit(objectId) val revCommit = revWalk.parseCommit(objectId)
val treeWalk = new TreeWalk(git.getRepository) val treeWalk = new TreeWalk(git.getRepository)
treeWalk.setRecursive(true) treeWalk.setRecursive(true)
treeWalk.addTree(revCommit.getTree) treeWalk.addTree(revCommit.getTree)
@@ -79,7 +92,7 @@ trait IndexControllerBase extends ControllerBase { self: RepositoryService
.replaceAll("(?i)(" + lowerQueries.map("\\Q" + _ + "\\E").mkString("|") + ")", .replaceAll("(?i)(" + lowerQueries.map("\\Q" + _ + "\\E").mkString("|") + ")",
"<span style=\"background-color: yellow;\">$1</span>") "<span style=\"background-color: yellow;\">$1</span>")
list.append((treeWalk.getPathString, highlightText)) list.append((treeWalk.getPathString, highlightText))
} }
} }
} }
} }
@@ -107,4 +120,8 @@ trait IndexControllerBase extends ControllerBase { self: RepositoryService
} }
case class IssueSearchResult(issueId: Int, title: String, openedUserName: String, registeredDate: java.util.Date,
commentCount: Int, highlightText: String)
case class FileSearchResult(path: String, lastModified: java.util.Date, highlightText: String) case class FileSearchResult(path: String, lastModified: java.util.Date, highlightText: String)

View File

@@ -8,6 +8,7 @@ import Q.interpolation
import model._ import model._
import util.StringUtil._ import util.StringUtil._
import util.Implicits._ import util.Implicits._
import scala.concurrent.duration.durationToPair
trait IssuesService { trait IssuesService {
import IssuesService._ import IssuesService._
@@ -234,6 +235,44 @@ trait IssuesService {
} }
.update (closed, currentDate) .update (closed, currentDate)
def queryIssues(owner: String, repository: String, query: String): List[(Issue, Int, String)] = {
val lowerQueries = query.toLowerCase.split("[ \\t ]+")
val issues = Query(Issues).filter { t =>
lowerQueries.map { query =>
(t.title.toLowerCase startsWith query) || (t.content.toLowerCase startsWith query)
} .reduceLeft { (a, b) =>
a && b
}
}.map { t => (t, 0, t.content) }
val comments = Query(IssueComments).innerJoin(Issues).on { case (t1, t2) =>
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
}.filter { case (t1, t2) =>
lowerQueries.map { query =>
t1.content.toLowerCase startsWith query
}.reduceLeft { (a, b) =>
a && b
}
}.map { case (t1, t2) => (t2, t1.commentId, t1.content) }
def getCommentCount(issue: Issue): Int = {
Query(IssueComments)
.filter(_.byIssue(issue.userName, issue.repositoryName, issue.issueId))
.map(_.issueId)
.list.length
}
issues.union(comments).sortBy { case (issue, commentId, _) =>
issue.issueId ~ commentId
}.list.splitWith { case ((issue1, _, _), (issue2, _, _)) =>
issue1.issueId == issue2.issueId
}.map { result =>
val (issue, _, content) = result.head
(issue, getCommentCount(issue) , content)
}.toList
}
} }
object IssuesService { object IssuesService {
@@ -279,4 +318,5 @@ object IssuesService {
param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"), param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"),
param(request, "direction", Seq("asc", "desc")).getOrElse("desc")) param(request, "direction", Seq("asc", "desc")).getOrElse("desc"))
} }
} }

View File

@@ -10,7 +10,7 @@
} }
@files.map { file => @files.map { file =>
<div> <div>
<div><a href="@url(repository)/blob/@repository.repository.defaultBranch/@file.path">@file.path</a></div> <h5><a href="@url(repository)/blob/@repository.repository.defaultBranch/@file.path">@file.path</a></h5>
<div class="muted">@datetime(file.lastModified)</div> <div class="muted">@datetime(file.lastModified)</div>
<pre>@Html(file.highlightText)</pre> <pre>@Html(file.highlightText)</pre>
</div> </div>

View File

@@ -0,0 +1,26 @@
@(issues: List[app.IssueSearchResult], query: String, repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
@import context._
@import view.helpers._
@html.main("Search Results", Some(repository)){
@menu("issue", query, repository){
@if(issues.isEmpty){
<h4>We couldn't find any code matching '@query'</h4>
} else {
<h4>We've found @issues.size code @plural(issues.size, "result")</h4>
}
@issues.map { issue =>
<div class="block">
<div class="pull-right muted">#@issue.issueId</div>
<h4><a href="@url(repository)/issues/@issue.issueId">@issue.title</a></h4>
<pre>@Html(issue.highlightText)</pre>
<div class="small muted">
Opened by <a href="@url(issue.openedUserName)" class="username">@issue.openedUserName</a>
at @datetime(issue.registeredDate)
@if(issue.commentCount > 0){
&nbsp;&nbsp;<i class="icon-comment"></i><strong>@issue.commentCount</strong> @plural(issue.commentCount, "comment")
}
</div>
</div>
}
}
}