chore(lms): work on stub for csv import of LmsResult

This commit is contained in:
Steffen Jost 2022-02-18 17:54:24 +01:00
parent e821b416f0
commit fea453a87e
9 changed files with 100 additions and 41 deletions

View File

@ -2,4 +2,6 @@ TableLmsIdent: Identifikation
TableLmsFailed: Gesperrt TableLmsFailed: Gesperrt
TableLmsSuccess: Bestanden TableLmsSuccess: Bestanden
CsvColumnLmsResultIdent: E-Lernen Identifikator, einzigartig pro Qualifikation und Teilnehmer CsvColumnLmsResultIdent: E-Lernen Identifikator, einzigartig pro Qualifikation und Teilnehmer
CsvColumnLmsResultSuccess: Zeitstempel der erfolgreichen Teilnahme CsvColumnLmsResultSuccess: Zeitstempel der erfolgreichen Teilnahme
LmsResultInsert: Neues LMS Ergebnis
LmsResultCsvExceptionDuplicatedKey: CSV Import fand uneindeutigen Schlüssel

View File

@ -2,4 +2,6 @@ TableLmsIdent: Identifier
TableLmsFailed: Blocked TableLmsFailed: Blocked
TableLmsSuccess: Completed TableLmsSuccess: Completed
CsvColumnLmsResultIdent: E-Learing identifier, unique for each qualfication and user CsvColumnLmsResultIdent: E-Learing identifier, unique for each qualfication and user
CsvColumnLmsResultSuccess: Timestamp of successful completion CsvColumnLmsResultSuccess: Timestamp of successful completion
LmsResultInsert: New LMS result
LmsResultCsvExceptionDuplicatedKey: CSV import with ambiguous key

View File

@ -115,13 +115,14 @@ LmsResult
ident LmsIdent ident LmsIdent
success Day success Day
timestamp UTCTime default=now() timestamp UTCTime default=now()
UniqueLmsResult qualification ident success
deriving Generic deriving Generic
-- Logs all processed rows from LmsUserlist and LmsResult -- Logs all processed rows from LmsUserlist and LmsResult
LmsAudit LmsAudit
qualification QualificationId qualification QualificationId
ident LmsIdent ident LmsIdent
notificationType LmsStatus -- LmsOpen | LmsBlocked | LmsSuccess Day notificationType LmsStatus -- LmsOpen | LmsBlocked | LmsSuccess Day
received UTCTime -- timestamp from LmsUserlist/LmsResult received UTCTime -- timestamp from LmsUserlist/LmsResult
processed UTCTime default=now() processed UTCTime default=now()
deriving Generic deriving Generic

View File

