mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-08 22:45:51 +01:00
feature/can select webhook events
This commit is contained in:
22
src/main/resources/update/3_8.sql
Normal file
22
src/main/resources/update/3_8.sql
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
DROP TABLE IF EXISTS WEB_HOOK_EVENT;
|
||||||
|
|
||||||
|
CREATE TABLE WEB_HOOK_EVENT(
|
||||||
|
USER_NAME VARCHAR(100) NOT NULL,
|
||||||
|
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
||||||
|
URL VARCHAR(200) NOT NULL,
|
||||||
|
EVENT VARCHAR(30) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE WEB_HOOK_EVENT ADD CONSTRAINT IDX_WEB_HOOK_EVENT_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, URL, EVENT);
|
||||||
|
ALTER TABLE WEB_HOOK_EVENT ADD CONSTRAINT IDX_WEB_HOOK_EVENT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, URL) REFERENCES WEB_HOOK (USER_NAME, REPOSITORY_NAME, URL)
|
||||||
|
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
CREATE TEMPORARY TABLE TMP_EVENTS (EVENT VARCHAR(30));
|
||||||
|
|
||||||
|
INSERT INTO TMP_EVENTS VALUES ('push'),('issue_comment'),('issues'),('pull_request');
|
||||||
|
|
||||||
|
INSERT INTO WEB_HOOK_EVENT (USER_NAME, REPOSITORY_NAME, URL, EVENT)
|
||||||
|
SELECT USER_NAME, REPOSITORY_NAME, URL, EVENT
|
||||||
|
FROM WEB_HOOK, TMP_EVENTS;
|
||||||
|
|
||||||
|
DROP TABLE TMP_EVENTS;
|
||||||
@@ -14,6 +14,7 @@ import org.apache.commons.io.FileUtils
|
|||||||
import org.scalatra.i18n.Messages
|
import org.scalatra.i18n.Messages
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.lib.Constants
|
import org.eclipse.jgit.lib.Constants
|
||||||
|
import scala.util.{Success, Failure}
|
||||||
|
|
||||||
|
|
||||||
class RepositorySettingsController extends RepositorySettingsControllerBase
|
class RepositorySettingsController extends RepositorySettingsControllerBase
|
||||||
@@ -42,10 +43,11 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
)(CollaboratorForm.apply)
|
)(CollaboratorForm.apply)
|
||||||
|
|
||||||
// for web hook url addition
|
// for web hook url addition
|
||||||
case class WebHookForm(url: String)
|
case class WebHookForm(url: String, events: Set[WebHook.Event])
|
||||||
|
|
||||||
val webHookForm = mapping(
|
def webHookForm(update:Boolean) = mapping(
|
||||||
"url" -> trim(label("url", text(required, webHook)))
|
"url" -> trim(label("url", text(required, webHook(update)))),
|
||||||
|
"events" -> webhookEvents
|
||||||
)(WebHookForm.apply)
|
)(WebHookForm.apply)
|
||||||
|
|
||||||
// for transfer ownership
|
// for transfer ownership
|
||||||
@@ -138,14 +140,23 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
* Display the web hook page.
|
* Display the web hook page.
|
||||||
*/
|
*/
|
||||||
get("/:owner/:repository/settings/hooks")(ownerOnly { repository =>
|
get("/:owner/:repository/settings/hooks")(ownerOnly { repository =>
|
||||||
html.hooks(getWebHookURLs(repository.owner, repository.name), flash.get("url"), repository, flash.get("info"))
|
html.hooks(getWebHooks(repository.owner, repository.name), repository, flash.get("info"))
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the web hook edit page.
|
||||||
|
*/
|
||||||
|
get("/:owner/:repository/settings/hooks/new")(ownerOnly { repository =>
|
||||||
|
val webhook = WebHook(repository.owner, repository.name, "")
|
||||||
|
html.editHooks(webhook, Set(WebHook.Push), repository, flash.get("info"), true)
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the web hook URL.
|
* Add the web hook URL.
|
||||||
*/
|
*/
|
||||||
post("/:owner/:repository/settings/hooks/add", webHookForm)(ownerOnly { (form, repository) =>
|
post("/:owner/:repository/settings/hooks/new", webHookForm(false))(ownerOnly { (form, repository) =>
|
||||||
addWebHookURL(repository.owner, repository.name, form.url)
|
addWebHook(repository.owner, repository.name, form.url, form.events)
|
||||||
|
flash += "info" -> s"Webhook ${form.url} created"
|
||||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -153,30 +164,71 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
* Delete the web hook URL.
|
* Delete the web hook URL.
|
||||||
*/
|
*/
|
||||||
get("/:owner/:repository/settings/hooks/delete")(ownerOnly { repository =>
|
get("/:owner/:repository/settings/hooks/delete")(ownerOnly { repository =>
|
||||||
deleteWebHookURL(repository.owner, repository.name, params("url"))
|
deleteWebHook(repository.owner, repository.name, params("url"))
|
||||||
|
flash += "info" -> s"Webhook ${params("url")} deleted"
|
||||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send the test request to registered web hook URLs.
|
* Send the test request to registered web hook URLs.
|
||||||
*/
|
*/
|
||||||
post("/:owner/:repository/settings/hooks/test", webHookForm)(ownerOnly { (form, repository) =>
|
ajaxPost("/:owner/:repository/settings/hooks/test")(ownerOnly { repository =>
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
import scala.collection.JavaConverters._
|
import scala.collection.JavaConverters._
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
import scala.concurrent._
|
||||||
|
import scala.util.control.NonFatal
|
||||||
|
import org.apache.http.util.EntityUtils
|
||||||
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
|
||||||
|
val url = params("url")
|
||||||
val commits = if(repository.commitCount == 0) List.empty else git.log
|
val commits = if(repository.commitCount == 0) List.empty else git.log
|
||||||
.add(git.getRepository.resolve(repository.repository.defaultBranch))
|
.add(git.getRepository.resolve(repository.repository.defaultBranch))
|
||||||
.setMaxCount(3)
|
.setMaxCount(3)
|
||||||
.call.iterator.asScala.map(new CommitInfo(_))
|
.call.iterator.asScala.map(new CommitInfo(_))
|
||||||
|
val ownerAccount = getAccountByUserName(repository.owner).get
|
||||||
getAccountByUserName(repository.owner).foreach { ownerAccount =>
|
def headers(h: Array[org.apache.http.Header]): Array[Array[String]] = h.map{ h => Array(h.getName, h.getValue) }
|
||||||
callWebHook("push",
|
val toErrorMap:PartialFunction[Throwable, Map[String,String]] = {
|
||||||
List(WebHook(repository.owner, repository.name, form.url)),
|
case e:java.net.UnknownHostException => Map("error"-> ("Unknown host "+ e.getMessage))
|
||||||
WebHookPushPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, commits.toList, ownerAccount)
|
case e:java.lang.IllegalArgumentException => Map("error"-> ("invalid url"))
|
||||||
)
|
case e:org.apache.http.client.ClientProtocolException => Map("error"-> ("invalid url"))
|
||||||
|
case NonFatal(e) => Map("error"-> (e.getClass + " "+ e.getMessage))
|
||||||
}
|
}
|
||||||
flash += "url" -> form.url
|
val (webHook, json, reqFuture, resFuture) = callWebHook(WebHook.Push,
|
||||||
flash += "info" -> "Test payload deployed!"
|
List(WebHook(repository.owner, repository.name, url)),
|
||||||
|
WebHookPushPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, commits.toList, ownerAccount)
|
||||||
|
).head
|
||||||
|
contentType = formats("json")
|
||||||
|
var result = Map(
|
||||||
|
"url" -> url,
|
||||||
|
"request" -> Await.result(reqFuture.map(req => Map(
|
||||||
|
"headers" -> headers(req.getAllHeaders),
|
||||||
|
"payload" -> json
|
||||||
|
)).recover(toErrorMap), 20 seconds),
|
||||||
|
"responce" -> Await.result(resFuture.map(res => Map(
|
||||||
|
"status" -> res.getStatusLine(),
|
||||||
|
"body" -> EntityUtils.toString(res.getEntity()),
|
||||||
|
"headers" -> headers(res.getAllHeaders())
|
||||||
|
)).recover(toErrorMap), 20 seconds))
|
||||||
|
org.json4s.jackson.Serialization.write(result)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the web hook edit page.
|
||||||
|
*/
|
||||||
|
get("/:owner/:repository/settings/hooks/edit/:url")(ownerOnly { repository =>
|
||||||
|
getWebHook(repository.owner, repository.name, params("url")).map{ case (webhook, events) =>
|
||||||
|
html.editHooks(webhook, events, repository, flash.get("info"), false)
|
||||||
|
} getOrElse NotFound
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update web hook settings.
|
||||||
|
*/
|
||||||
|
post("/:owner/:repository/settings/hooks/edit/:url", webHookForm(true))(ownerOnly { (form, repository) =>
|
||||||
|
updateWebHook(repository.owner, repository.name, form.url, form.events)
|
||||||
|
flash += "info" -> s"webhook ${form.url} updated"
|
||||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -226,9 +278,28 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
/**
|
/**
|
||||||
* Provides duplication check for web hook url.
|
* Provides duplication check for web hook url.
|
||||||
*/
|
*/
|
||||||
private def webHook: Constraint = new Constraint(){
|
private def webHook(needExists: Boolean): Constraint = new Constraint(){
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||||
getWebHookURLs(params("owner"), params("repository")).map(_.url).find(_ == value).map(_ => "URL had been registered already.")
|
if(getWebHook(params("owner"), params("repository"), value).isDefined != needExists){
|
||||||
|
Some(if(needExists){
|
||||||
|
"URL had not been registered yet."
|
||||||
|
}else{
|
||||||
|
"URL had been registered already."
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def webhookEvents = new ValueType[Set[WebHook.Event]]{
|
||||||
|
def convert(name: String, params: Map[String, String], messages: Messages): Set[WebHook.Event] = WebHook.Event.values.flatMap{ t =>
|
||||||
|
params.get(name+"."+t.name).map(_ => t)
|
||||||
|
}.toSet
|
||||||
|
def validate(name: String, params: Map[String, String], messages: Messages): Seq[(String, String)] = if(convert(name,params,messages).isEmpty){
|
||||||
|
Seq(name -> messages("error.required").format(name))
|
||||||
|
}else{
|
||||||
|
Nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import gitbucket.core.util.StringUtil._
|
|||||||
import gitbucket.core.util.ControlUtil._
|
import gitbucket.core.util.ControlUtil._
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import gitbucket.core.model.{Account, CommitState}
|
import gitbucket.core.model.{Account, CommitState, WebHook}
|
||||||
import gitbucket.core.service.CommitStatusService
|
import gitbucket.core.service.CommitStatusService
|
||||||
import gitbucket.core.service.WebHookService._
|
import gitbucket.core.service.WebHookService._
|
||||||
import gitbucket.core.view
|
import gitbucket.core.view
|
||||||
@@ -663,7 +663,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
// call web hook
|
// call web hook
|
||||||
callPullRequestWebHookByRequestBranch("synchronize", repository, branch, context.baseUrl, loginAccount)
|
callPullRequestWebHookByRequestBranch("synchronize", repository, branch, context.baseUrl, loginAccount)
|
||||||
val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||||
callWebHookOf(repository.owner, repository.name, "push") {
|
callWebHookOf(repository.owner, repository.name, WebHook.Push) {
|
||||||
getAccountByUserName(repository.owner).map{ ownerAccount =>
|
getAccountByUserName(repository.owner).map{ ownerAccount =>
|
||||||
WebHookPushPayload(git, loginAccount, headName, repository, List(commit), ownerAccount)
|
WebHookPushPayload(git, loginAccount, headName, repository, List(commit), ownerAccount)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ trait CoreProfile extends ProfileProvider with Profile
|
|||||||
with RepositoryComponent
|
with RepositoryComponent
|
||||||
with SshKeyComponent
|
with SshKeyComponent
|
||||||
with WebHookComponent
|
with WebHookComponent
|
||||||
|
with WebHookEventComponent
|
||||||
with PluginComponent
|
with PluginComponent
|
||||||
|
|
||||||
object Profile extends CoreProfile
|
object Profile extends CoreProfile
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ trait WebHookComponent extends TemplateComponent { self: Profile =>
|
|||||||
|
|
||||||
class WebHooks(tag: Tag) extends Table[WebHook](tag, "WEB_HOOK") with BasicTemplate {
|
class WebHooks(tag: Tag) extends Table[WebHook](tag, "WEB_HOOK") with BasicTemplate {
|
||||||
val url = column[String]("URL")
|
val url = column[String]("URL")
|
||||||
def * = (userName, repositoryName, url) <> (WebHook.tupled, WebHook.unapply)
|
def * = (userName, repositoryName, url) <> ((WebHook.apply _).tupled, WebHook.unapply)
|
||||||
|
|
||||||
def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind)
|
def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind)
|
||||||
}
|
}
|
||||||
@@ -18,3 +18,32 @@ case class WebHook(
|
|||||||
repositoryName: String,
|
repositoryName: String,
|
||||||
url: String
|
url: String
|
||||||
)
|
)
|
||||||
|
|
||||||
|
object WebHook {
|
||||||
|
sealed class Event(var name: String)
|
||||||
|
case object CommitComment extends Event("commit_comment")
|
||||||
|
case object Create extends Event("create")
|
||||||
|
case object Delete extends Event("delete")
|
||||||
|
case object Deployment extends Event("deployment")
|
||||||
|
case object DeploymentStatus extends Event("deployment_status")
|
||||||
|
case object Fork extends Event("fork")
|
||||||
|
case object Gollum extends Event("gollum")
|
||||||
|
case object IssueComment extends Event("issue_comment")
|
||||||
|
case object Issues extends Event("issues")
|
||||||
|
case object Member extends Event("member")
|
||||||
|
case object PageBuild extends Event("page_build")
|
||||||
|
case object Public extends Event("public")
|
||||||
|
case object PullRequest extends Event("pull_request")
|
||||||
|
case object PullRequestReviewComment extends Event("pull_request_review_comment")
|
||||||
|
case object Push extends Event("push")
|
||||||
|
case object Release extends Event("release")
|
||||||
|
case object Status extends Event("status")
|
||||||
|
case object TeamAdd extends Event("team_add")
|
||||||
|
case object Watch extends Event("watch")
|
||||||
|
object Event{
|
||||||
|
val values = List(CommitComment,Create,Delete,Deployment,DeploymentStatus,Fork,Gollum,IssueComment,Issues,Member,PageBuild,Public,PullRequest,PullRequestReviewComment,Push,Release,Status,TeamAdd,Watch)
|
||||||
|
private val map:Map[String,Event] = values.map(e => e.name -> e).toMap
|
||||||
|
def valueOf(name: String): Event = map(name)
|
||||||
|
def valueOpt(name: String): Option[Event] = map.get(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
30
src/main/scala/gitbucket/core/model/WebHookEvent.scala
Normal file
30
src/main/scala/gitbucket/core/model/WebHookEvent.scala
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package gitbucket.core.model
|
||||||
|
|
||||||
|
trait WebHookEventComponent extends TemplateComponent { self: Profile =>
|
||||||
|
import profile.simple._
|
||||||
|
import gitbucket.core.model.Profile.WebHooks
|
||||||
|
|
||||||
|
lazy val WebHookEvents = TableQuery[WebHookEvents]
|
||||||
|
|
||||||
|
implicit val typedType = MappedColumnType.base[WebHook.Event, String](_.name, WebHook.Event.valueOf(_))
|
||||||
|
|
||||||
|
class WebHookEvents(tag: Tag) extends Table[WebHookEvent](tag, "WEB_HOOK_EVENT") with BasicTemplate {
|
||||||
|
val url = column[String]("URL")
|
||||||
|
val event = column[WebHook.Event]("EVENT")
|
||||||
|
def * = (userName, repositoryName, url, event) <> ((WebHookEvent.apply _).tupled, WebHookEvent.unapply)
|
||||||
|
|
||||||
|
def byWebHook(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind)
|
||||||
|
def byWebHook(owner: Column[String], repository: Column[String], url: Column[String]) =
|
||||||
|
byRepository(userName, repositoryName) && (this.url === url)
|
||||||
|
def byWebHook(webhook: WebHooks) =
|
||||||
|
byRepository(webhook.userName, webhook.repositoryName) && (this.url === webhook.url)
|
||||||
|
def byPrimaryKey(owner: String, repository: String, url: String, event: WebHook.Event) = byWebHook(owner, repository, url) && (this.event === event.bind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class WebHookEvent(
|
||||||
|
userName: String,
|
||||||
|
repositoryName: String,
|
||||||
|
url: String,
|
||||||
|
event: WebHook.Event
|
||||||
|
)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package gitbucket.core.service
|
package gitbucket.core.service
|
||||||
|
|
||||||
import gitbucket.core.api._
|
import gitbucket.core.api._
|
||||||
import gitbucket.core.model.{WebHook, Account, Issue, PullRequest, IssueComment}
|
import gitbucket.core.model.{WebHook, Account, Issue, PullRequest, IssueComment, WebHookEvent}
|
||||||
import gitbucket.core.model.Profile._
|
import gitbucket.core.model.Profile._
|
||||||
import profile.simple._
|
import profile.simple._
|
||||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||||
@@ -13,6 +13,9 @@ import org.apache.http.client.entity.UrlEncodedFormEntity
|
|||||||
import org.apache.http.message.BasicNameValuePair
|
import org.apache.http.message.BasicNameValuePair
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
import scala.concurrent._
|
||||||
|
import org.apache.http.HttpRequest
|
||||||
|
import org.apache.http.HttpResponse
|
||||||
|
|
||||||
|
|
||||||
trait WebHookService {
|
trait WebHookService {
|
||||||
@@ -20,46 +23,91 @@ trait WebHookService {
|
|||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[WebHookService])
|
private val logger = LoggerFactory.getLogger(classOf[WebHookService])
|
||||||
|
|
||||||
def getWebHookURLs(owner: String, repository: String)(implicit s: Session): List[WebHook] =
|
/** get All WebHook informations of repository */
|
||||||
WebHooks.filter(_.byRepository(owner, repository)).sortBy(_.url).list
|
def getWebHooks(owner: String, repository: String)(implicit s: Session): List[(WebHook, Set[WebHook.Event])] =
|
||||||
|
WebHooks.filter(_.byRepository(owner, repository))
|
||||||
|
.innerJoin(WebHookEvents).on { (w, t) => t.byWebHook(w) }
|
||||||
|
.map{ case (w,t) => w -> t.event }
|
||||||
|
.list.groupBy(_._1).mapValues(_.map(_._2).toSet).toList.sortBy(_._1.url)
|
||||||
|
|
||||||
def addWebHookURL(owner: String, repository: String, url :String)(implicit s: Session): Unit =
|
/** get All WebHook informations of repository event */
|
||||||
|
def getWebHooksByEvent(owner: String, repository: String, event: WebHook.Event)(implicit s: Session): List[WebHook] =
|
||||||
|
WebHookEvents.filter(t => t.byRepository(owner, repository) && t.event === event.bind)
|
||||||
|
.list.map(t => WebHook(t.userName, t.repositoryName, t.url))
|
||||||
|
|
||||||
|
/** get All WebHook information from repository to url */
|
||||||
|
def getWebHook(owner: String, repository: String, url: String)(implicit s: Session): Option[(WebHook, Set[WebHook.Event])] =
|
||||||
|
WebHooks
|
||||||
|
.filter(_.byPrimaryKey(owner, repository, url))
|
||||||
|
.innerJoin(WebHookEvents).on { (w, t) => t.byWebHook(w) }
|
||||||
|
.map{ case (w,t) => w -> t.event }
|
||||||
|
.list.groupBy(_._1).mapValues(_.map(_._2).toSet).headOption
|
||||||
|
|
||||||
|
def addWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event])(implicit s: Session): Unit = {
|
||||||
WebHooks insert WebHook(owner, repository, url)
|
WebHooks insert WebHook(owner, repository, url)
|
||||||
|
events.toSet.map{ event: WebHook.Event =>
|
||||||
def deleteWebHookURL(owner: String, repository: String, url :String)(implicit s: Session): Unit =
|
WebHookEvents insert WebHookEvent(owner, repository, url, event)
|
||||||
WebHooks.filter(_.byPrimaryKey(owner, repository, url)).delete
|
|
||||||
|
|
||||||
def callWebHookOf(owner: String, repository: String, eventName: String)(makePayload: => Option[WebHookPayload])(implicit s: Session, c: JsonFormat.Context): Unit = {
|
|
||||||
val webHookURLs = getWebHookURLs(owner, repository)
|
|
||||||
if(webHookURLs.nonEmpty){
|
|
||||||
makePayload.map(callWebHook(eventName, webHookURLs, _))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def callWebHook(eventName: String, webHookURLs: List[WebHook], payload: WebHookPayload)(implicit c: JsonFormat.Context): Unit = {
|
def updateWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event])(implicit s: Session): Unit = {
|
||||||
import org.apache.http.client.methods.HttpPost
|
WebHookEvents.filter(_.byWebHook(owner, repository, url)).delete
|
||||||
|
events.toSet.map{ event: WebHook.Event =>
|
||||||
|
WebHookEvents insert WebHookEvent(owner, repository, url, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def deleteWebHook(owner: String, repository: String, url :String)(implicit s: Session): Unit =
|
||||||
|
WebHooks.filter(_.byPrimaryKey(owner, repository, url)).delete
|
||||||
|
|
||||||
|
def callWebHookOf(owner: String, repository: String, event: WebHook.Event)(makePayload: => Option[WebHookPayload])(implicit s: Session, c: JsonFormat.Context): Unit = {
|
||||||
|
val webHooks = getWebHooksByEvent(owner, repository, event)
|
||||||
|
if(webHooks.nonEmpty){
|
||||||
|
makePayload.map(callWebHook(event, webHooks, _))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def callWebHook(event: WebHook.Event, webHookURLs: List[WebHook], payload: WebHookPayload)(implicit c: JsonFormat.Context): List[(WebHook, String, Future[HttpRequest], Future[HttpResponse])] = {
|
||||||
import org.apache.http.impl.client.HttpClientBuilder
|
import org.apache.http.impl.client.HttpClientBuilder
|
||||||
import scala.concurrent._
|
|
||||||
import ExecutionContext.Implicits.global
|
import ExecutionContext.Implicits.global
|
||||||
|
import org.apache.http.protocol.HttpContext
|
||||||
|
import org.apache.http.client.methods.HttpPost
|
||||||
|
|
||||||
if(webHookURLs.nonEmpty){
|
if(webHookURLs.nonEmpty){
|
||||||
val json = JsonFormat(payload)
|
val json = JsonFormat(payload)
|
||||||
val httpClient = HttpClientBuilder.create.build
|
|
||||||
|
|
||||||
webHookURLs.foreach { webHookUrl =>
|
webHookURLs.map { webHookUrl =>
|
||||||
|
val reqPromise = Promise[HttpRequest]
|
||||||
val f = Future {
|
val f = Future {
|
||||||
logger.debug(s"start web hook invocation for ${webHookUrl}")
|
val itcp = new org.apache.http.HttpRequestInterceptor{
|
||||||
val httpPost = new HttpPost(webHookUrl.url)
|
def process(res: HttpRequest, ctx: HttpContext): Unit = {
|
||||||
httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded")
|
reqPromise.success(res)
|
||||||
httpPost.addHeader("X-Github-Event", eventName)
|
}
|
||||||
|
}
|
||||||
|
try{
|
||||||
|
val httpClient = HttpClientBuilder.create.addInterceptorLast(itcp).build
|
||||||
|
logger.debug(s"start web hook invocation for ${webHookUrl.url}")
|
||||||
|
val httpPost = new HttpPost(webHookUrl.url)
|
||||||
|
httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
httpPost.addHeader("X-Github-Event", event.name)
|
||||||
|
httpPost.addHeader("X-Github-Delivery", java.util.UUID.randomUUID().toString)
|
||||||
|
|
||||||
val params: java.util.List[NameValuePair] = new java.util.ArrayList()
|
val params: java.util.List[NameValuePair] = new java.util.ArrayList()
|
||||||
params.add(new BasicNameValuePair("payload", json))
|
params.add(new BasicNameValuePair("payload", json))
|
||||||
httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"))
|
httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"))
|
||||||
|
|
||||||
httpClient.execute(httpPost)
|
val res = httpClient.execute(httpPost)
|
||||||
httpPost.releaseConnection()
|
httpPost.releaseConnection()
|
||||||
logger.debug(s"end web hook invocation for ${webHookUrl}")
|
logger.debug(s"end web hook invocation for ${webHookUrl}")
|
||||||
|
res
|
||||||
|
}catch{
|
||||||
|
case e:Throwable => {
|
||||||
|
if(!reqPromise.isCompleted){
|
||||||
|
reqPromise.failure(e)
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
f.onSuccess {
|
f.onSuccess {
|
||||||
case s => logger.debug(s"Success: web hook request to ${webHookUrl.url}")
|
case s => logger.debug(s"Success: web hook request to ${webHookUrl.url}")
|
||||||
@@ -67,9 +115,12 @@ trait WebHookService {
|
|||||||
f.onFailure {
|
f.onFailure {
|
||||||
case t => logger.error(s"Failed: web hook request to ${webHookUrl.url}", t)
|
case t => logger.error(s"Failed: web hook request to ${webHookUrl.url}", t)
|
||||||
}
|
}
|
||||||
|
(webHookUrl, json, reqPromise.future, f)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Nil
|
||||||
}
|
}
|
||||||
logger.debug("end callWebHook")
|
// logger.debug("end callWebHook")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +131,7 @@ trait WebHookPullRequestService extends WebHookService {
|
|||||||
import WebHookService._
|
import WebHookService._
|
||||||
// https://developer.github.com/v3/activity/events/types/#issuesevent
|
// https://developer.github.com/v3/activity/events/types/#issuesevent
|
||||||
def callIssuesWebHook(action: String, repository: RepositoryService.RepositoryInfo, issue: Issue, baseUrl: String, sender: Account)(implicit s: Session, context:JsonFormat.Context): Unit = {
|
def callIssuesWebHook(action: String, repository: RepositoryService.RepositoryInfo, issue: Issue, baseUrl: String, sender: Account)(implicit s: Session, context:JsonFormat.Context): Unit = {
|
||||||
callWebHookOf(repository.owner, repository.name, "issues"){
|
callWebHookOf(repository.owner, repository.name, WebHook.Issues){
|
||||||
val users = getAccountsByUserNames(Set(repository.owner, issue.openedUserName), Set(sender))
|
val users = getAccountsByUserNames(Set(repository.owner, issue.openedUserName), Set(sender))
|
||||||
for{
|
for{
|
||||||
repoOwner <- users.get(repository.owner)
|
repoOwner <- users.get(repository.owner)
|
||||||
@@ -98,7 +149,7 @@ trait WebHookPullRequestService extends WebHookService {
|
|||||||
|
|
||||||
def callPullRequestWebHook(action: String, repository: RepositoryService.RepositoryInfo, issueId: Int, baseUrl: String, sender: Account)(implicit s: Session, context:JsonFormat.Context): Unit = {
|
def callPullRequestWebHook(action: String, repository: RepositoryService.RepositoryInfo, issueId: Int, baseUrl: String, sender: Account)(implicit s: Session, context:JsonFormat.Context): Unit = {
|
||||||
import WebHookService._
|
import WebHookService._
|
||||||
callWebHookOf(repository.owner, repository.name, "pull_request"){
|
callWebHookOf(repository.owner, repository.name, WebHook.PullRequest){
|
||||||
for{
|
for{
|
||||||
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
|
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
|
||||||
users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set(sender))
|
users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set(sender))
|
||||||
@@ -134,6 +185,7 @@ trait WebHookPullRequestService extends WebHookService {
|
|||||||
ru <- Accounts if ru.userName === pr.requestUserName
|
ru <- Accounts if ru.userName === pr.requestUserName
|
||||||
iu <- Accounts if iu.userName === is.openedUserName
|
iu <- Accounts if iu.userName === is.openedUserName
|
||||||
wh <- WebHooks if wh.byRepository(is.userName , is.repositoryName)
|
wh <- WebHooks if wh.byRepository(is.userName , is.repositoryName)
|
||||||
|
wht <- WebHookEvents if wht.event === WebHook.PullRequest.asInstanceOf[WebHook.Event].bind && wht.byWebHook(wh)
|
||||||
} yield {
|
} yield {
|
||||||
((is, iu, pr, bu, ru), wh)
|
((is, iu, pr, bu, ru), wh)
|
||||||
}).list.groupBy(_._1).mapValues(_.map(_._2))
|
}).list.groupBy(_._1).mapValues(_.map(_._2))
|
||||||
@@ -154,7 +206,7 @@ trait WebHookPullRequestService extends WebHookService {
|
|||||||
baseRepository = baseRepo,
|
baseRepository = baseRepo,
|
||||||
baseOwner = baseOwner,
|
baseOwner = baseOwner,
|
||||||
sender = sender)
|
sender = sender)
|
||||||
callWebHook("pull_request", webHooks, payload)
|
callWebHook(WebHook.PullRequest, webHooks, payload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,7 +216,7 @@ trait WebHookIssueCommentService extends WebHookPullRequestService {
|
|||||||
|
|
||||||
import WebHookService._
|
import WebHookService._
|
||||||
def callIssueCommentWebHook(repository: RepositoryService.RepositoryInfo, issue: Issue, issueCommentId: Int, sender: Account)(implicit s: Session, context:JsonFormat.Context): Unit = {
|
def callIssueCommentWebHook(repository: RepositoryService.RepositoryInfo, issue: Issue, issueCommentId: Int, sender: Account)(implicit s: Session, context:JsonFormat.Context): Unit = {
|
||||||
callWebHookOf(repository.owner, repository.name, "issue_comment"){
|
callWebHookOf(repository.owner, repository.name, WebHook.IssueComment){
|
||||||
for{
|
for{
|
||||||
issueComment <- getComment(repository.owner, repository.name, issueCommentId.toString())
|
issueComment <- getComment(repository.owner, repository.name, issueCommentId.toString())
|
||||||
users = getAccountsByUserNames(Set(issue.openedUserName, repository.owner, issueComment.commentedUserName), Set(sender))
|
users = getAccountsByUserNames(Set(issue.openedUserName, repository.owner, issueComment.commentedUserName), Set(sender))
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ object AutoUpdate {
|
|||||||
* The history of versions. A head of this sequence is the current BitBucket version.
|
* The history of versions. A head of this sequence is the current BitBucket version.
|
||||||
*/
|
*/
|
||||||
val versions = Seq(
|
val versions = Seq(
|
||||||
|
new Version(3, 8),
|
||||||
new Version(3, 7) with SystemSettingsService {
|
new Version(3, 7) with SystemSettingsService {
|
||||||
override def update(conn: Connection, cl: ClassLoader): Unit = {
|
override def update(conn: Connection, cl: ClassLoader): Unit = {
|
||||||
super.update(conn, cl)
|
super.update(conn, cl)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package gitbucket.core.servlet
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
import gitbucket.core.api
|
import gitbucket.core.api
|
||||||
import gitbucket.core.model.Session
|
import gitbucket.core.model.{Session, WebHook}
|
||||||
import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
|
import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
|
||||||
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
||||||
import gitbucket.core.service.WebHookService._
|
import gitbucket.core.service.WebHookService._
|
||||||
@@ -200,7 +200,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// call web hook
|
// call web hook
|
||||||
callWebHookOf(owner, repository, "push"){
|
callWebHookOf(owner, repository, WebHook.Push){
|
||||||
for(pusherAccount <- getAccountByUserName(pusher);
|
for(pusherAccount <- getAccountByUserName(pusher);
|
||||||
ownerAccount <- getAccountByUserName(owner)) yield {
|
ownerAccount <- getAccountByUserName(owner)) yield {
|
||||||
WebHookPushPayload(git, pusherAccount, command.getRefName, repositoryInfo, newCommits, ownerAccount)
|
WebHookPushPayload(git, pusherAccount, command.getRefName, repositoryInfo, newCommits, ownerAccount)
|
||||||
|
|||||||
165
src/main/twirl/gitbucket/core/settings/editHooks.scala.html
Normal file
165
src/main/twirl/gitbucket/core/settings/editHooks.scala.html
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
@(webHook: gitbucket.core.model.WebHook,
|
||||||
|
events: Set[gitbucket.core.model.WebHook.Event],
|
||||||
|
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||||
|
info: Option[Any],
|
||||||
|
create: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||||
|
@import context._
|
||||||
|
@import gitbucket.core.view.helpers._
|
||||||
|
@import gitbucket.core.model.WebHook._
|
||||||
|
@check(name: String, event: Event)={
|
||||||
|
name="@(name).@event.name" value="on" @if(events(event)){checked}
|
||||||
|
}
|
||||||
|
@html.main("Settings", Some(repository)){
|
||||||
|
@html.menu("settings", repository){
|
||||||
|
@menu("hooks", repository){
|
||||||
|
@helper.html.information(info)
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header">
|
||||||
|
Webhook / Manage webhook
|
||||||
|
</div>
|
||||||
|
<div class="box-content">
|
||||||
|
<form method="POST" validate="true">
|
||||||
|
<div>
|
||||||
|
<span class="error" id="error-url"></span>
|
||||||
|
</div>
|
||||||
|
<label class="strong">Payload URL</label>
|
||||||
|
@if(create){
|
||||||
|
<input type="text" name="url" id="url" value="@webHook.url" class="input-xxlarge" style="margin-bottom: 0px;" required />
|
||||||
|
}else{
|
||||||
|
<input type="text" value="@webHook.url" style="margin-bottom: 0px;" class="input-xxlarge" disabled />
|
||||||
|
<input type="hidden" value="@webHook.url" name="url" />
|
||||||
|
}
|
||||||
|
<button class="btn" id="test">Test Hook</button>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<div>
|
||||||
|
<span class="error" id="error-events"></span>
|
||||||
|
</div>
|
||||||
|
<label class="strong">Which events would you like to trigger this webhook?</label>
|
||||||
|
<!--
|
||||||
|
<label class="checkbox"><input type="checkbox" @check("events",CommitComment) />Commit comment <small class="help-block">Commit or diff commented on. </small> </label>
|
||||||
|
<label class="checkbox"><input type="checkbox" @check("events",Create) />Create <small class="help-block">Branch, or tag created. </small> </label>
|
||||||
|
<label class="checkbox"><input type="checkbox" @check("events",Delete) />Delete <small class="help-block">Branch, or tag deleted. </small> </label>
|
||||||
|
<label class="checkbox"><input type="checkbox" @check("events",Deployment) />Deployment <small class="help-block">Repository deployed. </small> </label>
|
||||||
|
<label class="checkbox"><input type="checkbox" @check("events",DeploymentStatus) />Deployment status <small class="help-block">Deployment status updated from the API. </small> </label>
|
||||||
|
<label class="checkbox"><input type="checkbox" @check("events",Fork) />Fork <small class="help-block">Repository forked. </small> </label>
|
||||||
|
<label class="checkbox"><input type="checkbox" @check("events",Gollum) />Gollum <small class="help-block">Wiki page updated. </small> </label>
|
||||||
|
<label class="checkbox"><input type="checkbox" @check("events",PullRequestReviewComment) />Pull Request review comment <small class="help-block">Pull Request diff commented on. </small> </label>
|
||||||
|
<label class="checkbox"><input type="checkbox" @check("events",Member) />Member <small class="help-block">Collaborator added to a non-organization repository. </small> </label>
|
||||||
|
<label class="checkbox"><input type="checkbox" @check("events",PageBuild) />Page build <small class="help-block">Pages site built. </small> </label>
|
||||||
|
<label class="checkbox"><input type="checkbox" @check("events",Public) />Public <small class="help-block">Repository changes from private to public. </small> </label>
|
||||||
|
<label class="checkbox"><input type="checkbox" @check("events",Release) />Release <small class="help-block">Release published in a repository. </small> </label>
|
||||||
|
<label class="checkbox"><input type="checkbox" @check("events",TeamAdd) />Team add <small class="help-block">Team added or modified on a repository. </small> </label>
|
||||||
|
<label class="checkbox"><input type="checkbox" @check("events",Watch) />Watch <small class="help-block">User stars a repository.</small></label>
|
||||||
|
<label class="checkbox"><input type="checkbox" @check("events",Status) />Status <small class="help-block">Commit status updated from the API. </small> </label>
|
||||||
|
-->
|
||||||
|
<label class="checkbox"><input type="checkbox" @check("events",IssueComment) />Issue comment <small class="help-block">Issue commented on. </small> </label>
|
||||||
|
<label class="checkbox"><input type="checkbox" @check("events",Issues) />Issues <small class="help-block">Issue opened, closed<!-- , assigned, or labeled -->. </small> </label>
|
||||||
|
<label class="checkbox"><input type="checkbox" @check("events",PullRequest) />Pull Request <small class="help-block">Pull Request opened, closed<!-- , assigned, labeled -->, or synchronized. </small> </label>
|
||||||
|
<label class="checkbox"><input type="checkbox" @check("events",Push) />Push <small class="help-block">Git push to a repository. </small> </label>
|
||||||
|
<hr />
|
||||||
|
@if(!create){
|
||||||
|
<input type="submit" class="btn btn-success" value="Update webhook" formaction="@url(repository)/settings/hooks/edit/@urlEncode(webHook.url)" />
|
||||||
|
<a href="@url(repository)/settings/hooks/delete?url=@urlEncode(webHook.url)" class="btn text-error" onclick="return confirm('delete webhook for @webHook.url ?')">
|
||||||
|
Delete webhook
|
||||||
|
</a>
|
||||||
|
}else{
|
||||||
|
<input type="submit" class="btn" value="Add webhook" formaction="@url(repository)/settings/hooks/new" />
|
||||||
|
}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal hide" id="test-report-modal">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h3>WebHook Test</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>request to <span id="test-modal-url" style="word-break: break-all; word-wrap: break-word; white-space: pre; white-space: pre-wrap;"></span></p>
|
||||||
|
<div id="test-report" style="display:none">
|
||||||
|
<ul class="nav nav-tabs" id="test-report-tab">
|
||||||
|
<li class="active"><a href="#REQUEST">REQUEST</a></li>
|
||||||
|
<li><a href="#RESPONCE">RESPONCE <span class="badge badge-success" id="res-status"></span></a></li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content" style="max-height: 300px;">
|
||||||
|
<div class="tab-pane active" id="REQUEST">
|
||||||
|
<div id="req-errors" class="alert alert-error">
|
||||||
|
ERROR<span id="req-errors-body"></span>
|
||||||
|
</div>
|
||||||
|
<div id="req-success" style="display:none">
|
||||||
|
Headers
|
||||||
|
<pre id="req-headers"></pre>
|
||||||
|
Payload
|
||||||
|
<pre id="req-payload"></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane" id="RESPONCE">
|
||||||
|
<div id="res-errors" class="alert alert-error">
|
||||||
|
ERROR<span id="res-errors-body"></span>
|
||||||
|
</div>
|
||||||
|
<div id="res-success" style="display:none">
|
||||||
|
Headers
|
||||||
|
<pre id="res-headers"></pre>
|
||||||
|
Body
|
||||||
|
<pre id="res-body"></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
$(function(){
|
||||||
|
$('#test-report-tab a').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$(this).tab('show');
|
||||||
|
});
|
||||||
|
$('#test').click(function(e){
|
||||||
|
e.stopPropagation();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
var url = this.form.url.value;
|
||||||
|
if(!/^https?:\/\/.+/.test(url)){
|
||||||
|
alert("invalid url");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$("#test-modal-url").text(url)
|
||||||
|
$("#test-report-modal").modal('show')
|
||||||
|
$("#test-report").hide();
|
||||||
|
$.ajax({
|
||||||
|
method:'POST',
|
||||||
|
url:'@url(repository)/settings/hooks/test?url='+encodeURIComponent(url),
|
||||||
|
success:function(e){
|
||||||
|
console.log(e);
|
||||||
|
$('#test-report-tab a:first').tab('show');
|
||||||
|
$("#test-report").show();
|
||||||
|
$("#req-success").toggle(e.request&&!e.request.error);
|
||||||
|
$("#req-errors").toggle(e.request&&!!e.request.error);
|
||||||
|
$("#req-errors-body").text(e.request.error);
|
||||||
|
function headers(h){
|
||||||
|
h = h["headers"];
|
||||||
|
return h ? $.map(h,function(h){
|
||||||
|
return $("<div>").append($('<b>').text(h[0]+":"),$('<span>').text(" "+h[1]))
|
||||||
|
}):"";
|
||||||
|
}
|
||||||
|
$("#req-headers").html(headers(e.request));
|
||||||
|
$("#req-payload").text(e.request && e.request.payload ? JSON.stringify(JSON.parse(e.request.payload),undefined,4) : "");
|
||||||
|
$("#res-success").toggle(e.responce&&!e.responce.error);
|
||||||
|
$("#res-errors").toggle(e.responce&&!!e.responce.error);
|
||||||
|
$("#res-errors-body").text(e.responce.error);
|
||||||
|
var success = !!(e.responce && e.responce.status && /^2\d\d$/.test(e.responce.status.statusCode));
|
||||||
|
$("#res-status").text((e.responce && e.responce.status && e.responce.status.statusCode) || "ERROR");
|
||||||
|
$("#res-status").toggleClass("badge-success", success).toggleClass("badge-important", !success);
|
||||||
|
$("#res-headers").html(headers(e.responce));
|
||||||
|
$("#res-body").text(e.responce && e.responce.body ? e.responce.body : "");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
@(webHooks: List[gitbucket.core.model.WebHook],
|
@(webHooks: List[(gitbucket.core.model.WebHook, Set[gitbucket.core.model.WebHook.Event])],
|
||||||
enteredUrl: Option[Any],
|
|
||||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||||
info: Option[Any])(implicit context: gitbucket.core.controller.Context)
|
info: Option[Any])(implicit context: gitbucket.core.controller.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@@ -8,20 +7,42 @@
|
|||||||
@html.menu("settings", repository){
|
@html.menu("settings", repository){
|
||||||
@menu("hooks", repository){
|
@menu("hooks", repository){
|
||||||
@helper.html.information(info)
|
@helper.html.information(info)
|
||||||
<h3>WebHook URLs</h3>
|
|
||||||
<ul>
|
<div class="box">
|
||||||
@webHooks.map { webHook =>
|
<div class="box-header">
|
||||||
<li>@webHook.url <a href="@url(repository)/settings/hooks/delete?url=@urlEncode(webHook.url)" class="remove">(remove)</a></li>
|
<a href="@url(repository)/settings/hooks/new" class="btn btn-small pull-right">Add webhook</a>
|
||||||
}
|
Webhooks
|
||||||
</ul>
|
</div>
|
||||||
<form method="POST" validate="true">
|
<div class="box-content">
|
||||||
<div>
|
<p>
|
||||||
<span class="error" id="error-url"></span>
|
Webhooks allow external services to be notified when certain events happen within your repository.
|
||||||
</div>
|
When the specified events happen, we’ll send a POST request to each of the URLs you provide.
|
||||||
<input type="text" name="url" id="url" value="@enteredUrl" style="width: 300px; margin-bottom: 0px;"/>
|
Learn more in <a href="https://github.com/takezoe/gitbucket/wiki/API-WebHook" target="_blank">GitBucket Wiki Webhook Page</a>.
|
||||||
<input type="submit" class="btn" formaction="@url(repository)/settings/hooks/add" value="Add"/>
|
</p>
|
||||||
<input type="submit" class="btn" formaction="@url(repository)/settings/hooks/test" value="Test Hook"/>
|
|
||||||
</form>
|
<table class="table table-condensed" style="margin-bottom:0">
|
||||||
|
@webHooks.map { case (webHook, events) =>
|
||||||
|
<tr><td style="vertical-align: middle;">
|
||||||
|
<a href="@url(repository)/settings/hooks/edit/@urlEncode(webHook.url)" class="css-truncate" style="max-width:360px">
|
||||||
|
<span class="css-truncate-target">@webHook.url</span>
|
||||||
|
</a>
|
||||||
|
<em class="css-truncate" style="max-width: 225px;">(<span class="css-truncate-target">@events.map(_.name).mkString(", ")</span>)</em>
|
||||||
|
</td><td>
|
||||||
|
<div class="btn-group">
|
||||||
|
<a href="@url(repository)/settings/hooks/edit/@urlEncode(webHook.url)" class="btn btn-small">
|
||||||
|
<span class="octicon octicon-pencil"></span>
|
||||||
|
</a>
|
||||||
|
<a href="@url(repository)/settings/hooks/delete?url=@urlEncode(webHook.url)" class="btn btn-small btn-danger" onclick="return confirm('delete webhook for @webHook.url ?')">
|
||||||
|
<span class="octicon octicon-x"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</td></tr>
|
||||||
|
}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package gitbucket.core.service
|
package gitbucket.core.service
|
||||||
|
|
||||||
import org.specs2.mutable.Specification
|
import org.specs2.mutable.Specification
|
||||||
|
import gitbucket.core.model.WebHook
|
||||||
|
|
||||||
|
|
||||||
class WebHookServiceSpec extends Specification with ServiceSpecBase {
|
class WebHookServiceSpec extends Specification with ServiceSpecBase {
|
||||||
@@ -16,12 +17,12 @@ class WebHookServiceSpec extends Specification with ServiceSpecBase {
|
|||||||
val (issue3, pullreq3) = generateNewPullRequest("user3/repo3/master3", "user2/repo2/master2", loginUser="root")
|
val (issue3, pullreq3) = generateNewPullRequest("user3/repo3/master3", "user2/repo2/master2", loginUser="root")
|
||||||
val (issue32, pullreq32) = generateNewPullRequest("user3/repo3/master32", "user2/repo2/master2", loginUser="root")
|
val (issue32, pullreq32) = generateNewPullRequest("user3/repo3/master32", "user2/repo2/master2", loginUser="root")
|
||||||
generateNewPullRequest("user2/repo2/master2", "user1/repo1/master2")
|
generateNewPullRequest("user2/repo2/master2", "user1/repo1/master2")
|
||||||
service.addWebHookURL("user1", "repo1", "webhook1-1")
|
service.addWebHook("user1", "repo1", "webhook1-1", Set(WebHook.PullRequest))
|
||||||
service.addWebHookURL("user1", "repo1", "webhook1-2")
|
service.addWebHook("user1", "repo1", "webhook1-2", Set(WebHook.PullRequest))
|
||||||
service.addWebHookURL("user2", "repo2", "webhook2-1")
|
service.addWebHook("user2", "repo2", "webhook2-1", Set(WebHook.PullRequest))
|
||||||
service.addWebHookURL("user2", "repo2", "webhook2-2")
|
service.addWebHook("user2", "repo2", "webhook2-2", Set(WebHook.PullRequest))
|
||||||
service.addWebHookURL("user3", "repo3", "webhook3-1")
|
service.addWebHook("user3", "repo3", "webhook3-1", Set(WebHook.PullRequest))
|
||||||
service.addWebHookURL("user3", "repo3", "webhook3-2")
|
service.addWebHook("user3", "repo3", "webhook3-2", Set(WebHook.PullRequest))
|
||||||
|
|
||||||
service.getPullRequestsByRequestForWebhook("user1","repo1","master1") must_== Map.empty
|
service.getPullRequestsByRequestForWebhook("user1","repo1","master1") must_== Map.empty
|
||||||
|
|
||||||
@@ -41,4 +42,35 @@ class WebHookServiceSpec extends Specification with ServiceSpecBase {
|
|||||||
r2((issue32, issueUser, pullreq32, user3, user2)) must_== Set("webhook3-1","webhook3-2")
|
r2((issue32, issueUser, pullreq32, user3, user2)) must_== Set("webhook3-1","webhook3-2")
|
||||||
} }
|
} }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"add and get and update and delete" in { withTestDB { implicit session =>
|
||||||
|
val user1 = generateNewUserWithDBRepository("user1","repo1")
|
||||||
|
service.addWebHook("user1", "repo1", "http://example.com", Set(WebHook.PullRequest))
|
||||||
|
service.getWebHooks("user1", "repo1") must_== List((WebHook("user1","repo1","http://example.com"),Set(WebHook.PullRequest)))
|
||||||
|
service.getWebHook("user1", "repo1", "http://example.com") must_== Some((WebHook("user1","repo1","http://example.com"),Set(WebHook.PullRequest)))
|
||||||
|
service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) must_== List((WebHook("user1","repo1","http://example.com")))
|
||||||
|
service.getWebHooksByEvent("user1", "repo1", WebHook.Push) must_== Nil
|
||||||
|
service.getWebHook("user1", "repo1", "http://example.com2") must_== None
|
||||||
|
service.getWebHook("user2", "repo1", "http://example.com") must_== None
|
||||||
|
service.getWebHook("user1", "repo2", "http://example.com") must_== None
|
||||||
|
service.updateWebHook("user1", "repo1", "http://example.com", Set(WebHook.Push, WebHook.Issues))
|
||||||
|
service.getWebHook("user1", "repo1", "http://example.com") must_== Some((WebHook("user1","repo1","http://example.com"),Set(WebHook.Push, WebHook.Issues)))
|
||||||
|
service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) must_== Nil
|
||||||
|
service.getWebHooksByEvent("user1", "repo1", WebHook.Push) must_== List((WebHook("user1","repo1","http://example.com")))
|
||||||
|
service.deleteWebHook("user1", "repo1", "http://example.com")
|
||||||
|
service.getWebHook("user1", "repo1", "http://example.com") must_== None
|
||||||
|
} }
|
||||||
|
"getWebHooks, getWebHooksByEvent" in { withTestDB { implicit session =>
|
||||||
|
val user1 = generateNewUserWithDBRepository("user1","repo1")
|
||||||
|
service.addWebHook("user1", "repo1", "http://example.com/1", Set(WebHook.PullRequest))
|
||||||
|
service.addWebHook("user1", "repo1", "http://example.com/2", Set(WebHook.Push))
|
||||||
|
service.addWebHook("user1", "repo1", "http://example.com/3", Set(WebHook.PullRequest,WebHook.Push))
|
||||||
|
service.getWebHooks("user1", "repo1") must_== List(
|
||||||
|
WebHook("user1","repo1","http://example.com/1")->Set(WebHook.PullRequest),
|
||||||
|
WebHook("user1","repo1","http://example.com/2")->Set(WebHook.Push),
|
||||||
|
WebHook("user1","repo1","http://example.com/3")->Set(WebHook.PullRequest,WebHook.Push))
|
||||||
|
service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) must_== List(
|
||||||
|
WebHook("user1","repo1","http://example.com/1"),
|
||||||
|
WebHook("user1","repo1","http://example.com/3"))
|
||||||
|
} }
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user