feat(corrections-r): csv export

Fixes #705
This commit is contained in:
Gregor Kleen 2021-08-18 16:54:50 +02:00
parent 51522efc7c
commit 2a6248e3d5
18 changed files with 444 additions and 55 deletions

View File

@ -228,4 +228,35 @@ SubmissionColumnAuthorshipStatementTime: Zeitstempel
SubmissionColumnAuthorshipStatementWording: Wortlaut SubmissionColumnAuthorshipStatementWording: Wortlaut
SubmissionFilterAuthorshipStatementCurrent: Aktueller Wortlaut SubmissionFilterAuthorshipStatementCurrent: Aktueller Wortlaut
SubmissionNoUsers: Diese Abgabe hat keine assoziierten Benutzer! SubmissionNoUsers: Diese Abgabe hat keine assoziierten Benutzer!
CsvColumnCorrectionTerm: Semester des Kurses der Abgabe
CsvColumnCorrectionSchool: Institut des Kurses der Abgabe
CsvColumnCorrectionCourse: Kürzel des Kurses der Abgabe
CsvColumnCorrectionSheet: Name des Übungsblatts der Abgabe
CsvColumnCorrectionSubmission: Nummer der Abgabe (uwa…)
CsvColumnCorrectionSurname: Nachnamen der Abgebenden als Semikolon (;) separierte Liste
CsvColumnCorrectionFirstName: Vornamen der Abgebenden als Semikolon (;) separierte Liste
CsvColumnCorrectionName: Volle Namen der Abgebenden als Semikolon (;) separierte Liste
CsvColumnCorrectionMatriculation: Matrikelnummern der Abgebenden als Semikolon (;) separierte Liste
CsvColumnCorrectionEmail: E-Mail Adressen der Abgebenden als Semikolon (;) separierte Liste
CsvColumnCorrectionPseudonym: Abgabe-Pseudonyme der Abgebenden als Semikolon (;) separierte Liste
CsvColumnCorrectionSubmissionGroup: Feste Abgabegruppen der Abgebenden als Semikolon (;) separierte Liste
CsvColumnCorrectionAuthorshipStatementState: Zustände der Eigenständigkeitserklärungen ("#{toPathPiece ASMissing}", "#{toPathPiece ASOldStatement}" oder "#{toPathPiece ASExists}") als Semikolon (;) separierte Liste
CsvColumnCorrectionCorrectorName: Voller Name des Korrektors der Abgabe
CsvColumnCorrectionCorrectorEmail: E-Mail Adresse des Korrektors der Abgabe
CsvColumnCorrectionRatingDone: Bewertung abgeschlossen ("t"/"f")
CsvColumnCorrectionRatedAt: Zeitpunkt der Bewertung (ISO 8601)
CsvColumnCorrectionAssigned: Zeitpunkt der Zuteilung des Korrektors (ISO 8601)
CsvColumnCorrectionLastEdit: Zeitpunkt der letzten Änderung der Abgabe (ISO 8601)
CsvColumnCorrectionRatingPoints: Erreichte Punktezahl (Für “_{MsgSheetGradingPassBinary}” entspricht 0 “_{MsgRatingNotPassed}” und alles andere “_{MsgRatingPassed}”)
CsvColumnCorrectionRatingComment: Bewertungskommentar
CorrectionTableCsvNameSheetCorrections tid@TermId ssh@SchoolId csh@CourseShorthand shn@SheetName: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldedCase shn}-abgaben
CorrectionTableCsvSheetNameSheetCorrections tid@TermId ssh@SchoolId csh@CourseShorthand shn@SheetName: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldedCase shn} Abgaben
CorrectionTableCsvNameCourseCorrections tid@TermId ssh@SchoolId csh@CourseShorthand: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-abgaben
CorrectionTableCsvSheetNameCourseCorrections tid@TermId ssh@SchoolId csh@CourseShorthand: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh} Abgaben
CorrectionTableCsvNameCorrections: abgaben
CorrectionTableCsvSheetNameCorrections: Abgaben
CorrectionTableCsvNameCourseUserCorrections tid@TermId ssh@SchoolId csh@CourseShorthand displayName@Text: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldCase displayName}-abgaben
CorrectionTableCsvSheetNameCourseUserCorrections tid@TermId ssh@SchoolId csh@CourseShorthand displayName@Text: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldCase displayName} Abgaben

View File

@ -228,3 +228,34 @@ SubmissionColumnAuthorshipStatementWording: Wording
SubmissionFilterAuthorshipStatementCurrent: Current wording SubmissionFilterAuthorshipStatementCurrent: Current wording
SubmissionNoUsers: This submission has no associated users! SubmissionNoUsers: This submission has no associated users!
CsvColumnCorrectionTerm: Term of the course of the submission
CsvColumnCorrectionSchool: School of the course of the submission
CsvColumnCorrectionCourse: Shorthand of the course of the submission
CsvColumnCorrectionSheet: Name of the sheet of the submission
CsvColumnCorrectionSubmission: Number of the submission (uwa…)
CsvColumnCorrectionSurname: Submittor's surnames, separated by semicolon (;)
CsvColumnCorrectionFirstName: Submittor's first names, separated by semicolon (;)
CsvColumnCorrectionName: Submittor's full names, separated by semicolon (;)
CsvColumnCorrectionMatriculation: Submittor's matriculations, separated by semicolon (;)
CsvColumnCorrectionEmail: Submittor's email addresses, separated by semicolon (;)
CsvColumnCorrectionPseudonym: Submittor's submission pseudonyms, separated by semicolon (;)
CsvColumnCorrectionSubmissionGroup: Submittor's submisson groups, separated by semicolon (;)
CsvColumnCorrectionAuthorshipStatementState: States of the statements of authorship ("#{toPathPiece ASMissing}", "#{toPathPiece ASOldStatement}", or "#{toPathPiece ASExists}"), separated by semicolon (;)
CsvColumnCorrectionCorrectorName: Full name of the corrector of the submission
CsvColumnCorrectionCorrectorEmail: Email address of the corrector of the submission
CsvColumnCorrectionRatingDone: Rating done ("t"/"f")
CsvColumnCorrectionRatedAt: Timestamp of rating (ISO 8601)
CsvColumnCorrectionAssigned: Timestamp of when corrector was assigned (ISO 8601)
CsvColumnCorrectionLastEdit: Timestamp of the last edit of the submission (ISO 8601)
CsvColumnCorrectionRatingPoints: Achieved points (for “_{MsgSheetGradingPassBinary}” 0 means “_{MsgRatingNotPassed}”, everything else means “_{MsgRatingPassed}”)
CsvColumnCorrectionRatingComment: Rating comment
CorrectionTableCsvNameSheetCorrections tid ssh csh shn: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldedCase shn}-submissions
CorrectionTableCsvSheetNameSheetCorrections tid ssh csh shn: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldedCase shn} Submissions
CorrectionTableCsvNameCourseCorrections tid ssh csh: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-submissions
CorrectionTableCsvSheetNameCourseCorrections tid ssh csh: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh} Submissions
CorrectionTableCsvNameCorrections: submissions
CorrectionTableCsvSheetNameCorrections: Submissions
CorrectionTableCsvNameCourseUserCorrections tid ssh csh displayName: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldCase displayName}-submissions
CorrectionTableCsvSheetNameCourseUserCorrections tid ssh csh displayName: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldCase displayName} Submissions

