chore(lms): lmsstatus to proper semigroup, notifications for qualifications added but need more work

This commit is contained in:
Steffen Jost 2022-04-08 17:28:55 +02:00
parent d4c4a60e7c
commit 39f16b90db
15 changed files with 64 additions and 21 deletions

View File

@ -68,6 +68,8 @@ synchronise-ldap-users-interval: "_env:SYNCHRONISE_LDAP_INTERVAL:3600"
study-features-recache-relevance-within: 172800 study-features-recache-relevance-within: 172800
study-features-recache-relevance-interval: 293 study-features-recache-relevance-interval: 293
qualification-check: 28800
log-settings: log-settings:
detailed: "_env:DETAILED_LOGGING:false" detailed: "_env:DETAILED_LOGGING:false"
all: "_env:LOG_ALL:false" all: "_env:LOG_ALL:false"

View File

@ -76,6 +76,7 @@ NotificationTriggerSubmissionEdited: Eine meiner Übungsblatt-Abgaben wurde ver
NotificationTriggerSubmissionUserDeleted: Ich wurde als Mitabgebender von einer Übungsblatt-Abgabe entfernt NotificationTriggerSubmissionUserDeleted: Ich wurde als Mitabgebender von einer Übungsblatt-Abgabe entfernt
NotificationTriggerAllocationNewCourse: Es wurde ein neuer Kurs eingetragen zu einer Zentralanmeldungen, zu der ich meine Teilnahme registriert habe NotificationTriggerAllocationNewCourse: Es wurde ein neuer Kurs eingetragen zu einer Zentralanmeldungen, zu der ich meine Teilnahme registriert habe
NotificationTriggerAllocationNewCourseTip: Kann pro Zentralanmeldung überschrieben werden NotificationTriggerAllocationNewCourseTip: Kann pro Zentralanmeldung überschrieben werden
NotificationTriggerQualification: Eine meiner Qualifikationen läuft ab
UserDisplayNameRules: Vorgaben für den angezeigten Namen UserDisplayNameRules: Vorgaben für den angezeigten Namen

View File

@ -76,6 +76,7 @@ NotificationTriggerSubmissionEdited: One of my exercise sheet submissions was ch
NotificationTriggerSubmissionUserDeleted: I was removed from one of my exercise sheet submissions NotificationTriggerSubmissionUserDeleted: I was removed from one of my exercise sheet submissions
NotificationTriggerAllocationNewCourse: A new course was added to a central allocation for which I have registered my participation NotificationTriggerAllocationNewCourse: A new course was added to a central allocation for which I have registered my participation
NotificationTriggerAllocationNewCourseTip: Can be overridden per central allocation NotificationTriggerAllocationNewCourseTip: Can be overridden per central allocation
NotificationTriggerQualification: My Qualifications are about to expire
UserDisplayNameRules: Specification for display names UserDisplayNameRules: Specification for display names

View File

@ -248,6 +248,7 @@ notificationForm template = wFormToAForm $ do
NTExamOfficeExamResults -> Just $ NTKFunctionary SchoolExamOffice NTExamOfficeExamResults -> Just $ NTKFunctionary SchoolExamOffice
NTExamOfficeExamResultsChanged -> Just $ NTKFunctionary SchoolExamOffice NTExamOfficeExamResultsChanged -> Just $ NTKFunctionary SchoolExamOffice
NTCourseRegistered -> Just NTKAll NTCourseRegistered -> Just NTKAll
NTQualification -> Just NTKAll
-- _other -> Nothing -- _other -> Nothing
forcedTriggers = [NTUserRightsUpdate, NTUserAuthModeUpdate] forcedTriggers = [NTUserRightsUpdate, NTUserAuthModeUpdate]

View File

