diff --git a/doc/notification.md b/doc/notification.md index f9fb1e0..db0e48d 100644 --- a/doc/notification.md +++ b/doc/notification.md @@ -17,6 +17,7 @@ Notified users are as follows: * individual repository's owner +* group members of group repository * collaborators * participants diff --git a/src/main/scala/gitbucket/core/controller/AccountController.scala b/src/main/scala/gitbucket/core/controller/AccountController.scala index 4786bea..c2235f7 100644 --- a/src/main/scala/gitbucket/core/controller/AccountController.scala +++ b/src/main/scala/gitbucket/core/controller/AccountController.scala @@ -206,9 +206,13 @@ // FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName)) // FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName)) // } -// // Remove from GROUP_MEMBER, COLLABORATOR and REPOSITORY + // Remove from GROUP_MEMBER and COLLABORATOR removeUserRelatedData(userName) updateAccount(account.copy(isRemoved = true)) + + // call hooks + PluginRegistry().getAccountHooks.foreach(_.deleted(userName)) + session.invalidate redirect("/") } diff --git a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala index 45b7627..4029ff0 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,10 +278,8 @@ // call web hook callPullRequestWebHook("closed", repository, issueId, context.baseUrl, context.loginAccount.get) - // notifications - Notifier().toNotify(repository, issue, "merge"){ - Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}") - } + // call hooks + PluginRegistry().getPullRequestHooks.foreach(_.merged(issue, repository)) redirect(s"/${owner}/${name}/pull/${issueId}") } @@ -484,10 +483,8 @@ // extract references and create refer comment createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get) - // notifications - Notifier().toNotify(repository, issue, form.content.getOrElse("")) { - Notifier.msgPullRequest(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}") - } + // call hooks + PluginRegistry().getPullRequestHooks.foreach(_.created(issue, repository)) } redirect(s"/${owner}/${name}/pull/${issueId}") diff --git a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala index caebd04..9220853 100644 --- a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala @@ -225,7 +225,7 @@ // FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName)) // FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName)) // } - // Remove from GROUP_MEMBER, COLLABORATOR and REPOSITORY + // Remove from GROUP_MEMBER and COLLABORATOR removeUserRelatedData(userName) } @@ -239,6 +239,10 @@ isRemoved = form.isRemoved)) updateImage(userName, form.fileId, form.clearImage) + + // call hooks + if(form.isRemoved) PluginRegistry().getAccountHooks.foreach(_.deleted(userName)) + redirect("/admin/users") } } getOrElse NotFound() diff --git a/src/main/scala/gitbucket/core/plugin/AccountHook.scala b/src/main/scala/gitbucket/core/plugin/AccountHook.scala new file mode 100644 index 0000000..b6db885 --- /dev/null +++ b/src/main/scala/gitbucket/core/plugin/AccountHook.scala @@ -0,0 +1,10 @@ +package gitbucket.core.plugin + +import gitbucket.core.model.Profile._ +import profile.api._ + +trait AccountHook { + + def deleted(userName: String)(implicit session: Session): Unit = () + +} 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..3d2b51b 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. @@ -70,6 +72,16 @@ def repositoryRoutings(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[GitRepositoryRouting] = Nil /** + * Override to add account hooks. + */ + val accountHooks: Seq[AccountHook] = Nil + + /** + * Override to add account hooks. + */ + def accountHooks(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[AccountHook] = Nil + + /** * Override to add receive hooks. */ val receiveHooks: Seq[ReceiveHook] = Nil @@ -90,6 +102,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 +192,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 @@ -209,12 +251,21 @@ (repositoryRoutings ++ repositoryRoutings(registry, context, settings)).foreach { routing => registry.addRepositoryRouting(routing) } + (accountHooks ++ accountHooks(registry, context, settings)).foreach { accountHook => + registry.addAccountHook(accountHook) + } (receiveHooks ++ receiveHooks(registry, context, settings)).foreach { receiveHook => registry.addReceiveHook(receiveHook) } (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 +287,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 9ab7bea..c87adbf 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 @@ -32,10 +33,17 @@ "md" -> MarkdownRenderer, "markdown" -> MarkdownRenderer ) private val repositoryRoutings = new ListBuffer[GitRepositoryRouting] + private val accountHooks = new ListBuffer[AccountHook] private val receiveHooks = new ListBuffer[ReceiveHook] receiveHooks += new ProtectedBranchReceiveHook() private val repositoryHooks = new ListBuffer[RepositoryHook] + private val issueHooks = new ListBuffer[IssueHook] + issueHooks += new gitbucket.core.util.Notifier.IssueHook() + + private val pullRequestHooks = new ListBuffer[PullRequestHook] + pullRequestHooks += new gitbucket.core.util.Notifier.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 +51,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] @@ -99,6 +108,10 @@ } } + def addAccountHook(accountHook: AccountHook): Unit = accountHooks += accountHook + + def getAccountHooks: Seq[AccountHook] = accountHooks.toSeq + def addReceiveHook(commitHook: ReceiveHook): Unit = receiveHooks += commitHook def getReceiveHooks: Seq[ReceiveHook] = receiveHooks.toSeq @@ -107,6 +120,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 +156,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..f7ce6ff 100644 --- a/src/main/scala/gitbucket/core/service/HandleCommentService.scala +++ b/src/main/scala/gitbucket/core/service/HandleCommentService.scala @@ -2,11 +2,10 @@ 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 trait HandleCommentService { self: RepositoryService with IssuesService with ActivityService @@ -21,7 +20,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,54 +35,55 @@ 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 - Notifier() match { - case f => - content foreach { - f.toNotify(repository, issue, _){ - Notifier.msgComment(s"${context.baseUrl}/${owner}/${name}/${ - if(issue.isPullRequest) "pull" else "issues"}/${issue.issueId}#comment-${commentId.get}") - } - } - action foreach { - f.toNotify(repository, issue, _){ - Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/issues/${issue.issueId}") - } - } + // 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)) } commentId.map( issue -> _ ) diff --git a/src/main/scala/gitbucket/core/service/IssueCreationService.scala b/src/main/scala/gitbucket/core/service/IssueCreationService.scala index a18dad3..09e7dfb 100644 --- a/src/main/scala/gitbucket/core/service/IssueCreationService.scala +++ b/src/main/scala/gitbucket/core/service/IssueCreationService.scala @@ -3,11 +3,10 @@ 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,10 +45,9 @@ // 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}") - } + // call hooks + PluginRegistry().getIssueHooks.foreach(_.created(issue, repository)) + issue } diff --git a/src/main/scala/gitbucket/core/util/Notifier.scala b/src/main/scala/gitbucket/core/util/Notifier.scala index 3c8dba5..3e8d39c 100644 --- a/src/main/scala/gitbucket/core/util/Notifier.scala +++ b/src/main/scala/gitbucket/core/util/Notifier.scala @@ -13,87 +13,157 @@ import org.slf4j.LoggerFactory import gitbucket.core.controller.Context import SystemSettingsService.Smtp -import SyntaxSugars.defining -trait Notifier extends RepositoryService with AccountService with IssuesService { +/** + * The trait for notifications. + * This is used by notifications plugin, which provides notifications feature on GitBucket. + * Please see the plugin for details. + */ +trait Notifier { - def toNotify(r: RepositoryService.RepositoryInfo, issue: Issue, content: String) - (msg: String => String)(implicit context: Context): Unit + def toNotify(subject: String, msg: String) + (recipients: Account => Session => Seq[String])(implicit context: Context): Unit - protected def recipients(issue: Issue, loginAccount: Account)(notify: String => Unit)(implicit session: Session) = - ( - // individual repository's owner - issue.userName :: - // group members of group repository - getGroupMembers(issue.userName).map(_.userName) ::: - // collaborators - getCollaboratorUserNames(issue.userName, issue.repositoryName) ::: - // participants - issue.openedUserName :: - getComments(issue.userName, issue.repositoryName, issue.issueId).map(_.commentedUserName) - ) - .distinct - .withFilter ( _ != loginAccount.userName ) // the operation in person is excluded - .foreach ( - getAccountByUserName(_) - .filterNot (_.isGroupAccount) - .filterNot (LDAPUtil.isDummyMailAddress(_)) - .foreach (x => notify(x.mailAddress)) - ) } object Notifier { - // TODO We want to be able to switch to mock. def apply(): Notifier = new SystemSettingsService {}.loadSystemSettings match { case settings if (settings.notification && settings.useSMTP) => new Mailer(settings.smtp.get) case _ => new MockMailer } - def msgIssue(url: String) = (content: String) => s""" - |${content}
- |--
- |View it on GitBucket - """.stripMargin - def msgPullRequest(url: String) = (content: String) => s""" - |${content}
- |View, comment on, or merge it at:
- |${url} - """.stripMargin + // TODO This class is temporary keeping the current feature until Notifications Plugin is available. + class IssueHook extends gitbucket.core.plugin.IssueHook + with RepositoryService with AccountService with IssuesService { - def msgComment(url: String) = (content: String) => s""" - |${content}
- |--
- |View it on GitBucket - """.stripMargin + override def created(issue: Issue, r: RepositoryService.RepositoryInfo)(implicit context: Context): Unit = { + Notifier().toNotify( + subject(issue, r), + message(issue.content getOrElse "", r)(content => s""" + |$content
+ |--
+ |View it on GitBucket + """.stripMargin) + )(recipients(issue)) + } - def msgStatus(url: String) = (content: String) => s""" - |${content} #${url split('/') last} - """.stripMargin + override def addedComment(commentId: Int, content: String, issue: Issue, r: RepositoryService.RepositoryInfo)(implicit context: Context): Unit = { + Notifier().toNotify( + subject(issue, r), + message(content, r)(content => s""" + |$content
+ |--
+ |View it on GitBucket + """.stripMargin) + )(recipients(issue)) + } + + override def closed(issue: Issue, r: RepositoryService.RepositoryInfo)(implicit context: Context): Unit = { + Notifier().toNotify( + subject(issue, r), + message("close", r)(content => s""" + |$content #${issue.issueId} + """.stripMargin) + )(recipients(issue)) + } + + override def reopened(issue: Issue, r: RepositoryService.RepositoryInfo)(implicit context: Context): Unit = { + Notifier().toNotify( + subject(issue, r), + message("reopen", r)(content => s""" + |$content #${issue.issueId} + """.stripMargin) + )(recipients(issue)) + } + + + protected def subject(issue: Issue, r: RepositoryService.RepositoryInfo): String = + s"[${r.owner}/${r.name}] ${issue.title} (#${issue.issueId})" + + protected def message(content: String, r: RepositoryService.RepositoryInfo)(msg: String => String)(implicit context: Context): String = + msg(Markdown.toHtml( + markdown = content, + repository = r, + enableWikiLink = false, + enableRefsLink = true, + enableAnchor = false, + enableLineBreaks = false + )) + + protected val recipients: Issue => Account => Session => Seq[String] = { + issue => loginAccount => implicit session => + ( + // individual repository's owner + issue.userName :: + // group members of group repository + getGroupMembers(issue.userName).map(_.userName) ::: + // collaborators + getCollaboratorUserNames(issue.userName, issue.repositoryName) ::: + // participants + issue.openedUserName :: + getComments(issue.userName, issue.repositoryName, issue.issueId).map(_.commentedUserName) + ) + .distinct + .withFilter ( _ != loginAccount.userName ) // the operation in person is excluded + .flatMap ( + getAccountByUserName(_) + .filterNot (_.isGroupAccount) + .filterNot (LDAPUtil.isDummyMailAddress) + .map (_.mailAddress) + ) + } + } + + // TODO This class is temporary keeping the current feature until Notifications Plugin is available. + class PullRequestHook extends IssueHook with gitbucket.core.plugin.PullRequestHook { + override def created(issue: Issue, r: RepositoryService.RepositoryInfo)(implicit context: Context): Unit = { + val url = s"${context.baseUrl}/${r.owner}/${r.name}/pull/${issue.issueId}" + Notifier().toNotify( + subject(issue, r), + message(issue.content getOrElse "", r)(content => s""" + |$content
+ |View, comment on, or merge it at:
+ |$url + """.stripMargin) + )(recipients(issue)) + } + + override def addedComment(commentId: Int, content: String, issue: Issue, r: RepositoryService.RepositoryInfo)(implicit context: Context): Unit = { + Notifier().toNotify( + subject(issue, r), + message(content, r)(content => s""" + |$content
+ |--
+ |View it on GitBucket + """.stripMargin) + )(recipients(issue)) + } + + override def merged(issue: Issue, r: RepositoryService.RepositoryInfo)(implicit context: Context): Unit = { + Notifier().toNotify( + subject(issue, r), + message("merge", r)(content => s""" + |$content #${issue.issueId} + """.stripMargin) + )(recipients(issue)) + } + } + } class Mailer(private val smtp: Smtp) extends Notifier { private val logger = LoggerFactory.getLogger(classOf[Mailer]) - def toNotify(r: RepositoryService.RepositoryInfo, issue: Issue, content: String) - (msg: String => String)(implicit context: Context): Unit = { + def toNotify(subject: String, msg: String) + (recipients: Account => Session => Seq[String])(implicit context: Context): Unit = { context.loginAccount.foreach { loginAccount => val database = Database() val f = Future { - database withSession { implicit session => - defining( - s"[${r.owner}/${r.name}] ${issue.title} (#${issue.issueId})" -> - msg(Markdown.toHtml( - markdown = content, - repository = r, - enableWikiLink = false, - enableRefsLink = true, - enableAnchor = false, - enableLineBreaks = false - )) - ) { case (subject, msg) => - recipients(issue, loginAccount) { to => send(to, subject, msg, loginAccount) } + database withSession { session => + recipients(loginAccount)(session) foreach { to => + send(to, subject, msg, loginAccount) } } "Notifications Successful." @@ -137,6 +207,6 @@ } class MockMailer extends Notifier { - def toNotify(r: RepositoryService.RepositoryInfo, issue: Issue, content: String) - (msg: String => String)(implicit context: Context): Unit = {} + def toNotify(subject: String, msg: String) + (recipients: Account => Session => Seq[String])(implicit context: Context): Unit = () } 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) + }
@defining((issue.openedUserName :: comments.map(_.commentedUserName)).distinct){ participants =>