Merge pull request #1404 from team-lab/feature-create-an-issue-api

add api 'create an issue'
This commit is contained in:
Naoki Takezoe
2017-01-06 15:09:58 +09:00
committed by GitHub
7 changed files with 116 additions and 66 deletions

View File

@@ -0,0 +1,11 @@
package gitbucket.core.api
/**
* https://developer.github.com/v3/issues/#create-an-issue
*/
case class CreateAnIssue(
title: String,
body: Option[String],
assignees: List[String],
milestone: Option[Int],
labels: List[String])

View File

@@ -21,10 +21,12 @@ class ApiController extends ApiControllerBase
with ProtectedBranchService
with IssuesService
with LabelsService
with MilestonesService
with PullRequestService
with CommitsService
with CommitStatusService
with RepositoryCreationService
with IssueCreationService
with HandleCommentService
with WebHookService
with WebHookPullRequestService
@@ -44,9 +46,11 @@ trait ApiControllerBase extends ControllerBase {
with ProtectedBranchService
with IssuesService
with LabelsService
with MilestonesService
with PullRequestService
with CommitStatusService
with RepositoryCreationService
with IssueCreationService
with HandleCommentService
with OwnerAuthenticator
with UsersAuthenticator
@@ -296,6 +300,23 @@ trait ApiControllerBase extends ControllerBase {
}) getOrElse NotFound()
})
/**
* https://developer.github.com/v3/issues/#create-an-issue
*/
post("/api/v3/repos/:owner/:repository/issues")(readableUsersOnly { repository =>
if(isIssueEditable(repository)){ // TODO Should this check is provided by authenticator?
(for{
data <- extractFromJsonBody[CreateAnIssue]
loginAccount <- context.loginAccount
} yield {
val milestone = data.milestone.flatMap(getMilestone(repository.owner, repository.name, _))
val issue = createIssue(repository, data.title, data.body, data.assignees.headOption,
milestone.map(_.milestoneId), data.labels)
JsonFormat(ApiIssue(issue, RepositoryName(repository), ApiUser(loginAccount)))
}) getOrElse NotFound()
} else Unauthorized()
})
/**
* https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue
*/

View File

@@ -21,6 +21,7 @@ class IssuesController extends IssuesControllerBase
with MilestonesService
with ActivityService
with HandleCommentService
with IssueCreationService
with ReadableUsersAuthenticator
with ReferrerAuthenticator
with WritableUsersAuthenticator
@@ -36,6 +37,7 @@ trait IssuesControllerBase extends ControllerBase {
with MilestonesService
with ActivityService
with HandleCommentService
with IssueCreationService
with ReadableUsersAuthenticator
with ReferrerAuthenticator
with WritableUsersAuthenticator
@@ -91,7 +93,7 @@ trait IssuesControllerBase extends ControllerBase {
getAssignableUserNames(owner, name),
getMilestonesWithIssueCount(owner, name),
getLabels(owner, name),
isEditable(repository),
isIssueEditable(repository),
isManageable(repository),
repository)
} getOrElse NotFound()
@@ -99,7 +101,7 @@ trait IssuesControllerBase extends ControllerBase {
})
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
if(isEditable(repository)){ // TODO Should this check is provided by authenticator?
if(isIssueEditable(repository)){ // TODO Should this check is provided by authenticator?
defining(repository.owner, repository.name){ case (owner, name) =>
html.create(
getAssignableUserNames(owner, name),
@@ -112,46 +114,10 @@ trait IssuesControllerBase extends ControllerBase {
})
post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
if(isEditable(repository)){ // TODO Should this check is provided by authenticator?
defining(repository.owner, repository.name){ case (owner, name) =>
val manageable = isManageable(repository)
val userName = context.loginAccount.get.userName
// insert issue
val issueId = createIssue(owner, name, userName, form.title, form.content,
if (manageable) form.assignedUserName else None,
if (manageable) form.milestoneId else None)
// insert labels
if (manageable) {
form.labelNames.map { value =>
val labels = getLabels(owner, name)
value.split(",").foreach { labelName =>
labels.find(_.labelName == labelName).map { label =>
registerIssueLabel(owner, name, issueId, label.labelId)
}
}
}
}
// record activity
recordCreateIssueActivity(owner, name, userName, issueId, form.title)
getIssue(owner, name, issueId.toString).foreach { issue =>
// extract references and create refer comment
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get)
// call web hooks
callIssuesWebHook("opened", repository, issue, context.baseUrl, context.loginAccount.get)
// notifications
Notifier().toNotify(repository, issue, form.content.getOrElse("")) {
Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
}
}
redirect(s"/${owner}/${name}/issues/${issueId}")
}
if(isIssueEditable(repository)){ // TODO Should this check is provided by authenticator?
val issue = createIssue(repository, form.title, form.content, form.assignedUserName,
form.milestoneId, form.labelNames.toArray.flatMap(_.split(",")))
redirect(s"/${issue.userName}/${issue.repositoryName}/issues/${issue.issueId}")
} else Unauthorized()
})
@@ -398,35 +364,15 @@ trait IssuesControllerBase extends ControllerBase {
countIssue(condition.copy(state = "closed"), false, owner -> repoName),
condition,
repository,
isEditable(repository),
isIssueEditable(repository),
isManageable(repository))
}
}
/**
* Tests whether an logged-in user can manage issues.
*/
private def isManageable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
}
/**
* Tests whether an logged-in user can post issues.
*/
private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
repository.repository.options.issuesOption match {
case "ALL" => !repository.repository.isPrivate && context.loginAccount.isDefined
case "PUBLIC" => hasGuestRole(repository.owner, repository.name, context.loginAccount)
case "PRIVATE" => hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
case "DISABLE" => false
}
}
/**
* Tests whether an issue or a comment is editable by a logged-in user.
*/
private def isEditableContent(owner: String, repository: String, author: String)(implicit context: Context): Boolean = {
hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
}
}