@ -360,6 +360,16 @@ determineCrontab = execWriterT $ do
, cronRateLimit = appStudyFeaturesRecacheRelevanceInterval , cronRateLimit = appStudyFeaturesRecacheRelevanceInterval
, cronNotAfter = Right . CronTimestamp . utcToLocalTimeTZ appTZ $ addUTCTime appStudyFeaturesRecacheRelevanceInterval nextIntervalTime , cronNotAfter = Right . CronTimestamp . utcToLocalTimeTZ appTZ $ addUTCTime appStudyFeaturesRecacheRelevanceInterval nextIntervalTime
} }
{-
whenIsJust appQualificationChecks $ \cInterval -> tell $ HashMap.singleton
(JobCtlQueue JobLmsQualifications)
Cron
{ cronInitial = CronAsap
, cronRepeat = CronRepeatScheduled . CronCalendar $ cronCalendarAny {}
, cronRateLimit = TODO: CONTINUE HERE
, cronNotAfter =
}
-}
let let
correctorNotifications :: Map (UserId, SheetId) (Max UTCTime) -> WriterT (Crontab JobCtl) (ReaderT SqlReadBackend (HandlerFor UniWorX)) () correctorNotifications :: Map (UserId, SheetId) (Max UTCTime) -> WriterT (Crontab JobCtl) (ReaderT SqlReadBackend (HandlerFor UniWorX)) ()

View File

@ -62,14 +62,12 @@ dispatchJobLmsEnqueue qid = JobHandlerAtomic act
let usr_job :: Entity QualificationUser -> Job let usr_job :: Entity QualificationUser -> Job
usr_job quser = usr_job quser =
let uid = quser ^. _entityVal . _qualificationUserUser let uid = quser ^. _entityVal . _qualificationUserUser
_uex = quser ^. _entityVal . _qualificationUserValidUntil uex = quser ^. _entityVal . _qualificationUserValidUntil
in if qualificationElearningStart quali in if qualificationElearningStart quali
then JobLmsEnqueueUser { jQualification = qid, jUser = uid } then JobLmsEnqueueUser { jQualification = qid, jUser = uid }
else error "TODO: send Notfification" else JobSendNotification { jRecipient = uid, jNotification =
{-
JobSendNotification { jRecipientEmail = uid, jNotification =
NotificationQualificationExpiry { nQualification = qid, nExpiry = uex } NotificationQualificationExpiry { nQualification = qid, nExpiry = uex }
} -} }
forM_ renewalUsers (queueDBJob . usr_job) forM_ renewalUsers (queueDBJob . usr_job)
@ -95,10 +93,10 @@ dispatchJobLmsEnqueueUser qid uid = JobHandlerAtomic act
inserted <- untilJustMaxM maxLmsUserIdentRetries startLmsUser inserted <- untilJustMaxM maxLmsUserIdentRetries startLmsUser
case inserted of case inserted of
Nothing -> $logErrorS "LMS" "Generating and inserting fresh LmsIdent failed!" Nothing -> $logErrorS "LMS" "Generating and inserting fresh LmsIdent failed!"
(Just _) -> error "TODO: send notification" (Just _) -> queueDBJob JobSendNotification { jRecipient = uid, jNotification =
{- queueDBJob JobSendNotification { jRecipientEmail = uid, jNotification =
NotificationQualificationRenewal { nQualification = qid } NotificationQualificationRenewal { nQualification = qid }
} -} }
dispatchJobLmsDequeue :: QualificationId -> JobHandler UniWorX dispatchJobLmsDequeue :: QualificationId -> JobHandler UniWorX
dispatchJobLmsDequeue _qid = dispatchJobLmsDequeue _qid =
@ -127,10 +125,15 @@ dispatchJobLmsResults qid = JobHandlerAtomic act
let lreceived = lmsResultTimestamp lresult let lreceived = lmsResultTimestamp lresult
newStatus = lmsResultSuccess lresult & LmsSuccess newStatus = lmsResultSuccess lresult & LmsSuccess
oldStatus = lmsUserStatus luser oldStatus = lmsUserStatus luser
saneDate = lmsResultSuccess lresult `inBetween` (utctDay $ lmsUserStarted luser, utctDay now)
-- always log success, since this is only transmitted once -- always log success, since this is only transmitted once
update luid [ LmsUserStatus =. (oldStatus <> Just newStatus) if saneDate
, LmsUserReceived =. Just lreceived then
] update luid [ LmsUserStatus =. (oldStatus <> Just newStatus)
, LmsUserReceived =. Just lreceived
]
else
$logErrorS "LmsResult" [st|LMS success with insane date ${tshow (lmsResultSuccess lresult)} received|]
insert_ $ LmsAudit qid (lmsUserIdent luser) newStatus lreceived now insert_ $ LmsAudit qid (lmsUserIdent luser) newStatus lreceived now
delete lrid delete lrid
$logInfoS "LmsResult" [st|Processed ${tshow (length results)} LMS results|] $logInfoS "LmsResult" [st|Processed ${tshow (length results)} LMS results|]

