fix(exam-office): better logic for isSynced
This commit is contained in:
parent
b638783f12
commit
cb9ff32063
@ -8,7 +8,7 @@ ExamOfficeUser
|
|||||||
user UserId
|
user UserId
|
||||||
UniqueExamOfficeUser office user
|
UniqueExamOfficeUser office user
|
||||||
ExamOfficeResultSynced
|
ExamOfficeResultSynced
|
||||||
|
school SchoolId Maybe
|
||||||
office UserId
|
office UserId
|
||||||
result ExamResultId
|
result ExamResultId
|
||||||
time UTCTime
|
time UTCTime
|
||||||
UniqueExamOfficeResultSynced office result
|
|
||||||
@ -6,6 +6,7 @@ import Import
|
|||||||
import Handler.Utils
|
import Handler.Utils
|
||||||
import Handler.Utils.Exam
|
import Handler.Utils.Exam
|
||||||
import Handler.Utils.Csv
|
import Handler.Utils.Csv
|
||||||
|
import qualified Handler.Utils.ExamOffice.Exam as Exam
|
||||||
import Handler.Utils.ExamOffice.Exam.Auth
|
import Handler.Utils.ExamOffice.Exam.Auth
|
||||||
|
|
||||||
import qualified Database.Esqueleto as E
|
import qualified Database.Esqueleto as E
|
||||||
@ -38,6 +39,7 @@ type ExamUserTableData = DBRow ( Entity ExamResult
|
|||||||
, Maybe (Entity StudyDegree)
|
, Maybe (Entity StudyDegree)
|
||||||
, Maybe (Entity StudyTerms)
|
, Maybe (Entity StudyTerms)
|
||||||
, Maybe (Entity ExamRegistration)
|
, Maybe (Entity ExamRegistration)
|
||||||
|
, Bool
|
||||||
, [(UserDisplayName, UserSurname, UTCTime, Set SchoolShorthand)]
|
, [(UserDisplayName, UserSurname, UTCTime, Set SchoolShorthand)]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -68,14 +70,8 @@ queryExamResult = to $ $(E.sqlIJproj 2 1) . $(E.sqlLOJproj 4 1)
|
|||||||
-- resultExamRegistration :: Traversal' ExamUserTableData (Entity ExamRegistration)
|
-- resultExamRegistration :: Traversal' ExamUserTableData (Entity ExamRegistration)
|
||||||
-- resultExamRegistration = _dbrOutput . _7 . _Just
|
-- resultExamRegistration = _dbrOutput . _7 . _Just
|
||||||
|
|
||||||
queryIsSynced :: Getter ExamUserTableExpr (E.SqlExpr (E.Value Bool))
|
queryIsSynced :: E.SqlExpr (E.Value UserId) -> Getter ExamUserTableExpr (E.SqlExpr (E.Value Bool))
|
||||||
queryIsSynced = to . runReader $ do
|
queryIsSynced authId = to $ Exam.resultIsSynced authId <$> view queryExamResult
|
||||||
examResult <- view queryExamResult
|
|
||||||
let
|
|
||||||
lastSync = E.sub_select . E.from $ \examOfficeResultSynced -> do
|
|
||||||
E.where_ $ examOfficeResultSynced E.^. ExamOfficeResultSyncedResult E.==. examResult E.^. ExamResultId
|
|
||||||
return . E.max_ $ examOfficeResultSynced E.^. ExamOfficeResultSyncedTime
|
|
||||||
return $ E.maybe E.false (E.>=. examResult E.^. ExamResultLastChanged) lastSync
|
|
||||||
|
|
||||||
resultUser :: Lens' ExamUserTableData (Entity User)
|
resultUser :: Lens' ExamUserTableData (Entity User)
|
||||||
resultUser = _dbrOutput . _2
|
resultUser = _dbrOutput . _2
|
||||||
@ -95,8 +91,11 @@ resultExamOccurrence = _dbrOutput . _3 . _Just
|
|||||||
resultExamResult :: Lens' ExamUserTableData (Entity ExamResult)
|
resultExamResult :: Lens' ExamUserTableData (Entity ExamResult)
|
||||||
resultExamResult = _dbrOutput . _1
|
resultExamResult = _dbrOutput . _1
|
||||||
|
|
||||||
|
resultIsSynced :: Lens' ExamUserTableData Bool
|
||||||
|
resultIsSynced = _dbrOutput . _8
|
||||||
|
|
||||||
resultSynchronised :: Traversal' ExamUserTableData (UserDisplayName, UserSurname, UTCTime, Set SchoolShorthand)
|
resultSynchronised :: Traversal' ExamUserTableData (UserDisplayName, UserSurname, UTCTime, Set SchoolShorthand)
|
||||||
resultSynchronised = _dbrOutput . _8 . traverse
|
resultSynchronised = _dbrOutput . _9 . traverse
|
||||||
|
|
||||||
data ExamUserTableCsv = ExamUserTableCsv
|
data ExamUserTableCsv = ExamUserTableCsv
|
||||||
{ csvEUserSurname :: Text
|
{ csvEUserSurname :: Text
|
||||||
@ -160,6 +159,7 @@ postEGradesR tid ssh csh examn = do
|
|||||||
|
|
||||||
csvName <- getMessageRender <*> pure (MsgExamUserCsvName tid ssh csh examn)
|
csvName <- getMessageRender <*> pure (MsgExamUserCsvName tid ssh csh examn)
|
||||||
isLecturer <- hasReadAccessTo $ CExamR tid ssh csh examn EUsersR
|
isLecturer <- hasReadAccessTo $ CExamR tid ssh csh examn EUsersR
|
||||||
|
userFunctions <- selectList [ UserFunctionUser ==. uid, UserFunctionFunction ==. SchoolExamOffice ] []
|
||||||
|
|
||||||
let
|
let
|
||||||
participantLink :: MonadCrypto m => UserId -> m (SomeRoute UniWorX)
|
participantLink :: MonadCrypto m => UserId -> m (SomeRoute UniWorX)
|
||||||
@ -167,6 +167,26 @@ postEGradesR tid ssh csh examn = do
|
|||||||
cID <- encrypt partId
|
cID <- encrypt partId
|
||||||
return . SomeRoute . CourseR tid ssh csh $ CUserR cID
|
return . SomeRoute . CourseR tid ssh csh $ CUserR cID
|
||||||
|
|
||||||
|
markSynced :: ExamResultId -> DB ()
|
||||||
|
markSynced resId
|
||||||
|
| null userFunctions =
|
||||||
|
insert_ ExamOfficeResultSynced
|
||||||
|
{ examOfficeResultSyncedOffice = uid
|
||||||
|
, examOfficeResultSyncedResult = resId
|
||||||
|
, examOfficeResultSyncedTime = now
|
||||||
|
, examOfficeResultSyncedSchool = Nothing
|
||||||
|
}
|
||||||
|
| otherwise =
|
||||||
|
insertMany_ [ ExamOfficeResultSynced
|
||||||
|
{ examOfficeResultSyncedOffice = uid
|
||||||
|
, examOfficeResultSyncedResult = resId
|
||||||
|
, examOfficeResultSyncedTime = now
|
||||||
|
, examOfficeResultSyncedSchool = Just userFunctionSchool
|
||||||
|
}
|
||||||
|
| Entity _ UserFunction{..} <- userFunctions
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
examUsersDBTable = DBTable{..}
|
examUsersDBTable = DBTable{..}
|
||||||
where
|
where
|
||||||
dbtSQLQuery = runReaderT $ do
|
dbtSQLQuery = runReaderT $ do
|
||||||
@ -179,6 +199,8 @@ postEGradesR tid ssh csh examn = do
|
|||||||
studyDegree <- view queryStudyDegree
|
studyDegree <- view queryStudyDegree
|
||||||
studyField <- view queryStudyField
|
studyField <- view queryStudyField
|
||||||
|
|
||||||
|
isSynced <- view . queryIsSynced $ E.val uid
|
||||||
|
|
||||||
lift $ do
|
lift $ do
|
||||||
E.on $ studyField E.?. StudyTermsId E.==. studyFeatures E.?. StudyFeaturesField
|
E.on $ studyField E.?. StudyTermsId E.==. studyFeatures E.?. StudyFeaturesField
|
||||||
E.on $ studyDegree E.?. StudyDegreeId E.==. studyFeatures E.?. StudyFeaturesDegree
|
E.on $ studyDegree E.?. StudyDegreeId E.==. studyFeatures E.?. StudyFeaturesDegree
|
||||||
@ -196,33 +218,31 @@ postEGradesR tid ssh csh examn = do
|
|||||||
unless isLecturer $
|
unless isLecturer $
|
||||||
E.where_ $ examOfficeExamResultAuth (E.val uid) examResult
|
E.where_ $ examOfficeExamResultAuth (E.val uid) examResult
|
||||||
|
|
||||||
return (examResult, user, occurrence, studyFeatures, studyDegree, studyField, examRegistration)
|
return (examResult, user, occurrence, studyFeatures, studyDegree, studyField, examRegistration, isSynced)
|
||||||
dbtRowKey = views queryExamResult (E.^. ExamResultId)
|
dbtRowKey = views queryExamResult (E.^. ExamResultId)
|
||||||
|
|
||||||
dbtProj :: DBRow _ -> MaybeT (YesodDB UniWorX) ExamUserTableData
|
dbtProj :: DBRow _ -> MaybeT (YesodDB UniWorX) ExamUserTableData
|
||||||
dbtProj = runReaderT $ (asks . set _dbrOutput) <=< magnify _dbrOutput $
|
dbtProj = runReaderT $ (asks . set _dbrOutput) <=< magnify _dbrOutput $
|
||||||
(,,,,,,,)
|
(,,,,,,,,)
|
||||||
<$> view _1 <*> view _2 <*> view _3 <*> view _4 <*> view _5 <*> view _6 <*> view _7
|
<$> view _1 <*> view _2 <*> view _3 <*> view _4 <*> view _5 <*> view _6 <*> view _7 <*> view (_8 . _Value)
|
||||||
<*> getSynchronised
|
<*> getSynchronised
|
||||||
where
|
where
|
||||||
getSynchronised :: ReaderT _ (MaybeT (YesodDB UniWorX)) [(UserDisplayName, UserSurname, UTCTime, Set SchoolShorthand)]
|
getSynchronised :: ReaderT _ (MaybeT (YesodDB UniWorX)) [(UserDisplayName, UserSurname, UTCTime, Set SchoolShorthand)]
|
||||||
getSynchronised = do
|
getSynchronised = do
|
||||||
resId <- view $ _1 . _entityKey
|
resId <- view $ _1 . _entityKey
|
||||||
syncs <- lift . lift . E.select . E.from $ \((examOfficeResultSynced `E.InnerJoin` user) `E.LeftOuterJoin` userFunction) -> do
|
syncs <- lift . lift . E.select . E.from $ \(examOfficeResultSynced `E.InnerJoin` user) -> do
|
||||||
E.on $ userFunction E.?. UserFunctionUser E.==. E.just (user E.^. UserId)
|
|
||||||
E.&&. userFunction E.?. UserFunctionFunction E.==. E.just (E.val SchoolExamOffice)
|
|
||||||
E.on $ examOfficeResultSynced E.^. ExamOfficeResultSyncedOffice E.==. user E.^. UserId
|
E.on $ examOfficeResultSynced E.^. ExamOfficeResultSyncedOffice E.==. user E.^. UserId
|
||||||
E.where_ $ examOfficeResultSynced E.^. ExamOfficeResultSyncedResult E.==. E.val resId
|
E.where_ $ examOfficeResultSynced E.^. ExamOfficeResultSyncedResult E.==. E.val resId
|
||||||
return ( examOfficeResultSynced E.^. ExamOfficeResultSyncedOffice
|
return ( examOfficeResultSynced E.^. ExamOfficeResultSyncedOffice
|
||||||
, ( user E.^. UserDisplayName
|
, ( user E.^. UserDisplayName
|
||||||
, user E.^. UserSurname
|
, user E.^. UserSurname
|
||||||
, examOfficeResultSynced E.^. ExamOfficeResultSyncedTime
|
, examOfficeResultSynced E.^. ExamOfficeResultSyncedTime
|
||||||
, userFunction E.?. UserFunctionSchool
|
, examOfficeResultSynced E.^. ExamOfficeResultSyncedSchool
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
let syncs' = Map.fromListWith
|
let syncs' = Map.fromListWith
|
||||||
(\(dn, sn, t, sshs) (_, _, _, sshs') -> (dn, sn, t, Set.union sshs sshs'))
|
(\(dn, sn, t, sshs) (_, _, _, sshs') -> (dn, sn, t, Set.union sshs sshs'))
|
||||||
[ (officeId, (dn, sn, t, maybe Set.empty Set.singleton ssh'))
|
[ ((officeId, t), (dn, sn, t, maybe Set.empty Set.singleton ssh'))
|
||||||
| (E.Value officeId, (E.Value dn, E.Value sn, E.Value t, fmap unSchoolKey . E.unValue -> ssh')) <- syncs
|
| (E.Value officeId, (E.Value dn, E.Value sn, E.Value t, fmap unSchoolKey . E.unValue -> ssh')) <- syncs
|
||||||
]
|
]
|
||||||
return $ Map.elems syncs'
|
return $ Map.elems syncs'
|
||||||
@ -231,8 +251,8 @@ postEGradesR tid ssh csh examn = do
|
|||||||
syncs <- asks $ sortOn (Down . view _3) . toListOf resultSynchronised
|
syncs <- asks $ sortOn (Down . view _3) . toListOf resultSynchronised
|
||||||
lastChange <- view $ resultExamResult . _entityVal . _examResultLastChanged
|
lastChange <- view $ resultExamResult . _entityVal . _examResultLastChanged
|
||||||
user <- view $ resultUser . _entityVal
|
user <- view $ resultUser . _entityVal
|
||||||
|
isSynced <- view resultIsSynced
|
||||||
let
|
let
|
||||||
lastSync = maximumOf (folded . _3) syncs
|
|
||||||
hasSyncs = has folded syncs
|
hasSyncs = has folded syncs
|
||||||
|
|
||||||
syncs' = [ Right sync | sync@(_, _, t, _) <- syncs, t > lastChange]
|
syncs' = [ Right sync | sync@(_, _, t, _) <- syncs, t > lastChange]
|
||||||
@ -240,13 +260,14 @@ postEGradesR tid ssh csh examn = do
|
|||||||
++ [ Right sync | sync@(_, _, t, _) <- syncs, t <= lastChange]
|
++ [ Right sync | sync@(_, _, t, _) <- syncs, t <= lastChange]
|
||||||
|
|
||||||
syncIcon :: Widget
|
syncIcon :: Widget
|
||||||
syncIcon = case lastSync of
|
syncIcon
|
||||||
Nothing -> mempty
|
| not isSynced
|
||||||
Just ts
|
, not hasSyncs
|
||||||
| ts >= lastChange
|
= mempty
|
||||||
-> toWidget iconOK
|
| not isSynced
|
||||||
| otherwise
|
= toWidget iconNotOK
|
||||||
-> toWidget iconNotOK
|
| otherwise
|
||||||
|
= toWidget iconOK
|
||||||
|
|
||||||
syncsModal :: Widget
|
syncsModal :: Widget
|
||||||
syncsModal = $(widgetFile "exam-office/exam-result-synced")
|
syncsModal = $(widgetFile "exam-office/exam-result-synced")
|
||||||
@ -275,7 +296,7 @@ postEGradesR tid ssh csh examn = do
|
|||||||
, sortStudyFeaturesSemester (queryStudyFeatures . to (E.?. StudyFeaturesSemester))
|
, sortStudyFeaturesSemester (queryStudyFeatures . to (E.?. StudyFeaturesSemester))
|
||||||
, sortOccurrenceStart (queryExamOccurrence . to (E.maybe (E.val examStart) E.just . (E.?. ExamOccurrenceStart)))
|
, sortOccurrenceStart (queryExamOccurrence . to (E.maybe (E.val examStart) E.just . (E.?. ExamOccurrenceStart)))
|
||||||
, maybeOpticSortColumn (sortExamResult examShowGrades) (queryExamResult . to (E.^. ExamResultResult))
|
, maybeOpticSortColumn (sortExamResult examShowGrades) (queryExamResult . to (E.^. ExamResultResult))
|
||||||
, singletonMap "is-synced" . SortColumn $ view queryIsSynced
|
, singletonMap "is-synced" . SortColumn $ view (queryIsSynced $ E.val uid)
|
||||||
]
|
]
|
||||||
dbtFilter = mconcat
|
dbtFilter = mconcat
|
||||||
[ fltrUserName' (queryUser . to (E.^. UserDisplayName))
|
[ fltrUserName' (queryUser . to (E.^. UserDisplayName))
|
||||||
@ -284,7 +305,7 @@ postEGradesR tid ssh csh examn = do
|
|||||||
, fltrStudyDegree queryStudyDegree
|
, fltrStudyDegree queryStudyDegree
|
||||||
, fltrStudyFeaturesSemester (queryStudyFeatures . to (E.?. StudyFeaturesSemester))
|
, fltrStudyFeaturesSemester (queryStudyFeatures . to (E.?. StudyFeaturesSemester))
|
||||||
, fltrExamResultPoints examShowGrades (queryExamResult . to (E.^. ExamResultResult))
|
, fltrExamResultPoints examShowGrades (queryExamResult . to (E.^. ExamResultResult))
|
||||||
, singletonMap "is-synced" . FilterColumn $ E.mkExactFilter (view queryIsSynced)
|
, singletonMap "is-synced" . FilterColumn $ E.mkExactFilter (view . queryIsSynced $ E.val uid)
|
||||||
]
|
]
|
||||||
dbtFilterUI = mconcat
|
dbtFilterUI = mconcat
|
||||||
[ fltrUserNameUI'
|
[ fltrUserNameUI'
|
||||||
@ -322,14 +343,7 @@ postEGradesR tid ssh csh examn = do
|
|||||||
{ dbtCsvExportForm = ExamUserCsvExportData
|
{ dbtCsvExportForm = ExamUserCsvExportData
|
||||||
<$> apopt checkBoxField (fslI MsgExamUserMarkSynchronisedCsv) (Just True)
|
<$> apopt checkBoxField (fslI MsgExamUserMarkSynchronisedCsv) (Just True)
|
||||||
, dbtCsvDoEncode = \ExamUserCsvExportData{..} -> C.mapM $ \(E.Value k, row) -> do
|
, dbtCsvDoEncode = \ExamUserCsvExportData{..} -> C.mapM $ \(E.Value k, row) -> do
|
||||||
when csvEUserMarkSynchronised $
|
when csvEUserMarkSynchronised $ markSynced k
|
||||||
void $ upsert ExamOfficeResultSynced
|
|
||||||
{ examOfficeResultSyncedOffice = uid
|
|
||||||
, examOfficeResultSyncedResult = k
|
|
||||||
, examOfficeResultSyncedTime = now
|
|
||||||
}
|
|
||||||
[ ExamOfficeResultSyncedTime =. now
|
|
||||||
]
|
|
||||||
return $ ExamUserTableCsv
|
return $ ExamUserTableCsv
|
||||||
(row ^. resultUser . _entityVal . _userSurname)
|
(row ^. resultUser . _entityVal . _userSurname)
|
||||||
(row ^. resultUser . _entityVal . _userFirstName)
|
(row ^. resultUser . _entityVal . _userFirstName)
|
||||||
@ -353,20 +367,18 @@ postEGradesR tid ssh csh examn = do
|
|||||||
(First (Just act), regMap) <- inp
|
(First (Just act), regMap) <- inp
|
||||||
let regSet = Map.keysSet . Map.filter id $ getDBFormResult (const False) regMap
|
let regSet = Map.keysSet . Map.filter id $ getDBFormResult (const False) regMap
|
||||||
return (act, regSet)
|
return (act, regSet)
|
||||||
over _1 postprocess <$> dbTable examUsersDBTableValidator examUsersDBTable
|
(usersResult, examUsersTable) <- over _1 postprocess <$> dbTable examUsersDBTableValidator examUsersDBTable
|
||||||
|
|
||||||
formResult usersResult $ \case
|
usersResult' <- formResultMaybe usersResult $ \case
|
||||||
(ExamUserMarkSynchronisedData, selectedResults) -> do
|
(ExamUserMarkSynchronisedData, selectedResults) -> do
|
||||||
runDB . forM_ selectedResults $ \resId ->
|
forM_ selectedResults markSynced
|
||||||
void $ upsert ExamOfficeResultSynced
|
return . Just $ do
|
||||||
{ examOfficeResultSyncedOffice = uid
|
addMessageI Success $ MsgExamUserMarkedSynchronised (length selectedResults)
|
||||||
, examOfficeResultSyncedResult = resId
|
redirect $ CExamR tid ssh csh examn EGradesR
|
||||||
, examOfficeResultSyncedTime = now
|
|
||||||
}
|
return (usersResult', examUsersTable)
|
||||||
[ ExamOfficeResultSyncedTime =. now
|
|
||||||
]
|
whenIsJust usersResult join
|
||||||
addMessageI Success $ MsgExamUserMarkedSynchronised (length selectedResults)
|
|
||||||
redirect $ CExamR tid ssh csh examn EGradesR
|
|
||||||
|
|
||||||
siteLayoutMsg (prependCourseTitle tid ssh csh MsgExamOfficeExamUsersHeading) $ do
|
siteLayoutMsg (prependCourseTitle tid ssh csh MsgExamOfficeExamUsersHeading) $ do
|
||||||
setTitleI $ prependCourseTitle tid ssh csh MsgExamOfficeExamUsersHeading
|
setTitleI $ prependCourseTitle tid ssh csh MsgExamOfficeExamUsersHeading
|
||||||
|
|||||||
27
src/Handler/Utils/ExamOffice/Exam.hs
Normal file
27
src/Handler/Utils/ExamOffice/Exam.hs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
module Handler.Utils.ExamOffice.Exam
|
||||||
|
( resultIsSynced
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Import.NoFoundation
|
||||||
|
|
||||||
|
import qualified Database.Esqueleto as E
|
||||||
|
|
||||||
|
resultIsSynced :: E.SqlExpr (E.Value UserId) -- ^ office
|
||||||
|
-> E.SqlExpr (Entity ExamResult)
|
||||||
|
-> E.SqlExpr (E.Value Bool)
|
||||||
|
resultIsSynced authId examResult = (hasSchool E.&&. allSchools) E.||. anySync
|
||||||
|
where
|
||||||
|
anySync = E.exists . E.from $ \synced ->
|
||||||
|
E.where_ $ synced E.^. ExamOfficeResultSyncedResult E.==. examResult E.^. ExamResultId
|
||||||
|
E.&&. synced E.^. ExamOfficeResultSyncedTime E.>=. examResult E.^. ExamResultLastChanged
|
||||||
|
|
||||||
|
hasSchool = E.exists . E.from $ \userFunction ->
|
||||||
|
E.where_ $ userFunction E.^. UserFunctionUser E.==. authId
|
||||||
|
E.&&. userFunction E.^. UserFunctionFunction E.==. E.val SchoolExamOffice
|
||||||
|
allSchools = E.not_ . E.exists . E.from $ \userFunction -> do
|
||||||
|
E.where_ $ userFunction E.^. UserFunctionUser E.==. authId
|
||||||
|
E.&&. userFunction E.^. UserFunctionFunction E.==. E.val SchoolExamOffice
|
||||||
|
E.where_ . E.not_ . E.exists . E.from $ \synced ->
|
||||||
|
E.where_ $ synced E.^. ExamOfficeResultSyncedSchool E.==. E.just (userFunction E.^. UserFunctionSchool)
|
||||||
|
E.&&. synced E.^. ExamOfficeResultSyncedResult E.==. examResult E.^. ExamResultId
|
||||||
|
E.&&. synced E.^. ExamOfficeResultSyncedTime E.>=. examResult E.^. ExamResultLastChanged
|
||||||
Reference in New Issue
Block a user