diff --git a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala index 45b7627..596d99e 100644 --- a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala +++ b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala @@ -1,6 +1,7 @@ package gitbucket.core.controller import gitbucket.core.model.WebHook +import gitbucket.core.plugin.PluginRegistry import gitbucket.core.pulls.html import gitbucket.core.service.CommitStatusService import gitbucket.core.service.MergeService @@ -277,7 +278,10 @@ // call web hook callPullRequestWebHook("closed", repository, issueId, context.baseUrl, context.loginAccount.get) - // notifications + // call hooks + PluginRegistry().getPullRequestHooks.foreach(_.merged(issue, repository)) + + // notifications TODO move to plugin Notifier().toNotify(repository, issue, "merge"){ Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}") } @@ -484,7 +488,10 @@ // extract references and create refer comment createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get) - // notifications + // call hooks + PluginRegistry().getPullRequestHooks.foreach(_.created(issue, repository)) + + // notifications TODO move to plugin Notifier().toNotify(repository, issue, form.content.getOrElse("")) { Notifier.msgPullRequest(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}") } diff --git a/src/main/scala/gitbucket/core/plugin/IssueHook.scala b/src/main/scala/gitbucket/core/plugin/IssueHook.scala new file mode 100644 index 0000000..8bed047 --- /dev/null +++ b/src/main/scala/gitbucket/core/plugin/IssueHook.scala @@ -0,0 +1,20 @@ +package gitbucket.core.plugin + +import gitbucket.core.controller.Context +import gitbucket.core.model.Issue +import gitbucket.core.service.RepositoryService.RepositoryInfo + +trait IssueHook { + + def created(issue: Issue, repository: RepositoryInfo)(implicit context: Context): Unit = () + def addedComment(commentId: Int, content: String, issue: Issue, repository: RepositoryInfo)(implicit context: Context): Unit = () + def closed(issue: Issue, repository: RepositoryInfo)(implicit context: Context): Unit = () + def reopened(issue: Issue, repository: RepositoryInfo)(implicit context: Context): Unit = () + +} + +trait PullRequestHook extends IssueHook { + + def merged(issue: Issue, repository: RepositoryInfo)(implicit context: Context): Unit = () + +} diff --git a/src/main/scala/gitbucket/core/plugin/Plugin.scala b/src/main/scala/gitbucket/core/plugin/Plugin.scala index 4367c68..5be1caa 100644 --- a/src/main/scala/gitbucket/core/plugin/Plugin.scala +++ b/src/main/scala/gitbucket/core/plugin/Plugin.scala @@ -1,12 +1,14 @@ package gitbucket.core.plugin import javax.servlet.ServletContext + import gitbucket.core.controller.{Context, ControllerBase} -import gitbucket.core.model.Account +import gitbucket.core.model.{Account, Issue} import gitbucket.core.service.RepositoryService.RepositoryInfo import gitbucket.core.service.SystemSettingsService.SystemSettings import gitbucket.core.util.SyntaxSugars._ import io.github.gitbucket.solidbase.model.Version +import play.twirl.api.Html /** * Trait for define plugin interface. @@ -90,6 +92,26 @@ def repositoryHooks(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[RepositoryHook] = Nil /** + * Override to add issue hooks. + */ + val issueHooks: Seq[IssueHook] = Nil + + /** + * Override to add issue hooks. + */ + def issueHooks(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[IssueHook] = Nil + + /** + * Override to add pull request hooks. + */ + val pullRequestHooks: Seq[PullRequestHook] = Nil + + /** + * Override to add pull request hooks. + */ + def pullRequestHooks(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[PullRequestHook] = Nil + + /** * Override to add global menus. */ val globalMenus: Seq[(Context) => Option[Link]] = Nil @@ -160,6 +182,16 @@ def dashboardTabs(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Context) => Option[Link]] = Nil /** + * Override to add issue sidebars. + */ + val issueSidebars: Seq[(Issue, RepositoryInfo, Context) => Option[Html]] = Nil + + /** + * Override to add issue sidebars. + */ + def issueSidebars(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Issue, RepositoryInfo, Context) => Option[Html]] = Nil + + /** * Override to add assets mappings. */ val assetsMappings: Seq[(String, String)] = Nil @@ -215,6 +247,12 @@ (repositoryHooks ++ repositoryHooks(registry, context, settings)).foreach { repositoryHook => registry.addRepositoryHook(repositoryHook) } + (issueHooks ++ issueHooks(registry, context, settings)).foreach { issueHook => + registry.addIssueHook(issueHook) + } + (pullRequestHooks ++ pullRequestHooks(registry, context, settings)).foreach { pullRequestHook => + registry.addPullRequestHook(pullRequestHook) + } (globalMenus ++ globalMenus(registry, context, settings)).foreach { globalMenu => registry.addGlobalMenu(globalMenu) } @@ -236,6 +274,9 @@ (dashboardTabs ++ dashboardTabs(registry, context, settings)).foreach { dashboardTab => registry.addDashboardTab(dashboardTab) } + (issueSidebars ++ issueSidebars(registry, context, settings)).foreach { issueSidebar => + registry.addIssueSidebar(issueSidebar) + } (assetsMappings ++ assetsMappings(registry, context, settings)).foreach { assetMapping => registry.addAssetsMapping((assetMapping._1, assetMapping._2, getClass.getClassLoader)) } diff --git a/src/main/scala/gitbucket/core/plugin/PluginRegistory.scala b/src/main/scala/gitbucket/core/plugin/PluginRegistory.scala index a2e8976..b81afa0 100644 --- a/src/main/scala/gitbucket/core/plugin/PluginRegistory.scala +++ b/src/main/scala/gitbucket/core/plugin/PluginRegistory.scala @@ -6,7 +6,7 @@ import javax.servlet.ServletContext import gitbucket.core.controller.{Context, ControllerBase} -import gitbucket.core.model.Account +import gitbucket.core.model.{Account, Issue} import gitbucket.core.service.ProtectedBranchService.ProtectedBranchReceiveHook import gitbucket.core.service.RepositoryService.RepositoryInfo import gitbucket.core.service.SystemSettingsService.SystemSettings @@ -17,6 +17,7 @@ import io.github.gitbucket.solidbase.manager.JDBCVersionManager import io.github.gitbucket.solidbase.model.Module import org.slf4j.LoggerFactory +import play.twirl.api.Html import scala.collection.mutable import scala.collection.mutable.ListBuffer @@ -36,6 +37,8 @@ receiveHooks += new ProtectedBranchReceiveHook() private val repositoryHooks = new ListBuffer[RepositoryHook] + private val issueHooks = new ListBuffer[IssueHook] + private val pullRequestHooks = new ListBuffer[PullRequestHook] private val globalMenus = new ListBuffer[(Context) => Option[Link]] private val repositoryMenus = new ListBuffer[(RepositoryInfo, Context) => Option[Link]] private val repositorySettingTabs = new ListBuffer[(RepositoryInfo, Context) => Option[Link]] @@ -43,6 +46,7 @@ private val systemSettingMenus = new ListBuffer[(Context) => Option[Link]] private val accountSettingMenus = new ListBuffer[(Context) => Option[Link]] private val dashboardTabs = new ListBuffer[(Context) => Option[Link]] + private val issueSidebars = new ListBuffer[(Issue, RepositoryInfo, Context) => Option[Html]] private val assetsMappings = new ListBuffer[(String, String, ClassLoader)] private val textDecorators = new ListBuffer[TextDecorator] @@ -107,6 +111,14 @@ def getRepositoryHooks: Seq[RepositoryHook] = repositoryHooks.toSeq + def addIssueHook(issueHook: IssueHook): Unit = issueHooks += issueHook + + def getIssueHooks: Seq[IssueHook] = issueHooks.toSeq + + def addPullRequestHook(pullRequestHook: PullRequestHook): Unit = pullRequestHooks += pullRequestHook + + def getPullRequestHooks: Seq[PullRequestHook] = pullRequestHooks.toSeq + def addGlobalMenu(globalMenu: (Context) => Option[Link]): Unit = globalMenus += globalMenu def getGlobalMenus: Seq[(Context) => Option[Link]] = globalMenus.toSeq @@ -135,6 +147,10 @@ def getDashboardTabs: Seq[(Context) => Option[Link]] = dashboardTabs.toSeq + def addIssueSidebar(issueSidebar: (Issue, RepositoryInfo, Context) => Option[Html]): Unit = issueSidebars += issueSidebar + + def getIssueSidebars: Seq[(Issue, RepositoryInfo, Context) => Option[Html]] = issueSidebars.toSeq + def addAssetsMapping(assetsMapping: (String, String, ClassLoader)): Unit = assetsMappings += assetsMapping def getAssetsMappings: Seq[(String, String, ClassLoader)] = assetsMappings.toSeq diff --git a/src/main/scala/gitbucket/core/service/HandleCommentService.scala b/src/main/scala/gitbucket/core/service/HandleCommentService.scala index aaa4cf7..7fc0fc6 100644 --- a/src/main/scala/gitbucket/core/service/HandleCommentService.scala +++ b/src/main/scala/gitbucket/core/service/HandleCommentService.scala @@ -2,8 +2,8 @@ import gitbucket.core.controller.Context import gitbucket.core.model.Issue -import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile.profile.blockingApi._ +import gitbucket.core.plugin.PluginRegistry import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.Implicits._ import gitbucket.core.util.Notifier @@ -21,7 +21,7 @@ defining(repository.owner, repository.name){ case (owner, name) => val userName = loginAccount.userName - val (action, recordActivity) = actionOpt + val (action, actionActivity) = actionOpt .collect { case "close" if(!issue.closed) => true -> (Some("close") -> Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _)) @@ -36,41 +36,58 @@ val commentId = (content, action) match { case (None, None) => None - case (None, Some(action)) => Some(createComment(owner, name, userName, issue.issueId, action.capitalize, action)) - case (Some(content), _) => Some(createComment(owner, name, userName, issue.issueId, content, action.map(_+ "_comment").getOrElse("comment"))) + case (None, Some(action)) => + Some(createComment(owner, name, userName, issue.issueId, action.capitalize, action)) + case (Some(content), _) => + val id = Some(createComment(owner, name, userName, issue.issueId, content, action.map(_+ "_comment").getOrElse("comment"))) + + // record comment activity + if(issue.isPullRequest) recordCommentPullRequestActivity(owner, name, userName, issue.issueId, content) + else recordCommentIssueActivity(owner, name, userName, issue.issueId, content) + + // extract references and create refer comment + createReferComment(owner, name, issue, content, loginAccount) + + id } - // record comment activity if comment is entered - content foreach { - (if(issue.isPullRequest) recordCommentPullRequestActivity _ else recordCommentIssueActivity _) - (owner, name, userName, issue.issueId, _) - } - recordActivity foreach ( _ (owner, name, userName, issue.issueId, issue.title) ) - - // extract references and create refer comment - content.map { content => - createReferComment(owner, name, issue, content, loginAccount) - } + actionActivity foreach ( _ (owner, name, userName, issue.issueId, issue.title) ) // call web hooks action match { - case None => commentId.map { commentIdSome => callIssueCommentWebHook(repository, issue, commentIdSome, loginAccount) } - case Some(act) => { + case None => commentId foreach (callIssueCommentWebHook(repository, issue, _, loginAccount)) + case Some(act) => val webHookAction = act match { - case "open" => "opened" - case "reopen" => "reopened" case "close" => "closed" - case _ => act + case "reopen" => "reopened" } - if (issue.isPullRequest) { + if(issue.isPullRequest) callPullRequestWebHook(webHookAction, repository, issue.issueId, context.baseUrl, loginAccount) - } else { + else callIssuesWebHook(webHookAction, repository, issue, context.baseUrl, loginAccount) - } - } } - // notifications + // call hooks + content foreach { x => + if(issue.isPullRequest) + PluginRegistry().getPullRequestHooks.foreach(_.addedComment(commentId.get, x, issue, repository)) + else + PluginRegistry().getIssueHooks.foreach(_.addedComment(commentId.get, x, issue, repository)) + } + action foreach { + case "close" => + if(issue.isPullRequest) + PluginRegistry().getPullRequestHooks.foreach(_.closed(issue, repository)) + else + PluginRegistry().getIssueHooks.foreach(_.closed(issue, repository)) + case "reopen" => + if(issue.isPullRequest) + PluginRegistry().getPullRequestHooks.foreach(_.reopened(issue, repository)) + else + PluginRegistry().getIssueHooks.foreach(_.reopened(issue, repository)) + } + + // notifications // TODO move to plugin Notifier() match { case f => content foreach { diff --git a/src/main/scala/gitbucket/core/service/IssueCreationService.scala b/src/main/scala/gitbucket/core/service/IssueCreationService.scala index a18dad3..1439c04 100644 --- a/src/main/scala/gitbucket/core/service/IssueCreationService.scala +++ b/src/main/scala/gitbucket/core/service/IssueCreationService.scala @@ -3,11 +3,11 @@ import gitbucket.core.controller.Context import gitbucket.core.model.{Account, Issue} import gitbucket.core.model.Profile.profile.blockingApi._ +import gitbucket.core.plugin.PluginRegistry import gitbucket.core.service.RepositoryService.RepositoryInfo import gitbucket.core.util.Notifier import gitbucket.core.util.Implicits._ -// TODO: Merged with IssuesService? trait IssueCreationService { self: RepositoryService with WebHookIssueCommentService with LabelsService with IssuesService with ActivityService => @@ -46,7 +46,10 @@ // call web hooks callIssuesWebHook("opened", repository, issue, context.baseUrl, loginAccount) - // notifications + // call hooks + PluginRegistry().getIssueHooks.foreach(_.created(issue, repository)) + + // notifications TODO move to plugin Notifier().toNotify(repository, issue, body.getOrElse("")) { Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}") } diff --git a/src/main/twirl/gitbucket/core/issues/issueinfo.scala.html b/src/main/twirl/gitbucket/core/issues/issueinfo.scala.html index eb48da1..27cd255 100644 --- a/src/main/twirl/gitbucket/core/issues/issueinfo.scala.html +++ b/src/main/twirl/gitbucket/core/issues/issueinfo.scala.html @@ -112,6 +112,9 @@ } @issue.map { issue => + @gitbucket.core.plugin.PluginRegistry().getIssueSidebars.map { sidebar => + @sidebar(issue, repository, context) + }