Allow a custom message part for the periodic query task

This commit is contained in:
eikek
2022-01-11 22:50:19 +01:00
parent dd9937740a
commit 404fb2a37f
19 changed files with 128 additions and 44 deletions

View File

@ -106,6 +106,7 @@ object PeriodicDueItemsTask {
ctx.args.account,
ctx.args.baseUrl,
items,
None,
limit,
now
)(cont)

View File

@ -143,6 +143,7 @@ object PeriodicQueryTask {
ctx.args.account,
ctx.args.baseUrl,
items,
ctx.args.contentStart,
limit,
now
)(cont)

View File

@ -45,6 +45,7 @@ trait TaskOperations {
account: AccountId,
baseUrl: Option[LenientUri],
items: Vector[ListItem],
contentStart: Option[String],
limit: Int,
now: Timestamp
)(cont: EventContext => F[Unit]): F[Unit] =
@ -52,7 +53,7 @@ trait TaskOperations {
case Some(nel) =>
val more = items.size >= limit
val eventCtx = ItemSelectionCtx(
Event.ItemSelection(account, nel.map(_.id), more, baseUrl),
Event.ItemSelection(account, nel.map(_.id), more, baseUrl, contentStart),
ItemSelectionCtx.Data
.create(account, items, baseUrl, more, now)
)

View File

@ -148,7 +148,8 @@ object Event {
account: AccountId,
items: Nel[Ident],
more: Boolean,
baseUrl: Option[LenientUri]
baseUrl: Option[LenientUri],
contentStart: Option[String]
) extends Event {
val eventType = ItemSelection
}
@ -161,7 +162,7 @@ object Event {
for {
id1 <- Ident.randomId[F]
id2 <- Ident.randomId[F]
} yield ItemSelection(account, Nel.of(id1, id2), true, baseUrl)
} yield ItemSelection(account, Nel.of(id1, id2), true, baseUrl, None)
}
/** Event when a new job is added to the queue */

View File

