feat(notification triggers): redesign interface

Fixes #410
This commit is contained in:
Gregor Kleen 2019-07-29 17:10:38 +02:00
parent 9f0a91f0dd
commit 84c12b5fc7
6 changed files with 127 additions and 15 deletions

View File

@ -737,6 +737,12 @@ NotificationTriggerCorrectionsNotDistributed: Nicht alle Abgaben eines meiner Ü
NotificationTriggerUserRightsUpdate: Meine Berechtigungen wurden geändert NotificationTriggerUserRightsUpdate: Meine Berechtigungen wurden geändert
NotificationTriggerUserAuthModeUpdate: Mein Anmelde-Modus wurde geändert NotificationTriggerUserAuthModeUpdate: Mein Anmelde-Modus wurde geändert
NotificationTriggerKindAll: Für alle Benutzer
NotificationTriggerKindCourseParticipant: Für Kursteilnehmer
NotificationTriggerKindCorrector: Für Korrektoren
NotificationTriggerKindLecturer: Für Dozenten
NotificationTriggerKindAdmin: Für Administratoren
CorrCreate: Abgaben erstellen CorrCreate: Abgaben erstellen
UnknownPseudonymWord pseudonymWord@Text: Unbekanntes Pseudonym-Wort "#{pseudonymWord}" UnknownPseudonymWord pseudonymWord@Text: Unbekanntes Pseudonym-Wort "#{pseudonymWord}"
InvalidPseudonym pseudonym@Text: Invalides Pseudonym "#{pseudonym}" InvalidPseudonym pseudonym@Text: Invalides Pseudonym "#{pseudonym}"

View File

@ -14,6 +14,7 @@ module Database.Esqueleto.Utils
, orderByList , orderByList
, orderByOrd, orderByEnum , orderByOrd, orderByEnum
, lower, ciEq , lower, ciEq
, selectExists
) where ) where
@ -189,3 +190,12 @@ lower = E.unsafeSqlFunction "LOWER"
ciEq :: E.SqlString s => E.SqlExpr (E.Value s) -> E.SqlExpr (E.Value s) -> E.SqlExpr (E.Value Bool) ciEq :: E.SqlString s => E.SqlExpr (E.Value s) -> E.SqlExpr (E.Value s) -> E.SqlExpr (E.Value Bool)
ciEq a b = lower a E.==. lower b ciEq a b = lower a E.==. lower b
selectExists :: forall m a. MonadIO m => E.SqlQuery a -> E.SqlReadT m Bool
selectExists query = do
res <- E.select . return . E.exists $ void query
case res of
[E.Value b] -> return b
_other -> error "SELECT EXISTS ... returned zero or more than one rows"

View File