View File

@@ -424,7 +424,7 @@ trait PullRequestsControllerBase extends ControllerBase {
if(editable) {
val loginUserName = context.loginAccount.get.userName
val issueId = createIssue(
val issueId = insertIssue(
owner = repository.owner,
repository = repository.name,
loginUser = loginUserName,

View File

@@ -0,0 +1,72 @@
package gitbucket.core.service
import gitbucket.core.controller.Context
import gitbucket.core.model.Issue
import gitbucket.core.model.Profile.profile.simple.Session
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.util.Notifier
import gitbucket.core.util.Implicits._
trait IssueCreationService {
self: RepositoryService with WebHookIssueCommentService with LabelsService with IssuesService with ActivityService =>
def createIssue(repository: RepositoryInfo, title:String, body:Option[String],
assiginee: Option[String], milestoneId: Option[Int], labelNames:Seq[String])(implicit context: Context, s:Session) : Issue = {
val name = repository.name
val owner = repository.owner
val loginAccount = context.loginAccount.get
val userName = loginAccount.userName
val manageable = isManageable(repository)
// insert issue
val issueId = insertIssue(owner, name, userName, title, body,
if (manageable) assiginee else None,
if (manageable) milestoneId else None)
val issue: Issue = getIssue(owner, name, issueId.toString).get
// insert labels
if (manageable) {
val labels = getLabels(owner, name)
labelNames.map { labelName =>
labels.find(_.labelName == labelName).map { label =>
registerIssueLabel(owner, name, issueId, label.labelId)
}
}
}
// record activity
recordCreateIssueActivity(owner, name, userName, issueId, title)
// extract references and create refer comment
createReferComment(owner, name, issue, title + " " + body.getOrElse(""), loginAccount)
// call web hooks
callIssuesWebHook("opened", repository, issue, context.baseUrl, loginAccount)
// notifications
Notifier().toNotify(repository, issue, body.getOrElse("")) {
Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
}
issue
}
/**
* Tests whether an logged-in user can manage issues.
*/
protected def isManageable(repository: RepositoryInfo)(implicit context: Context, s:Session): Boolean = {
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
}
/**
* Tests whether an logged-in user can post issues.
*/
protected def isIssueEditable(repository: RepositoryInfo)(implicit context: Context, s:Session): Boolean = {
repository.repository.options.issuesOption match {
case "ALL" => !repository.repository.isPrivate && context.loginAccount.isDefined
case "PUBLIC" => hasGuestRole(repository.owner, repository.name, context.loginAccount)
case "PRIVATE" => hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
case "DISABLE" => false
}
}
}

View File

@@ -269,7 +269,7 @@ trait IssuesService {
} exists), condition.mentioned.isDefined)
}
def createIssue(owner: String, repository: String, loginUser: String, title: String, content: Option[String],
def insertIssue(owner: String, repository: String, loginUser: String, title: String, content: Option[String],
assignedUserName: Option[String], milestoneId: Option[Int],
isPullRequest: Boolean = false)(implicit s: Session) =
// next id number

View File

@@ -53,7 +53,7 @@ trait ServiceSpecBase {
}
def generateNewIssue(userName:String, repositoryName:String, loginUser:String="root")(implicit s:Session): Int = {
dummyService.createIssue(
dummyService.insertIssue(
owner = userName,
repository = repositoryName,
loginUser = loginUser,