View File

@ -300,6 +300,9 @@ determineNotificationCandidates = awaitForever $ \notif -> do
E.&&. application E.^. CourseApplicationCourse E.==. E.val nCourse E.&&. application E.^. CourseApplicationCourse E.==. E.val nCourse
return (hasOverride, user) return (hasOverride, user)
NotificationQualificationExpiry{} -> return mempty -- Not used with JobQueueNotification; recipients already known
NotificationQualificationRenewal{} -> return mempty -- Not used with JobQueueNotification; recipients already known
classifyNotification :: Notification -> DB NotificationTrigger classifyNotification :: Notification -> DB NotificationTrigger
@ -335,3 +338,5 @@ classifyNotification NotificationSubmissionEdited{} = return NTSub
classifyNotification NotificationSubmissionUserCreated{} = return NTSubmissionUserCreated classifyNotification NotificationSubmissionUserCreated{} = return NTSubmissionUserCreated
classifyNotification NotificationSubmissionUserDeleted{} = return NTSubmissionUserDeleted classifyNotification NotificationSubmissionUserDeleted{} = return NTSubmissionUserDeleted
classifyNotification NotificationAllocationNewCourse{} = return NTAllocationNewCourse classifyNotification NotificationAllocationNewCourse{} = return NTAllocationNewCourse
classifyNotification NotificationQualificationExpiry{} = return NTQualification
classifyNotification NotificationQualificationRenewal{} = return NTQualification

View File

@ -20,7 +20,7 @@ import Jobs.Handler.SendNotification.Allocation
import Jobs.Handler.SendNotification.ExamOffice import Jobs.Handler.SendNotification.ExamOffice
import Jobs.Handler.SendNotification.CourseRegistered import Jobs.Handler.SendNotification.CourseRegistered
import Jobs.Handler.SendNotification.SubmissionEdited import Jobs.Handler.SendNotification.SubmissionEdited
-- import Jobs.Handler.SendNotification.Qualification -- TODO import Jobs.Handler.SendNotification.Qualification
dispatchJobSendNotification :: UserId -> Notification -> JobHandler UniWorX dispatchJobSendNotification :: UserId -> Notification -> JobHandler UniWorX

View File

@ -43,7 +43,7 @@ dispatchNotificationQualificationExpiry _nQualification _nExpiry _jRecipient =
addHtmlMarkdownAlternatives $(ihamletFile "templates/mail/allocationNewCourse.hamlet") addHtmlMarkdownAlternatives $(ihamletFile "templates/mail/allocationNewCourse.hamlet")
-} -}
dispatchNotificationQualificationRenewal :: QualificationId -> Day -> UserId -> Handler () dispatchNotificationQualificationRenewal :: QualificationId -> UserId -> Handler ()
dispatchNotificationQualificationRenewal _nQualification _nExpiry _jRecipient = dispatchNotificationQualificationRenewal _nQualification _jRecipient =
error "dispatchNotificationQualificationRenewal not yet implemented TODO" error "dispatchNotificationQualificationRenewal not yet implemented TODO"
-- userMailT jRecipient $ do -- userMailT jRecipient $ do

View File