@ -17,7 +17,8 @@ final case class PeriodicQueryArgs(
channel: ChannelOrRef,
query: Option[ItemQueryString],
bookmark: Option[String],
baseUrl: Option[LenientUri]
baseUrl: Option[LenientUri],
contentStart: Option[String]
)
object PeriodicQueryArgs {

View File

@ -1,7 +1,14 @@
/*
* Copyright 2020 Eike K. & Contributors
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package docspell.notification.impl
import docspell.notification.api.EventContext
import docspell.common.Logger
import docspell.notification.api.EventContext
import io.circe.Json
trait EventContextSyntax {
@ -21,7 +28,9 @@ trait EventContextSyntax {
case Left(err) => logError(logger)(err)
}
def withDefaultBoth[F[_]](logger: Logger[F])(f: (String, String) => F[Unit]): F[Unit] =
def withDefaultBoth[F[_]](
logger: Logger[F]
)(f: (String, String) => F[Unit]): F[Unit] =
(for {
md <- self.defaultBoth
html <- self.defaultBothHtml

View File

@ -32,7 +32,9 @@ final case class DeleteFieldValueCtx(
val titleTemplate = Right(mustache"{{eventType}} (by *{{account.user}}*)")
val bodyTemplate =
Right(mustache"""{{#content}}{{#field.label}}*{{field.label}}* {{/field.label}}{{^field.label}}*{{field.name}}* {{/field.label}} was removed from {{#items}}{{^-first}}, {{/-first}}{{#itemUrl}}[`{{name}}`]({{{itemUrl}}}/{{{id}}}){{/itemUrl}}{{^itemUrl}}`{{name}}`{{/itemUrl}}{{/items}}.{{/content}}""")
Right(
mustache"""{{#content}}{{#field.label}}*{{field.label}}* {{/field.label}}{{^field.label}}*{{field.name}}* {{/field.label}} was removed from {{#items}}{{^-first}}, {{/-first}}{{#itemUrl}}[`{{name}}`]({{{itemUrl}}}/{{{id}}}){{/itemUrl}}{{^itemUrl}}`{{name}}`{{/itemUrl}}{{/items}}.{{/content}}"""
)
}

View File

@ -19,39 +19,29 @@ import doobie._
import io.circe.Encoder
import io.circe.syntax._
import yamusca.implicits._
import yamusca.imports._
final case class ItemSelectionCtx(event: Event.ItemSelection, data: ItemSelectionCtx.Data)
extends AbstractEventContext {
val content = data.asJson
val titleTemplate = Right(mustache"Your items")
val bodyTemplate = Right(mustache"""
Hello {{{ content.username }}},
val bodyTemplate = event.contentStart match {
case Some(cnt) =>
mustache
.parse(cnt)
.leftMap { case (in, err) =>
s"Error parsing template: $err! Near ${in.pos}: ${in.raw}."
}
.map(start => start ++ ItemSelectionCtx.basicBody)
this is Docspell informing you about your next items.
case None =>
Right(ItemSelectionCtx.basicBodyStart ++ ItemSelectionCtx.basicBody)
}
{{#content}}
{{#itemUrl}}
{{#items}}
- {{#overDue}}**(OVERDUE)** {{/overDue}}[{{name}}]({{itemUrl}}/{{id}}){{#dueDate}}, {{#overDue}}was {{/overDue}}due {{dueIn}} on *{{dueDate}}*{{/dueDate}}; {{#corrOrg}}from {{corrOrg}}{{/corrOrg}} received on {{date}} via {{source}}
{{/items}}
{{/itemUrl}}
{{^itemUrl}}
{{#items}}
- {{#overDue}}**(OVERDUE)** {{/overDue}}*{{name}}*{{#dueDate}}, {{#overDue}}was {{/overDue}}due {{dueIn}} on *{{dueDate}}*{{/dueDate}}; {{#corrOrg}}from {{corrOrg}}{{/corrOrg}} received on {{date}} via {{source}}
{{/items}}
{{/itemUrl}}
{{#more}}
- … more have been left out for brevity
{{/more}}
{{/content}}
Sincerely yours,
Docspell
""")
implicit final class TemplateOps(self: Template) {
def ++(next: Template) = Template(self.els ++ next.els)
}
}
object ItemSelectionCtx {
@ -113,4 +103,26 @@ object ItemSelectionCtx {
account.user.id
)
}
private val basicBodyStart = mustache"""
Hello {{{ content.username }}},
this is Docspell informing you about your next items."""
private val basicBody = mustache"""
{{#content}}
{{#itemUrl}}
{{#items}}
- {{#overDue}}**(OVERDUE)** {{/overDue}}[{{name}}]({{itemUrl}}/{{id}}){{#dueDate}}, {{#overDue}}was {{/overDue}}due {{dueIn}} on *{{dueDate}}*{{/dueDate}}; {{#corrOrg}}from {{corrOrg}}{{/corrOrg}} received on {{date}} via {{source}}
{{/items}}
{{/itemUrl}}
{{^itemUrl}}
{{#items}}
- {{#overDue}}**(OVERDUE)** {{/overDue}}*{{name}}*{{#dueDate}}, {{#overDue}}was {{/overDue}}due {{dueIn}} on *{{dueDate}}*{{/dueDate}}; {{#corrOrg}}from {{corrOrg}}{{/corrOrg}} received on {{date}} via {{source}}
{{/items}}
{{/itemUrl}}
{{#more}}
- … more have been left out for brevity
{{/more}}
{{/content}}"""
}

View File

@ -23,7 +23,9 @@ 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 = Right(
mustache"""{{#content}}_'{{subject}}'_ finished {{/content}}"""
)
}
object JobDoneCtx {

View File

@ -24,7 +24,9 @@ final case class JobSubmittedCtx(event: Event.JobSubmitted, data: JobSubmittedCt
val titleTemplate = Right(mustache"{{eventType}} (by *{{account.user}}*)")
val bodyTemplate =
Right(mustache"""{{#content}}_'{{subject}}'_ submitted by {{submitter}} {{/content}}""")
Right(
mustache"""{{#content}}_'{{subject}}'_ submitted by {{submitter}} {{/content}}"""
)
}
object JobSubmittedCtx {

View File

@ -30,7 +30,9 @@ final case class SetFieldValueCtx(event: Event.SetFieldValue, data: SetFieldValu
val titleTemplate = Right(mustache"{{eventType}} (by *{{account.user}}*)")
val bodyTemplate =
Right(mustache"""{{#content}}{{#field.label}}*{{field.label}}* {{/field.label}}{{^field.label}}*{{field.name}}* {{/field.label}} was set to '{{value}}' on {{#items}}{{^-first}}, {{/-first}}{{#itemUrl}}[`{{name}}`]({{{itemUrl}}}/{{{id}}}){{/itemUrl}}{{^itemUrl}}`{{name}}`{{/itemUrl}}{{/items}}.{{/content}}""")
Right(
mustache"""{{#content}}{{#field.label}}*{{field.label}}* {{/field.label}}{{^field.label}}*{{field.name}}* {{/field.label}} was set to '{{value}}' on {{#items}}{{^-first}}, {{/-first}}{{#itemUrl}}[`{{name}}`]({{{itemUrl}}}/{{{id}}}){{/itemUrl}}{{^itemUrl}}`{{name}}`{{/itemUrl}}{{/items}}.{{/content}}"""
)
}

View File

@ -28,7 +28,9 @@ final case class TagsChangedCtx(event: Event.TagsChanged, data: TagsChangedCtx.D
val titleTemplate = Right(mustache"{{eventType}} (by *{{account.user}}*)")
val bodyTemplate =
Right(mustache"""{{#content}}{{#added}}{{#-first}}Adding {{/-first}}{{^-first}}, {{/-first}}*{{name}}*{{/added}}{{#removed}}{{#added}}{{#-first}};{{/-first}}{{/added}}{{#-first}} Removing {{/-first}}{{^-first}}, {{/-first}}*{{name}}*{{/removed}} on {{#items}}{{^-first}}, {{/-first}}{{#itemUrl}}[`{{name}}`]({{{itemUrl}}}/{{{id}}}){{/itemUrl}}{{^itemUrl}}`{{name}}`{{/itemUrl}}{{/items}}.{{/content}}""")
Right(
mustache"""{{#content}}{{#added}}{{#-first}}Adding {{/-first}}{{^-first}}, {{/-first}}*{{name}}*{{/added}}{{#removed}}{{#added}}{{#-first}};{{/-first}}{{/added}}{{#-first}} Removing {{/-first}}{{^-first}}, {{/-first}}*{{name}}*{{/removed}} on {{#items}}{{^-first}}, {{/-first}}{{#itemUrl}}[`{{name}}`]({{{itemUrl}}}/{{{id}}}){{/itemUrl}}{{^itemUrl}}`{{name}}`{{/itemUrl}}{{/items}}.{{/content}}"""
)
}
object TagsChangedCtx {

View File

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

View File

@ -23,6 +23,7 @@ final case class PeriodicQuerySettings(
channel: NotificationChannel,
query: Option[ItemQuery],
bookmark: Option[String],
contentStart: Option[String],
schedule: CalEvent
) {}

View File

@ -144,7 +144,8 @@ object PeriodicQueryRoutes extends MailAddressCodec {
Right(channel),
qstr,
settings.bookmark,
Some(baseUrl / "app" / "item")
Some(baseUrl / "app" / "item"),
settings.contentStart
)
)
}
@ -167,6 +168,7 @@ object PeriodicQueryRoutes extends MailAddressCodec {
ch,
task.args.query.map(_.query).map(ItemQueryParser.parseUnsafe),
task.args.bookmark,
task.args.contentStart,
task.timer
)
}

View File

@ -18,8 +18,9 @@ import io.circe.generic.semiauto._
* If the structure changes, there must be some database migration to update or remove
* the json data of the corresponding task.
*
* @deprecated note: This has been removed and copied to this place to be able to
* migrate away from this structure. Replaced by PeriodicDueItemsArgs
* @deprecated
* note: This has been removed and copied to this place to be able to migrate away from
* this structure. Replaced by PeriodicDueItemsArgs
*/
case class NotifyDueItemsArgs(
account: AccountId,

View File

@ -46,6 +46,7 @@ type alias Model =
, queryModel : Comp.PowerSearchInput.Model
, channelModel : Comp.ChannelForm.Model
, bookmarkDropdown : Comp.BookmarkDropdown.Model
, contentStart : Maybe String
, formState : FormState
, loading : Int
, deleteRequested : Bool
@ -79,6 +80,7 @@ type Msg
| QueryMsg Comp.PowerSearchInput.Msg
| ChannelMsg Comp.ChannelForm.Msg
| BookmarkMsg Comp.BookmarkDropdown.Msg
| SetContentStart String
| StartOnce
| Cancel
| RequestDelete
@ -115,6 +117,7 @@ initWith flags s =
, queryModel = res.model
, channelModel = cfm
, bookmarkDropdown = bm
, contentStart = Nothing
, formState = FormStateInitial
, loading = 0
, summary = s.summary
@ -151,6 +154,7 @@ init flags ct =
, queryModel = Comp.PowerSearchInput.init
, channelModel = cfm
, bookmarkDropdown = bm
, contentStart = Nothing
, formState = FormStateInitial
, loading = 0
, summary = Nothing
@ -218,6 +222,7 @@ makeSettings model =
, channel = channel
, query = Tuple.first q
, bookmark = Tuple.second q
, contentStart = model.contentStart
}
in
Result.map3 make
@ -299,6 +304,13 @@ update flags msg model =
, sub = Sub.none
}
SetContentStart str ->
{ model = { model | contentStart = Util.Maybe.fromString str }
, action = NoAction
, cmd = Cmd.none
, sub = Sub.none
}
ToggleEnabled ->
{ model =
{ model
@ -545,6 +557,22 @@ view texts extraClasses settings model =
, queryInput
]
]
, div [ class "mb-4" ]
[ formHeader texts.messageContentTitle False
, label [ class S.inputLabel ]
[ text texts.messageContentLabel
]
, textarea
[ onInput SetContentStart
, Maybe.withDefault "" model.contentStart |> value
, placeholder texts.messageContentPlaceholder
, class S.textAreaInput
]
[]
, span [ class "text-sm opacity-75" ]
[ text texts.messageContentInfo
]
]
, div [ class "mb-4" ]
[ formHeader texts.schedule False
, label [ class S.inputLabel ]

View File

@ -20,6 +20,7 @@ type alias PeriodicQuerySettings =
, channel : NotificationChannel
, query : Maybe String
, bookmark : Maybe String
, contentStart : Maybe String
, schedule : String
}
@ -32,19 +33,21 @@ empty ct =
, channel = Data.NotificationChannel.empty ct
, query = Nothing
, bookmark = Nothing
, contentStart = Nothing
, schedule = ""
}
decoder : D.Decoder PeriodicQuerySettings
decoder =
D.map7 PeriodicQuerySettings
D.map8 PeriodicQuerySettings
(D.field "id" D.string)
(D.field "enabled" D.bool)
(D.maybe (D.field "summary" D.string))
(D.field "channel" Data.NotificationChannel.decoder)
(D.maybe (D.field "query" D.string))
(D.maybe (D.field "bookmark" D.string))
(D.maybe (D.field "contentStart" D.string))
(D.field "schedule" D.string)
@ -57,5 +60,6 @@ encode s =
, ( "channel", Data.NotificationChannel.encode s.channel )
, ( "query", Maybe.map E.string s.query |> Maybe.withDefault E.null )
, ( "bookmark", Maybe.map E.string s.bookmark |> Maybe.withDefault E.null )
, ( "contentStart", Maybe.map E.string s.contentStart |> Maybe.withDefault E.null )
, ( "schedule", E.string s.schedule )
]

