Improve form

This commit is contained in:
Eike Kettner 2020-04-20 22:53:08 +02:00
parent 5a2e28415a
commit 3a90d874a5
10 changed files with 297 additions and 132 deletions

View File

@ -15,6 +15,10 @@ trait OTag[F[_]] {
def update(s: RTag): F[AddResult] def update(s: RTag): F[AddResult]
def delete(id: Ident, collective: Ident): F[AddResult] def delete(id: Ident, collective: Ident): F[AddResult]
/** Load all tags given their ids. Ids that are not available are ignored.
*/
def loadAll(ids: List[Ident]): F[Vector[RTag]]
} }
object OTag { object OTag {
@ -48,5 +52,9 @@ object OTag {
} yield n0.getOrElse(0) + n1.getOrElse(0) } yield n0.getOrElse(0) + n1.getOrElse(0)
store.transact(io).attempt.map(AddResult.fromUpdate) store.transact(io).attempt.map(AddResult.fromUpdate)
} }
def loadAll(ids: List[Ident]): F[Vector[RTag]] =
if (ids.isEmpty) Vector.empty.pure[F]
else store.transact(RTag.findAllById(ids))
}) })
} }

View File

@ -1597,7 +1597,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/NotificationData" $ref: "#/components/schemas/NotificationSettings"
post: post:
tags: [ Notification ] tags: [ Notification ]
summary: Change current settings for "Notify Due Items" task summary: Change current settings for "Notify Due Items" task
@ -1683,27 +1683,11 @@ components:
tagsInclude: tagsInclude:
type: array type: array
items: items:
type: string $ref: "#/components/schemas/Tag"
format: ident
tagsExclude: tagsExclude:
type: array type: array
items: items:
type: string $ref: "#/components/schemas/Tag"
format: ident
NotificationData:
description: |
Data for the notification settings.
required:
- settings
properties:
settings:
$ref: "#/components/schemas/NotificationSettings"
nextRun:
type: integer
format: date-time
lastRun:
type: integer
format: date-time
SentMails: SentMails:
description: | description: |
A list of sent mails. A list of sent mails.

View File

@ -25,7 +25,8 @@ object NotifyDueItemsRoutes {
case GET -> Root => case GET -> Root =>
for { for {
task <- ut.getNotifyDueItems(user.account) task <- ut.getNotifyDueItems(user.account)
resp <- Ok(convert(task)) res <- taskToSettings(user.account, backend, task)
resp <- Ok(res)
} yield resp } yield resp
case req @ POST -> Root => case req @ POST -> Root =>
@ -41,9 +42,6 @@ object NotifyDueItemsRoutes {
} }
} }
def convert(task: UserTask[NotifyDueItemsArgs]): NotificationData =
NotificationData(taskToSettings(task), None, None)
def makeTask( def makeTask(
user: AccountId, user: AccountId,
settings: NotificationSettings settings: NotificationSettings
@ -58,20 +56,34 @@ object NotifyDueItemsRoutes {
settings.smtpConnection, settings.smtpConnection,
settings.recipients, settings.recipients,
settings.remindDays, settings.remindDays,
settings.tagsInclude.map(Ident.unsafe), settings.tagsInclude.map(_.id),
settings.tagsExclude.map(Ident.unsafe) settings.tagsExclude.map(_.id)
) )
) )
def taskToSettings(task: UserTask[NotifyDueItemsArgs]): NotificationSettings = // TODO this should be inside the backend code and not here
NotificationSettings( def taskToSettings[F[_]: Sync](
account: AccountId,
backend: BackendApp[F],
task: UserTask[NotifyDueItemsArgs]
): F[NotificationSettings] =
for {
tinc <- backend.tag.loadAll(task.args.tagsInclude)
texc <- backend.tag.loadAll(task.args.tagsExclude)
conn <- backend.mail
.getSettings(account, None)
.map(
_.find(_.name == task.args.smtpConnection)
.map(_.name)
)
} yield NotificationSettings(
task.id, task.id,
task.enabled, task.enabled,
task.args.smtpConnection, conn.getOrElse(Ident.unsafe("none")),
task.args.recipients, task.args.recipients,
task.timer, task.timer,
task.args.remindDays, task.args.remindDays,
task.args.tagsInclude.map(_.id), tinc.map(Conversions.mkTag).toList,
task.args.tagsExclude.map(_.id) texc.map(Conversions.mkTag).toList
) )
} }

View File

