mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-10 15:35:59 +01:00
show commit status on pull-request-list view
This commit is contained in:
@@ -46,4 +46,5 @@ trait CommitStatusService {
|
||||
|
||||
protected def byCommitStatues(userName: String, repositoryName: String, sha: String)(implicit s: Session) =
|
||||
CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha) ).sortBy(_.updatedDate desc)
|
||||
|
||||
}
|
||||
@@ -84,6 +84,47 @@ trait IssuesService {
|
||||
.toMap
|
||||
}
|
||||
|
||||
def getCommitStatues(issueList:Seq[(String, String, Int)])(implicit s: Session) :Map[(String, String, Int), CommitStatusInfo] ={
|
||||
if(issueList.isEmpty){
|
||||
Map.empty
|
||||
}else{
|
||||
import scala.slick.jdbc._
|
||||
val issueIdQuery = issueList.map(i => "(PR.USER_NAME=? AND PR.REPOSITORY_NAME=? AND PR.ISSUE_ID=?)").mkString(" OR ")
|
||||
implicit val qset = SetParameter[Seq[(String, String, Int)]] {
|
||||
case (seq, pp) =>
|
||||
for (a <- seq) {
|
||||
pp.setString(a._1)
|
||||
pp.setString(a._2)
|
||||
pp.setInt(a._3)
|
||||
}
|
||||
}
|
||||
import model.Profile.commitStateColumnType
|
||||
val query = Q.query[Seq[(String, String, Int)], (String, String, Int, Int, Int, Option[String], Option[model.CommitState], Option[String], Option[String])](s"""
|
||||
SELECT SUMM.USER_NAME, SUMM.REPOSITORY_NAME, SUMM.ISSUE_ID, CS_ALL, CS_SUCCESS
|
||||
, CSD.CONTEXT, CSD.STATE, CSD.TARGET_URL, CSD.DESCRIPTION
|
||||
FROM (SELECT
|
||||
PR.USER_NAME
|
||||
, PR.REPOSITORY_NAME
|
||||
, PR.ISSUE_ID
|
||||
, COUNT(CS.STATE) AS CS_ALL
|
||||
, SUM(CS.STATE='success') AS CS_SUCCESS
|
||||
, PR.COMMIT_ID_TO AS COMMIT_ID
|
||||
FROM PULL_REQUEST PR
|
||||
JOIN COMMIT_STATUS CS
|
||||
ON PR.USER_NAME=CS.USER_NAME
|
||||
AND PR.REPOSITORY_NAME=CS.REPOSITORY_NAME
|
||||
AND PR.COMMIT_ID_TO=CS.COMMIT_ID
|
||||
WHERE $issueIdQuery
|
||||
GROUP BY PR.USER_NAME, PR.REPOSITORY_NAME, PR.ISSUE_ID) as SUMM
|
||||
LEFT OUTER JOIN COMMIT_STATUS CSD
|
||||
ON SUMM.CS_ALL = 1 AND SUMM.COMMIT_ID = CSD.COMMIT_ID""");
|
||||
query(issueList).list.map{
|
||||
case(userName, repositoryName, issueId, count, successCount, context, state, targetUrl, description) =>
|
||||
(userName, repositoryName, issueId) -> CommitStatusInfo(count, successCount, context, state, targetUrl, description)
|
||||
}.toMap
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the search result against issues.
|
||||
*
|
||||
@@ -96,9 +137,8 @@ trait IssuesService {
|
||||
*/
|
||||
def searchIssue(condition: IssueSearchCondition, pullRequest: Boolean, offset: Int, limit: Int, repos: (String, String)*)
|
||||
(implicit s: Session): List[IssueInfo] = {
|
||||
|
||||
// get issues and comment count and labels
|
||||
searchIssueQueryBase(condition, pullRequest, offset, limit, repos)
|
||||
val result = searchIssueQueryBase(condition, pullRequest, offset, limit, repos)
|
||||
.leftJoin (IssueLabels) .on { case ((t1, t2), t3) => t1.byIssue(t3.userName, t3.repositoryName, t3.issueId) }
|
||||
.leftJoin (Labels) .on { case (((t1, t2), t3), t4) => t3.byLabel(t4.userName, t4.repositoryName, t4.labelId) }
|
||||
.leftJoin (Milestones) .on { case ((((t1, t2), t3), t4), t5) => t1.byMilestone(t5.userName, t5.repositoryName, t5.milestoneId) }
|
||||
@@ -111,14 +151,17 @@ trait IssuesService {
|
||||
c1._1.repositoryName == c2._1.repositoryName &&
|
||||
c1._1.issueId == c2._1.issueId
|
||||
}
|
||||
.map { issues => issues.head match {
|
||||
val status = getCommitStatues(result.map(_.head._1).map(is => (is.userName, is.repositoryName, is.issueId)))
|
||||
|
||||
result.map { issues => issues.head match {
|
||||
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)
|
||||
commentCount,
|
||||
status.get(issue.userName, issue.repositoryName, issue.issueId))
|
||||
}} toList
|
||||
}
|
||||
|
||||
@@ -490,6 +533,8 @@ object IssuesService {
|
||||
}
|
||||
}
|
||||
|
||||
case class IssueInfo(issue: Issue, labels: List[Label], milestone: Option[String], commentCount: Int)
|
||||
case class CommitStatusInfo(count: Int, successCount: Int, context: Option[String], state: Option[model.CommitState], targetUrl: Option[String], description: Option[String])
|
||||
|
||||
case class IssueInfo(issue: Issue, labels: List[Label], milestone: Option[String], commentCount: Int, status:Option[CommitStatusInfo])
|
||||
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
@dashboard.html.header(openCount, closedCount, condition, groups)
|
||||
</th>
|
||||
</tr>
|
||||
@issues.map { case IssueInfo(issue, labels, milestone, commentCount) =>
|
||||
@issues.map { case IssueInfo(issue, labels, milestone, commentCount, commitStatus) =>
|
||||
<tr>
|
||||
<td style="padding-top: 15px; padding-bottom: 15px;">
|
||||
@if(issue.isPullRequest){
|
||||
@@ -28,6 +28,7 @@
|
||||
} else {
|
||||
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-title">@issue.title</a>
|
||||
}
|
||||
@_root_.issues.html.commitstatus(issue, commitStatus)
|
||||
@labels.map { label =>
|
||||
<span class="label-color small" style="background-color: #@label.color; color: #@label.fontColor; padding-left: 4px; padding-right: 4px">@label.labelName</span>
|
||||
}
|
||||
|
||||
19
src/main/twirl/issues/commitstatus.scala.html
Normal file
19
src/main/twirl/issues/commitstatus.scala.html
Normal file
@@ -0,0 +1,19 @@
|
||||
@(issue: model.Issue, statusInfo: Option[service.IssuesService.CommitStatusInfo])(implicit context: app.Context)
|
||||
@import view.helpers._
|
||||
@statusInfo.map{ status =>
|
||||
@if(status.count==1 && status.state.isDefined){
|
||||
@if(status.targetUrl.isDefined){
|
||||
<a href="@status.targetUrl.get" class="text-@status.state.get.name" data-toggle="tooltip" title="@status.state.get.name : @status.description.getOrElse(status.context.get)">@commitStateIcon(status.state.get)</a>
|
||||
}else{
|
||||
<span class="text-@status.state.get.name">@commitStateIcon(status.state.get)
|
||||
}
|
||||
}else{
|
||||
@defining(status.count==status.successCount){ isSuccess =>
|
||||
<a href="@context.path/@issue.userName/@issue.repositoryName/@issue.issueId" class="@if(isSuccess){ text-success }else{ text-error }" data-toggle="tooltip" title="@status.successCount / @status.count checks OK">@if(isSuccess){
|
||||
✔
|
||||
}else{
|
||||
×
|
||||
}</a>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,7 +170,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@issues.map { case IssueInfo(issue, labels, milestone, commentCount) =>
|
||||
@issues.map { case IssueInfo(issue, labels, milestone, commentCount, commitStatus) =>
|
||||
<tr>
|
||||
<td style="padding-top: 15px; padding-bottom: 15px;">
|
||||
@if(hasWritePermission){
|
||||
@@ -185,6 +185,7 @@
|
||||
} else {
|
||||
<a href="@path/@issue.userName/@issue.repositoryName/pull/@issue.issueId" class="issue-title">@issue.title</a>
|
||||
}
|
||||
@commitstatus(issue, commitStatus)
|
||||
@labels.map { label =>
|
||||
<span class="label-color small" style="background-color: #@label.color; color: #@label.fontColor; padding-left: 4px; padding-right: 4px">@label.labelName</span>
|
||||
}
|
||||
|
||||
47
src/test/scala/service/IssuesServiceSpec.scala
Normal file
47
src/test/scala/service/IssuesServiceSpec.scala
Normal file
@@ -0,0 +1,47 @@
|
||||
package service
|
||||
|
||||
import org.specs2.mutable.Specification
|
||||
import java.util.Date
|
||||
import model._
|
||||
import service.IssuesService._
|
||||
|
||||
class IssuesServiceSpec extends Specification with ServiceSpecBase {
|
||||
"IssuesService" should {
|
||||
"getCommitStatues" in { withTestDB { implicit session =>
|
||||
val user1 = generateNewUserWithDBRepository("user1","repo1")
|
||||
|
||||
def getCommitStatues = dummyService.getCommitStatues(List(("user1","repo1",1),("user1","repo1",2)))
|
||||
|
||||
getCommitStatues must_== Map.empty
|
||||
|
||||
val now = new java.util.Date()
|
||||
val issueId = generateNewIssue("user1","repo1")
|
||||
issueId must_== 1
|
||||
|
||||
getCommitStatues must_== Map.empty
|
||||
|
||||
val cs = dummyService.createCommitStatus("user1","repo1","shasha", "default", CommitState.SUCCESS, Some("http://exmple.com/ci"), Some("exampleService"), now, user1)
|
||||
|
||||
getCommitStatues must_== Map.empty
|
||||
|
||||
val (is2, pr2) = generateNewPullRequest("user1/repo1/master","user1/repo1/feature1")
|
||||
pr2.issueId must_== 2
|
||||
|
||||
// if there are no statuses, state is none
|
||||
getCommitStatues must_== Map.empty
|
||||
|
||||
// if there is a status, state is that
|
||||
val cs2 = dummyService.createCommitStatus("user1","repo1","feature1", "default", CommitState.SUCCESS, Some("http://exmple.com/ci"), Some("exampleService"), now, user1)
|
||||
getCommitStatues must_== Map(("user1","repo1",2) -> CommitStatusInfo(1,1,Some("default"),Some(CommitState.SUCCESS),Some("http://exmple.com/ci"),Some("exampleService")))
|
||||
|
||||
// if there are two statuses, state is none
|
||||
val cs3 = dummyService.createCommitStatus("user1","repo1","feature1", "pend", CommitState.PENDING, Some("http://exmple.com/ci"), Some("exampleService"), now, user1)
|
||||
getCommitStatues must_== Map(("user1","repo1",2) -> CommitStatusInfo(2,1,None,None,None,None))
|
||||
|
||||
// get only statuses in query issues
|
||||
val (is3, pr3) = generateNewPullRequest("user1/repo1/master","user1/repo1/feature3")
|
||||
val cs4 = dummyService.createCommitStatus("user1","repo1","feature3", "none", CommitState.PENDING, None, None, now, user1)
|
||||
getCommitStatues must_== Map(("user1","repo1",2) -> CommitStatusInfo(2,1,None,None,None,None))
|
||||
} }
|
||||
}
|
||||
}
|
||||
@@ -30,26 +30,31 @@ trait ServiceSpecBase {
|
||||
AccountService.getAccountByUserName(name).get
|
||||
}
|
||||
|
||||
lazy val dummyService = new RepositoryService with AccountService with IssuesService with PullRequestService (){}
|
||||
lazy val dummyService = new RepositoryService with AccountService with IssuesService with PullRequestService
|
||||
with CommitStatusService (){}
|
||||
|
||||
def generateNewUserWithDBRepository(userName:String, repositoryName:String)(implicit s:Session):Account = {
|
||||
val ac = generateNewAccount(userName)
|
||||
dummyService.createRepository(repositoryName, userName, None, false)
|
||||
ac
|
||||
}
|
||||
def generateNewPullRequest(base:String, request:String)(implicit s:Session):(Issue, PullRequest) = {
|
||||
val Array(baseUserName, baseRepositoryName, baesBranch)=base.split("/")
|
||||
val Array(requestUserName, requestRepositoryName, requestBranch)=request.split("/")
|
||||
val issueId = dummyService.createIssue(
|
||||
owner = baseUserName,
|
||||
repository = baseRepositoryName,
|
||||
|
||||
def generateNewIssue(userName:String, repositoryName:String, requestUserName:String="root")(implicit s:Session): Int = {
|
||||
dummyService.createIssue(
|
||||
owner = userName,
|
||||
repository = repositoryName,
|
||||
loginUser = requestUserName,
|
||||
title = "issue title",
|
||||
content = None,
|
||||
assignedUserName = None,
|
||||
milestoneId = None,
|
||||
isPullRequest = true)
|
||||
}
|
||||
|
||||
def generateNewPullRequest(base:String, request:String)(implicit s:Session):(Issue, PullRequest) = {
|
||||
val Array(baseUserName, baseRepositoryName, baesBranch)=base.split("/")
|
||||
val Array(requestUserName, requestRepositoryName, requestBranch)=request.split("/")
|
||||
val issueId = generateNewIssue(baseUserName, baseRepositoryName, requestUserName)
|
||||
dummyService.createPullRequest(
|
||||
originUserName = baseUserName,
|
||||
originRepositoryName = baseRepositoryName,
|
||||
|
||||
Reference in New Issue
Block a user