Show the commit status in various places (#2601)

This commit is contained in:
Naoki Takezoe
2020-12-13 02:30:48 +09:00
committed by GitHub
parent c02a722799
commit 85263474a7
28 changed files with 1181 additions and 333 deletions

View File

@@ -21,11 +21,17 @@ class DashboardController
with WebHookPullRequestService
with WebHookPullRequestReviewCommentService
with MilestonesService
with CommitStatusService
with UsersAuthenticator
with RequestCache
trait DashboardControllerBase extends ControllerBase {
self: IssuesService with PullRequestService with RepositoryService with AccountService with UsersAuthenticator =>
self: IssuesService
with PullRequestService
with RepositoryService
with AccountService
with CommitStatusService
with UsersAuthenticator =>
get("/dashboard/repos")(usersOnly {
val repos = getVisibleRepositories(
@@ -86,9 +92,10 @@ trait DashboardControllerBase extends ControllerBase {
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName)
val userRepos = getUserRepositories(userName, true).map(repo => repo.owner -> repo.name)
val page = IssueSearchCondition.page(request)
val issues = searchIssue(condition, IssueSearchOption.Issues, (page - 1) * IssueLimit, IssueLimit, userRepos: _*)
html.issues(
searchIssue(condition, IssueSearchOption.Issues, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
issues.map(issue => (issue, None)),
page,
countIssue(condition.copy(state = "open"), IssueSearchOption.Issues, userRepos: _*),
countIssue(condition.copy(state = "closed"), IssueSearchOption.Issues, userRepos: _*),
@@ -116,15 +123,21 @@ trait DashboardControllerBase extends ControllerBase {
val condition = getOrCreateCondition(Keys.Session.DashboardPulls, filter, userName)
val allRepos = getAllRepositories(userName)
val page = IssueSearchCondition.page(request)
val issues = searchIssue(
condition,
IssueSearchOption.PullRequests,
(page - 1) * PullRequestLimit,
PullRequestLimit,
allRepos: _*
)
val status = issues.map { issue =>
issue.commitId.flatMap { commitId =>
getCommitStatusWithSummary(issue.issue.userName, issue.issue.repositoryName, commitId)
}
}
html.pulls(
searchIssue(
condition,
IssueSearchOption.PullRequests,
(page - 1) * PullRequestLimit,
PullRequestLimit,
allRepos: _*
),
issues.zip(status),
page,
countIssue(condition.copy(state = "open"), IssueSearchOption.PullRequests, allRepos: _*),
countIssue(condition.copy(state = "closed"), IssueSearchOption.PullRequests, allRepos: _*),

View File

@@ -467,13 +467,15 @@ trait IssuesControllerBase extends ControllerBase {
defining(repository.owner, repository.name) {
case (owner, repoName) =>
val page = IssueSearchCondition.page(request)
// retrieve search condition
val condition = IssueSearchCondition(request)
// search issues
val issues =
searchIssue(condition, IssueSearchOption.Issues, (page - 1) * IssueLimit, IssueLimit, owner -> repoName)
html.list(
"issues",
searchIssue(condition, IssueSearchOption.Issues, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
issues.map(issue => (issue, None)),
page,
getAssignableUserNames(owner, repoName),
getMilestones(owner, repoName),

View File

@@ -2,7 +2,13 @@ package gitbucket.core.controller
import gitbucket.core.issues.milestones.html
import gitbucket.core.service.IssuesService.{IssueLimit, IssueSearchCondition}
import gitbucket.core.service.{AccountService, IssueSearchOption, MilestonesService, RepositoryService}
import gitbucket.core.service.{
AccountService,
CommitStatusService,
IssueSearchOption,
MilestonesService,
RepositoryService
}
import gitbucket.core.util.Implicits._
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
import gitbucket.core.util.SyntaxSugars._
@@ -15,11 +21,16 @@ class MilestonesController
with MilestonesService
with RepositoryService
with AccountService
with CommitStatusService
with ReferrerAuthenticator
with WritableUsersAuthenticator
trait MilestonesControllerBase extends ControllerBase {
self: MilestonesService with RepositoryService with ReferrerAuthenticator with WritableUsersAuthenticator =>
self: MilestonesService
with RepositoryService
with CommitStatusService
with ReferrerAuthenticator
with WritableUsersAuthenticator =>
case class MilestoneForm(title: String, description: Option[String], dueDate: Option[java.util.Date])
@@ -45,15 +56,22 @@ trait MilestonesControllerBase extends ControllerBase {
request,
milestone.get.title
)
val issues = searchIssue(
condition,
IssueSearchOption.Both,
(page - 1) * IssueLimit,
IssueLimit,
repository.owner -> repository.name
)
val status = issues.map { issue =>
issue.commitId.flatMap { commitId =>
getCommitStatusWithSummary(issue.issue.userName, issue.issue.repositoryName, commitId)
}
}
html.milestone(
condition.state,
searchIssue(
condition,
IssueSearchOption.Both,
(page - 1) * IssueLimit,
IssueLimit,
repository.owner -> repository.name
),
issues.zip(status),
page,
getAssignableUserNames(repository.owner, repository.name),
getPriorities(repository.owner, repository.name),

View File

@@ -219,7 +219,7 @@ trait PullRequestsControllerBase extends ControllerBase {
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.branch)
val mergeStatus = PullRequestService.MergeStatus(
conflictMessage = conflictMessage,
commitStatues = getCommitStatues(owner, name, pullreq.commitIdTo),
commitStatuses = getCommitStatuses(owner, name, pullreq.commitIdTo),
branchProtection = branchProtection,
branchIsOutOfDate = JGitUtil.getShaByRef(owner, name, pullreq.branch) != Some(pullreq.commitIdFrom),
needStatusCheck = context.loginAccount
@@ -637,19 +637,26 @@ trait PullRequestsControllerBase extends ControllerBase {
defining(repository.owner, repository.name) {
case (owner, repoName) =>
val page = IssueSearchCondition.page(request)
// retrieve search condition
val condition = IssueSearchCondition(request)
// search issues
val issues = searchIssue(
condition,
IssueSearchOption.PullRequests,
(page - 1) * PullRequestLimit,
PullRequestLimit,
owner -> repoName
)
// commit status
val status = issues.map { issue =>
issue.commitId.flatMap { commitId =>
getCommitStatusWithSummary(owner, repoName, commitId)
}
}
gitbucket.core.issues.html.list(
"pulls",
searchIssue(
condition,
IssueSearchOption.PullRequests,
(page - 1) * PullRequestLimit,
PullRequestLimit,
owner -> repoName
),
issues.zip(status),
page,
getAssignableUserNames(owner, repoName),
getMilestones(owner, repoName),

View File

@@ -186,7 +186,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
redirect(s"/${repository.owner}/${repository.name}/settings/branches")
} else {
val protection = ApiBranchProtection(getProtectedBranchInfo(repository.owner, repository.name, branch))
val lastWeeks = getRecentStatuesContexts(
val lastWeeks = getRecentStatusContexts(
repository.owner,
repository.name,
Date.from(LocalDateTime.now.minusWeeks(1).toInstant(ZoneOffset.UTC))

View File

@@ -4,7 +4,6 @@ import java.io.{File, FileInputStream, FileOutputStream}
import scala.util.Using
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.repo.html
import gitbucket.core.helper
import gitbucket.core.model.activity.DeleteBranchInfo
@@ -15,9 +14,9 @@ import gitbucket.core.util.StringUtil._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.Directory._
import gitbucket.core.model.{Account, CommitState, CommitStatus}
import gitbucket.core.model.Account
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.util.JGitUtil.{CommitInfo, createBranch}
import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.view
import gitbucket.core.view.helpers
import org.apache.commons.compress.archivers.{ArchiveEntry, ArchiveOutputStream}
@@ -263,23 +262,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
val (branchName, path) = repository.splitPath(multiParams("splat").head)
val page = params.get("page").flatMap(_.toIntOpt).getOrElse(1)
def getStatuses(sha: String): List[CommitStatus] = {
getCommitStatues(repository.owner, repository.name, sha)
}
def getSummary(statuses: List[CommitStatus]): (CommitState, String) = {
val stateMap = statuses.groupBy(_.state)
val state = CommitState.combine(stateMap.keySet)
val summary = stateMap.map { case (keyState, states) => s"${states.size} ${keyState.name}" }.mkString(", ")
state -> summary
}
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
git =>
def getTags(sha: String): List[String] = {
JGitUtil.getTagsOnCommit(git, sha)
}
JGitUtil.getCommitLog(git, branchName, page, 30, path) match {
case Right((logs, hasNext)) =>
html.commits(
@@ -288,34 +272,33 @@ trait RepositoryViewerControllerBase extends ControllerBase {
repository,
logs
.map {
c =>
CommitInfo(
id = c.id,
shortMessage = c.shortMessage,
fullMessage = c.fullMessage,
parents = c.parents,
authorTime = c.authorTime,
authorName = c.authorName,
authorEmailAddress = c.authorEmailAddress,
commitTime = c.commitTime,
committerName = c.committerName,
committerEmailAddress = c.committerEmailAddress,
commitSign = c.commitSign,
verified = c.commitSign
.flatMap { s =>
GpgUtil.verifySign(s)
}
commit =>
(
CommitInfo(
id = commit.id,
shortMessage = commit.shortMessage,
fullMessage = commit.fullMessage,
parents = commit.parents,
authorTime = commit.authorTime,
authorName = commit.authorName,
authorEmailAddress = commit.authorEmailAddress,
commitTime = commit.commitTime,
committerName = commit.committerName,
committerEmailAddress = commit.committerEmailAddress,
commitSign = commit.commitSign,
verified = commit.commitSign.flatMap(GpgUtil.verifySign)
),
JGitUtil.getTagsOnCommit(git, commit.id),
getCommitStatusWithSummary(repository.owner, repository.name, commit.id)
)
}
.splitWith { (commit1, commit2) =>
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
.splitWith {
case ((commit1, _, _), (commit2, _, _)) =>
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
},
page,
hasNext,
hasDeveloperRole(repository.owner, repository.name, context.loginAccount),
getStatuses,
getSummary,
getTags
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
)
case Left(_) => NotFound()
}
@@ -731,6 +714,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
new JGitUtil.CommitInfo(revCommit),
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
JGitUtil.getTagsOfCommit(git, revCommit.getName),
getCommitStatusWithSummary(repository.owner, repository.name, revCommit.getName),
getCommitComments(repository.owner, repository.name, id, true),
repository,
diffs,
@@ -889,19 +873,20 @@ trait RepositoryViewerControllerBase extends ControllerBase {
defaultBranch = repository.repository.defaultBranch,
origin = repository.repository.originUserName.isEmpty
)
.sortBy(br => (br.mergeInfo.isEmpty, br.commitTime))
.sortBy(branch => (branch.mergeInfo.isEmpty, branch.commitTime))
.map(
br =>
branch =>
(
br,
branch,
getPullRequestByRequestCommit(
repository.owner,
repository.name,
repository.repository.defaultBranch,
br.name,
br.commitId
branch.name,
branch.commitId
),
protectedBranches.contains(br.name)
protectedBranches.contains(branch.name),
getCommitStatusWithSummary(repository.owner, repository.name, branch.commitId)
)
)
.reverse
@@ -1085,6 +1070,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
repository,
if (path == ".") Nil else path.split("/").toList, // current path
new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit
getCommitStatusWithSummary(repository.owner, repository.name, lastModifiedCommit.getName),
commitCount,
files,
readme,

View File

@@ -47,7 +47,7 @@ trait ApiRepositoryStatusControllerBase extends ControllerBase {
ref <- params.get("ref")
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
} yield {
JsonFormat(getCommitStatuesWithCreator(repository.owner, repository.name, sha).map {
JsonFormat(getCommitStatusesWithCreator(repository.owner, repository.name, sha).map {
case (status, creator) =>
ApiCommitStatus(status, ApiUser(creator))
})
@@ -73,7 +73,7 @@ trait ApiRepositoryStatusControllerBase extends ControllerBase {
owner <- getAccountByUserName(repository.owner)
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
} yield {
val statuses = getCommitStatuesWithCreator(repository.owner, repository.name, sha)
val statuses = getCommitStatusesWithCreator(repository.owner, repository.name, sha)
JsonFormat(ApiCombinedCommitStatus(sha, statuses, ApiRepository(repository, owner)))
}) getOrElse NotFound()
})

View File

@@ -47,6 +47,18 @@ trait CommitStatusService {
)
}
def getCommitStatusWithSummary(userName: String, repositoryName: String, sha: String)(
implicit s: Session
): Option[(CommitState, List[CommitStatus])] = {
val statuses = getCommitStatuses(userName, repositoryName, sha)
if (statuses.isEmpty) {
None
} else {
val summary = CommitState.combine(statuses.groupBy(_.state).keySet)
Some((summary, statuses))
}
}
def getCommitStatus(userName: String, repositoryName: String, id: Int)(implicit s: Session): Option[CommitStatus] =
CommitStatuses.filter(t => t.byPrimaryKey(id) && t.byRepository(userName, repositoryName)).firstOption
@@ -55,10 +67,12 @@ trait CommitStatusService {
): Option[CommitStatus] =
CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha) && t.context === context.bind).firstOption
def getCommitStatues(userName: String, repositoryName: String, sha: String)(implicit s: Session): List[CommitStatus] =
byCommitStatues(userName, repositoryName, sha).list
def getCommitStatuses(userName: String, repositoryName: String, sha: String)(
implicit s: Session
): List[CommitStatus] =
byCommitStatus(userName, repositoryName, sha).list
def getRecentStatuesContexts(userName: String, repositoryName: String, time: java.util.Date)(
def getRecentStatusContexts(userName: String, repositoryName: String, time: java.util.Date)(
implicit s: Session
): List[String] =
CommitStatuses
@@ -68,15 +82,15 @@ trait CommitStatusService {
.map(_._1)
.list
def getCommitStatuesWithCreator(userName: String, repositoryName: String, sha: String)(
def getCommitStatusesWithCreator(userName: String, repositoryName: String, sha: String)(
implicit s: Session
): List[(CommitStatus, Account)] =
byCommitStatues(userName, repositoryName, sha)
byCommitStatus(userName, repositoryName, sha)
.join(Accounts)
.filter { case (t, a) => t.creator === a.userName }
.list
protected def byCommitStatues(userName: String, repositoryName: String, sha: String)(implicit s: Session) =
protected def byCommitStatus(userName: String, repositoryName: String, sha: String)(implicit s: Session) =
CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha)).sortBy(_.updatedDate desc)
}

View File

@@ -188,37 +188,6 @@ trait IssuesService {
.toMap
}
def getCommitStatues(userName: String, repositoryName: String, issueId: Int)(
implicit s: Session
): Option[CommitStatusInfo] = {
val status = PullRequests
.filter { pr =>
pr.userName === userName.bind && pr.repositoryName === repositoryName.bind && pr.issueId === issueId.bind
}
.join(CommitStatuses)
.on {
case pr ~ cs =>
pr.userName === cs.userName && pr.repositoryName === cs.repositoryName && pr.commitIdTo === cs.commitId
}
.list
if (status.nonEmpty) {
val (_, cs) = status.head
Some(
CommitStatusInfo(
count = status.length,
successCount = status.count(_._2.state == CommitState.SUCCESS),
context = (if (status.length == 1) Some(cs.context) else None),
state = (if (status.length == 1) Some(cs.state) else None),
targetUrl = (if (status.length == 1) cs.targetUrl else None),
description = (if (status.length == 1) cs.description else None)
)
)
} else {
None
}
}
/**
* Returns the search result against issues.
*
@@ -246,9 +215,11 @@ trait IssuesService {
.on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 => t1.byMilestone(t5.userName, t5.repositoryName, t5.milestoneId) }
.joinLeft(Priorities)
.on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 => t1.byPriority(t6.userName, t6.repositoryName, t6.priorityId) }
.sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 => i asc }
.joinLeft(PullRequests)
.on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 => t1.byIssue(t7.userName, t7.repositoryName, t7.issueId) }
.sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 => i asc }
.map {
case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 =>
case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 =>
(
t1,
t2.commentCount,
@@ -256,7 +227,8 @@ trait IssuesService {
t4.map(_.labelName),
t4.map(_.color),
t5.map(_.title),
t6.map(_.priorityName)
t6.map(_.priorityName),
t7.map(_.commitIdTo)
)
}
.list
@@ -266,7 +238,7 @@ trait IssuesService {
result.map { issues =>
issues.head match {
case (issue, commentCount, _, _, _, milestone, priority) =>
case (issue, commentCount, _, _, _, milestone, priority, commitId) =>
IssueInfo(
issue,
issues.flatMap { t =>
@@ -275,7 +247,7 @@ trait IssuesService {
milestone,
priority,
commentCount,
getCommitStatues(issue.userName, issue.repositoryName, issue.issueId)
commitId
)
}
} toList
@@ -974,24 +946,14 @@ object IssuesService {
}
}
case class CommitStatusInfo(
count: Int,
successCount: Int,
context: Option[String],
state: Option[CommitState],
targetUrl: Option[String],
description: Option[String]
)
case class IssueInfo(
issue: Issue,
labels: List[Label],
milestone: Option[String],
priority: Option[String],
commentCount: Int,
status: Option[CommitStatusInfo]
commitId: Option[String]
)
}
sealed trait IssueSearchOption

View File

@@ -154,7 +154,7 @@ object ProtectedBranchService {
if (contexts.isEmpty) {
Set.empty
} else {
contexts.toSet -- getCommitStatues(owner, repository, sha1)
contexts.toSet -- getCommitStatuses(owner, repository, sha1)
.filter(_.state == CommitState.SUCCESS)
.map(_.context)
.toSet

View File

@@ -611,7 +611,7 @@ object PullRequestService {
case class MergeStatus(
conflictMessage: Option[String],
commitStatues: List[CommitStatus],
commitStatuses: List[CommitStatus],
branchProtection: ProtectedBranchService.ProtectedBranchInfo,
branchIsOutOfDate: Boolean,
hasUpdatePermission: Boolean,
@@ -622,7 +622,7 @@ object PullRequestService {
val hasConflict = conflictMessage.isDefined
val statuses: List[CommitStatus] =
commitStatues ++ (branchProtection.contexts.toSet -- commitStatues.map(_.context).toSet)
commitStatuses ++ (branchProtection.contexts.toSet -- commitStatuses.map(_.context).toSet)
.map(CommitStatus.pending(branchProtection.owner, branchProtection.repository, _))
val hasRequiredStatusProblem = needStatusCheck && branchProtection.contexts.exists(
context => statuses.find(_.context == context).map(_.state) != Some(CommitState.SUCCESS)

View File

@@ -1,4 +1,4 @@
@(issues: List[gitbucket.core.service.IssuesService.IssueInfo],
@(issues: List[(gitbucket.core.service.IssuesService.IssueInfo, Option[(gitbucket.core.model.CommitState, List[gitbucket.core.model.CommitStatus])])],
page: Int,
openCount: Int,
closedCount: Int,

View File

@@ -1,4 +1,4 @@
@(issues: List[gitbucket.core.service.IssuesService.IssueInfo],
@(issues: List[(gitbucket.core.service.IssuesService.IssueInfo, Option[(gitbucket.core.model.CommitState, List[gitbucket.core.model.CommitStatus])])],
page: Int,
openCount: Int,
closedCount: Int,
@@ -17,7 +17,7 @@
</tr>
</thead>
<tbody>
@issues.map { case IssueInfo(issue, labels, milestone, priority, commentCount, commitStatus) => {
@issues.map { case (IssueInfo(issue, labels, milestone, priority, commentCount, commitId), status) =>
<tr>
<td style="padding-top: 12px; padding-bottom: 12px;">
<a href="@context.path/@issue.userName/@issue.repositoryName">@issue.userName/@issue.repositoryName</a>&nbsp;&#xFF65;
@@ -26,7 +26,9 @@
} else {
<a id="issue_@(issue.issueId)_link" href="@context.path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-title">@issue.title</a>
}
@gitbucket.core.issues.html.commitstatus(issue, commitStatus)
@status.map { case (summary, statuses) =>
@gitbucket.core.helper.html.commitstatus(commitId.get, summary, statuses)
}
@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>
}
@@ -52,7 +54,7 @@
</div>
</td>
</tr>
}}
}
@if(issues.isEmpty){
<tr>
<td style="padding: 20px; background-color: #eee; text-align: center;">

View File

@@ -1,4 +1,4 @@
@(issues: List[gitbucket.core.service.IssuesService.IssueInfo],
@(issues: List[(gitbucket.core.service.IssuesService.IssueInfo, Option[(gitbucket.core.model.CommitState, List[gitbucket.core.model.CommitStatus])])],
page: Int,
openCount: Int,
closedCount: Int,

View File

@@ -0,0 +1,26 @@
@(commitId: String, summary: gitbucket.core.model.CommitState, statuses: List[gitbucket.core.model.CommitStatus])(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
<strong class="text-@{summary.name}">@helpers.commitStateIcon(summary)</strong>
<span class="text-@{summary.name}" id="commit-status-@commitId" style="cursor : pointer;">@{statuses.groupBy(_.state).map { case (keyState, states) => s"${states.size} ${keyState.name}" }.mkString(", ")} checks</span>
<div id="tooltip-commit-status-@commitId" style="display: none; width: 300px;">
@statuses.map{ status =>
<div class="build-status-item">
<span class="build-status-icon text-@{status.state.name}">@helpers.commitStateIcon(status.state)</span>
<strong>@status.context</strong>
@status.description.map { desc => <span class="muted">- @desc</span> }
<span>@status.targetUrl.map { url => - <a href="@url">Details</a> }</span>
</div>
}
</div>
<script>
$(document).ready(function() {
Tipped.create('#commit-status-@commitId', {
skin: 'light',
inline: 'tooltip-commit-status-@commitId',
size: 'large',
position: 'bottomleft'
});
});
</script>

View File

@@ -1,19 +0,0 @@
@(issue: gitbucket.core.model.Issue, statusInfo: Option[gitbucket.core.service.IssuesService.CommitStatusInfo])(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.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)">@helpers.commitStateIcon(status.state.get)</a>
}else{
<span class="text-@status.state.get.name">@helpers.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){
&#x2714;
}else{
×
}</a>
}
}
}

View File

@@ -1,5 +1,5 @@
@(target: String,
issues: List[gitbucket.core.service.IssuesService.IssueInfo],
issues: List[(gitbucket.core.service.IssuesService.IssueInfo, Option[(gitbucket.core.model.CommitState, List[gitbucket.core.model.CommitStatus])])],
page: Int,
collaborators: List[String],
milestones: List[gitbucket.core.model.Milestone],

View File

@@ -1,5 +1,5 @@
@(target: String,
issues: List[gitbucket.core.service.IssuesService.IssueInfo],
issues: List[(gitbucket.core.service.IssuesService.IssueInfo, Option[(gitbucket.core.model.CommitState, List[gitbucket.core.model.CommitStatus])])],
page: Int,
openCount: Int,
closedCount: Int,
@@ -206,7 +206,7 @@
</td>
</tr>
}
@issues.map { case IssueInfo(issue, labels, milestone, priority, commentCount, commitStatus) => {
@issues.map { case (IssueInfo(issue, labels, milestone, priority, commentCount, commitId), status) =>
<tr>
<td style="padding-top: 12px; padding-bottom: 12px;">
@if(isManageable){
@@ -223,7 +223,9 @@
} else {
<a id="issue_@(issue.issueId)_link" href="@context.path/@issue.userName/@issue.repositoryName/pull/@issue.issueId" class="issue-title">@issue.title</a>
}
@gitbucket.core.issues.html.commitstatus(issue, commitStatus)
@status.map { case (summary, statuses) =>
@gitbucket.core.helper.html.commitstatus(commitId.get, summary, statuses)
}
@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>
}
@@ -253,7 +255,7 @@
</div>
</td>
</tr>
}}
}
</tbody>
</table>
<div class="pull-right">

View File

@@ -1,4 +1,4 @@
@(issues: List[gitbucket.core.service.IssuesService.IssueInfo],
@(issues: List[(gitbucket.core.service.IssuesService.IssueInfo, Option[(gitbucket.core.model.CommitState, List[gitbucket.core.model.CommitStatus])])],
page: Int,
openCount: Int,
closedCount: Int,
@@ -62,7 +62,7 @@
</td>
</tr>
}
@issues.map { case IssueInfo(issue, labels, milestone, priority, commentCount, commitStatus) => {
@issues.map { case (IssueInfo(issue, labels, milestone, priority, commentCount, commitId), status) =>
<tr>
<td style="padding-top: 12px; padding-bottom: 12px;">
@if(isManageable){
@@ -83,7 +83,9 @@
@if(issue.closed){<i class="menu-icon octicon octicon-issue-closed"></i>}else{<i class="menu-icon octicon octicon-issue-opened"></i>}@issue.title@issue.title
</a>
}
@gitbucket.core.issues.html.commitstatus(issue, commitStatus)
@status.map { case (summary, statuses) =>
@gitbucket.core.helper.html.commitstatus(commitId.get, summary, statuses)
}
@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>
}
@@ -113,7 +115,7 @@
</div>
</td>
</tr>
}}
}
</tbody>
</table>
<div class="pull-right">

View File

@@ -1,5 +1,5 @@
@(state: String,
issues: List[gitbucket.core.service.IssuesService.IssueInfo],
issues: List[(gitbucket.core.service.IssuesService.IssueInfo, Option[(gitbucket.core.model.CommitState, List[gitbucket.core.model.CommitStatus])])],
page: Int,
collaborators: List[String],
priorities: List[gitbucket.core.model.Priority],

View File

@@ -36,6 +36,7 @@
<link href="@helpers.assets("/vendors/jquery-ui/jquery-ui.structure.min.css")" rel="stylesheet">
<link href="@helpers.assets("/vendors/jquery-ui/jquery-ui.theme.min.css")" rel="stylesheet">
<link href="@helpers.assets("/common/css/gitbucket.css")" rel="stylesheet">
<link href="@helpers.assets("/vendors/tipped/tipped.css")" rel="stylesheet">
<script src="@helpers.assets("/vendors/jquery/jquery-3.5.1.min.js")"></script>
<script src="@helpers.assets("/vendors/jquery-ui/jquery-ui.min.js")"></script>
<script src="@helpers.assets("/vendors/dropzone/dropzone.min.js")"></script>
@@ -51,6 +52,7 @@
<script src="@helpers.assets("/vendors/facebox/facebox.js")"></script>
<script src="@helpers.assets("/vendors/jquery-hotkeys/jquery.hotkeys.js")"></script>
<script src="@helpers.assets("/vendors/jquery-textcomplete-1.8.4/jquery.textcomplete.min.js")"></script>
<script src="@helpers.assets("/vendors/tipped/tipped.min.js")"></script>
@repository.map { repository =>
<meta name="go-import" content="@context.baseUrl.replaceFirst("^https?://", "")/@repository.owner/@repository.name git @repository.httpUrl" />
}

View File

@@ -1,4 +1,9 @@
@(branchInfo: Seq[(gitbucket.core.util.JGitUtil.BranchInfo, Option[(gitbucket.core.model.PullRequest, gitbucket.core.model.Issue)], Boolean)],
@(branchInfo: Seq[(
gitbucket.core.util.JGitUtil.BranchInfo,
Option[(gitbucket.core.model.PullRequest, gitbucket.core.model.Issue)],
Boolean,
Option[(gitbucket.core.model.CommitState, List[gitbucket.core.model.CommitStatus])]
)],
hasWritePermission: Boolean,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@@ -11,7 +16,7 @@
</tr>
</thead>
<tbody>
@branchInfo.map { case (branch, prs, isProtected) =>
@branchInfo.map { case (branch, prs, isProtected, status) =>
<tr>
<td class="branch-details">
@if(isProtected){ <span class="octicon octicon-shield" title="This branch is protected"></span> }
@@ -21,6 +26,9 @@
by <span>@helpers.user(branch.committerName, branch.committerEmailAddress, "muted-link")</span>
</span>
</span>
@status.map { case (summary, statuses) =>
@gitbucket.core.helper.html.commitstatus(branch.commitId, summary, statuses)
}
</td>
<td class="branch-a-b-count">
@if(repository.repository.defaultBranch == branch.name){

View File

@@ -2,6 +2,7 @@
commit: gitbucket.core.util.JGitUtil.CommitInfo,
branches: List[String],
tags: List[String],
status: Option[(gitbucket.core.model.CommitState, List[gitbucket.core.model.CommitStatus])],
comments: List[gitbucket.core.model.Comment],
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
diffs: Seq[gitbucket.core.util.JGitUtil.DiffInfo],
@@ -49,22 +50,22 @@
</tr>
<tr>
<td>
<div class="pull-right monospace small" style="text-align: right;">
<div class="pull-right">
<div>
@if(commit.parents.size == 0){
<span class="muted">0 parent</span>
}
@if(commit.parents.size == 1){
<span class="muted">1 parent</span>
<a href="@helpers.url(repository)/commit/@commit.parents(0)" class="commit-id">@commit.parents(0).substring(0, 7)</a>
<a href="@helpers.url(repository)/commit/@commit.parents(0)" class="commit-id monospace strong">@commit.parents(0).substring(0, 7)</a>
}
<span class="muted">commit</span> @commit.id
<span class="muted ">commit</span> <span class="commit-id monospace strong">@commit.id</span>
</div>
@if(commit.parents.size > 1){
<div>
<span class="muted">@commit.parents.size parents
@commit.parents.map { parent =>
<a href="@helpers.url(repository)/commit/@parent" class="commit-id">@parent.substring(0, 7)</a>
<a href="@helpers.url(repository)/commit/@parent" class="commit-id monospace strong">@parent.substring(0, 7)</a>
}.mkHtml(" + ")
</span>
</div>
@@ -76,13 +77,16 @@
@helpers.avatarLink(commit, 20)
<span>@helpers.user(commit.authorName, commit.authorEmailAddress, "username strong")</span>
<span class="muted">authored @gitbucket.core.helper.html.datetimeago(commit.authorTime)</span>
@status.map { case (summary, statuses) =>
@gitbucket.core.helper.html.commitstatus(commit.id, summary, statuses)
}
</div>
@if(commit.isDifferentFromAuthor) {
<div class="committer">
<span class="octicon octicon-arrow-right"></span>
<span>@helpers.user(commit.committerName, commit.committerEmailAddress, "username strong")</span>
<span class="muted"> committed @gitbucket.core.helper.html.datetimeago(commit.commitTime)</span>
</div>
<div class="committer">
<span class="octicon octicon-arrow-right"></span>
<span>@helpers.user(commit.committerName, commit.committerEmailAddress, "username strong")</span>
<span class="muted"> committed @gitbucket.core.helper.html.datetimeago(commit.commitTime)</span>
</div>
}
</div>
</td>

View File

@@ -1,13 +1,10 @@
@(pathList: List[String],
branch: String,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
commits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]],
commits: Seq[Seq[(gitbucket.core.util.JGitUtil.CommitInfo, List[String], Option[(gitbucket.core.model.CommitState, List[gitbucket.core.model.CommitStatus])])]],
page: Int,
hasNext: Boolean,
hasWritePermission: Boolean,
getStatuses: String => List[gitbucket.core.model.CommitStatus],
getSummary: List[gitbucket.core.model.CommitStatus] => (gitbucket.core.model.CommitState, String),
getTags: String => List[String])(implicit context: gitbucket.core.controller.Context)
hasWritePermission: Boolean)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@gitbucket.core.html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
@gitbucket.core.html.menu("files", repository){
@@ -35,8 +32,8 @@
<table class="table table-bordered table-hover">
@commits.map { day =>
<tr>
<th rowspan="@day.size" width="100">@helpers.date(day.head.commitTime)</th>
@day.zipWithIndex.map { case (commit, i) =>
<th rowspan="@day.size" width="100">@helpers.date(day.head._1.commitTime)</th>
@day.zipWithIndex.map { case ((commit, tags, status), i) =>
@if(i != 0){ <tr> }
<td>
<div class="pull-right text-right">
@@ -47,15 +44,13 @@
<span class="gpg-unverified">Unverified</span>
}
}
@defining(getTags(commit.id)) { tags =>
@if(tags.nonEmpty){
<span class="muted">
<i class="octicon octicon-tag"></i>
@tags.map { tag =>
<a href="@helpers.url(repository)/tree/@tag" class="tag">@tag</a>
}
</span>
}
@if(tags.nonEmpty){
<span class="muted">
<i class="octicon octicon-tag"></i>
@tags.map { tag =>
<a href="@helpers.url(repository)/tree/@tag" class="tag">@tag</a>
}
</span>
}
<a href="@helpers.url(repository)/commit/@commit.id" class="monospace commit-message strong"><i class="octicon octicon-diff" style="color: black;"></i>@commit.id.substring(0, 7)</a><br>
<a href="@helpers.url(repository)/tree/@commit.id" class="button-link">Browse files »</a>
@@ -79,30 +74,9 @@
}
@helpers.user(commit.committerName, commit.committerEmailAddress, "username")
<span class="muted">committed @gitbucket.core.helper.html.datetimeago(commit.commitTime)</span>
@defining({
val statuses = getStatuses(commit.id)
val (summary, summaryText) = getSummary(statuses)
(statuses, summary, summaryText)
}){ case (statuses, summaryState, summaryText) =>
@if(statuses.nonEmpty){
@helpers.commitStateIcon(summaryState)
<strong class="text-@{summaryState.name}">@helpers.commitStateText(summaryState, commit.id)</strong>
<span class="text-@{summaryState.name}">- @summaryText checks</span>
<a href="#" class="toggle-check">Show all checks</a>
<div style="display: none;">
@statuses.map{ status =>
<div class="build-status-item">
<span class="build-status-icon text-@{status.state.name}">@helpers.commitStateIcon(status.state)</span>
<strong>@status.context</strong>
@status.description.map { desc => <span class="muted">- @desc</span> }
<span>
@status.targetUrl.map { url => - <a href="@url">Details</a> }
</span>
</div>
}
</div>
}
}
@status.map { case (summary, statuses) =>
@gitbucket.core.helper.html.commitstatus(commit.id, summary, statuses)
}
</div>
</div>
</div>
@@ -127,20 +101,9 @@
</ul>
</nav>
<script>
$(function () {
$(function() {
$('[data-toggle="tooltip"]').tooltip();
$('.toggle-check').click(function(){
var div = $(this).next('div');
if(div.is(':visible')){
$(this).text('Show all checks');
} else {
$(this).text('Hide all checks');
}
div.toggle();
return false;
});
})
});
</script>
<style type="text/css">
a.tag {

View File

@@ -2,6 +2,7 @@
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
pathList: List[String],
latestCommit: gitbucket.core.util.JGitUtil.CommitInfo,
commitStatus: Option[(gitbucket.core.model.CommitState, List[gitbucket.core.model.CommitStatus])],
commitCount: Int,
files: List[gitbucket.core.util.JGitUtil.FileInfo],
readme: Option[(List[String], String)],
@@ -109,16 +110,24 @@
</tr>
*@
<tr>
<th colspan="4" class="latest-commit">
<th colspan="4" class="latest-commit" style="font-weight: normal;">
<div>
<div class="pull-right align-right" style="line-height: 18px;">
<a href="@helpers.url(repository)/commit/@latestCommit.id" class="commit-id"><span class="muted">latest commit</span> <span class="monospace">@latestCommit.id.substring(0, 10)</span></a>
<a href="@helpers.url(repository)/commit/@latestCommit.id" class="commit-id">
<span class="muted">latest commit</span>
<span class="monospace commit-message strong">@latestCommit.id.substring(0, 10)</span>
</a>
</div>
<div class="author-info">
<div class="author">
@helpers.avatarLink(latestCommit, 20)
<span>@helpers.user(latestCommit.authorName, latestCommit.authorEmailAddress, "username strong")</span>
<span class="muted"> authored @gitbucket.core.helper.html.datetimeago(latestCommit.authorTime)</span>
<span>
@commitStatus.map { case (summary, statuses) =>
@gitbucket.core.helper.html.commitstatus(latestCommit.id, summary, statuses)
}
</span>
</div>
@if(latestCommit.isDifferentFromAuthor) {
<div class="committer">

View File

@@ -0,0 +1,935 @@
.tpd-tooltip {
position: absolute;
}
/* Fix for CSS frameworks that don't keep the use of box-sizing: border-box
within their own namespace */
.tpd-tooltip {
box-sizing: content-box;
}
.tpd-tooltip [class^="tpd-"] {
box-sizing: inherit;
}
/* Content */
.tpd-content-wrapper {
position: absolute;
top: 0;
left: 0;
float: left;
width: 100%;
height: 100%;
overflow: hidden;
}
.tpd-content-spacer,
.tpd-content-relative,
.tpd-content-relative-padder {
float: left;
position: relative;
}
.tpd-content-relative {
width: 100%;
}
.tpd-content {
float: left;
clear: both;
position: relative;
padding: 10px;
font-size: 11px;
line-height: 16px;
color: #fff;
box-sizing: border-box !important;
}
.tpd-has-inner-close .tpd-content-relative .tpd-content {
padding-right: 0 !important;
}
.tpd-tooltip .tpd-content-no-padding {
padding: 0 !important;
}
.tpd-title-wrapper {
float: left;
position: relative;
overflow: hidden;
}
.tpd-title-spacer {
float: left;
}
.tpd-title-relative,
.tpd-title-relative-padder {
float: left;
position: relative;
}
.tpd-title-relative {
width: 100%;
}
.tpd-title {
float: left;
position: relative;
font-size: 11px;
line-height: 16px;
padding: 10px;
font-weight: bold;
text-transform: uppercase;
color: #fff;
box-sizing: border-box !important;
}
.tpd-has-title-close .tpd-title {
padding-right: 0 !important;
}
.tpd-close {
position: absolute;
top: 0;
right: 0;
width: 28px;
height: 28px;
cursor: pointer;
overflow: hidden;
color: #fff;
}
.tpd-close-icon {
float: left;
font-family: Arial, Baskerville, monospace;
font-weight: normal;
font-style: normal;
text-decoration: none;
width: 28px;
height: 28px;
font-size: 28px;
line-height: 28px;
text-align: center;
}
/* Skin */
.tpd-skin {
position: absolute;
top: 0;
left: 0;
}
.tpd-frames {
position: absolute;
top: 0;
left: 0;
}
.tpd-frames .tpd-frame {
float: left;
width: 100%;
height: 100%;
clear: both;
display: none;
}
.tpd-visible-frame-top .tpd-frame-top {
display: block;
}
.tpd-visible-frame-bottom .tpd-frame-bottom {
display: block;
}
.tpd-visible-frame-left .tpd-frame-left {
display: block;
}
.tpd-visible-frame-right .tpd-frame-right {
display: block;
}
.tpd-backgrounds {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
-webkit-transform-origin: 0% 0%;
transform-origin: 0% 0%;
}
.tpd-background-shadow {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: transparent;
pointer-events: none;
}
.tpd-no-shadow .tpd-skin .tpd-background-shadow {
box-shadow: none !important;
}
.tpd-background-box {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
/* only the top background box should be shown when not using a stem */
.tpd-no-stem .tpd-background-box,
.tpd-no-stem .tpd-shift-stem {
display: none;
}
.tpd-no-stem .tpd-background-box-top {
display: block;
}
.tpd-background-box-shift,
.tpd-background-box-shift-further {
position: relative;
float: left;
width: 100%;
height: 100%;
}
.tpd-background {
border-radius: 10px;
float: left;
clear: both;
background: none;
-webkit-background-clip: padding-box; /* Safari */
background-clip: padding-box; /* IE9+, Firefox 4+, Opera, Chrome */
border-style: solid;
border-width: 1px;
border-color: rgba(
255,
255,
255,
0.1
); /* opacity here bugs out in firefox, .tpd-background-content should have no opacity if this opacity is less than 1 */
}
.tpd-background-loading {
display: none;
}
/* no radius */
.tpd-no-radius
.tpd-skin
.tpd-frames
.tpd-frame
.tpd-backgrounds
.tpd-background {
border-radius: 0;
}
.tpd-background-title {
float: left;
clear: both;
width: 100%;
background-color: #282828;
}
.tpd-background-content {
float: left;
clear: both;
width: 100%;
background-color: #282828;
}
.tpd-background-border-hack {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-style: solid;
}
.tpd-background-box-top {
top: 0;
}
.tpd-background-box-bottom {
bottom: 0;
}
.tpd-background-box-left {
left: 0;
}
.tpd-background-box-right {
right: 0;
}
/* Skin / Stems */
.tpd-shift-stem {
position: absolute;
top: 0;
left: 0;
overflow: hidden;
}
.tpd-shift-stem-side {
position: absolute;
}
.tpd-frame-top .tpd-shift-stem-side,
.tpd-frame-bottom .tpd-shift-stem-side {
width: 100%;
}
.tpd-frame-left .tpd-shift-stem-side,
.tpd-frame-right .tpd-shift-stem-side {
height: 100%;
}
.tpd-stem {
position: absolute;
top: 0;
left: 0;
overflow: hidden; /* shows possible invalid subpx rendering */
width: 16px; /* best cross browser stem: width = 2 x height (90deg angle) */
height: 8px;
margin-left: 3px; /* space from the side */
margin-top: 2px; /* space between target and stem */
-webkit-transform-origin: 0% 0%;
transform-origin: 0% 0%;
}
/* remove margins once we're done measuring */
.tpd-tooltip .tpd-skin .tpd-frames .tpd-frame .tpd-shift-stem .tpd-stem-reset {
margin: 0 !important;
}
.tpd-stem-spacer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.tpd-stem-reset .tpd-stem-spacer {
margin-top: 0;
}
.tpd-stem-point {
width: 100px;
position: absolute;
top: 0;
left: 50%;
}
.tpd-stem-downscale,
.tpd-stem-transform {
float: left;
width: 100%;
height: 100%;
-webkit-transform-origin: 0% 0%;
transform-origin: 0% 0%;
position: relative;
}
.tpd-stem-side {
width: 50%;
height: 100%;
float: left;
position: relative;
overflow: hidden;
}
.tpd-stem-side-inversed {
-webkit-transform: scale(-1, 1);
transform: scale(-1, 1);
}
.tpd-stem-triangle {
width: 0;
height: 0;
border-bottom-style: solid;
border-left-color: transparent;
border-left-style: solid;
position: absolute;
top: 0;
left: 0;
}
.tpd-stem-border {
width: 20px;
height: 100%;
position: absolute;
top: 0;
left: 50%;
background-color: #fff; /* will become transparent */
border-right-color: #fff;
border-right-style: solid;
border-right-width: 0;
}
.tpd-stem-border-corner {
position: absolute;
top: 0;
left: 50%;
height: 100%;
border-right-style: solid;
border-right-width: 0;
}
/* fixes rendering issue in IE */
.tpd-stem * {
z-index: 0;
zoom: 1;
}
/* used by IE < 9 */
.tpd-stem-border-center-offset,
.tpd-stem-border-center-offset-inverse {
float: left;
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
.tpd-stem-notransform {
float: left;
width: 100%;
height: 100%;
position: relative;
}
.tpd-stem-notransform .tpd-stem-border {
height: 100%;
position: relative;
float: left;
top: 0;
left: 0;
margin: 0;
}
.tpd-stem-notransform .tpd-stem-border-center {
position: absolute;
}
.tpd-stem-notransform .tpd-stem-border-corner {
background: #fff;
border: 0;
top: auto;
left: auto;
}
.tpd-stem-notransform .tpd-stem-border-center,
.tpd-stem-notransform .tpd-stem-triangle {
height: 0;
border: 0;
left: 50%;
}
/* transformations for left/right/bottom */
.tpd-stem-transform-left {
-webkit-transform: rotate(-90deg) scale(-1, 1);
transform: rotate(-90deg) scale(-1, 1);
}
.tpd-stem-transform-right {
-webkit-transform: rotate(90deg) translate(0, -100%);
transform: rotate(90deg) translate(0, -100%);
}
.tpd-stem-transform-bottom {
-webkit-transform: scale(1, -1) translate(0, -100%);
transform: scale(1, -1) translate(0, -100%);
}
/* Spinner */
.tpd-spinner {
position: absolute;
top: 50%;
left: 50%;
width: 46px;
height: 36px;
}
.tpd-spinner-spin {
position: relative;
float: left;
margin: 8px 0 0 13px;
text-indent: -9999em;
border-top: 2px solid rgba(255, 255, 255, 0.2);
border-right: 2px solid rgba(255, 255, 255, 0.2);
border-bottom: 2px solid rgba(255, 255, 255, 0.2);
border-left: 2px solid #fff;
-webkit-animation: tpd-spinner-animation 1.1s infinite linear;
animation: tpd-spinner-animation 1.1s infinite linear;
box-sizing: border-box !important;
}
.tpd-spinner-spin,
.tpd-spinner-spin:after {
border-radius: 50%;
width: 20px;
height: 20px;
}
@-webkit-keyframes tpd-spinner-animation {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes tpd-spinner-animation {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
/* show the loader while loading and hide all the content */
.tpd-is-loading .tpd-content-wrapper,
.tpd-is-loading .tpd-title-wrapper {
display: none;
}
.tpd-is-loading .tpd-background {
display: none;
}
.tpd-is-loading .tpd-background-loading {
display: block;
}
/* Resets while measuring content */
.tpd-tooltip-measuring {
top: 0;
left: 0;
position: absolute;
max-width: 100%;
width: 100%;
}
.tpd-tooltip-measuring .tpd-skin,
.tpd-tooltip-measuring .tpd-spinner {
display: none;
}
.tpd-tooltip-measuring .tpd-content-wrapper,
.tpd-tooltip-measuring .tpd-title-wrapper {
display: block;
}
/* Links */
.tpd-tooltip a,
.tpd-tooltip a:hover {
color: #808080;
text-decoration: underline;
}
.tpd-tooltip a:hover {
color: #6c6c6c;
}
/*
* Sizes
*/
/* x-small */
.tpd-size-x-small .tpd-content,
.tpd-size-x-small .tpd-title {
padding: 7px 8px;
font-size: 10px;
line-height: 15px;
}
.tpd-size-x-small .tpd-background {
border-radius: 5px;
}
.tpd-size-x-small .tpd-stem {
width: 12px;
height: 6px;
margin-left: 4px;
margin-top: 2px; /* space between target and stem */
}
.tpd-size-x-small.tpd-no-radius .tpd-stem {
margin-left: 7px;
}
.tpd-size-x-small .tpd-close {
margin-bottom: 1px;
}
.tpd-size-x-small .tpd-spinner {
width: 35px;
height: 29px;
}
.tpd-size-x-small .tpd-spinner-spin {
margin: 6px 0 0 9px;
}
.tpd-size-x-small .tpd-spinner-spin,
.tpd-size-x-small .tpd-spinner-spin:after {
width: 17px;
height: 17px;
}
/* small */
.tpd-size-small .tpd-content,
.tpd-size-small .tpd-title {
padding: 8px;
font-size: 10px;
line-height: 16px;
}
.tpd-size-small .tpd-background {
border-radius: 6px;
}
.tpd-size-small .tpd-stem {
width: 14px;
height: 7px;
margin-left: 5px;
margin-top: 2px; /* space between target and stem */
}
.tpd-size-small.tpd-no-radius .tpd-stem {
margin-left: 8px;
}
.tpd-size-small .tpd-close {
margin: 2px 1px;
}
.tpd-size-small .tpd-spinner {
width: 42px;
height: 32px;
}
.tpd-size-small .tpd-spinner-spin {
margin: 7px 0 0 13px;
}
.tpd-size-small .tpd-spinner-spin,
.tpd-size-small .tpd-spinner-spin:after {
width: 18px;
height: 18px;
}
/* medium (default) */
.tpd-size-medium .tpd-content,
.tpd-size-medium .tpd-title {
padding: 10px;
font-size: 11px;
line-height: 16px;
}
.tpd-size-medium .tpd-background {
border-radius: 8px;
}
.tpd-size-medium .tpd-stem {
width: 16px; /* best cross browser stem width is 2xheight, for a 90deg angle */
height: 8px;
margin-left: 6px; /* space from the side */
margin-top: 2px; /* space between target and stem */
}
.tpd-size-medium.tpd-no-radius .tpd-stem {
margin-left: 10px;
}
.tpd-size-medium .tpd-close {
margin: 4px 2px;
}
/* ideal spinner dimensions don't cause movement op top and
on the stem when switching to text using position:'topleft' */
.tpd-size-medium .tpd-spinner {
width: 50px;
height: 36px;
}
.tpd-size-medium .tpd-spinner-spin {
margin: 8px 0 0 15px;
}
.tpd-size-medium .tpd-spinner-spin,
.tpd-size-medium .tpd-spinner-spin:after {
width: 20px;
height: 20px;
}
/* large */
.tpd-size-large .tpd-content,
.tpd-size-large .tpd-title {
padding: 10px;
font-size: 13px;
line-height: 18px;
}
.tpd-size-large .tpd-background {
border-radius: 8px;
}
.tpd-size-large .tpd-stem {
width: 18px;
height: 9px;
margin-left: 7px;
margin-top: 2px; /* space between target and stem */
}
.tpd-size-large.tpd-no-radius .tpd-stem {
margin-left: 10px;
}
.tpd-size-large .tpd-close {
margin: 5px 2px 5px 2px;
}
.tpd-size-large .tpd-spinner {
width: 54px;
height: 38px;
}
.tpd-size-large .tpd-spinner-spin {
margin: 9px 0 0 17px;
}
.tpd-size-large .tpd-spinner-spin,
.tpd-size-large .tpd-spinner-spin:after {
width: 20px;
height: 20px;
}
/* Skins */
/* default (dark) */
.tpd-skin-dark .tpd-content,
.tpd-skin-dark .tpd-title,
.tpd-skin-dark .tpd-close {
color: #fff;
}
.tpd-skin-dark .tpd-background-content,
.tpd-skin-dark .tpd-background-title {
background-color: #282828;
}
.tpd-skin-dark .tpd-background {
border-width: 1px;
border-color: rgba(255, 255, 255, 0.1);
}
/* line below the title */
.tpd-skin-dark .tpd-title-wrapper {
border-bottom: 1px solid #404040;
}
/* spinner */
.tpd-skin-dark .tpd-spinner-spin {
border-color: rgba(255, 255, 255, 0.2);
border-left-color: #fff;
}
/* links */
.tpd-skin-dark a {
color: #ccc;
}
.tpd-skin-dark a:hover {
color: #c0c0c0;
}
/* light */
.tpd-skin-light .tpd-content,
.tpd-skin-light .tpd-title,
.tpd-skin-light .tpd-close {
color: #333;
}
.tpd-skin-light .tpd-background-content {
background-color: #fff;
}
.tpd-skin-light .tpd-background {
border-width: 1px;
border-color: rgba(0, 0, 0, 0.3);
}
.tpd-skin-light .tpd-background-title {
background-color: #f7f7f7;
}
.tpd-skin-light .tpd-title-wrapper {
border-bottom: 1px solid #c0c0c0;
}
.tpd-skin-light .tpd-background-shadow {
box-shadow: 0 0 8px rgba(0, 0, 0, 0.15);
}
/* fallback for no/disabled shadow */
.tpd-skin-light.tpd-no-shadow .tpd-background {
border-color: rgba(100, 100, 100, 0.3);
}
.tpd-skin-light .tpd-spinner-spin {
border-color: rgba(51, 51, 51, 0.2);
border-left-color: #333;
}
.tpd-skin-light a {
color: #808080;
}
.tpd-skin-light a:hover {
color: #6c6c6c;
}
/* gray */
.tpd-skin-gray .tpd-content,
.tpd-skin-gray .tpd-title,
.tpd-skin-gray .tpd-close {
color: #fff;
}
.tpd-skin-gray .tpd-background-content,
.tpd-skin-gray .tpd-background-title {
background-color: #727272;
}
.tpd-skin-gray .tpd-background {
border-width: 1px;
border-color: rgba(255, 255, 255, 0.1);
}
.tpd-skin-gray .tpd-title-wrapper {
border-bottom: 1px solid #505050;
}
.tpd-skin-gray .tpd-spinner-spin {
border-color: rgba(255, 255, 255, 0.2);
border-left-color: #fff;
}
.tpd-skin-gray a {
color: #ccc;
}
.tpd-skin-gray a:hover {
color: #b6b6b6;
}
/* red */
.tpd-skin-red .tpd-content,
.tpd-skin-red .tpd-title,
.tpd-skin-red .tpd-close {
color: #fff;
}
.tpd-skin-red .tpd-background-content {
background-color: #e13c37;
}
.tpd-skin-red .tpd-background {
border-width: 1px;
border-color: rgba(12, 0, 0, 0.6);
}
.tpd-skin-red .tpd-background-title {
background-color: #e13c37;
}
.tpd-skin-red .tpd-title-wrapper {
border-bottom: 1px solid #a30500;
}
.tpd-skin-red .tpd-background-shadow {
box-shadow: 0 0 8px rgba(0, 0, 0, 0.15);
}
.tpd-skin-red .tpd-spinner-spin {
border-color: rgba(255, 255, 255, 0.2);
border-left-color: #fff;
}
.tpd-skin-red a {
color: #ddd;
}
.tpd-skin-red a:hover {
color: #c6c6c6;
}
/* green */
.tpd-skin-green .tpd-content,
.tpd-skin-green .tpd-title,
.tpd-skin-green .tpd-close {
color: #fff;
}
.tpd-skin-green .tpd-background-content {
background-color: #4aab3a;
}
.tpd-skin-green .tpd-background {
border-width: 1px;
border-color: rgba(0, 12, 0, 0.6);
}
.tpd-skin-green .tpd-background-title {
background-color: #4aab3a;
}
.tpd-skin-green .tpd-title-wrapper {
border-bottom: 1px solid #127c00;
}
.tpd-skin-green .tpd-background-shadow {
box-shadow: 0 0 8px rgba(0, 0, 0, 0.15);
}
.tpd-skin-green .tpd-spinner-spin {
border-color: rgba(255, 255, 255, 0.2);
border-left-color: #fff;
}
.tpd-skin-green a {
color: #ddd;
}
.tpd-skin-green a:hover {
color: #c6c6c6;
}
/* blue */
.tpd-skin-blue .tpd-content,
.tpd-skin-blue .tpd-title,
.tpd-skin-blue .tpd-close {
color: #fff;
}
.tpd-skin-blue .tpd-background-content {
background-color: #45a3e3;
}
.tpd-skin-blue .tpd-background {
border-width: 1px;
border-color: rgba(0, 0, 12, 0.6);
}
.tpd-skin-blue .tpd-background-title {
background-color: #45a3e3;
}
.tpd-skin-blue .tpd-title-wrapper {
border-bottom: 1px solid #1674b4;
}
.tpd-skin-blue .tpd-background-shadow {
box-shadow: 0 0 8px rgba(0, 0, 0, 0.15);
}
.tpd-skin-blue .tpd-spinner-spin {
border-color: rgba(255, 255, 255, 0.2);
border-left-color: #fff;
}
.tpd-skin-blue a {
color: #ddd;
}
.tpd-skin-blue a:hover {
color: #c6c6c6;
}
/* lightyellow */
.tpd-skin-lightyellow .tpd-content,
.tpd-skin-lightyellow .tpd-title,
.tpd-skin-lightyellow .tpd-close {
color: #333;
}
.tpd-skin-lightyellow .tpd-background-content {
background-color: #ffffa9;
}
.tpd-skin-lightyellow .tpd-background {
border-width: 1px;
border-color: rgba(8, 8, 0, 0.35);
}
.tpd-skin-lightyellow .tpd-background-title {
background-color: #ffffa9;
}
.tpd-skin-lightyellow .tpd-title-wrapper {
border-bottom: 1px solid #a7a697;
}
.tpd-skin-lightyellow .tpd-background-shadow {
box-shadow: 0 0 8px rgba(0, 0, 0, 0.15);
}
.tpd-skin-lightyellow .tpd-spinner-spin {
border-color: rgba(51, 51, 51, 0.2);
border-left-color: #333;
}
.tpd-skin-lightyellow a {
color: #777;
}
.tpd-skin-lightyellow a:hover {
color: #868686;
}
/* lightblue */
.tpd-skin-lightblue .tpd-content,
.tpd-skin-lightblue .tpd-title,
.tpd-skin-lightblue .tpd-close {
color: #333;
}
.tpd-skin-lightblue .tpd-background-content {
background-color: #bce5ff;
}
.tpd-skin-lightblue .tpd-background {
border-width: 1px;
border-color: rgba(0, 0, 8, 0.35);
}
.tpd-skin-lightblue .tpd-background-title {
background-color: #bce5ff;
}
.tpd-skin-lightblue .tpd-title-wrapper {
border-bottom: 1px solid #909b9f;
}
.tpd-skin-lightblue .tpd-background-shadow {
box-shadow: 0 0 8px rgba(0, 0, 0, 0.15);
}
.tpd-skin-lightblue .tpd-spinner-spin {
border-color: rgba(51, 51, 51, 0.2);
border-left-color: #333;
}
.tpd-skin-lightblue a {
color: #777;
}
.tpd-skin-lightblue a:hover {
color: #868686;
}
/* lightpink */
.tpd-skin-lightpink .tpd-content,
.tpd-skin-lightpink .tpd-title,
.tpd-skin-lightpink .tpd-close {
color: #333;
}
.tpd-skin-lightpink .tpd-background-content {
background-color: #ffc4bf;
}
.tpd-skin-lightpink .tpd-background {
border-width: 1px;
border-color: rgba(8, 0, 0, 0.35);
}
.tpd-skin-lightpink .tpd-background-title {
background-color: #ffc4bf;
}
.tpd-skin-lightpink .tpd-title-wrapper {
border-bottom: 1px solid #a08f8f;
}
.tpd-skin-lightpink .tpd-background-shadow {
box-shadow: 0 0 8px rgba(0, 0, 0, 0.15);
}
.tpd-skin-lightpink .tpd-spinner-spin {
border-color: rgba(51, 51, 51, 0.2);
border-left-color: #333;
}
.tpd-skin-lightpink a {
color: #777;
}
.tpd-skin-lightpink a:hover {
color: #868686;
}

File diff suppressed because one or more lines are too long

View File

@@ -1,97 +0,0 @@
package gitbucket.core.service
import gitbucket.core.model._
import gitbucket.core.service.IssuesService._
import org.scalatest.funsuite.AnyFunSuite
class IssuesServiceSpec extends AnyFunSuite with ServiceSpecBase {
test("getCommitStatues") {
withTestDB { implicit session =>
val user1 = generateNewUserWithDBRepository("user1", "repo1")
def getCommitStatues(issueId: Int) = dummyService.getCommitStatues("user1", "repo1", issueId)
assert(getCommitStatues(1) == None)
val now = new java.util.Date()
val issueId = generateNewIssue("user1", "repo1")
assert(issueId == 1)
assert(getCommitStatues(1) == None)
val cs = dummyService.createCommitStatus(
"user1",
"repo1",
"shasha",
"default",
CommitState.SUCCESS,
Some("http://exmple.com/ci"),
Some("exampleService"),
now,
user1
)
assert(getCommitStatues(1) == None)
val (is2, pr2) = generateNewPullRequest("user1/repo1/master", "user1/repo1/feature1", loginUser = "root")
assert(pr2.issueId == 2)
// if there are no statuses, state is none
assert(getCommitStatues(2) == None)
// 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
)
assert(
getCommitStatues(2) == Some(
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
)
assert(getCommitStatues(2) == Some(CommitStatusInfo(2, 1, None, None, None, None)))
// get only statuses in query issues
val (is3, pr3) = generateNewPullRequest("user1/repo1/master", "user1/repo1/feature3", loginUser = "root")
val cs4 = dummyService.createCommitStatus(
"user1",
"repo1",
"feature3",
"none",
CommitState.PENDING,
None,
None,
now,
user1
)
assert(getCommitStatues(2) == Some(CommitStatusInfo(2, 1, None, None, None, None)))
}
}
}