198 lines
9.1 KiB
Haskell
198 lines
9.1 KiB
Haskell
{-# OPTIONS_GHC -fno-warn-orphans #-} -- needed for HasEntity instances
|
|
{-# OPTIONS -Wno-unused-top-binds #-} -- TODO: remove me, for debugging only
|
|
|
|
module Handler.LMS.Result
|
|
( getLmsResultR, postLmsResultR
|
|
)
|
|
where
|
|
|
|
import Import
|
|
|
|
import Handler.Utils
|
|
import Handler.Utils.Csv
|
|
import Handler.Utils.LMS
|
|
|
|
import qualified Data.Map as Map
|
|
import qualified Data.Csv as Csv
|
|
import qualified Data.Conduit.List as C
|
|
import qualified Database.Esqueleto.Legacy as E
|
|
import qualified Database.Esqueleto.Utils as E
|
|
|
|
|
|
data LmsResultTableCsv = LmsResultTableCsv
|
|
{ csvLRTident :: LmsIdent
|
|
, csvLRTsuccess :: Day
|
|
}
|
|
deriving Generic
|
|
makeLenses_ ''LmsResultTableCsv
|
|
|
|
-- csv without headers -- TODO not yet supported
|
|
instance Csv.ToRecord LmsResultTableCsv -- default suffices
|
|
instance Csv.FromRecord LmsResultTableCsv -- default suffices
|
|
|
|
-- csv with headers
|
|
lmsResultTableCsvHeader :: Csv.Header
|
|
lmsResultTableCsvHeader = Csv.header [ csvResultIdent, csvResultSuccess ]
|
|
|
|
instance ToNamedRecord LmsResultTableCsv where
|
|
toNamedRecord LmsResultTableCsv{..} = Csv.namedRecord
|
|
[ csvResultIdent Csv..= csvLRTident
|
|
, csvResultSuccess Csv..= csvLRTsuccess
|
|
]
|
|
|
|
instance FromNamedRecord LmsResultTableCsv where
|
|
parseNamedRecord (lsfHeaderTranslate -> csv)
|
|
= LmsResultTableCsv
|
|
<$> csv Csv..: csvResultIdent
|
|
<*> csv Csv..: csvResultSuccess
|
|
|
|
instance CsvColumnsExplained LmsResultTableCsv where
|
|
csvColumnsExplanations _ = mconcat
|
|
[ single csvResultIdent MsgCsvColumnLmsIdent
|
|
, single csvResultSuccess MsgCsvColumnLmsSuccess
|
|
]
|
|
where
|
|
single :: RenderMessage UniWorX msg => Csv.Name -> msg -> Map Csv.Name Widget
|
|
single k v = singletonMap k [whamlet|_{v}|]
|
|
|
|
data LmsResultCsvActionClass = LmsResultInsert | LmsResultUpdate
|
|
deriving (Eq, Ord, Read, Show, Generic, Typeable, Enum, Bounded)
|
|
embedRenderMessage ''UniWorX ''LmsResultCsvActionClass id
|
|
|
|
-- By coincidence the action type is identical to LmsResultTableCsv
|
|
data LmsResultCsvAction = LmsResultInsertData { lmsResultInsertIdent :: LmsIdent, lmsResultInsertSuccess :: Day }
|
|
| LmsResultUpdateData { lmsResultInsertIdent :: LmsIdent, lmsResultInsertSuccess :: Day }
|
|
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
|
|
|
deriveJSON defaultOptions
|
|
{ constructorTagModifier = camelToPathPiece'' 2 1 -- LmsResultInsertData -> insert
|
|
, fieldLabelModifier = camelToPathPiece' 2 -- lmsResultInsertIdent -> insert-ident | lmsResultInsertSuccess -> insert-success
|
|
, sumEncoding = TaggedObject "action" "data"
|
|
} ''LmsResultCsvAction
|
|
|
|
data LmsResultCsvException
|
|
= LmsResultCsvExceptionDuplicatedKey -- TODO: this is not used anywhere?!
|
|
deriving (Show, Generic, Typeable)
|
|
|
|
instance Exception LmsResultCsvException
|
|
embedRenderMessage ''UniWorX ''LmsResultCsvException id
|
|
|
|
mkResultTable :: SchoolId -> QualificationShorthand -> QualificationId -> DB (Any, Widget)
|
|
mkResultTable sid qsh qid = do
|
|
dbtCsvName <- csvFilenameLmsResult qsh
|
|
let dbtCsvSheetName = dbtCsvName
|
|
let
|
|
resultDBTable = DBTable{..}
|
|
where
|
|
|
|
dbtSQLQuery lmsresult = do
|
|
E.where_ $ lmsresult E.^. LmsResultQualification E.==. E.val qid
|
|
return lmsresult
|
|
dbtRowKey = (E.^. LmsResultId)
|
|
dbtProj = dbtProjFilteredPostId -- TODO: or dbtProjSimple what is the difference?
|
|
dbtColonnade = dbColonnade $ mconcat
|
|
[ sortable (Just csvResultIdent) (i18nCell MsgTableLmsIdent) $ \(view $ _dbrOutput . _entityVal . _lmsResultIdent . _getLmsIdent -> ident) -> textCell ident
|
|
, sortable (Just csvResultSuccess) (i18nCell MsgTableLmsSuccess) $ \(view $ _dbrOutput . _entityVal . _lmsResultSuccess -> success) -> dayCell success
|
|
, sortable (Just csvLmsTimestamp) (i18nCell MsgTableLmsReceived) $ \(view $ _dbrOutput . _entityVal . _lmsResultTimestamp -> timestamp) -> dateTimeCell timestamp
|
|
]
|
|
dbtSorting = Map.fromList
|
|
[ (csvResultIdent , SortColumn (E.^. LmsResultIdent))
|
|
, (csvResultSuccess, SortColumn (E.^. LmsResultSuccess))
|
|
, (csvLmsTimestamp , SortColumn (E.^. LmsResultTimestamp))
|
|
]
|
|
dbtFilter = Map.fromList
|
|
[ (csvResultIdent , FilterColumn $ E.mkContainsFilterWith LmsIdent (E.^. LmsResultIdent))
|
|
, (csvResultSuccess, FilterColumn $ E.mkExactFilter (E.^. LmsResultSuccess))
|
|
]
|
|
dbtFilterUI = \mPrev -> mconcat
|
|
[ prismAForm (singletonFilter csvResultIdent . maybePrism _PathPiece) mPrev $ aopt (hoistField lift textField) (fslI MsgTableLmsIdent)
|
|
, prismAForm (singletonFilter csvResultSuccess . maybePrism _PathPiece) mPrev $ aopt (hoistField lift dayField) (fslI MsgTableLmsSuccess)
|
|
]
|
|
dbtStyle = def
|
|
dbtParams = def
|
|
dbtIdent :: Text
|
|
dbtIdent = "lms-result"
|
|
dbtCsvEncode = Just DBTCsvEncode
|
|
{ dbtCsvExportForm = pure ()
|
|
, dbtCsvDoEncode = \() -> C.map (doEncode' . view _2)
|
|
, dbtCsvName
|
|
, dbtCsvSheetName
|
|
, dbtCsvNoExportData = Just id
|
|
, dbtCsvHeader = const $ return lmsResultTableCsvHeader
|
|
, dbtCsvExampleData = Nothing
|
|
}
|
|
where
|
|
doEncode' = LmsResultTableCsv
|
|
<$> view (_dbrOutput . _entityVal . _lmsResultIdent)
|
|
<*> view (_dbrOutput . _entityVal . _lmsResultSuccess)
|
|
dbtCsvDecode = Just DBTCsvDecode -- Just save to DB; Job will process data later
|
|
{ dbtCsvRowKey = \LmsResultTableCsv{..} ->
|
|
fmap E.Value . MaybeT . getKeyBy $ UniqueLmsResult qid csvLRTident csvLRTsuccess
|
|
, dbtCsvComputeActions = \case -- purpose is to show a diff to the user first
|
|
DBCsvDiffNew{dbCsvNewKey = Nothing, dbCsvNew} -> do
|
|
yield $ LmsResultInsertData
|
|
{ lmsResultInsertIdent = csvLRTident dbCsvNew
|
|
, lmsResultInsertSuccess = csvLRTsuccess dbCsvNew
|
|
}
|
|
DBCsvDiffNew{dbCsvNewKey = Just _, dbCsvNew = _} -> error "UniqueLmsResult was found, but the key no longer exists." -- TODO: how can this ever happen? Check Pagination-Code
|
|
DBCsvDiffExisting{dbCsvNew = LmsResultTableCsv{..}} -> do
|
|
yield $ LmsResultUpdateData
|
|
{ lmsResultInsertIdent = csvLRTident
|
|
, lmsResultInsertSuccess = csvLRTsuccess
|
|
}
|
|
DBCsvDiffMissing{} -> return () -- no deletion
|
|
, dbtCsvClassifyAction = \case
|
|
LmsResultInsertData{} -> LmsResultInsert
|
|
LmsResultUpdateData{} -> LmsResultUpdate
|
|
, dbtCsvCoarsenActionClass = \case
|
|
LmsResultInsert -> DBCsvActionNew
|
|
LmsResultUpdate -> DBCsvActionExisting
|
|
, dbtCsvValidateActions = return () -- no validation, since this is an automatic upload, i.e. no user to review error
|
|
, dbtCsvExecuteActions = do
|
|
C.mapM_ $ \actionData -> do
|
|
now <- liftIO getCurrentTime
|
|
void $ upsert
|
|
LmsResult
|
|
{ lmsResultQualification = qid
|
|
, lmsResultIdent = lmsResultInsertIdent actionData
|
|
, lmsResultSuccess = lmsResultInsertSuccess actionData
|
|
, lmsResultTimestamp = now -- lmsResultInsertTimestamp -- does it matter which one to choose?
|
|
}
|
|
[ LmsResultSuccess =. lmsResultInsertSuccess actionData
|
|
, LmsResultTimestamp =. now
|
|
]
|
|
-- queueDBJob?? -- todo
|
|
-- audit
|
|
return $ LmsResultR sid qsh
|
|
, dbtCsvRenderKey = const $ \case
|
|
LmsResultInsertData{..} -> do -- TODO: i18n
|
|
[whamlet|
|
|
$newline never
|
|
Insert: Ident #{getLmsIdent lmsResultInsertIdent} #
|
|
had success on ^{formatTimeW SelFormatDate lmsResultInsertSuccess}
|
|
|]
|
|
LmsResultUpdateData{..} -> do -- TODO: i18n
|
|
[whamlet|
|
|
$newline never
|
|
Update: Ident #{getLmsIdent lmsResultInsertIdent} #
|
|
had success on ^{formatTimeW SelFormatDate lmsResultInsertSuccess}
|
|
|]
|
|
, dbtCsvRenderActionClass = toWidget <=< ap getMessageRender . pure
|
|
, dbtCsvRenderException = ap getMessageRender . pure :: LmsResultCsvException -> DB Text
|
|
}
|
|
dbtExtraReps = []
|
|
|
|
resultDBTableValidator = def
|
|
& defaultSorting [SortAscBy csvResultIdent]
|
|
dbTable resultDBTableValidator resultDBTable
|
|
|
|
getLmsResultR, postLmsResultR :: SchoolId -> QualificationShorthand -> Handler Html
|
|
getLmsResultR = postLmsResultR
|
|
postLmsResultR sid qsh = do
|
|
lmsTable <- runDB $ do
|
|
qid <- getKeyBy404 $ UniqueSchoolShort sid qsh
|
|
view _2 <$> mkResultTable sid qsh qid
|
|
siteLayoutMsg MsgMenuLmsResult $ do
|
|
setTitleI MsgMenuLmsResult
|
|
$(widgetFile "lms-result")
|