@ -141,8 +141,8 @@ data Notification
| NotificationSubmissionEdited { nInitiator :: UserId, nSubmission :: SubmissionId } | NotificationSubmissionEdited { nInitiator :: UserId, nSubmission :: SubmissionId }
| NotificationSubmissionUserCreated { nUser :: UserId, nSubmission :: SubmissionId } | NotificationSubmissionUserCreated { nUser :: UserId, nSubmission :: SubmissionId }
| NotificationSubmissionUserDeleted { nUser :: UserId, nSheet :: SheetId, nSubmission :: SubmissionId } | NotificationSubmissionUserDeleted { nUser :: UserId, nSheet :: SheetId, nSubmission :: SubmissionId }
-- | NotificationQualificationExpiry { nQualification :: QualificationId, nExpiry :: Day } | NotificationQualificationExpiry { nQualification :: QualificationId, nExpiry :: Day }
-- | NotificationQualificationRenewal { nQualification :: QualificationId } | NotificationQualificationRenewal { nQualification :: QualificationId }
deriving (Eq, Ord, Show, Read, Generic, Typeable) deriving (Eq, Ord, Show, Read, Generic, Typeable)
instance Hashable Job instance Hashable Job

View File

@ -32,12 +32,13 @@ data LmsStatus = LmsBlocked { lmsStatusDay :: Day }
| LmsSuccess { lmsStatusDay :: Day } | LmsSuccess { lmsStatusDay :: Day }
deriving (Eq, Ord, Read, Show, Generic, Typeable, NFData) deriving (Eq, Ord, Read, Show, Generic, Typeable, NFData)
-- Variante 1: Der spätere Zeitstempel gewinnt, bei gleichem Zeitstempel gewinnt LmsSuccess? TODO: Macht das Sinn?! isLmsSuccess :: LmsStatus -> Bool
-- Variante 2: LmsSuccess gewinnt immer über LmsBlocked oder umgekehrt? isLmsSuccess LmsSuccess{} = True
isLmsSuccess _other = False
-- Entscheidung 08.04.22: LmsSuccess gewinnt immer über LmsBlocked oder umgekehrt
instance Semigroup LmsStatus where instance Semigroup LmsStatus where
a <> b | lmsStatusDay a > lmsStatusDay b = a -- nur Variante 1 a <> b | a >= b = a
| lmsStatusDay a < lmsStatusDay b = b -- nur Variante 1
| a >= b = a -- Variante 1 & 2, berücksichtigt Ord
| otherwise = b | otherwise = b
deriveJSON defaultOptions deriveJSON defaultOptions

View File

@ -63,6 +63,7 @@ data NotificationTrigger
| NTExamOfficeExamResults | NTExamOfficeExamResults
| NTExamOfficeExamResultsChanged | NTExamOfficeExamResultsChanged
| NTCourseRegistered | NTCourseRegistered
| NTQualification
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable) deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
deriving anyclass (Universe, Finite, Hashable, NFData) deriving anyclass (Universe, Finite, Hashable, NFData)

View File

@ -219,6 +219,8 @@ data AppSettings = AppSettings
, appStudyFeaturesRecacheRelevanceWithin :: Maybe NominalDiffTime , appStudyFeaturesRecacheRelevanceWithin :: Maybe NominalDiffTime
, appStudyFeaturesRecacheRelevanceInterval :: NominalDiffTime , appStudyFeaturesRecacheRelevanceInterval :: NominalDiffTime
, appQualificationCheck :: Maybe NominalDiffTime
, appFileSourceARCConf :: Maybe (ARCConf Int) , appFileSourceARCConf :: Maybe (ARCConf Int)
, appFileSourcePrewarmConf :: Maybe PrewarmCacheConf , appFileSourcePrewarmConf :: Maybe PrewarmCacheConf
@ -681,6 +683,8 @@ instance FromJSON AppSettings where
appStudyFeaturesRecacheRelevanceWithin <- o .:? "study-features-recache-relevance-within" appStudyFeaturesRecacheRelevanceWithin <- o .:? "study-features-recache-relevance-within"
appStudyFeaturesRecacheRelevanceInterval <- o .: "study-features-recache-relevance-interval" appStudyFeaturesRecacheRelevanceInterval <- o .: "study-features-recache-relevance-interval"
appQualificationCheck <- o .:? "qualification-check"
appFileSourceARCConf <- assertM isValidARCConf <$> o .:? "file-source-arc" appFileSourceARCConf <- assertM isValidARCConf <$> o .:? "file-source-arc"
let isValidPrewarmConf PrewarmCacheConf{..} = and let isValidPrewarmConf PrewarmCacheConf{..} = and

View File

