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 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 {
@ -48,5 +52,9 @@ object OTag {
} yield n0.getOrElse(0) + n1.getOrElse(0)
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:
application/json:
schema:
$ref: "#/components/schemas/NotificationData"
$ref: "#/components/schemas/NotificationSettings"
post:
tags: [ Notification ]
summary: Change current settings for "Notify Due Items" task
@ -1683,27 +1683,11 @@ components:
tagsInclude:
type: array
items:
type: string
format: ident
$ref: "#/components/schemas/Tag"
tagsExclude:
type: array
items:
type: string
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
$ref: "#/components/schemas/Tag"
SentMails:
description: |
A list of sent mails.

View File

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

View File

@ -82,6 +82,11 @@ object RTag {
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]] = {
val rcol = all.map(_.prefix("t"))
(selectSimple(

View File

@ -21,6 +21,7 @@ module Api exposing
, getJobQueueState
, getJobQueueStateIn
, getMailSettings
, getNotifyDueItems
, getOrgLight
, getOrganizations
, getPersons
@ -85,6 +86,7 @@ import Api.Model.ItemProposals exposing (ItemProposals)
import Api.Model.ItemSearch exposing (ItemSearch)
import Api.Model.ItemUploadMeta exposing (ItemUploadMeta)
import Api.Model.JobQueueState exposing (JobQueueState)
import Api.Model.NotificationSettings exposing (NotificationSettings)
import Api.Model.OptionalDate exposing (OptionalDate)
import Api.Model.OptionalId exposing (OptionalId)
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

View File

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

View File

@ -15,7 +15,9 @@ import Comp.CalEventInput
import Comp.Dropdown
import Comp.EmailInput
import Comp.IntField
import Data.CalEvent exposing (CalEvent)
import Data.Flags exposing (Flags)
import Data.Validated exposing (Validated)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onCheck, onClick)
@ -35,7 +37,7 @@ type alias Model =
, remindDays : Maybe Int
, remindDaysModel : Comp.IntField.Model
, enabled : Bool
, schedule : String
, schedule : Validated CalEvent
, scheduleModel : Comp.CalEventInput.Model
, formError : Maybe String
}
@ -52,6 +54,7 @@ type Msg
| RemindDaysMsg Comp.IntField.Msg
| ToggleEnabled
| CalEventMsg Comp.CalEventInput.Msg
| SetNotificationSettings (Result Http.Error NotificationSettings)
initCmd : Flags -> Cmd Msg
@ -59,14 +62,18 @@ initCmd flags =
Cmd.batch
[ Api.getMailSettings flags "" ConnResp
, Api.getTags flags "" GetTagsResp
, Api.getNotifyDueItems flags SetNotificationSettings
]
init : Flags -> ( Model, Cmd Msg )
init flags =
let
initialSchedule =
Data.Validated.Unknown Data.CalEvent.everyMonth
( sm, sc ) =
Comp.CalEventInput.init flags
Comp.CalEventInput.init flags Data.CalEvent.everyMonth
in
( { settings = Api.Model.NotificationSettings.empty
, connectionModel =
@ -81,7 +88,7 @@ init flags =
, remindDays = Just 1
, remindDaysModel = Comp.IntField.init (Just 1) Nothing True "Remind Days"
, enabled = False
, schedule = Comp.CalEventInput.initialSchedule
, schedule = initialSchedule
, scheduleModel = sm
, formError = Nothing
}
@ -98,10 +105,13 @@ update flags msg model =
CalEventMsg lmsg ->
let
( cm, cc, cs ) =
Comp.CalEventInput.update flags lmsg model.scheduleModel
Comp.CalEventInput.update flags
(Data.Validated.value model.schedule)
lmsg
model.scheduleModel
in
( { model
| schedule = Maybe.withDefault model.schedule cs
| schedule = cs
, scheduleModel = cm
}
, Cmd.map CalEventMsg cc
@ -141,7 +151,7 @@ update flags msg model =
| connectionModel = cm
, formError =
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
Nothing
@ -199,7 +209,40 @@ update flags msg model =
ToggleEnabled ->
( { 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 )
@ -275,14 +318,22 @@ view extraClasses model =
]
]
, Html.map CalEventMsg
(Comp.CalEventInput.view "" model.scheduleModel)
(Comp.CalEventInput.view ""
(Data.Validated.value model.schedule)
model.scheduleModel
)
, span [ class "small-info" ]
[ text "Specify how often and when this task should run. "
, 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 error message" ]
[ Maybe.withDefault "" model.formError
|> text
]
, button
[ class "ui primary button"
, 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 {
border: 0 !important;
font-family: monospace !important;
text-align: center;
}
.calevent-input .separator {
@ -27,6 +28,7 @@
text-align: center;
}
.default-layout .right-float {
float: right;
}