View File

@ -9,7 +9,9 @@ import Data.Scientific
import Web.PathPieces import Web.PathPieces
import Text.ParserCombinators.ReadP (readP_to_S)
instance PathPiece Scientific where instance PathPiece Scientific where
toPathPiece = pack . formatScientific Fixed Nothing toPathPiece = pack . formatScientific Fixed Nothing
fromPathPiece = readFromPathPiece fromPathPiece = fmap fst . listToMaybe . filter (\(_, rStr) -> null rStr) . readP_to_S scientificP . unpack

View File

@ -12,6 +12,8 @@ import System.Random (Random(..))
import Data.Aeson (FromJSON(..), ToJSON(..)) import Data.Aeson (FromJSON(..), ToJSON(..))
import qualified Data.Aeson.Types as Aeson import qualified Data.Aeson.Types as Aeson
import Web.PathPieces
import Data.Word.Word24 import Data.Word.Word24
import Control.Lens import Control.Lens
@ -19,6 +21,7 @@ import Control.Lens
import Control.Monad.Fail import Control.Monad.Fail
import qualified Data.Scientific as Scientific import qualified Data.Scientific as Scientific
import Data.Scientific.Instances ()
import Data.Binary import Data.Binary
import Data.Bits import Data.Bits
@ -51,6 +54,10 @@ instance FromJSON Word24 where
instance ToJSON Word24 where instance ToJSON Word24 where
toJSON = Aeson.Number . fromIntegral toJSON = Aeson.Number . fromIntegral
instance PathPiece Word24 where
toPathPiece p = toPathPiece (fromIntegral p :: Word32)
fromPathPiece = Scientific.toBoundedInteger <=< fromPathPiece
-- | Big Endian -- | Big Endian
instance Binary Word24 where instance Binary Word24 where

View File

