Provide tasks with ability to return data and human message

To allow better communication from background tasks, tasks can return
not only data (json), but also a human readable message which is send
via notification channels
This commit is contained in:
eikek
2022-03-11 22:56:14 +01:00
parent c1ce0769eb
commit 290b4ca58b
16 changed files with 250 additions and 76 deletions

View File

@ -204,7 +204,8 @@ object Event {
state: JobState,
subject: String,
submitter: Ident,
result: Json
resultData: Json,
resultMsg: Option[String]
) extends Event {
val eventType = JobDone
val baseUrl = None
@ -222,7 +223,8 @@ object Event {
JobState.running,
"Process 3 files",
account.user,
Json.Null
Json.Null,
None
)
} yield ev
}

View File

@ -31,30 +31,25 @@ trait EventContext {
"content" -> content
)
def defaultTitle: Either[String, String]
def defaultTitleHtml: Either[String, String]
def defaultBody: Either[String, String]
def defaultBodyHtml: Either[String, String]
def defaultMessage: Either[String, EventMessage]
def defaultMessageHtml: Either[String, EventMessage]
def defaultBoth: Either[String, String]
def defaultBothHtml: Either[String, String]
lazy val asJsonWithMessage: Either[String, Json] =
for {
tt1 <- defaultTitle
tb1 <- defaultBody
tt2 <- defaultTitleHtml
tb2 <- defaultBodyHtml
dm1 <- defaultMessage
dm2 <- defaultMessageHtml
data = asJson
msg = Json.obj(
"message" -> Json.obj(
"title" -> tt1.asJson,
"body" -> tb1.asJson
"title" -> dm1.title.asJson,
"body" -> dm1.body.asJson
),
"messageHtml" -> Json.obj(
"title" -> tt2.asJson,
"body" -> tb2.asJson
"title" -> dm2.title.asJson,
"body" -> dm2.body.asJson
)
)
} yield data.withObject(o1 => msg.withObject(o2 => o1.deepMerge(o2).asJson))
@ -65,10 +60,8 @@ object EventContext {
new EventContext {
val event = ev
def content = Json.obj()
def defaultTitle = Right("")
def defaultTitleHtml = Right("")
def defaultBody = Right("")
def defaultBodyHtml = Right("")
def defaultMessage = Right(EventMessage.empty)
def defaultMessageHtml = Right(EventMessage.empty)
def defaultBoth = Right("")
def defaultBothHtml = Right("")
}

View File

@ -0,0 +1,13 @@
/*
* Copyright 2020 Eike K. & Contributors
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package docspell.notification.api
final case class EventMessage(title: String, body: String)
object EventMessage {
val empty: EventMessage = EventMessage("", "")
}

View File

@ -6,7 +6,7 @@
package docspell.notification.impl
import docspell.notification.api.EventContext
import docspell.notification.api.{EventContext, EventMessage}
import yamusca.circe._
import yamusca.implicits._
@ -24,17 +24,17 @@ abstract class AbstractEventContext extends EventContext {
def renderHtml(template: Template): String =
Markdown.toHtml(render(template))
lazy val defaultTitle: Either[String, String] =
titleTemplate.map(render)
lazy val defaultMessage: Either[String, EventMessage] =
for {
title <- titleTemplate.map(render)
body <- bodyTemplate.map(render)
} yield EventMessage(title, body)
lazy val defaultTitleHtml: Either[String, String] =
titleTemplate.map(renderHtml)
lazy val defaultBody: Either[String, String] =
bodyTemplate.map(render)
lazy val defaultBodyHtml: Either[String, String] =
bodyTemplate.map(renderHtml)
lazy val defaultMessageHtml: Either[String, EventMessage] =
for {
title <- titleTemplate.map(renderHtml)
body <- bodyTemplate.map(renderHtml)
} yield EventMessage(title, body)
lazy val defaultBoth: Either[String, String] =
for {

View File

@ -18,8 +18,9 @@ trait EventContextSyntax {
implicit final class EventContextOps(self: EventContext) {
def withDefault[F[_]](logger: Logger[F])(f: (String, String) => F[Unit]): F[Unit] =
(for {
tt <- self.defaultTitle
tb <- self.defaultBody
dm <- self.defaultMessage
tt = dm.title
tb = dm.body
} yield f(tt, tb)).fold(logError(logger), identity)
def withJsonMessage[F[_]](logger: Logger[F])(f: Json => F[Unit]): F[Unit] =

View File

@ -23,9 +23,14 @@ final case class JobDoneCtx(event: Event.JobDone, data: JobDoneCtx.Data)
val content = data.asJson
val titleTemplate = Right(mustache"{{eventType}} (by *{{account.user}}*)")
val bodyTemplate = Right(
mustache"""{{#content}}_'{{subject}}'_ finished {{/content}}"""
)
val bodyTemplate =
data.resultMsg match {
case None =>
Right(mustache"""{{#content}}_'{{subject}}'_ finished {{/content}}""")
case Some(msg) =>
val tpl = s"""{{#content}}$msg{{/content}}"""
yamusca.imports.mustache.parse(tpl).left.map(_._2)
}
}
object JobDoneCtx {
@ -46,7 +51,8 @@ object JobDoneCtx {
state: JobState,
subject: String,
submitter: Ident,
result: Json
resultData: Json,
resultMsg: Option[String]
)
object Data {
implicit val jsonEncoder: Encoder[Data] =
@ -61,7 +67,8 @@ object JobDoneCtx {
ev.state,
ev.subject,
ev.submitter,
ev.result
ev.resultData,
ev.resultMsg
)
}
}

View File

@ -46,9 +46,10 @@ class TagsChangedCtxTest extends FunSuite {
TagsChangedCtx.Data(account, List(item), List(tag), Nil, url.some.map(_.asString))
)
assertEquals(ctx.defaultTitle.toOption.get, "TagsChanged (by *user2*)")
val dm = ctx.defaultMessage.toOption.get
assertEquals(dm.title, "TagsChanged (by *user2*)")
assertEquals(
ctx.defaultBody.toOption.get,
dm.body,
"Adding *tag-red* on [`Report 2`](http://test/item-1)."
)
}
@ -65,9 +66,10 @@ class TagsChangedCtxTest extends FunSuite {
)
)
assertEquals(ctx.defaultTitle.toOption.get, "TagsChanged (by *user2*)")
val dm = ctx.defaultMessage.toOption.get
assertEquals(dm.title, "TagsChanged (by *user2*)")
assertEquals(
ctx.defaultBody.toOption.get,
dm.body,
"Adding *tag-red*; Removing *tag-blue* on [`Report 2`](http://test/item-1)."
)
}