@ -1524,6 +1524,10 @@ minOn,maxOn :: Ord b => (a -> b) -> a -> a -> a
minOn = minBy . comparing minOn = minBy . comparing
maxOn = maxBy . comparing maxOn = maxBy . comparing
inBetween:: Ord a => a -> (a,a) -> Bool
inBetween x (lower,upper) = lower <= x && x <= upper
------------ ------------
-- Random -- -- Random --
------------ ------------

View File

@ -352,6 +352,8 @@ instance Arbitrary SchoolAuthorshipStatementMode where
instance Arbitrary SheetAuthorshipStatementMode where instance Arbitrary SheetAuthorshipStatementMode where
arbitrary = genericArbitrary arbitrary = genericArbitrary
instance Arbitrary LmsStatus where
arbitrary = genericArbitrary
spec :: Spec spec :: Spec
spec = do spec = do
@ -460,6 +462,8 @@ spec = do
[ eqLaws, ordLaws, showLaws, showReadLaws, boundedEnumLaws, finiteLaws, pathPieceLaws, jsonLaws, jsonKeyLaws, persistFieldLaws, binaryLaws, httpApiDataLaws ] [ eqLaws, ordLaws, showLaws, showReadLaws, boundedEnumLaws, finiteLaws, pathPieceLaws, jsonLaws, jsonKeyLaws, persistFieldLaws, binaryLaws, httpApiDataLaws ]
lawsCheckHspec (Proxy @SheetAuthorshipStatementMode) lawsCheckHspec (Proxy @SheetAuthorshipStatementMode)
[ eqLaws, ordLaws, showLaws, showReadLaws, boundedEnumLaws, finiteLaws, pathPieceLaws, jsonLaws, jsonKeyLaws, persistFieldLaws, binaryLaws, httpApiDataLaws ] [ eqLaws, ordLaws, showLaws, showReadLaws, boundedEnumLaws, finiteLaws, pathPieceLaws, jsonLaws, jsonKeyLaws, persistFieldLaws, binaryLaws, httpApiDataLaws ]
lawsCheckHspec (Proxy @LmsStatus)
[ eqLaws, ordLaws, showLaws, showReadLaws, jsonLaws ]
describe "TermIdentifier" $ do describe "TermIdentifier" $ do
it "has compatible encoding/decoding to/from Text" . property $ it "has compatible encoding/decoding to/from Text" . property $
@ -564,6 +568,12 @@ spec = do
showCompactCorrectorLoad Load{ byTutorial = Nothing, byProportion = 0, byDeficit = 0 } CorrectorNormal `shouldBe` "-D" showCompactCorrectorLoad Load{ byTutorial = Nothing, byProportion = 0, byDeficit = 0 } CorrectorNormal `shouldBe` "-D"
showCompactCorrectorLoad Load{ byTutorial = Nothing, byProportion = 1, byDeficit = 0 } CorrectorMissing `shouldBe` "[1.0 - D]" showCompactCorrectorLoad Load{ byTutorial = Nothing, byProportion = 1, byDeficit = 0 } CorrectorMissing `shouldBe` "[1.0 - D]"
showCompactCorrectorLoad Load{ byTutorial = Nothing, byProportion = 1, byDeficit = 0 } CorrectorExcused `shouldBe` "{1.0 - D}" showCompactCorrectorLoad Load{ byTutorial = Nothing, byProportion = 1, byDeficit = 0 } CorrectorExcused `shouldBe` "{1.0 - D}"
describe "Semigroup LmsStatus" $ do
it "LmsSuccess supersedes LmsBlocked" . property $
\p1 p2 -> (isLmsSuccess p1 || isLmsSuccess p2) == isLmsSuccess (p1 <> p2)
it "lmsStatusDay merges to latest" . property $
\p1 p2 -> (isLmsSuccess p1 == isLmsSuccess p2) ==> lmsStatusDay (p1 <> p2) == max (lmsStatusDay p1) (lmsStatusDay p2)
termExample :: (TermIdentifier, Text) -> Expectation termExample :: (TermIdentifier, Text) -> Expectation
termExample (term, encoded) = example $ do termExample (term, encoded) = example $ do