@ -308,6 +308,8 @@ embedRenderMessageVariant ''UniWorX ''ADInvalidCredentials ("InvalidCredentials"
embedRenderMessage ''UniWorX ''SchoolAuthorshipStatementMode id embedRenderMessage ''UniWorX ''SchoolAuthorshipStatementMode id
embedRenderMessage ''UniWorX ''SheetAuthorshipStatementMode id embedRenderMessage ''UniWorX ''SheetAuthorshipStatementMode id
embedRenderMessage ''UniWorX ''AuthorshipStatementSubmissionState $ concat . ("SubmissionAuthorshipStatementState" :) . drop 1 . splitCamel
newtype ShortSex = ShortSex Sex newtype ShortSex = ShortSex Sex
embedRenderMessageVariant ''UniWorX ''ShortSex ("Short" <>) embedRenderMessageVariant ''UniWorX ''ShortSex ("Short" <>)

View File

@ -240,7 +240,7 @@ courseUserNoteSection (Entity cid Course{..}) (Entity uid _) = do
courseUserSubmissionsSection :: Entity Course -> Entity User -> MaybeT Handler Widget courseUserSubmissionsSection :: Entity Course -> Entity User -> MaybeT Handler Widget
courseUserSubmissionsSection (Entity cid Course{..}) (Entity uid _) = do courseUserSubmissionsSection (Entity cid Course{..}) (Entity uid User{..}) = do
guardM . lift . hasWriteAccessTo $ CourseR courseTerm courseSchool courseShorthand CCorrectionsR guardM . lift . hasWriteAccessTo $ CourseR courseTerm courseSchool courseShorthand CCorrectionsR
let whereClause :: CorrectionTableWhere let whereClause :: CorrectionTableWhere
@ -268,7 +268,13 @@ courseUserSubmissionsSection (Entity cid Course{..}) (Entity uid _) = do
, filterUISubmission , filterUISubmission
] ]
psValidator = def & defaultPagesize PagesizeAll -- Assisstant always want to see them all at once anyway psValidator = def & defaultPagesize PagesizeAll -- Assisstant always want to see them all at once anyway
(cWdgt, statistics) <- lift . correctionsR' whereClause colonnade filterUI psValidator $ Map.fromList csvSettings = Just CorrectionTableCsvSettings
{ cTableCsvQualification = CorrectionTableCsvQualifySheet
, cTableCsvName = MsgCorrectionTableCsvNameCourseUserCorrections courseTerm courseSchool courseShorthand userDisplayName
, cTableCsvSheetName = MsgCorrectionTableCsvSheetNameCourseUserCorrections courseTerm courseSchool courseShorthand userDisplayName
, cTableShowCorrector = True
}
(cWdgt, statistics) <- lift . correctionsR' whereClause colonnade filterUI csvSettings psValidator $ Map.fromList
[ downloadAction [ downloadAction
, assignAction (Left cid) , assignAction (Left cid)
, deleteAction , deleteAction

View File

@ -197,17 +197,13 @@ instance Csv.ToNamedRecord UserTableCsv where
, "email" Csv..= csvUserEmail , "email" Csv..= csvUserEmail
, "study-features" Csv..= csvUserStudyFeatures , "study-features" Csv..= csvUserStudyFeatures
, "submission-group" Csv..= csvUserSubmissionGroup , "submission-group" Csv..= csvUserSubmissionGroup
] ++ , "tutorial" Csv..= CsvSemicolonList (csvUserTutorials ^. _1)
[ let tutsStr = Text.intercalate "; " . map CI.original $ csvUserTutorials ^. _1
in "tutorial" Csv..= tutsStr
] ++ ] ++
[ encodeUtf8 (CI.foldedCase regGroup) Csv..= (CI.original <$> mTut) [ encodeUtf8 (CI.foldedCase regGroup) Csv..= (CI.original <$> mTut)
| (regGroup, mTut) <- Map.toList $ csvUserTutorials ^. _2 | (regGroup, mTut) <- Map.toList $ csvUserTutorials ^. _2
] ++ ] ++
[ let examsStr = Text.intercalate "; " $ map CI.original csvUserExams [ "exams" Csv..= CsvSemicolonList csvUserExams
in "exams" Csv..= examsStr , "registration" Csv..= csvUserRegistration
] ++
[ "registration" Csv..= csvUserRegistration
] ++ ] ++
[ encodeUtf8 (CI.foldedCase shn) Csv..= res [ encodeUtf8 (CI.foldedCase shn) Csv..= res
| (shn, res) <- Map.toList csvUserSheets | (shn, res) <- Map.toList csvUserSheets

View File

@ -64,7 +64,7 @@ postCorrectionsGradeR = do
& defaultSorting [SortDescBy "ratingtime"] :: PSValidator (MForm (HandlerFor UniWorX)) (FormResult (DBFormResult SubmissionId (Bool, Maybe Points, Maybe Text) CorrectionTableData)) & defaultSorting [SortDescBy "ratingtime"] :: PSValidator (MForm (HandlerFor UniWorX)) (FormResult (DBFormResult SubmissionId (Bool, Maybe Points, Maybe Text) CorrectionTableData))
unFormResult = getDBFormResult $ \(view $ resultSubmission . _entityVal -> sub@Submission{..}) -> (submissionRatingDone sub, submissionRatingPoints, submissionRatingComment) unFormResult = getDBFormResult $ \(view $ resultSubmission . _entityVal -> sub@Submission{..}) -> (submissionRatingDone sub, submissionRatingPoints, submissionRatingComment)
(fmap unFormResult -> tableRes, table) <- runDB $ makeCorrectionsTable whereClause displayColumns filterUI psValidator $ def (fmap unFormResult -> tableRes, table) <- runDB $ makeCorrectionsTable whereClause displayColumns filterUI Nothing psValidator $ def
{ dbParamsFormAction = Just $ SomeRoute CorrectionsGradeR { dbParamsFormAction = Just $ SomeRoute CorrectionsGradeR
} }

View File

@ -14,6 +14,7 @@ module Handler.Submission.List
, makeCorrectionsTable , makeCorrectionsTable
, CorrectionTableData, CorrectionTableWhere , CorrectionTableData, CorrectionTableWhere
, ActionCorrections(..), downloadAction, deleteAction, assignAction, autoAssignAction , ActionCorrections(..), downloadAction, deleteAction, assignAction, autoAssignAction
, CorrectionTableCsvQualification(..), CorrectionTableCsvSettings(..)
) where ) where
import Import hiding (link) import Import hiding (link)
@ -23,7 +24,6 @@ import Handler.Utils.Submission
import Handler.Utils.SheetType import Handler.Utils.SheetType
import Handler.Utils.Delete import Handler.Utils.Delete
import Data.List as List (foldr)
import qualified Data.Set as Set import qualified Data.Set as Set
import qualified Data.Map.Strict as Map import qualified Data.Map.Strict as Map
@ -42,6 +42,8 @@ import Database.Persist.Sql (updateWhereCount)
import Data.List (genericLength) import Data.List (genericLength)
import qualified Data.Csv as Csv
data CorrectionTableFilterProj = CorrectionTableFilterProj data CorrectionTableFilterProj = CorrectionTableFilterProj
{ corrProjFilterSubmission :: Maybe (Set [CI Char]) { corrProjFilterSubmission :: Maybe (Set [CI Char])
@ -66,7 +68,7 @@ type CorrectionTableExpr = ( E.SqlExpr (Entity Course)
`E.LeftOuterJoin` E.SqlExpr (Maybe (Entity User)) `E.LeftOuterJoin` E.SqlExpr (Maybe (Entity User))
type CorrectionTableWhere = forall m. MonadReader CorrectionTableExpr m => m (E.SqlExpr (E.Value Bool)) type CorrectionTableWhere = forall m. MonadReader CorrectionTableExpr m => m (E.SqlExpr (E.Value Bool))
type CorrectionTableCourseData = (CourseName, CourseShorthand, TermId, SchoolId) type CorrectionTableCourseData = (CourseName, CourseShorthand, TermId, SchoolId)
type CorrectionTableUserData = (User, Maybe Pseudonym, Maybe SubmissionGroupName) type CorrectionTableUserData = (User, Maybe Pseudonym, Maybe SubmissionGroupName, Maybe AuthorshipStatementSubmissionState)
type CorrectionTableData = DBRow ( Entity Submission type CorrectionTableData = DBRow ( Entity Submission
, Entity Sheet , Entity Sheet
, CorrectionTableCourseData , CorrectionTableCourseData
@ -135,6 +137,9 @@ resultUserPseudonym = _2 . _Just
resultUserSubmissionGroup :: Traversal' CorrectionTableUserData SubmissionGroupName resultUserSubmissionGroup :: Traversal' CorrectionTableUserData SubmissionGroupName
resultUserSubmissionGroup = _3 . _Just resultUserSubmissionGroup = _3 . _Just
resultUserAuthorshipStatementState :: Traversal' CorrectionTableUserData AuthorshipStatementSubmissionState
resultUserAuthorshipStatementState = _4 . _Just
resultCryptoID :: Lens' CorrectionTableData CryptoFileNameSubmission resultCryptoID :: Lens' CorrectionTableData CryptoFileNameSubmission
resultCryptoID = _dbrOutput . _7 resultCryptoID = _dbrOutput . _7
@ -145,6 +150,159 @@ resultASState :: Lens' CorrectionTableData (Maybe AuthorshipStatementSubmissionS
resultASState = _dbrOutput . _9 resultASState = _dbrOutput . _9
data CorrectionTableCsv = CorrectionTableCsv
{ csvCorrectionTerm :: Maybe TermIdentifier
, csvCorrectionSchool :: Maybe SchoolShorthand
, csvCorrectionCourse :: Maybe CourseShorthand
, csvCorrectionSheet :: Maybe SheetName
, csvCorrectionSubmission :: Maybe (CI Text)
, csvCorrectionLastEdit :: Maybe UTCTime
, csvCorrectionSurname :: Maybe [Maybe UserSurname]
, csvCorrectionFirstName :: Maybe [Maybe UserFirstName]
, csvCorrectionName :: Maybe [Maybe UserDisplayName]
, csvCorrectionMatriculation :: Maybe [Maybe UserMatriculation]
, csvCorrectionEmail :: Maybe [Maybe UserEmail]
, csvCorrectionPseudonym :: Maybe [Maybe Pseudonym]
, csvCorrectionSubmissionGroup :: Maybe [Maybe SubmissionGroupName]
, csvCorrectionAuthorshipStatementState :: Maybe [Maybe AuthorshipStatementSubmissionState]
, csvCorrectionAssigned :: Maybe UTCTime
, csvCorrectionCorrectorName :: Maybe UserDisplayName
, csvCorrectionCorrectorEmail :: Maybe UserEmail
, csvCorrectionRatingDone :: Maybe Bool
, csvCorrectionRatedAt :: Maybe UTCTime
, csvCorrectionRatingPoints :: Maybe Points
, csvCorrectionRatingComment :: Maybe Text
} deriving (Generic)
makeLenses_ ''CorrectionTableCsv
correctionTableCsvOptions :: Csv.Options
correctionTableCsvOptions = Csv.defaultOptions { Csv.fieldLabelModifier = camelToPathPiece' 2 }
instance Csv.ToNamedRecord CorrectionTableCsv where
toNamedRecord CorrectionTableCsv{..} = Csv.namedRecord
[ "term" Csv..= csvCorrectionTerm
, "school" Csv..= csvCorrectionSchool
, "course" Csv..= csvCorrectionCourse
, "sheet" Csv..= csvCorrectionSheet
, "submission" Csv..= csvCorrectionSubmission
, "last-edit" Csv..= csvCorrectionLastEdit
, "surname" Csv..= maybe mempty (Csv.toField . CsvSemicolonList) csvCorrectionSurname
, "first-name" Csv..= maybe mempty (Csv.toField . CsvSemicolonList) csvCorrectionFirstName
, "name" Csv..= maybe mempty (Csv.toField . CsvSemicolonList) csvCorrectionName
, "matriculation" Csv..= maybe mempty (Csv.toField . CsvSemicolonList . mkEmpty) csvCorrectionMatriculation
, "email" Csv..= maybe mempty (Csv.toField . CsvSemicolonList) csvCorrectionEmail
, "pseudonym" Csv..= maybe mempty (Csv.toField . CsvSemicolonList . mkEmpty) csvCorrectionPseudonym
, "submission-group" Csv..= maybe mempty (Csv.toField . CsvSemicolonList . mkEmpty) csvCorrectionSubmissionGroup
, "authorship-statement-state" Csv..= maybe mempty (Csv.toField . CsvSemicolonList . mkEmpty) csvCorrectionAuthorshipStatementState
, "assigned" Csv..= csvCorrectionAssigned
, "corrector-name" Csv..= csvCorrectionCorrectorName
, "corrector-email" Csv..= csvCorrectionCorrectorEmail
, "rating-done" Csv..= csvCorrectionRatingDone
, "rated-at" Csv..= csvCorrectionRatedAt
, "rating-points" Csv..= csvCorrectionRatingPoints
, "rating-comment" Csv..= csvCorrectionRatingComment
]
where
mkEmpty = \case
[Nothing] -> []
x -> x
instance Csv.DefaultOrdered CorrectionTableCsv where
headerOrder = Csv.genericHeaderOrder correctionTableCsvOptions
instance Csv.FromNamedRecord CorrectionTableCsv where
parseNamedRecord csv
= CorrectionTableCsv
<$> csv .:?? "term"
<*> csv .:?? "school"
<*> csv .:?? "course"
<*> csv .:?? "sheet"
<*> csv .:?? "submission"
<*> csv .:?? "last-edit"
<*> fmap (fmap unCsvSemicolonList) (csv .:?? "surname")
<*> fmap (fmap unCsvSemicolonList) (csv .:?? "first-name")
<*> fmap (fmap unCsvSemicolonList) (csv .:?? "name")
<*> fmap (fmap unCsvSemicolonList) (csv .:?? "matriculation")
<*> fmap (fmap unCsvSemicolonList) (csv .:?? "email")
<*> fmap (fmap unCsvSemicolonList) (csv .:?? "pseudonym")
<*> fmap (fmap unCsvSemicolonList) (csv .:?? "submission-group")
<*> fmap (fmap unCsvSemicolonList) (csv .:?? "authorship-statement-state")
<*> csv .:?? "assigned"
<*> csv .:?? "corrector-name"
<*> csv .:?? "corrector-email"
<*> csv .:?? "rating-done"
<*> csv .:?? "rated-at"
<*> csv .:?? "rating-points"
<*> csv .:?? "rating-comment"
instance CsvColumnsExplained CorrectionTableCsv where
csvColumnsExplanations = genericCsvColumnsExplanations correctionTableCsvOptions $ Map.fromList
[ ('csvCorrectionTerm , MsgCsvColumnCorrectionTerm)
, ('csvCorrectionSchool , MsgCsvColumnCorrectionSchool)
, ('csvCorrectionCourse , MsgCsvColumnCorrectionCourse)
, ('csvCorrectionSheet , MsgCsvColumnCorrectionSheet)
, ('csvCorrectionSubmission , MsgCsvColumnCorrectionSubmission)
, ('csvCorrectionLastEdit , MsgCsvColumnCorrectionLastEdit)
, ('csvCorrectionSurname , MsgCsvColumnCorrectionSurname)
, ('csvCorrectionFirstName , MsgCsvColumnCorrectionFirstName)
, ('csvCorrectionName , MsgCsvColumnCorrectionName)
, ('csvCorrectionMatriculation , MsgCsvColumnCorrectionMatriculation)
, ('csvCorrectionEmail , MsgCsvColumnCorrectionEmail)
, ('csvCorrectionPseudonym , MsgCsvColumnCorrectionPseudonym)
, ('csvCorrectionSubmissionGroup, MsgCsvColumnCorrectionSubmissionGroup)
, ('csvCorrectionAuthorshipStatementState, MsgCsvColumnCorrectionAuthorshipStatementState)
, ('csvCorrectionAssigned , MsgCsvColumnCorrectionAssigned)
, ('csvCorrectionCorrectorName , MsgCsvColumnCorrectionCorrectorName)
, ('csvCorrectionCorrectorEmail , MsgCsvColumnCorrectionCorrectorEmail)
, ('csvCorrectionRatingDone , MsgCsvColumnCorrectionRatingDone)
, ('csvCorrectionRatedAt , MsgCsvColumnCorrectionRatedAt)
, ('csvCorrectionRatingPoints , MsgCsvColumnCorrectionRatingPoints)
, ('csvCorrectionRatingComment , MsgCsvColumnCorrectionRatingComment)
]
data CorrectionTableCsvQualification
= CorrectionTableCsvNoQualification
| CorrectionTableCsvQualifySheet
| CorrectionTableCsvQualifyCourse
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
deriving anyclass (Universe, Finite)
correctionTableCsvHeader :: Bool -- ^ @showCorrector@
-> CorrectionTableCsvQualification -> Csv.Header
correctionTableCsvHeader showCorrector qual = Csv.header $ catMaybes
[ guardOn (qual >= CorrectionTableCsvQualifyCourse) "term"
, guardOn (qual >= CorrectionTableCsvQualifyCourse) "school"
, guardOn (qual >= CorrectionTableCsvQualifyCourse) "course"
, guardOn (qual >= CorrectionTableCsvQualifySheet) "sheet"
, pure "submission"
, pure "last-edit"
, pure "surname"
, pure "first-name"
, pure "name"
, pure "matriculation"
, pure "email"
, pure "pseudonym"
, pure "submission-group"
, pure "authorship-statement-state"
, pure "assigned"
, guardOn showCorrector "corrector-name"
, guardOn showCorrector "corrector-email"
, pure "rating-done"
, pure "rated-at"
, pure "rating-points"
, pure "rating-comment"
]
data CorrectionTableCsvSettings = forall filename sheetName.
( RenderMessage UniWorX filename, RenderMessage UniWorX sheetName
) => CorrectionTableCsvSettings
{ cTableCsvQualification :: CorrectionTableCsvQualification
, cTableCsvName :: filename
, cTableCsvSheetName :: sheetName
, cTableShowCorrector :: Bool
}
-- Where Clauses -- Where Clauses
ratedBy :: UserId -> CorrectionTableWhere ratedBy :: UserId -> CorrectionTableWhere
ratedBy uid = views querySubmission $ (E.==. E.justVal uid) . (E.^. SubmissionRatingBy) ratedBy uid = views querySubmission $ (E.==. E.justVal uid) . (E.^. SubmissionRatingBy)
@ -206,10 +364,10 @@ colSubmittors = sortable (Just "submittors") (i18nCell MsgSubmissionUsers) $ \x
ssh = x ^. resultCourseSchool ssh = x ^. resultCourseSchool
csh = x ^. resultCourseShorthand csh = x ^. resultCourseShorthand
link uCID = CourseR tid ssh csh $ CUserR uCID link uCID = CourseR tid ssh csh $ CUserR uCID
protoCell = listCell (sortOn (view $ _2 . resultUserUser . $(multifocusG 2) _userSurname _userDisplayName) $ itoListOf resultSubmittors x) $ \((encrypt -> mkUCID), u) -> protoCell = listCell (sortOn (view $ _2 . resultUserUser . $(multifocusG 2) _userSurname _userDisplayName) $ itoListOf resultSubmittors x) $ \(encrypt -> mkUCID, u) ->
let User{..} = u ^. resultUserUser let User{..} = u ^. resultUserUser
mPseudo = u ^? resultUserPseudonym mPseudo = u ^? resultUserPseudonym
in anchorCellCM $cacheIdentHere (link <$> mkUCID) $ in anchorCellCM $cacheIdentHere (link <$> mkUCID)
[whamlet| [whamlet|
$newline never $newline never
^{nameWidget userDisplayName userSurname} ^{nameWidget userDisplayName userSurname}
@ -298,7 +456,7 @@ colCommentField' l = sortable (Just "comment") (i18nCell MsgRatingComment) $ (ce
(\(view (resultSubmission . _entityVal) -> Submission{..}) mkUnique -> over (_1.mapped) ((l .~) . assertM (not . null) . fmap (Text.strip . unTextarea)) . over _2 fvWidget <$> mopt textareaField (fsUniq mkUnique "comment") (Just $ Textarea <$> submissionRatingComment)) (\(view (resultSubmission . _entityVal) -> Submission{..}) mkUnique -> over (_1.mapped) ((l .~) . assertM (not . null) . fmap (Text.strip . unTextarea)) . over _2 fvWidget <$> mopt textareaField (fsUniq mkUnique "comment") (Just $ Textarea <$> submissionRatingComment))
colLastEdit :: IsDBTable m a => Colonnade Sortable CorrectionTableData (DBCell m a) colLastEdit :: IsDBTable m a => Colonnade Sortable CorrectionTableData (DBCell m a)
colLastEdit = sortable (Just "last-edit") (i18nCell MsgTableLastEdit) $ \x -> maybeCell (x ^? resultLastEdit) dateTimeCell colLastEdit = sortable (Just "last-edit") (i18nCell MsgTableLastEdit) $ \x -> maybeCell (guardOnM (x ^. resultNonAnonymousAccess) $ x ^? resultLastEdit) dateTimeCell
colAuthorshipStatementState :: forall m a. IsDBTable m a => Colonnade Sortable CorrectionTableData (DBCell m a) colAuthorshipStatementState :: forall m a. IsDBTable m a => Colonnade Sortable CorrectionTableData (DBCell m a)
colAuthorshipStatementState = sortable (Just "as-state") (i18nCell MsgSubmissionUserAuthorshipStatementState) $ \x -> colAuthorshipStatementState = sortable (Just "as-state") (i18nCell MsgSubmissionUserAuthorshipStatementState) $ \x ->
@ -314,7 +472,7 @@ colAuthorshipStatementState = sortable (Just "as-state") (i18nCell MsgSubmission
cID = x ^. resultCryptoID cID = x ^. resultCryptoID
asRoute = CSubmissionR tid ssh csh shn cID SubAuthorshipStatementsR asRoute = CSubmissionR tid ssh csh shn cID SubAuthorshipStatementsR
in maybeCell (x ^. resultASState) (\s -> heatC s $ anchorCell asRoute (i18n s :: Widget)) in maybeCell (guardOnM (x ^. resultNonAnonymousAccess) $ x ^. resultASState) (\s -> heatC s $ anchorCell asRoute (i18n s :: Widget))
filterUICourse :: Handler (OptionList Text) -> DBFilterUI filterUICourse :: Handler (OptionList Text) -> DBFilterUI
@ -364,8 +522,8 @@ filterUIAuthorshipStatementState = flip (prismAForm $ singletonFilter "as-state"
makeCorrectionsTable :: ( IsDBTable m x, ToSortable h, Functor h ) makeCorrectionsTable :: ( IsDBTable m x, ToSortable h, Functor h )
=> CorrectionTableWhere -> Colonnade h CorrectionTableData (DBCell m x) -> _ -> PSValidator m x -> DBParams m x -> DB (DBResult m x) => CorrectionTableWhere -> Colonnade h CorrectionTableData (DBCell m x) -> _ -> Maybe CorrectionTableCsvSettings -> PSValidator m x -> DBParams m x -> DB (DBResult m x)
makeCorrectionsTable whereClause dbtColonnade dbtFilterUI' psValidator dbtParams makeCorrectionsTable whereClause dbtColonnade dbtFilterUI' mCSVSettings psValidator dbtParams
= let dbtSQLQuery = runReaderT $ do = let dbtSQLQuery = runReaderT $ do
course <- view queryCourse course <- view queryCourse
sheet <- view querySheet sheet <- view querySheet
@ -396,12 +554,6 @@ makeCorrectionsTable whereClause dbtColonnade dbtFilterUI' psValidator dbtParams
let haystack = map CI.mk . unpack $ toPathPiece cid let haystack = map CI.mk . unpack $ toPathPiece cid
in guard $ any (`isInfixOf` haystack) criteria in guard $ any (`isInfixOf` haystack) criteria
mASDefinition <- lift . lift . $cachedHereBinary shId $ getSheetAuthorshipStatement sheet
asState <- for mASDefinition $ \_ ->
lift . lift . $cachedHereBinary sId $ getSubmissionAuthorshipStatement mASDefinition sId
forMM_ (preview $ _dbtProjFilter . _corrProjFilterAuthorshipStatementState . _Wrapped . _Just) $ \criterion ->
guard $ asState == Just criterion
submittors <- lift . lift . E.select . E.from $ \((submissionUser `E.InnerJoin` user) `E.LeftOuterJoin` pseudonym) -> do submittors <- lift . lift . E.select . E.from $ \((submissionUser `E.InnerJoin` user) `E.LeftOuterJoin` pseudonym) -> do
E.on $ pseudonym E.?. SheetPseudonymUser E.==. E.just (user E.^. UserId) E.on $ pseudonym E.?. SheetPseudonymUser E.==. E.just (user E.^. UserId)
@ -416,8 +568,15 @@ makeCorrectionsTable whereClause dbtColonnade dbtFilterUI' psValidator dbtParams
return . E.just $ submissionGroup E.^. SubmissionGroupName return . E.just $ submissionGroup E.^. SubmissionGroupName
return (user, pseudonym E.?. SheetPseudonymPseudonym, submissionGroup') return (user, pseudonym E.?. SheetPseudonymPseudonym, submissionGroup')
let
submittorMap = List.foldr (\(Entity userId user, E.Value pseudo, E.Value sGroup) -> Map.insert userId (user, pseudo, sGroup)) Map.empty submittors mASDefinition <- lift . lift . $cachedHereBinary shId $ getSheetAuthorshipStatement sheet
(submittorMap, fmap getMax -> asState) <- runWriterT . flip foldMapM submittors $ \(Entity userId user, E.Value pseudo, E.Value sGroup) -> do
asState <- for mASDefinition $ \_ -> lift . lift . lift $ getUserAuthorshipStatement mASDefinition sId userId
tell $ Max <$> asState
return $ Map.singleton userId (user, pseudo, sGroup, asState)
forMM_ (preview $ _dbtProjFilter . _corrProjFilterAuthorshipStatementState . _Wrapped . _Just) $ \criterion ->
guard $ asState == Just criterion
forMM_ (view $ _dbtProjFilter . _corrProjFilterPseudonym) $ \criteria -> forMM_ (view $ _dbtProjFilter . _corrProjFilterPseudonym) $ \criteria ->
let haystacks = setOf (folded . resultUserPseudonym . re _PseudonymText . to (map CI.mk . unpack)) submittorMap let haystacks = setOf (folded . resultUserPseudonym . re _PseudonymText . to (map CI.mk . unpack)) submittorMap
@ -502,7 +661,41 @@ makeCorrectionsTable whereClause dbtColonnade dbtFilterUI' psValidator dbtParams
dbtFilterUI = fromMaybe mempty dbtFilterUI' dbtFilterUI = fromMaybe mempty dbtFilterUI'
dbtStyle = def { dbsFilterLayout = maybe (\_ _ _ -> id) (const defaultDBSFilterLayout) dbtFilterUI' } dbtStyle = def { dbsFilterLayout = maybe (\_ _ _ -> id) (const defaultDBSFilterLayout) dbtFilterUI' }
dbtIdent = "corrections" :: Text dbtIdent = "corrections" :: Text
dbtCsvEncode = noCsvEncode dbtCsvEncode = do
CorrectionTableCsvSettings{..} <- mCSVSettings
return DBTCsvEncode
{ dbtCsvExportForm = pure ()
, dbtCsvNoExportData = Just id
, dbtCsvDoEncode = \() -> awaitForever $ \(_, row) -> runReaderC row $ do
submittors <- asks $ sortOn (view $ resultUserUser . $(multifocusG 2) _userSurname _userDisplayName ) . toListOf resultSubmittors
forM_ (bool pure (map pure) False submittors) $ \submittors' -> transPipe (withReaderT (, submittors')) $ do
let guardNonAnonymous = runMaybeT . guardMOnM (view $ _1 . resultNonAnonymousAccess) . MaybeT
yieldM $ CorrectionTableCsv
<$> preview (_1 . resultCourseTerm . _TermId)
<*> preview (_1 . resultCourseSchool . _SchoolId)
<*> preview (_1 . resultCourseShorthand)
<*> preview (_1 . resultSheet . _entityVal . _sheetName)
<*> preview (_1 . resultCryptoID . re (_CI . _PathPiece))
<*> guardNonAnonymous (preview $ _1 . resultLastEdit)
<*> guardNonAnonymous (previews _2 (toListOf $ folded . resultUserUser . _userSurname . re _Just))
<*> guardNonAnonymous (previews _2 (toListOf $ folded . resultUserUser . _userFirstName . re _Just))
<*> guardNonAnonymous (previews _2 (toListOf $ folded . resultUserUser . _userDisplayName . re _Just))
<*> guardNonAnonymous (previews _2 (toListOf $ folded . resultUserUser . _userMatrikelnummer))
<*> guardNonAnonymous (previews _2 (toListOf $ folded . resultUserUser . _userEmail . re _Just))
<*> guardNonAnonymous (previews _2 (toListOf $ folded . pre resultUserPseudonym))
<*> guardNonAnonymous (previews _2 (toListOf $ folded . pre resultUserSubmissionGroup))
<*> guardNonAnonymous (previews _2 (toListOf $ folded . pre resultUserAuthorshipStatementState))
<*> preview (_1 . resultSubmission . _entityVal . _submissionRatingAssigned . _Just)
<*> preview (_1 . resultCorrector . _entityVal . _userDisplayName)
<*> preview (_1 . resultCorrector . _entityVal . _userEmail)
<*> preview (_1 . resultSubmission . _entityVal . to submissionRatingDone)
<*> preview (_1 . resultSubmission . _entityVal . _submissionRatingTime . _Just)
<*> preview (_1 . resultSubmission . _entityVal . _submissionRatingPoints . _Just)
<*> preview (_1 . resultSubmission . _entityVal . _submissionRatingComment . _Just)
, dbtCsvName = cTableCsvName, dbtCsvSheetName = cTableCsvSheetName
, dbtCsvHeader = \_ -> return $ correctionTableCsvHeader cTableShowCorrector cTableCsvQualification
, dbtCsvExampleData = Nothing
}
dbtCsvDecode = Nothing dbtCsvDecode = Nothing
dbtExtraReps = [] dbtExtraReps = []
in dbTable psValidator DBTable{..} in dbTable psValidator DBTable{..}
@ -524,16 +717,16 @@ data ActionCorrectionsData = CorrDownloadData SubmissionDownloadAnonymous Submis
| CorrAutoSetCorrectorData SheetId | CorrAutoSetCorrectorData SheetId
| CorrDeleteData | CorrDeleteData
correctionsR :: CorrectionTableWhere -> _ -> _ -> _ -> Map ActionCorrections (AForm (HandlerFor UniWorX) ActionCorrectionsData) -> Handler TypedContent correctionsR :: CorrectionTableWhere -> _ -> _ -> Maybe CorrectionTableCsvSettings -> _ -> Map ActionCorrections (AForm (HandlerFor UniWorX) ActionCorrectionsData) -> Handler TypedContent
correctionsR whereClause displayColumns dbtFilterUI psValidator actions = do correctionsR whereClause displayColumns dbtFilterUI csvSettings psValidator actions = do
(table, statistics) <- correctionsR' whereClause displayColumns dbtFilterUI psValidator actions (table, statistics) <- correctionsR' whereClause displayColumns dbtFilterUI csvSettings psValidator actions
fmap toTypedContent . defaultLayout $ do fmap toTypedContent . defaultLayout $ do
setTitleI MsgCourseCorrectionsTitle setTitleI MsgCourseCorrectionsTitle
$(widgetFile "corrections") $(widgetFile "corrections")
correctionsR' :: CorrectionTableWhere -> _ -> _ -> _ -> Map ActionCorrections (AForm (HandlerFor UniWorX) ActionCorrectionsData) -> Handler (Widget, SheetTypeSummary SqlBackendKey) correctionsR' :: CorrectionTableWhere -> _ -> _ -> Maybe CorrectionTableCsvSettings -> _ -> Map ActionCorrections (AForm (HandlerFor UniWorX) ActionCorrectionsData) -> Handler (Widget, SheetTypeSummary SqlBackendKey)
correctionsR' whereClause displayColumns dbtFilterUI psValidator actions = do correctionsR' whereClause displayColumns dbtFilterUI csvSettings psValidator actions = do
currentRoute <- fromMaybe (error "correctionsR called from 404-handler") <$> getCurrentRoute -- This should never be called from a 404 handler currentRoute <- fromMaybe (error "correctionsR called from 404-handler") <$> getCurrentRoute -- This should never be called from a 404 handler
postDeleteR $ \drRecords -> (submissionDeleteRoute drRecords) postDeleteR $ \drRecords -> (submissionDeleteRoute drRecords)
@ -542,7 +735,7 @@ correctionsR' whereClause displayColumns dbtFilterUI psValidator actions = do
} }
((actionRes', statistics), table) <- runDB $ ((actionRes', statistics), table) <- runDB $
makeCorrectionsTable whereClause displayColumns dbtFilterUI psValidator DBParamsForm makeCorrectionsTable whereClause displayColumns dbtFilterUI csvSettings psValidator DBParamsForm
{ dbParamsFormMethod = POST { dbParamsFormMethod = POST
, dbParamsFormAction = Just $ SomeRoute currentRoute , dbParamsFormAction = Just $ SomeRoute currentRoute
, dbParamsFormAttrs = [] , dbParamsFormAttrs = []
@ -682,7 +875,12 @@ restrictAnonymous :: PSValidator m x -> PSValidator m x
restrictAnonymous = restrictFilter (\k _ -> k /= "user-matriclenumber") restrictAnonymous = restrictFilter (\k _ -> k /= "user-matriclenumber")
. restrictFilter (\k _ -> k /= "user-name-email") . restrictFilter (\k _ -> k /= "user-name-email")
. restrictFilter (\k _ -> k /= "submission-group") . restrictFilter (\k _ -> k /= "submission-group")
. restrictFilter (\k _ -> k /= "as-state")
. restrictSorting (\k _ -> k /= "submittors")
. restrictSorting (\k _ -> k /= "submittors-matriculation")
. restrictSorting (\k _ -> k /= "submittors-group")
. restrictSorting (\k _ -> k /= "last-edit") . restrictSorting (\k _ -> k /= "last-edit")
. restrictSorting (\k _ -> k /= "as-state")
restrictCorrector :: PSValidator m x -> PSValidator m x restrictCorrector :: PSValidator m x -> PSValidator m x
restrictCorrector = restrictFilter (\k _ -> k /= "corrector") restrictCorrector = restrictFilter (\k _ -> k /= "corrector")
@ -772,7 +970,14 @@ postCorrectionsR = do
& restrictAnonymous & restrictAnonymous
& defaultSorting [SortDescBy "ratingtime", SortAscBy "assignedtime" ] & defaultSorting [SortDescBy "ratingtime", SortAscBy "assignedtime" ]
& defaultFilter (singletonMap "israted" [toPathPiece False]) & defaultFilter (singletonMap "israted" [toPathPiece False])
correctionsR whereClause colonnade filterUI psValidator $ Map.fromList
csvSettings = Just CorrectionTableCsvSettings
{ cTableCsvQualification = CorrectionTableCsvQualifyCourse
, cTableCsvName = MsgCorrectionTableCsvNameCorrections
, cTableCsvSheetName = MsgCorrectionTableCsvSheetNameCorrections
, cTableShowCorrector = False
}
correctionsR whereClause colonnade filterUI csvSettings psValidator $ Map.fromList
[ downloadAction [ downloadAction
] ]
@ -817,7 +1022,13 @@ postCCorrectionsR tid ssh csh = do
, filterUISubmission , filterUISubmission
] ]
psValidator = def & defaultPagesize PagesizeAll -- Assisstant always want to see them all at once anyway psValidator = def & defaultPagesize PagesizeAll -- Assisstant always want to see them all at once anyway
correctionsR whereClause colonnade filterUI psValidator $ Map.fromList csvSettings = Just CorrectionTableCsvSettings
{ cTableCsvQualification = CorrectionTableCsvQualifySheet
, cTableCsvName = MsgCorrectionTableCsvNameCourseCorrections tid ssh csh
, cTableCsvSheetName = MsgCorrectionTableCsvSheetNameCourseCorrections tid ssh csh
, cTableShowCorrector = True
}
correctionsR whereClause colonnade filterUI csvSettings psValidator $ Map.fromList
[ downloadAction [ downloadAction
, assignAction (Left cid) , assignAction (Left cid)
, deleteAction , deleteAction
@ -858,7 +1069,13 @@ postSSubsR tid ssh csh shn = do
, filterUISubmission , filterUISubmission
] ]
psValidator = def & defaultPagesize PagesizeAll -- Assisstant always want to see them all at once anyway psValidator = def & defaultPagesize PagesizeAll -- Assisstant always want to see them all at once anyway
correctionsR whereClause colonnade filterUI psValidator $ Map.fromList csvSettings = Just CorrectionTableCsvSettings
{ cTableCsvQualification = CorrectionTableCsvNoQualification
, cTableCsvName = MsgCorrectionTableCsvNameSheetCorrections tid ssh csh shn
, cTableCsvSheetName = MsgCorrectionTableCsvSheetNameSheetCorrections tid ssh csh shn
, cTableShowCorrector = True
}
correctionsR whereClause colonnade filterUI csvSettings psValidator $ Map.fromList
[ downloadAction [ downloadAction
, assignAction (Right shid) , assignAction (Right shid)
, autoAssignAction shid , autoAssignAction shid

View File

@ -22,8 +22,6 @@ import Handler.Utils.StudyFeatures.Parse
import qualified Data.Csv as Csv import qualified Data.Csv as Csv
import qualified Data.ByteString as ByteString
import qualified Data.Set as Set import qualified Data.Set as Set
import Data.RFC5051 (compareUnicode) import Data.RFC5051 (compareUnicode)
@ -65,7 +63,7 @@ instance Csv.ToField UserTableStudyFeature where
[] $ ShortStudyFieldType userTableFieldType [] $ ShortStudyFieldType userTableFieldType
instance Csv.ToField UserTableStudyFeatures where instance Csv.ToField UserTableStudyFeatures where
toField = ByteString.intercalate "; " . map Csv.toField . view _UserTableStudyFeatures toField = Csv.toField . CsvSemicolonList . view _UserTableStudyFeatures
userTableStudyFeatureSort :: UserTableStudyFeature userTableStudyFeatureSort :: UserTableStudyFeature
-> UserTableStudyFeature -> UserTableStudyFeature

View File

@ -981,20 +981,6 @@ correctionInvisibleWidget tid ssh csh shn cID (Entity subId sub) = runMaybeT $ d
return $ notification NotificationBroad =<< messageIconWidget Warning IconInvisible $(widgetFile "submission-correction-invisible") return $ notification NotificationBroad =<< messageIconWidget Warning IconInvisible $(widgetFile "submission-correction-invisible")
data AuthorshipStatementSubmissionState
= ASMissing
| ASOldStatement
| ASExists
deriving (Eq, Read, Show, Enum, Bounded, Generic, Typeable)
deriving anyclass (Universe, Finite)
deriving stock instance Ord AuthorshipStatementSubmissionState -- ^ Larger roughly encodes better; summaries are taken with `max`
nullaryPathPiece ''AuthorshipStatementSubmissionState $ camelToPathPiece' 1
embedRenderMessage ''UniWorX ''AuthorshipStatementSubmissionState $ concat . ("SubmissionAuthorshipStatementState" :) . drop 1 . splitCamel
getUserAuthorshipStatement :: ( MonadResource m getUserAuthorshipStatement :: ( MonadResource m
, IsSqlBackend backend, SqlBackendCanRead backend , IsSqlBackend backend, SqlBackendCanRead backend
) )

View File

@ -133,6 +133,8 @@ instance ToJSON TermIdentifier where
instance FromJSON TermIdentifier where instance FromJSON TermIdentifier where
parseJSON = withText "Term" $ either (fail . Text.unpack) return . termFromText parseJSON = withText "Term" $ either (fail . Text.unpack) return . termFromText
pathPieceCsv ''TermIdentifier
{- Must be defined in a later module: {- Must be defined in a later module:
termField :: Field (HandlerT UniWorX IO) TermIdentifier termField :: Field (HandlerT UniWorX IO) TermIdentifier
termField = checkMMap (return . termFromText) termToText textField termField = checkMMap (return . termFromText) termToText textField

View File

@ -130,3 +130,26 @@ pseudonymWords = folding
pseudonymFragments :: Fold Text [PseudonymWord] pseudonymFragments :: Fold Text [PseudonymWord]
pseudonymFragments = folding pseudonymFragments = folding
$ mapM (toListOf pseudonymWords) . (\l -> guard (length l == 2) *> l) . filter (not . null) . Text.split (\(CI.mk -> c) -> not $ Set.member c pseudonymCharacters) $ mapM (toListOf pseudonymWords) . (\l -> guard (length l == 2) *> l) . filter (not . null) . Text.split (\(CI.mk -> c) -> not $ Set.member c pseudonymCharacters)
instance PathPiece Pseudonym where
toPathPiece = review _PseudonymText
fromPathPiece t
| Just p <- t ^? _PseudonymText = Just p
| Just n <- fromPathPiece t = Just $ Pseudonym n
| otherwise = Nothing
pathPieceCsv ''Pseudonym
data AuthorshipStatementSubmissionState
= ASMissing
| ASOldStatement
| ASExists
deriving (Eq, Read, Show, Enum, Bounded, Generic, Typeable)
deriving anyclass (Universe, Finite)
deriving stock instance Ord AuthorshipStatementSubmissionState -- ^ Larger roughly encodes better; summaries are taken with `max`
nullaryPathPiece ''AuthorshipStatementSubmissionState $ camelToPathPiece' 1
pathPieceCsv ''AuthorshipStatementSubmissionState

View File

@ -10,6 +10,7 @@ module Utils.Csv
, toCsvRendered , toCsvRendered
, toDefaultOrderedCsvRendered , toDefaultOrderedCsvRendered
, csvRenderedToXlsx, Xlsx, Xlsx.fromXlsx , csvRenderedToXlsx, Xlsx, Xlsx.fromXlsx
, CsvSemicolonList(..)
) where ) where
import ClassyPrelude hiding (lookup) import ClassyPrelude hiding (lookup)
@ -39,6 +40,19 @@ import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text import qualified Data.Text.Encoding as Text
import qualified Data.CaseInsensitive as CI import qualified Data.CaseInsensitive as CI
import qualified Data.Binary.Builder as Builder
import qualified Data.ByteString.Lazy as LBS
import qualified Data.Attoparsec.ByteString as Attoparsec
import qualified Data.Csv.Parser as Csv
import qualified Data.Csv.Builder as Csv
import qualified Data.Vector as Vector
import Data.Char (ord)
import Control.Monad.Fail
deriving instance Typeable CsvParseError deriving instance Typeable CsvParseError
instance Exception CsvParseError instance Exception CsvParseError
@ -114,3 +128,25 @@ csvRenderedToXlsx sheetName CsvRendered{..} = def & Xlsx.atSheet sheetName ?~ (d
addValues = flip foldMap (zip [2..] csvRenderedData) $ \(r, nr) -> flip foldMap (zip [1..] $ toList csvRenderedHeader) $ \(c, hBS) -> case HashMap.lookup hBS nr of addValues = flip foldMap (zip [2..] csvRenderedData) $ \(r, nr) -> flip foldMap (zip [1..] $ toList csvRenderedHeader) $ \(c, hBS) -> case HashMap.lookup hBS nr of
Nothing -> mempty Nothing -> mempty
Just vBS -> Endo $ Xlsx.cellValueAtRC (r, c) ?~ Xlsx.CellText (decodeUtf8 vBS) Just vBS -> Endo $ Xlsx.cellValueAtRC (r, c) ?~ Xlsx.CellText (decodeUtf8 vBS)
newtype CsvSemicolonList a = CsvSemicolonList { unCsvSemicolonList :: [a] }
deriving stock (Read, Show, Generic, Typeable)
deriving newtype (Eq, Ord)
instance ToField a => ToField (CsvSemicolonList a) where
toField (CsvSemicolonList xs) = dropEnd 2 . LBS.toStrict . Builder.toLazyByteString $ Csv.encodeRecordWith encOpts fs
where
fs = map toField xs
encOpts = defaultEncodeOptions
{ encDelimiter = fromIntegral $ ord ';'
, encQuoting = bool QuoteMinimal QuoteAll $ all null fs
, encUseCrLf = True
}
instance FromField a => FromField (CsvSemicolonList a) where
parseField f
| null f = pure $ CsvSemicolonList []
| otherwise = fmap CsvSemicolonList . mapM parseField . Vector.toList <=< either fail return $ Attoparsec.parseOnly (Csv.record sep) f
where
sep = fromIntegral $ ord ';'

View File

@ -0,0 +1,2 @@
$newline never
Tabellen von Übungsblattabgaben können nun als CSV exportiert werden

View File

@ -0,0 +1,2 @@
$newline never
Tables of exercise sheet submissions can now be exported as CSV

View File

@ -0,0 +1,10 @@
module Data.Scientific.InstancesSpec where
import TestImport
import Data.Scientific
spec :: Spec
spec = modifyMaxSuccess (* 10) $
lawsCheckHspec (Proxy @Scientific)
[ pathPieceLaws ]

38
test/Utils/CsvSpec.hs Normal file
View File

@ -0,0 +1,38 @@
module Utils.CsvSpec where
import TestImport
import Utils.Csv
import Data.Csv (toField, runParser, parseField)
import Data.Char (ord)
import qualified Data.ByteString as BS
deriving newtype instance Arbitrary a => Arbitrary (CsvSemicolonList a)
spec :: Spec
spec = modifyMaxSuccess (* 10) . parallel $ do
lawsCheckHspec (Proxy @(CsvSemicolonList ByteString))
[ csvFieldLaws ]
describe "CsvSemicolonList" $ do
let
test :: [ByteString] -> ByteString -> Expectation
test (CsvSemicolonList -> x) t = do
toField x `shouldBe` t
runParser (parseField t) `shouldBe` Right x
it "is transparent" . property $ \(bs :: ByteString)
-> let expectTransparent = BS.all (`notElem` [34, 10, 13, fromIntegral $ ord ';']) bs
&& not (BS.null bs)
in expectTransparent ==> test [bs] bs
it "behaves as expected on some examples" $ do
test ["foo"] "foo"
test ["foo", "bar"] "foo;bar"
test [] ""
test [""] "\"\""
test ["", ""] "\"\";\"\""
test ["foo", ""] "foo;"
test ["", "foo"] ";foo"
test ["", "", "foo", ""] ";;foo;"