@ -42,8 +42,8 @@ instance PathPiece UTCTime where
fromPathPiece = iso8601ParseM . unpack fromPathPiece = iso8601ParseM . unpack
-- UTCTime, Day, etc. -- UTCTime, Day, etc.
instance {-# OVERLAPPABLE #-} ISO8601 t => Csv.ToField t where -- Error: overlaps Csv.ToField String, but no instance ISO8601 String exists?! --instance {-# OVERLAPPABLE #-} ISO8601 t => Csv.ToField t where -- Error: overlaps Csv.ToField String, but no instance ISO8601 String exists?!
toField = Csv.toField . iso8601Show -- toField = (Csv.toField::String -> Field) . iso8601Show
{- {-
Overlapping instances for Csv.ToField String Overlapping instances for Csv.ToField String
arising from a use of Csv.toField arising from a use of Csv.toField
@ -57,10 +57,24 @@ instance {-# OVERLAPPABLE #-} ISO8601 t => Csv.ToField t where -- Error: overla
In an equation for Csv.toField: In an equation for Csv.toField:
Csv.toField = Csv.toField . iso8601Show Csv.toField = Csv.toField . iso8601Show
-} -}
--
--instance {-# OVERLAPPABLE #-} ISO8601 t => Csv.FromField t where -- overlapped for ZonedTime in Handler.Utils.DateTime
-- parseField = iso8601ParseM <=< Csv.parseField
--
instance {-# OVERLAPPABLE #-} ISO8601 t => Csv.FromField t where -- overlapped for ZonedTime in Handler.Utils.DateTime instance Csv.ToField UTCTime where
toField = Csv.toField . iso8601Show
instance Csv.FromField UTCTime where
parseField = iso8601ParseM <=< Csv.parseField parseField = iso8601ParseM <=< Csv.parseField
instance Csv.ToField Day where
toField = Csv.toField . iso8601Show
instance Csv.FromField Day where
parseField = iso8601ParseM <=< Csv.parseField
-- CalendarDiffDays -- CalendarDiffDays
-- --
-- CalendarDiffDays is basically a pair of Integers, we are stored in the DB as an Array of Word (Word8 probably suffices already) -- CalendarDiffDays is basically a pair of Integers, we are stored in the DB as an Array of Word (Word8 probably suffices already)

View File

@ -140,6 +140,8 @@ getLmsR sid qsh = do
$(widgetFile "lms") $(widgetFile "lms")
mkUserlistTable :: QualificationId -> DB (Any, Widget) mkUserlistTable :: QualificationId -> DB (Any, Widget)
mkUserlistTable qid = do mkUserlistTable qid = do
let let
@ -176,9 +178,9 @@ mkUserlistTable qid = do
getLmsUserlistR :: SchoolId -> QualificationShorthand -> Handler Html getLmsUserlistR :: SchoolId -> QualificationShorthand -> Handler Html
getLmsUserlistR sid qsh = do getLmsUserlistR sid qsh = do
(_qid, lmsTable) <- runDB $ bind2 lmsTable <- runDB $ do
(getKeyBy404 $ UniqueSchoolShort sid qsh) qid <- getKeyBy404 $ UniqueSchoolShort sid qsh
((view _2 <$>) . mkUserlistTable) view _2 <$> mkUserlistTable qid
siteLayoutMsg MsgMenuLmsUserlist $ do siteLayoutMsg MsgMenuLmsUserlist $ do
setTitleI MsgMenuLmsUserlist setTitleI MsgMenuLmsUserlist
$(widgetFile "lms-userlist") $(widgetFile "lms-userlist")

View File

@ -104,9 +104,31 @@ instance CsvColumnsExplained LmsResultTableCsv where
single :: RenderMessage UniWorX msg => Csv.Name -> msg -> Map Csv.Name Widget single :: RenderMessage UniWorX msg => Csv.Name -> msg -> Map Csv.Name Widget
single k v = singletonMap k [whamlet|_{v}|] single k v = singletonMap k [whamlet|_{v}|]
data LmsResultCsvActionClass = LmsResultInsert
deriving (Eq, Ord, Read, Show, Generic, Typeable, Enum, Bounded)
embedRenderMessage ''UniWorX ''LmsResultCsvActionClass id
data LmsResultCsvAction = LmsResultInsertData { lmsResultInsertIdent :: LmsIdent, lmsResultInsertSuccess :: Day, lmsResultInsertTimestamp :: UTCTime }
deriving (Eq, Ord, Read, Show, Generic, Typeable)
deriveJSON defaultOptions
{ constructorTagModifier = over Text.packed $ Text.intercalate "-" . map Text.toLower . drop 2 . dropEnd 1 . splitCamel
, fieldLabelModifier = camelToPathPiece' 2
, sumEncoding = TaggedObject "action" "data"
} ''LmsResultCsvAction
data LmsResultCsvException
= LmsResultCsvExceptionDuplicatedKey
deriving (Show, Generic, Typeable)
instance Exception LmsResultCsvException
embedRenderMessage ''UniWorX ''LmsResultCsvException id
mkResultTable :: QualificationId -> DB (Any, Widget) mkResultTable :: QualificationId -> DB (Any, Widget)
mkResultTable qid = do mkResultTable qid = do
--now <- liftIO getCurrentTime
let let
resultDBTable = DBTable{..} resultDBTable = DBTable{..}
where where
@ -129,38 +151,48 @@ mkResultTable qid = do
] -- TODO: add more columns for manual debugging view !!! ] -- TODO: add more columns for manual debugging view !!!
dbtSorting = Map.fromList dbtSorting = Map.fromList
[ ("ident" , SortColumn $ queryLmsResult >>> (E.^. LmsResultIdent)) [ ("ident" , SortColumn $ queryLmsResult >>> (E.^. LmsResultIdent))
-- , ("success", SortColumn $ queryLmsResult >>> (E.^. LmsResultSuccess)) -- , ("success", SortColumn $ queryLmsResult >>> (E.^. LmsResultSuccess))
, ("success", SortColumn $ views (to queryLmsResult) (E.^. LmsResultSuccess)) , ("success", SortColumn $ views (to queryLmsResult) (E.^. LmsResultSuccess))
] ]
dbtFilter = Map.fromList dbtFilter = Map.fromList
[ -- singletonMap "ident" . FilterColumn . E.mkContainsFilter $ views (to queryLmsResult) (E.^. LmsResultIdent) [ ("ident" , FilterColumn . E.mkContainsFilterWith LmsIdent $ views (to queryLmsResult) (E.^. LmsResultIdent))
("ident", FilterColumn . E.mkContainsFilter $ views (to queryLmsResult) (E.^. LmsResultIdent)) , ("success" , FilterColumn . E.mkExactFilter $ views (to queryLmsResult) (E.^. LmsResultSuccess))
]
dbtFilterUI = \mPrev -> mconcat
[ prismAForm (singletonFilter "ident" . maybePrism _PathPiece) mPrev $ aopt (hoistField lift textField) (fslI MsgTableLmsIdent)
, prismAForm (singletonFilter "success" . maybePrism _PathPiece) mPrev $ aopt (hoistField lift checkBoxField) (fslI MsgTableLmsIdent)
] ]
dbtFilterUI = const mempty -- TODO !!! continue here !!! Manual filtering useful to deal with user complaints!
dbtStyle = def dbtStyle = def
dbtParams = def dbtParams = def
dbtIdent :: Text dbtIdent :: Text
dbtIdent = "lms-userlist" dbtIdent = "lms-userlist"
dbtCsvEncode = noCsvEncode dbtCsvEncode = Nothing
dbtCsvDecode = Nothing {- dbtCsvDecode = Just $ DBTCsvDecode -- Just save to DB; Job will process data later
dbtCsvDecode = Just DBTCsvDecode -- Just save to DB; Job will process data later { dbtCsvRowKey = \LmsResultTableCsv{..} ->
{ dbtCsvRowKey = const $ return Nothing -- always generate a fres key, or should we use ident? fmap E.Value . MaybeT . getKeyBy $ UniqueLmsResult qid csvLRTident csvLRTsuccess
, dbtCsvComputeActions = \case , dbtCsvComputeActions = \case -- purpose is to show a diff to the user first
DBCsvDiffMissing{} DBCsvDiffNew{dbCsvNewKey = Nothing, dbCsvNew} -> do
-> return () -- no deletion now <- liftIO getCurrentTime
DBCsvDiffExisting{} --let LmsResultTableCsv{..} = dbCsvNew
-> return () -- no deletion --let csvLRTident = error "TODO"
DBCsvDiffNew{dbCsvNewKey, dbCsvNew} -- csvLRTsuccess = error "TODO"
-> _insert yield $ LmsResultInsertData
, dbtCsvClassifyAction = const () -- there is only one action: insert into table { lmsResultInsertIdent = csvLRTident dbCsvNew
, dbtCsvCoarsenActionClass = const () -- there is only one action: insert into table , lmsResultInsertSuccess = csvLRTsuccess dbCsvNew
, dbtCsvValidateActions = return () -- no validation, since this is an automatic upload, i.e. no user to review error , lmsResultInsertTimestamp = now
, dbtCsvExecuteActions = _savetodb }
, dbtCsvRenderKey = _renderKey -- what is the purpose? DBCsvDiffNew{dbCsvNewKey = Just _, dbCsvNew} -> error "UniqueLmsResult was found, but Key no longer exists."
, dbtCsvRenderActionClass = _renderActioCalss -- what is the purpose? DBCsvDiffMissing{} -> return () -- no deletion
, dbtCsvRenderException = _renderException DBCsvDiffExisting{} -> return () -- no merge
} , dbtCsvClassifyAction = \case
-} LmsResultInsertData{} -> LmsResultInsert
, dbtCsvCoarsenActionClass = const DBCsvActionNew -- there is only one action: insert into table
, dbtCsvValidateActions = return () -- no validation, since this is an automatic upload, i.e. no user to review error
, dbtCsvExecuteActions = error "TODO"
, dbtCsvRenderKey = error "TODO" -- what is the purpose?
, dbtCsvRenderActionClass = toWidget <=< ap getMessageRender . pure
, dbtCsvRenderException = ap getMessageRender . pure :: LmsResultCsvException -> DB Text
}
dbtExtraReps = [] dbtExtraReps = []
resultDBTableValidator = def resultDBTableValidator = def

View File

@ -28,7 +28,7 @@ import Data.Time.Zones
import qualified Data.Time.Zones as TZ import qualified Data.Time.Zones as TZ
import qualified Data.Time.Format as Time import qualified Data.Time.Format as Time
-- import Data.Time.Format.ISO8601 (iso8601Show) import Data.Time.Format.ISO8601 (iso8601Show)
import qualified Data.Set as Set import qualified Data.Set as Set
@ -317,12 +317,11 @@ formatTimeRangeMail = formatTimeRange' formatTimeMail
formatGregorianW :: (YesodAuthPersist UniWorX, AuthEntity UniWorX ~ User, AuthId UniWorX ~ UserId) => Integer -> Int -> Int -> WidgetFor UniWorX () formatGregorianW :: (YesodAuthPersist UniWorX, AuthEntity UniWorX ~ User, AuthId UniWorX ~ UserId) => Integer -> Int -> Int -> WidgetFor UniWorX ()
formatGregorianW y m d = formatTimeW SelFormatDate $ fromGregorian y m d formatGregorianW y m d = formatTimeW SelFormatDate $ fromGregorian y m d
-- generic instance from Data.Time.Clock.Instances suffices instance Csv.ToField ZonedTime where
--instance Csv.ToField ZonedTime where toField = Csv.toField . iso8601Show
-- toField = Csv.toField . iso8601Show
-- overlaps instance from Data.Time.Clock.Instances -- also see Data.Time.Clock.Instances
instance {-# OVERLAPS #-} Csv.FromField ZonedTime where instance Csv.FromField ZonedTime where
parseField = parse <=< Csv.parseField parseField = parse <=< Csv.parseField
where where
parse t = asum $ do parse t = asum $ do

View File

@ -17,9 +17,14 @@ newtype LmsIdent = LmsIdent { getLmsIdent :: Text }
deriving (Eq, Ord, Read, Show, Generic, Typeable) deriving (Eq, Ord, Read, Show, Generic, Typeable)
deriving newtype (NFData, PathPiece, PersistField, PersistFieldSql, Csv.ToField, Csv.FromField) deriving newtype (NFData, PathPiece, PersistField, PersistFieldSql, Csv.ToField, Csv.FromField)
instance E.SqlString LmsIdent where instance E.SqlString LmsIdent where
makeLenses_ ''LmsIdent makeLenses_ ''LmsIdent
deriveJSON defaultOptions
{ constructorTagModifier = camelToPathPiece' 1
, fieldLabelModifier = camelToPathPiece' 2
, omitNothingFields = True
} ''LmsIdent
-- TODO: is this a good idea? Maybe just an ordinary Enum and a separate Day Column in the DB would be better, especially since LmsBlocked should really also encode a Day -- TODO: is this a good idea? Maybe just an ordinary Enum and a separate Day Column in the DB would be better, especially since LmsBlocked should really also encode a Day
data LmsStatus = LmsOpen | LmsBlocked | LmsSuccess Day data LmsStatus = LmsOpen | LmsBlocked | LmsSuccess Day
deriving (Eq, Ord, Read, Show, Generic, Typeable, NFData) deriving (Eq, Ord, Read, Show, Generic, Typeable, NFData)

View File

@ -456,6 +456,8 @@ fillDb = do
void . insert' $ UserSchool uid mi False void . insert' $ UserSchool uid mi False
for_ [jost] $ \uid -> for_ [jost] $ \uid ->
void . insert' $ UserSchool uid avn False void . insert' $ UserSchool uid avn False
-- void . insert'
let let
sdBsc = StudyDegreeKey' 82 sdBsc = StudyDegreeKey' 82
sdMst = StudyDegreeKey' 88 sdMst = StudyDegreeKey' 88