@ -82,6 +82,11 @@ object RTag {
sql.query[RTag].to[Vector] sql.query[RTag].to[Vector]
} }
def findAllById(ids: List[Ident]): ConnectionIO[Vector[RTag]] =
selectSimple(all, table, tid.isIn(ids.map(id => sql"$id").toSeq))
.query[RTag]
.to[Vector]
def findByItem(itemId: Ident): ConnectionIO[Vector[RTag]] = { def findByItem(itemId: Ident): ConnectionIO[Vector[RTag]] = {
val rcol = all.map(_.prefix("t")) val rcol = all.map(_.prefix("t"))
(selectSimple( (selectSimple(

View File

@ -21,6 +21,7 @@ module Api exposing
, getJobQueueState , getJobQueueState
, getJobQueueStateIn , getJobQueueStateIn
, getMailSettings , getMailSettings
, getNotifyDueItems
, getOrgLight , getOrgLight
, getOrganizations , getOrganizations
, getPersons , getPersons
@ -85,6 +86,7 @@ import Api.Model.ItemProposals exposing (ItemProposals)
import Api.Model.ItemSearch exposing (ItemSearch) import Api.Model.ItemSearch exposing (ItemSearch)
import Api.Model.ItemUploadMeta exposing (ItemUploadMeta) import Api.Model.ItemUploadMeta exposing (ItemUploadMeta)
import Api.Model.JobQueueState exposing (JobQueueState) import Api.Model.JobQueueState exposing (JobQueueState)
import Api.Model.NotificationSettings exposing (NotificationSettings)
import Api.Model.OptionalDate exposing (OptionalDate) import Api.Model.OptionalDate exposing (OptionalDate)
import Api.Model.OptionalId exposing (OptionalId) import Api.Model.OptionalId exposing (OptionalId)
import Api.Model.OptionalText exposing (OptionalText) import Api.Model.OptionalText exposing (OptionalText)
@ -117,6 +119,22 @@ import Util.Http as Http2
--- NotifyDueItems
getNotifyDueItems :
Flags
-> (Result Http.Error NotificationSettings -> msg)
-> Cmd msg
getNotifyDueItems flags receive =
Http2.authGet
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems"
, account = getAccount flags
, expect = Http.expectJson receive Api.Model.NotificationSettings.decoder
}
--- CalEvent --- CalEvent

View File

@ -2,7 +2,6 @@ module Comp.CalEventInput exposing
( Model ( Model
, Msg , Msg
, init , init
, initialSchedule
, update , update
, view , view
) )
@ -10,7 +9,9 @@ module Comp.CalEventInput exposing
import Api import Api
import Api.Model.CalEventCheck exposing (CalEventCheck) import Api.Model.CalEventCheck exposing (CalEventCheck)
import Api.Model.CalEventCheckResult exposing (CalEventCheckResult) import Api.Model.CalEventCheckResult exposing (CalEventCheckResult)
import Data.CalEvent exposing (CalEvent)
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Data.Validated exposing (Validated(..))
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
import Html.Events exposing (onInput) import Html.Events exposing (onInput)
@ -21,14 +22,7 @@ import Util.Time
type alias Model = type alias Model =
{ year : String { checkResult : Maybe CalEventCheckResult
, month : String
, day : String
, hour : String
, minute : String
, weekday : Maybe String
, event : Maybe String
, checkResult : Maybe CalEventCheckResult
} }
@ -39,68 +33,29 @@ type Msg
| SetHour String | SetHour String
| SetMinute String | SetMinute String
| SetWeekday String | SetWeekday String
| CheckInputMsg (Result Http.Error CalEventCheckResult) | CheckInputMsg CalEvent (Result Http.Error CalEventCheckResult)
initialSchedule : String init : Flags -> CalEvent -> ( Model, Cmd Msg )
initialSchedule = init flags ev =
"*-*-01 00:00" ( Model Nothing, checkInput flags ev )
init : Flags -> ( Model, Cmd Msg ) checkInput : Flags -> CalEvent -> Cmd Msg
init flags = checkInput flags ev =
let let
model = eventStr =
{ year = "*" Data.CalEvent.makeEvent ev
, month = "*"
, day = "1"
, hour = "0"
, minute = "0"
, weekday = Nothing
, event = Nothing
, checkResult = Nothing
}
in
( model, checkInput flags model )
toEvent : Model -> String
toEvent model =
let
datetime =
model.year
++ "-"
++ model.month
++ "-"
++ model.day
++ " "
++ model.hour
++ ":"
++ model.minute
in
case model.weekday of
Just wd ->
wd ++ " " ++ datetime
Nothing ->
datetime
checkInput : Flags -> Model -> Cmd Msg
checkInput flags model =
let
event =
toEvent model
input = input =
CalEventCheck event CalEventCheck eventStr
in in
Api.checkCalEvent flags input CheckInputMsg Api.checkCalEvent flags input (CheckInputMsg ev)
withCheckInput : Flags -> Model -> ( Model, Cmd Msg, Maybe String ) withCheckInput : Flags -> CalEvent -> Model -> ( Model, Cmd Msg, Validated CalEvent )
withCheckInput flags model = withCheckInput flags ev model =
( model, checkInput flags model, Nothing ) ( model, checkInput flags ev, Unknown ev )
isCheckError : Model -> Bool isCheckError : Model -> Bool
@ -110,46 +65,49 @@ isCheckError model =
|> not |> not
update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Maybe String ) update : Flags -> CalEvent -> Msg -> Model -> ( Model, Cmd Msg, Validated CalEvent )
update flags msg model = update flags ev msg model =
case msg of case msg of
SetYear str -> SetYear str ->
withCheckInput flags { model | year = str } withCheckInput flags { ev | year = str } model
SetMonth str -> SetMonth str ->
withCheckInput flags { model | month = str } withCheckInput flags { ev | month = str } model
SetDay str -> SetDay str ->
withCheckInput flags { model | day = str } withCheckInput flags { ev | day = str } model
SetHour str -> SetHour str ->
withCheckInput flags { model | hour = str } withCheckInput flags { ev | hour = str } model
SetMinute str -> SetMinute str ->
withCheckInput flags { model | minute = str } withCheckInput flags { ev | minute = str } model
SetWeekday str -> SetWeekday str ->
withCheckInput flags { model | weekday = Util.Maybe.fromString str } withCheckInput flags { ev | weekday = Util.Maybe.fromString str } model
CheckInputMsg (Ok res) -> CheckInputMsg event (Ok res) ->
let let
m = m =
{ model { model | checkResult = Just res }
| event = res.event
, checkResult = Just res
}
in in
( m, Cmd.none, res.event ) ( m
, Cmd.none
, if res.success then
Valid event
CheckInputMsg (Err err) -> else
Invalid event
)
CheckInputMsg event (Err err) ->
let let
emptyResult = emptyResult =
Api.Model.CalEventCheckResult.empty Api.Model.CalEventCheckResult.empty
m = m =
{ model { model
| event = Nothing | checkResult =
, checkResult =
Just Just
{ emptyResult { emptyResult
| success = False | success = False
@ -157,14 +115,14 @@ update flags msg model =
} }
} }
in in
( m, Cmd.none, Nothing ) ( m, Cmd.none, Unknown event )
view : String -> Model -> Html Msg view : String -> CalEvent -> Model -> Html Msg
view extraClasses model = view extraClasses ev model =
let let
yearLen = yearLen =
Basics.max 4 (String.length model.year) Basics.max 4 (String.length ev.year)
otherLen str = otherLen str =
Basics.max 2 (String.length str) Basics.max 2 (String.length str)
@ -181,10 +139,10 @@ view extraClasses model =
[ type_ "text" [ type_ "text"
, class "time-input" , class "time-input"
, size , size
(Maybe.map otherLen model.weekday (Maybe.map otherLen ev.weekday
|> Maybe.withDefault 4 |> Maybe.withDefault 4
) )
, Maybe.withDefault "" model.weekday , Maybe.withDefault "" ev.weekday
|> value |> value
, onInput SetWeekday , onInput SetWeekday
] ]
@ -196,7 +154,7 @@ view extraClasses model =
[ type_ "text" [ type_ "text"
, class "time-input" , class "time-input"
, size yearLen , size yearLen
, value model.year , value ev.year
, onInput SetYear , onInput SetYear
] ]
[] []
@ -209,8 +167,8 @@ view extraClasses model =
, input , input
[ type_ "text" [ type_ "text"
, class "time-input" , class "time-input"
, size (otherLen model.month) , size (otherLen ev.month)
, value model.month , value ev.month
, onInput SetMonth , onInput SetMonth
] ]
[] []
@ -223,8 +181,8 @@ view extraClasses model =
, input , input
[ type_ "text" [ type_ "text"
, class "time-input" , class "time-input"
, size (otherLen model.day) , size (otherLen ev.day)
, value model.day , value ev.day
, onInput SetDay , onInput SetDay
] ]
[] []
@ -237,8 +195,8 @@ view extraClasses model =
, input , input
[ type_ "text" [ type_ "text"
, class "time-input" , class "time-input"
, size (otherLen model.hour) , size (otherLen ev.hour)
, value model.hour , value ev.hour
, onInput SetHour , onInput SetHour
] ]
[] []
@ -251,8 +209,8 @@ view extraClasses model =
, input , input
[ type_ "text" [ type_ "text"
, class "time-input" , class "time-input"
, size (otherLen model.minute) , size (otherLen ev.minute)
, value model.minute , value ev.minute
, onInput SetMinute , onInput SetMinute
] ]
[] []

View File

@ -15,7 +15,9 @@ import Comp.CalEventInput
import Comp.Dropdown import Comp.Dropdown
import Comp.EmailInput import Comp.EmailInput
import Comp.IntField import Comp.IntField
import Data.CalEvent exposing (CalEvent)
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Data.Validated exposing (Validated)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
import Html.Events exposing (onCheck, onClick) import Html.Events exposing (onCheck, onClick)
@ -35,7 +37,7 @@ type alias Model =
, remindDays : Maybe Int , remindDays : Maybe Int
, remindDaysModel : Comp.IntField.Model , remindDaysModel : Comp.IntField.Model
, enabled : Bool , enabled : Bool
, schedule : String , schedule : Validated CalEvent
, scheduleModel : Comp.CalEventInput.Model , scheduleModel : Comp.CalEventInput.Model
, formError : Maybe String , formError : Maybe String
} }
@ -52,6 +54,7 @@ type Msg
| RemindDaysMsg Comp.IntField.Msg | RemindDaysMsg Comp.IntField.Msg
| ToggleEnabled | ToggleEnabled
| CalEventMsg Comp.CalEventInput.Msg | CalEventMsg Comp.CalEventInput.Msg
| SetNotificationSettings (Result Http.Error NotificationSettings)
initCmd : Flags -> Cmd Msg initCmd : Flags -> Cmd Msg
@ -59,14 +62,18 @@ initCmd flags =
Cmd.batch Cmd.batch
[ Api.getMailSettings flags "" ConnResp [ Api.getMailSettings flags "" ConnResp
, Api.getTags flags "" GetTagsResp , Api.getTags flags "" GetTagsResp
, Api.getNotifyDueItems flags SetNotificationSettings
] ]
init : Flags -> ( Model, Cmd Msg ) init : Flags -> ( Model, Cmd Msg )
init flags = init flags =
let let
initialSchedule =
Data.Validated.Unknown Data.CalEvent.everyMonth
( sm, sc ) = ( sm, sc ) =
Comp.CalEventInput.init flags Comp.CalEventInput.init flags Data.CalEvent.everyMonth
in in
( { settings = Api.Model.NotificationSettings.empty ( { settings = Api.Model.NotificationSettings.empty
, connectionModel = , connectionModel =
@ -81,7 +88,7 @@ init flags =
, remindDays = Just 1 , remindDays = Just 1
, remindDaysModel = Comp.IntField.init (Just 1) Nothing True "Remind Days" , remindDaysModel = Comp.IntField.init (Just 1) Nothing True "Remind Days"
, enabled = False , enabled = False
, schedule = Comp.CalEventInput.initialSchedule , schedule = initialSchedule
, scheduleModel = sm , scheduleModel = sm
, formError = Nothing , formError = Nothing
} }
@ -98,10 +105,13 @@ update flags msg model =
CalEventMsg lmsg -> CalEventMsg lmsg ->
let let
( cm, cc, cs ) = ( cm, cc, cs ) =
Comp.CalEventInput.update flags lmsg model.scheduleModel Comp.CalEventInput.update flags
(Data.Validated.value model.schedule)
lmsg
model.scheduleModel
in in
( { model ( { model
| schedule = Maybe.withDefault model.schedule cs | schedule = cs
, scheduleModel = cm , scheduleModel = cm
} }
, Cmd.map CalEventMsg cc , Cmd.map CalEventMsg cc
@ -141,7 +151,7 @@ update flags msg model =
| connectionModel = cm | connectionModel = cm
, formError = , formError =
if names == [] then if names == [] then
Just "No E-Mail connections configured. Goto user settings to add one." Just "No E-Mail connections configured. Goto E-Mail Settings to add one."
else else
Nothing Nothing
@ -199,7 +209,40 @@ update flags msg model =
ToggleEnabled -> ToggleEnabled ->
( { model | enabled = not model.enabled }, Cmd.none ) ( { model | enabled = not model.enabled }, Cmd.none )
_ -> SetNotificationSettings (Ok s) ->
let
( nm, nc ) =
Util.Update.andThen1
[ update flags (ConnMsg (Comp.Dropdown.SetSelection [ s.smtpConnection ]))
, update flags (TagIncMsg (Comp.Dropdown.SetSelection s.tagsInclude))
, update flags (TagExcMsg (Comp.Dropdown.SetSelection s.tagsExclude))
]
model
newSchedule =
Data.CalEvent.fromEvent s.schedule
|> Maybe.withDefault Data.CalEvent.everyMonth
( sm, sc ) =
Comp.CalEventInput.init flags newSchedule
in
( { nm
| settings = s
, recipients = s.recipients
, remindDays = Just s.remindDays
, enabled = s.enabled
, schedule = Data.Validated.Unknown newSchedule
}
, Cmd.batch
[ nc
, Cmd.map CalEventMsg sc
]
)
SetNotificationSettings (Err err) ->
( { model | formError = Just (Util.Http.errorToString err) }, Cmd.none )
Submit ->
( model, Cmd.none ) ( model, Cmd.none )
@ -275,14 +318,22 @@ view extraClasses model =
] ]
] ]
, Html.map CalEventMsg , Html.map CalEventMsg
(Comp.CalEventInput.view "" model.scheduleModel) (Comp.CalEventInput.view ""
(Data.Validated.value model.schedule)
model.scheduleModel
)
, span [ class "small-info" ] , span [ class "small-info" ]
[ text "Specify how often and when this task should run. " [ text "Specify how often and when this task should run. "
, text "Use English 3-letter weekdays. Either a single value, " , text "Use English 3-letter weekdays. Either a single value, "
, text "a list or a '*' (meaning all) is allowed for each part." , text "a list (ex. 1,2,3), a range (ex. 1..3) or a '*' (meaning all) "
, text "is allowed for each part."
] ]
] ]
, div [ class "ui divider" ] [] , div [ class "ui divider" ] []
, div [ class "ui error message" ]
[ Maybe.withDefault "" model.formError
|> text
]
, button , button
[ class "ui primary button" [ class "ui primary button"
, onClick Submit , onClick Submit

View File

@ -0,0 +1,107 @@
module Data.CalEvent exposing
( CalEvent
, everyMonth
, fromEvent
, makeEvent
)
import Util.Maybe
type alias CalEvent =
{ weekday : Maybe String
, year : String
, month : String
, day : String
, hour : String
, minute : String
}
everyMonth : CalEvent
everyMonth =
CalEvent Nothing "*" "*" "01" "00" "00"
makeEvent : CalEvent -> String
makeEvent ev =
let
datetime =
ev.year
++ "-"
++ ev.month
++ "-"
++ ev.day
++ " "
++ ev.hour
++ ":"
++ ev.minute
in
case ev.weekday of
Just wd ->
wd ++ " " ++ datetime
Nothing ->
datetime
fromEvent : String -> Maybe CalEvent
fromEvent str =
let
init =
everyMonth
parts =
String.split " " str
in
case parts of
wd :: date :: time :: [] ->
Maybe.andThen
(fromDate date)
(fromTime time init)
|> Maybe.map (withWeekday wd)
date :: time :: [] ->
Maybe.andThen
(fromDate date)
(fromTime time init)
_ ->
Nothing
fromDate : String -> CalEvent -> Maybe CalEvent
fromDate date ev =
let
parts =
String.split "-" date
in
case parts of
y :: m :: d :: [] ->
Just
{ ev
| year = y
, month = m
, day = d
}
_ ->
Nothing
fromTime : String -> CalEvent -> Maybe CalEvent
fromTime time ev =
case String.split ":" time of
h :: m :: _ :: [] ->
Just { ev | hour = h, minute = m }
h :: m :: [] ->
Just { ev | hour = h, minute = m }
_ ->
Nothing
withWeekday : String -> CalEvent -> CalEvent
withWeekday wd ev =
{ ev | weekday = Util.Maybe.fromString wd }

View File

@ -0,0 +1,20 @@
module Data.Validated exposing (Validated(..), value)
type Validated a
= Valid a
| Invalid a
| Unknown a
value : Validated a -> a
value va =
case va of
Valid a ->
a
Invalid a ->
a
Unknown a ->
a

View File

@ -11,6 +11,7 @@
} }
.calevent-input input.time-input { .calevent-input input.time-input {
border: 0 !important; border: 0 !important;
font-family: monospace !important;
text-align: center; text-align: center;
} }
.calevent-input .separator { .calevent-input .separator {
@ -27,6 +28,7 @@
text-align: center; text-align: center;
} }
.default-layout .right-float { .default-layout .right-float {
float: right; float: right;
} }