@ -9,9 +9,11 @@ import Utils.Lens
-- import Colonnade hiding (fromMaybe, singleton) -- import Colonnade hiding (fromMaybe, singleton)
-- import Yesod.Colonnade -- import Yesod.Colonnade
import Data.Monoid (Any(..)) import Data.Monoid (Any(..))
import Data.Map ((!))
import qualified Data.Map as Map import qualified Data.Map as Map
import qualified Data.Set as Set import qualified Data.Set as Set
import qualified Database.Esqueleto as E import qualified Database.Esqueleto as E
import qualified Database.Esqueleto.Utils as E
-- import Database.Esqueleto ((^.)) -- import Database.Esqueleto ((^.))
@ -26,6 +28,14 @@ data SettingsForm = SettingsForm
, stgNotificationSettings :: NotificationSettings , stgNotificationSettings :: NotificationSettings
} }
data NotificationTriggerKind = NTKAll | NTKCourseParticipant | NTKCorrector | NTKLecturer | NTKAdmin
deriving (Eq, Ord, Enum, Bounded, Generic, Typeable)
instance Universe NotificationTriggerKind
instance Finite NotificationTriggerKind
embedRenderMessage ''UniWorX ''NotificationTriggerKind $ ("NotificationTriggerKind" <>) . mconcat . drop 1 . splitCamel
makeSettingForm :: Maybe SettingsForm -> Form SettingsForm makeSettingForm :: Maybe SettingsForm -> Form SettingsForm
makeSettingForm template html = do makeSettingForm template html = do
(result, widget) <- flip (renderAForm FormStandard) html $ SettingsForm (result, widget) <- flip (renderAForm FormStandard) html $ SettingsForm
@ -38,7 +48,7 @@ makeSettingForm template html = do
<*> areq (selectField $ dateTimeFormatOptions SelFormatDate) (fslI MsgDateFormat) (stgDate <$> template) <*> areq (selectField $ dateTimeFormatOptions SelFormatDate) (fslI MsgDateFormat) (stgDate <$> template)
<*> areq (selectField $ dateTimeFormatOptions SelFormatTime) (fslI MsgTimeFormat) (stgTime <$> template) <*> areq (selectField $ dateTimeFormatOptions SelFormatTime) (fslI MsgTimeFormat) (stgTime <$> template)
<* aformSection MsgFormBehaviour <* aformSection MsgFormBehaviour
<*> areq checkBoxField (fslI MsgDownloadFiles <*> apopt checkBoxField (fslI MsgDownloadFiles
& setTooltip MsgDownloadFilesTip & setTooltip MsgDownloadFilesTip
) (stgDownloadFiles <$> template) ) (stgDownloadFiles <$> template)
<* aformSection MsgFormNotifications <* aformSection MsgFormNotifications
@ -76,9 +86,64 @@ makeSettingForm template html = do
-- nsForm nt = fromMaybe False <$> aopt checkBoxField (fslI nt) (Just $ flip notificationAllowed nt . stgNotificationSettings <$> template) -- nsForm nt = fromMaybe False <$> aopt checkBoxField (fslI nt) (Just $ flip notificationAllowed nt . stgNotificationSettings <$> template)
notificationForm :: Maybe NotificationSettings -> AForm Handler NotificationSettings notificationForm :: Maybe NotificationSettings -> AForm Handler NotificationSettings
notificationForm template = NotificationSettings <$> funcForm nsForm (fslI MsgNotificationSettings) True notificationForm template = wFormToAForm $ do
where mbUid <- liftHandlerT maybeAuthId
nsForm nt = fromMaybe False <$> aopt checkBoxField (fslI nt) (Just $ flip notificationAllowed nt <$> template) isAdmin <- hasReadAccessTo AdminR
let
sectionIsHidden :: NotificationTriggerKind -> DB Bool
sectionIsHidden nt
| isAdmin
= return False
| Just uid <- mbUid
, NTKAdmin <- nt
= E.selectExists . E.from $ \userAdmin ->
E.where_ $ userAdmin E.^. UserAdminUser E.==. E.val uid
| Just uid <- mbUid
, NTKLecturer <- nt
= E.selectExists . E.from $ \userLecturer ->
E.where_ $ userLecturer E.^. UserLecturerUser E.==. E.val uid
| Just uid <- mbUid
, NTKCorrector <- nt
= E.selectExists . E.from $ \sheetCorrector ->
E.where_ $ sheetCorrector E.^. SheetCorrectorUser E.==. E.val uid
| Just uid <- mbUid
, NTKCourseParticipant <- nt
= E.selectExists . E.from $ \courseParticipant ->
E.where_ $ courseParticipant E.^. CourseParticipantUser E.==. E.val uid
| otherwise
= return False
ntHidden <- liftHandlerT . runDB
$ Set.fromList universeF
& Map.fromSet sectionIsHidden
& sequenceA
& fmap (!)
let
nsForm nt
| maybe False ntHidden $ ntSection nt
= pure $ notificationAllowed def nt
| nt `elem` forcedTriggers
= aforced checkBoxField (fslI nt) (notificationAllowed def nt)
| otherwise
= apopt checkBoxField (fslI nt) (flip notificationAllowed nt <$> template)
ntSection = \case
NTSubmissionRatedGraded -> Just NTKCourseParticipant
NTSubmissionRated -> Just NTKCourseParticipant
NTSheetActive -> Just NTKCourseParticipant
NTSheetSoonInactive -> Just NTKCourseParticipant
NTSheetInactive -> Just NTKLecturer
NTCorrectionsAssigned -> Just NTKCorrector
NTCorrectionsNotDistributed -> Just NTKLecturer
NTUserRightsUpdate -> Just NTKAll
NTUserAuthModeUpdate -> Just NTKAll
-- _other -> Nothing
forcedTriggers = [NTUserRightsUpdate, NTUserAuthModeUpdate]
aFormToWForm $ NotificationSettings <$> sectionedFuncForm ntSection nsForm (fslI MsgNotificationSettings) False
data ButtonResetTokens = BtnResetTokens data ButtonResetTokens = BtnResetTokens

View File

