Allow to start a user task once

This commit is contained in:
Eike Kettner 2020-04-21 22:32:43 +02:00
parent af5b62c057
commit bbfd694b45
9 changed files with 157 additions and 55 deletions

View File

@ -54,7 +54,7 @@ object BackendApp {
jobImpl <- OJob(store, joexImpl)
itemImpl <- OItem(store)
mailImpl <- OMail(store, JavaMailEmil(blocker))
userTaskImpl <- OUserTask(utStore, joexImpl)
userTaskImpl <- OUserTask(utStore, queue, joexImpl)
} yield new BackendApp[F] {
val login: Login[F] = loginImpl
val signup: OSignup[F] = signupImpl

View File

@ -2,26 +2,54 @@ package docspell.backend.ops
import cats.implicits._
import cats.effect._
import com.github.eikek.calev.CalEvent
import io.circe.Encoder
import docspell.store.queue.JobQueue
import docspell.store.usertask._
import docspell.common._
import com.github.eikek.calev.CalEvent
trait OUserTask[F[_]] {
/** Return the settings for the notify-due-items task of the current
* user. There is at most one such task per user.
*/
def getNotifyDueItems(account: AccountId): F[UserTask[NotifyDueItemsArgs]]
/** Updates the notify-due-items tasks and notifies the joex nodes.
*/
def submitNotifyDueItems(
account: AccountId,
task: UserTask[NotifyDueItemsArgs]
): F[Unit]
/** Discards the schedule and immediately submits the task to the job
* executor's queue. It will not update the corresponding periodic
* task.
*/
def executeNow[A](account: AccountId, task: UserTask[A])(
implicit E: Encoder[A]
): F[Unit]
}
object OUserTask {
def apply[F[_]: Effect](store: UserTaskStore[F], joex: OJoex[F]): Resource[F, OUserTask[F]] =
def apply[F[_]: Effect](
store: UserTaskStore[F],
queue: JobQueue[F],
joex: OJoex[F]
): Resource[F, OUserTask[F]] =
Resource.pure[F, OUserTask[F]](new OUserTask[F] {
def executeNow[A](account: AccountId, task: UserTask[A])(
implicit E: Encoder[A]
): F[Unit] =
for {
ptask <- task.encode.toPeriodicTask(account)
job <- ptask.toJob
_ <- queue.insert(job)
_ <- joex.notifyAllNodes
} yield ()
def getNotifyDueItems(account: AccountId): F[UserTask[NotifyDueItemsArgs]] =
store
.getOneByName[NotifyDueItemsArgs](account, NotifyDueItemsArgs.taskName)

View File

@ -1583,7 +1583,7 @@ paths:
$ref: "#/components/schemas/CalEventCheckResult"
/sec/usertask/notifydueitems:
get:
tags: [ Notification ]
tags: [ User Tasks ]
summary: Get settings for "Notify Due Items" task
description: |
Return the current notification settings of the authenticated
@ -1599,7 +1599,7 @@ paths:
schema:
$ref: "#/components/schemas/NotificationSettings"
post:
tags: [ Notification ]
tags: [ User Tasks ]
summary: Change current settings for "Notify Due Items" task
description: |
Change the current notification settings of the authenticated
@ -1618,6 +1618,27 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/BasicResult"
/sec/usertask/notifydueitems/startonce:
post:
tags: [ User Tasks ]
summary: Start the "Notify Due Items" task once
description: |
Starts the notify-due-items task just once, discarding the
schedule and not updating the periodic task.
security:
- authTokenHeader: []
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/NotificationSettings"
responses:
200:
description: Ok
content:
application/json:
schema:
$ref: "#/components/schemas/BasicResult"
components:
schemas:

View File

@ -22,6 +22,17 @@ object NotifyDueItemsRoutes {
import dsl._
HttpRoutes.of {
case req @ POST -> Root / "startonce" =>
for {
data <- req.as[NotificationSettings]
task = makeTask(user.account, data)
res <- ut
.executeNow(user.account, task)
.attempt
.map(Conversions.basicResult(_, "Submitted successfully."))
resp <- Ok(res)
} yield resp
case GET -> Root =>
for {
task <- ut.getNotifyDueItems(user.account)
@ -36,7 +47,7 @@ object NotifyDueItemsRoutes {
res <- ut
.submitNotifyDueItems(user.account, task)
.attempt
.map(Conversions.basicResult(_, "Update ok."))
.map(Conversions.basicResult(_, "Saved successfully."))
resp <- Ok(res)
} yield resp
}
@ -61,7 +72,6 @@ object NotifyDueItemsRoutes {
)
)
// TODO this should be inside the backend code and not here
def taskToSettings[F[_]: Sync](
account: AccountId,
backend: BackendApp[F],

View File

@ -33,13 +33,13 @@ object QUserTask {
def insert(account: AccountId, task: UserTask[String]): ConnectionIO[Int] =
for {
r <- makePeriodicTask(account, task)
r <- task.toPeriodicTask[ConnectionIO](account)
n <- RPeriodicTask.insert(r)
} yield n
def update(account: AccountId, task: UserTask[String]): ConnectionIO[Int] =
for {
r <- makePeriodicTask(account, task)
r <- task.toPeriodicTask[ConnectionIO](account)
n <- RPeriodicTask.update(r)
} yield n
@ -69,21 +69,4 @@ object QUserTask {
def makeUserTask(r: RPeriodicTask): UserTask[String] =
UserTask(r.id, r.task, r.enabled, r.timer, r.args)
def makePeriodicTask(
account: AccountId,
t: UserTask[String]
): ConnectionIO[RPeriodicTask] =
RPeriodicTask
.create[ConnectionIO](
t.enabled,
t.name,
account.collective,
t.args,
s"${account.user.id}: ${t.name.id}",
account.user,
Priority.Low,
t.timer
)
.map(r => r.copy(id = t.id))
}

View File

@ -36,6 +36,8 @@ trait PeriodicTaskStore[F[_]] {
*/
def add(task: RPeriodicTask): F[AddResult]
/** Find all joex nodes as registered in the database.
*/
def findJoexNodes: F[Vector[RNode]]
}

View File

@ -1,33 +1,53 @@
package docspell.store.usertask
import cats.effect._
import cats.implicits._
import com.github.eikek.calev.CalEvent
import io.circe.Decoder
import io.circe.Encoder
import docspell.common._
import docspell.common.syntax.all._
import docspell.store.records.RPeriodicTask
case class UserTask[A](
id: Ident,
name: Ident,
enabled: Boolean,
timer: CalEvent,
args: A
id: Ident,
name: Ident,
enabled: Boolean,
timer: CalEvent,
args: A
) {
def encode(implicit E: Encoder[A]): UserTask[String] =
copy(args = E(args).noSpaces)
}
object UserTask {
implicit final class UserTaskCodec(ut: UserTask[String]) {
def decode[A](implicit D: Decoder[A]): Either[String, UserTask[A]] =
ut.args.parseJsonAs[A]
.left.map(_.getMessage)
ut.args
.parseJsonAs[A]
.left
.map(_.getMessage)
.map(a => ut.copy(args = a))
def toPeriodicTask[F[_]: Sync](
account: AccountId
): F[RPeriodicTask] =
RPeriodicTask
.create[F](
ut.enabled,
ut.name,
account.collective,
ut.args,
s"${account.user.id}: ${ut.name.id}",
account.user,
Priority.Low,
ut.timer
)
.map(r => r.copy(id = ut.id))
}
}

View File

@ -59,6 +59,7 @@ module Api exposing
, setItemNotes
, setTags
, setUnconfirmed
, startOnceNotifyDueItems
, submitNotifyDueItems
, upload
, uploadSingle
@ -123,6 +124,20 @@ import Util.Http as Http2
--- NotifyDueItems
startOnceNotifyDueItems :
Flags
-> NotificationSettings
-> (Result Http.Error BasicResult -> msg)
-> Cmd msg
startOnceNotifyDueItems flags settings receive =
Http2.authPost
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems/startonce"
, account = getAccount flags
, body = Http.jsonBody (Api.Model.NotificationSettings.encode settings)
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
submitNotifyDueItems :
Flags
-> NotificationSettings

View File

@ -59,6 +59,7 @@ type Msg
| CalEventMsg Comp.CalEventInput.Msg
| SetNotificationSettings (Result Http.Error NotificationSettings)
| SubmitResp (Result Http.Error BasicResult)
| StartOnce
initCmd : Flags -> Cmd Msg
@ -149,6 +150,27 @@ makeSettings model =
model.schedule
withValidSettings : (NotificationSettings -> Cmd Msg) -> Model -> ( Model, Cmd Msg )
withValidSettings mkcmd model =
case makeSettings model of
Valid set ->
( { model | formMsg = Nothing }
, mkcmd set
)
Invalid errs _ ->
let
errMsg =
String.join ", " errs
in
( { model | formMsg = Just (BasicResult False errMsg) }, Cmd.none )
Unknown _ ->
( { model | formMsg = Just (BasicResult False "An unknown error occured") }
, Cmd.none
)
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
update flags msg model =
case msg of
@ -341,23 +363,14 @@ update flags msg model =
)
Submit ->
case makeSettings model of
Valid set ->
( { model | formMsg = Nothing }
, Api.submitNotifyDueItems flags set SubmitResp
)
withValidSettings
(\set -> Api.submitNotifyDueItems flags set SubmitResp)
model
Invalid errs _ ->
let
errMsg =
String.join ", " errs
in
( { model | formMsg = Just (BasicResult False errMsg) }, Cmd.none )
Unknown _ ->
( { model | formMsg = Just (BasicResult False "An unknown error occured") }
, Cmd.none
)
StartOnce ->
withValidSettings
(\set -> Api.startOnceNotifyDueItems flags set SubmitResp)
model
SubmitResp (Ok res) ->
( { model | formMsg = Just res }
@ -484,18 +497,28 @@ view extraClasses model =
]
]
, div [ class "ui divider" ] []
, div [ class "ui error message" ]
, div
[ classList
[ ( "ui message", True )
, ( "success", isFormSuccess model )
, ( "error", isFormError model )
, ( "hidden", model.formMsg == Nothing )
]
]
[ Maybe.map .message model.formMsg
|> Maybe.withDefault ""
|> text
]
, div [ class "ui success message" ]
[ text "Successfully saved."
]
, button
[ class "ui primary button"
, onClick Submit
]
[ text "Submit"
]
, button
[ class "ui right floated button"
, onClick StartOnce
]
[ text "Start Once"
]
]