View File

@ -42,6 +42,10 @@ type alias Texts =
, channelRequired : String
, queryStringRequired : String
, channelHeader : ChannelType -> String
, messageContentTitle : String
, messageContentLabel : String
, messageContentInfo : String
, messageContentPlaceholder : String
}
@ -71,6 +75,10 @@ gb =
, channelRequired = "A valid channel must be given."
, queryStringRequired = "A query string and/or bookmark must be supplied"
, channelHeader = \ct -> "Connection details for " ++ Messages.Data.ChannelType.gb ct
, messageContentTitle = "Customize message"
, messageContentLabel = "Beginning of message"
, messageContentInfo = "Insert text that is prependend to the generated message."
, messageContentPlaceholder = "Hello, this is Docspell informing you about new items "
}
@ -100,4 +108,8 @@ de =
, channelRequired = "Ein Versandkanal muss angegeben werden."
, queryStringRequired = "Eine Suchabfrage und/oder ein Bookmark muss angegeben werden."
, channelHeader = \ct -> "Details für " ++ Messages.Data.ChannelType.de ct
, messageContentTitle = "Nachricht anpassen"
, messageContentLabel = "Anfang der Nachricht"
, messageContentInfo = "Dieser Text wird an den Anfang der generierten Nachricht angefügt."
, messageContentPlaceholder = "Hallo, hier ist Docspell mit den nächsten Themen "
}