@ -855,16 +855,34 @@ boolField = Field
funcForm :: forall k v m. sectionedFuncForm :: forall k v m sec.
( Finite k, Ord k ( Finite k, Ord k
, MonadHandler m , MonadHandler m
, HandlerSite m ~ UniWorX , HandlerSite m ~ UniWorX
) , RenderMessage UniWorX sec
=> (k -> AForm m v) -> FieldSettings UniWorX -> Bool -> AForm m (k -> v) , Ord sec
funcForm mkForm FieldSettings{fsName = _, fsAttrs = _, ..} isRequired = formToAForm $ funcFieldView =<< renderAForm FormStandard funcForm' mempty )
=> (k -> Maybe sec) -> (k -> AForm m v) -> FieldSettings UniWorX -> Bool -> AForm m (k -> v)
sectionedFuncForm mkSection mkForm FieldSettings{fsName = _, fsAttrs = _, ..} isRequired = formToAForm $ funcFieldView =<< renderAForm FormStandard funcForm' mempty
where where
funcForm' :: AForm m (k -> v) funcForm' :: AForm m (k -> v)
funcForm' = fmap (!) . sequenceA . Map.fromSet mkForm $ Set.fromList universeF funcForm' = Set.fromList universeF
& foldr (\v -> Map.unionWith Set.union $ Map.singleton (mkSection v) (Set.singleton v)) Map.empty
& fmap (Map.fromSet mkForm)
& fmap sequenceA
& Map.foldrWithKey accSections (pure Map.empty)
& fmap (!)
accSections mSection optsForm acc = wFormToAForm $ do
(res, fs) <- wFormFields $ aFormToWForm optsForm
if
| not $ null fs
, Just section <- mSection
-> wformSection section
| otherwise
-> return ()
lift $ tell fs
aFormToWForm $ Map.union <$> wFormToAForm (pure res) <*> acc
funcFieldView :: (FormResult (k -> v), Widget) -> MForm m (FormResult (k -> v), [FieldView UniWorX]) funcFieldView :: (FormResult (k -> v), Widget) -> MForm m (FormResult (k -> v), [FieldView UniWorX])
funcFieldView (res, fvInput) = do funcFieldView (res, fvInput) = do
mr <- getMessageRender mr <- getMessageRender
@ -879,6 +897,15 @@ funcForm mkForm FieldSettings{fsName = _, fsAttrs = _, ..} isRequired = formToAF
-- areq nsField (fslI MsgNotificationSettings) (stgNotficationSettings <$> template) -- areq nsField (fslI MsgNotificationSettings) (stgNotficationSettings <$> template)
funcForm :: forall k v m.
( Finite k, Ord k
, MonadHandler m
, HandlerSite m ~ UniWorX
)
=> (k -> AForm m v) -> FieldSettings UniWorX -> Bool -> AForm m (k -> v)
funcForm = sectionedFuncForm $ const (Nothing :: Maybe Text)
fsm :: RenderMessage UniWorX msg => msg -> FieldSettings UniWorX -- DEPRECATED fsm :: RenderMessage UniWorX msg => msg -> FieldSettings UniWorX -- DEPRECATED

View File

@ -55,7 +55,7 @@ newtype NotificationSettings = NotificationSettings { notificationAllowed :: Not
instance Default NotificationSettings where instance Default NotificationSettings where
def = NotificationSettings $ \case def = NotificationSettings $ \case
NTSubmissionRatedGraded -> True NTSubmissionRatedGraded -> True
NTSubmissionRated -> False NTSubmissionRated -> True
NTSheetActive -> True NTSheetActive -> True
NTSheetSoonInactive -> False NTSheetSoonInactive -> False
NTSheetInactive -> True NTSheetInactive -> True

View File

@ -25,7 +25,7 @@ import qualified Data.Set as Set
import Control.Monad.Trans.Maybe (MaybeT(..)) import Control.Monad.Trans.Maybe (MaybeT(..))
import Control.Monad.Reader.Class (MonadReader(..)) import Control.Monad.Reader.Class (MonadReader(..))
import Control.Monad.Writer.Class (MonadWriter(..)) import Control.Monad.Writer.Class (MonadWriter(..), censor)
import Control.Monad.State.Class (MonadState(..)) import Control.Monad.State.Class (MonadState(..))
import Control.Monad.Trans.RWS (RWST, execRWST, mapRWST) import Control.Monad.Trans.RWS (RWST, execRWST, mapRWST)
import Control.Monad.Trans.Except (ExceptT, runExceptT) import Control.Monad.Trans.Except (ExceptT, runExceptT)
@ -920,6 +920,10 @@ infixl 4 `fmapAForm`
fmapAForm :: Functor m => (FormResult a -> FormResult b) -> (AForm m a -> AForm m b) fmapAForm :: Functor m => (FormResult a -> FormResult b) -> (AForm m a -> AForm m b)
fmapAForm f (AForm act) = AForm $ \app env ints -> over _1 f <$> act app env ints fmapAForm f (AForm act) = AForm $ \app env ints -> over _1 f <$> act app env ints
wFormFields :: Monad m => WForm m a -> WForm m (a, [FieldView (HandlerSite m)])
-- ^ Suppress side effect of appending `FieldView`s and instead add them to the result
wFormFields = mapRWST (fmap (\((a, s, w'), w) -> ((a, w), s, w')) . censor (const mempty) . listen)
--------------------------------------------- ---------------------------------------------
-- Special variants of @mopt@, @mreq@, ... -- -- Special variants of @mopt@, @mreq@, ... --
--------------------------------------------- ---------------------------------------------