From df66c9b58dd1ea6119d428470d2d089edd70e2d0 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Thu, 10 Oct 2019 10:34:16 +0200 Subject: [PATCH 01/65] feat(correction-interface): wire up ECorrectR --- messages/uniworx/de.msg | 1 + routes | 1 + src/Foundation.hs | 23 +++++++++++++++++++++ src/Handler/Exam.hs | 1 + src/Handler/Exam/Correct.hs | 40 +++++++++++++++++++++++++++++++++++++ src/Handler/Exam/Form.hs | 2 +- 6 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/Handler/Exam/Correct.hs diff --git a/messages/uniworx/de.msg b/messages/uniworx/de.msg index 514420c38..3802a2b29 100644 --- a/messages/uniworx/de.msg +++ b/messages/uniworx/de.msg @@ -1428,6 +1428,7 @@ ExamFormCorrection: Korrektur ExamFormParts: Teile ExamCorrectors: Korrektoren +ExamCorrectorsTip: Hier eingetragene Korrektoren können zwischen Beginn der Prüfung und "Bewertung abgeschlossen ab" Ergebnisse für alle Teilprüfungen und alle Teilnehmer im System hinterlegen. ExamCorrectorAlreadyAdded: Ein Korrektor mit dieser E-Mail ist bereits für diese Prüfung eingetragen ExamParts: Teilprüfungen/Aufgaben diff --git a/routes b/routes index 00a90b6e8..1a23f7cf3 100644 --- a/routes +++ b/routes @@ -175,6 +175,7 @@ /users/invite EInviteR GET POST /register ERegisterR POST !timeANDcourse-registeredAND¬exam-registered !timeANDexam-registeredAND¬exam-result /grades EGradesR GET POST !exam-office + /correct ECorrectR GET POST !exam-correctorANDtime /apps CApplicationsR GET POST !/apps/files CAppsFilesR GET /apps/#CryptoFileNameCourseApplication CourseApplicationR: diff --git a/src/Foundation.hs b/src/Foundation.hs index 3cf3ae881..38daf44af 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -776,6 +776,18 @@ tagAccessPredicate AuthCorrector = APDB $ \mAuthId route _ -> exceptT return ret _ -> do guardMExceptT (not $ Map.null resMap) (unauthorizedI MsgUnauthorizedCorrectorAny) return Authorized +tagAccessPredicate AuthExamCorrector = APDB $ \mAuthId route _ -> case route of + CExamR tid ssh csh examn _ -> exceptT return return $ do + authId <- maybeExceptT AuthenticationRequired $ return mAuthId + isCorrector <- $cachedHereBinary (mAuthId, tid, ssh, csh, examn) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` examCorrector) -> do + E.on $ examCorrector E.^. ExamCorrectorExam E.==. exam E.^. ExamId + E.&&. examCorrector E.^. ExamCorrectorUser E.==. E.val authId + E.on $ exam E.^. ExamCourse E.==. course E.^. CourseId + E.where_ $ course E.^. CourseTerm E.==. E.val tid + E.&&. course E.^. CourseSchool E.==. E.val ssh + E.&&. course E.^. CourseShorthand E.==. E.val csh + E.&&. exam E.^. ExamName E.==. E.val examn + r -> $unsupportedAuthPredicate AuthExamCorrector r tagAccessPredicate AuthTutor = APDB $ \mAuthId route _ -> exceptT return return $ do authId <- maybeExceptT AuthenticationRequired $ return mAuthId resList <- $cachedHereBinary authId . lift . E.select . E.from $ \(course `E.InnerJoin` tutorial `E.InnerJoin` tutor) -> do @@ -834,6 +846,8 @@ tagAccessPredicate AuthTime = APDB $ \mAuthId route _ -> case route of && NTop (Just cTime) <= NTop examRegisterTo | otherwise -> guard $ visible && NTop (Just cTime) <= NTop examDeregisterUntil + ECorrectR -> guard $ NTop (Just cTime) >= NTop examStart + && NTop (Just cTime) <= NTop examFinished _ -> return () return Authorized @@ -1946,6 +1960,7 @@ instance YesodBreadcrumbs UniWorX where breadcrumb (CExamR tid ssh csh examn EUsersR) = return ("Teilnehmer", Just $ CExamR tid ssh csh examn EShowR) breadcrumb (CExamR tid ssh csh examn EAddUserR) = return ("Prüfungsteilnehmer hinzufügen", Just $ CExamR tid ssh csh examn EUsersR) breadcrumb (CExamR tid ssh csh examn EGradesR) = return ("Prüfungsleistungen", Just $ CExamR tid ssh csh examn EShowR) + breadcrumb (CExamR tid ssh csh examn ECorrectR) = return ("Korrekturen eintragen", Just $ CExamR tid ssh csh examn EShowR) breadcrumb (CTutorialR tid ssh csh tutn TUsersR) = return (original tutn, Just $ CourseR tid ssh csh CTutorialListR) breadcrumb (CTutorialR tid ssh csh tutn TEditR) = return ("Bearbeiten", Just $ CTutorialR tid ssh csh tutn TUsersR) @@ -2793,6 +2808,14 @@ pageActions (CExamR tid ssh csh examn EShowR) = , menuItemModal = False , menuItemAccessCallback' = return True } + , MenuItem + { menuItemType = PageActionPrime + , menuItemLabel = MsgMenuExamCorrect + , menuItemIcon = Nothing + , menuItemRoute = SomeRoute $ CExamR tid ssh csh examn ECorrectR + , menuItemModal = False + , menuItemAccessCallback' = return True + } ] pageActions (CExamR tid ssh csh examn EUsersR) = [ MenuItem diff --git a/src/Handler/Exam.hs b/src/Handler/Exam.hs index 6580c90f4..c48854461 100644 --- a/src/Handler/Exam.hs +++ b/src/Handler/Exam.hs @@ -11,3 +11,4 @@ import Handler.Exam.Edit as Handler.Exam import Handler.Exam.Show as Handler.Exam import Handler.Exam.Users as Handler.Exam import Handler.Exam.AddUser as Handler.Exam +import Handler.Exam.Correct as Handler.Exam diff --git a/src/Handler/Exam/Correct.hs b/src/Handler/Exam/Correct.hs new file mode 100644 index 000000000..e9a7bd692 --- /dev/null +++ b/src/Handler/Exam/Correct.hs @@ -0,0 +1,40 @@ +module Handler.Exam.Correct + ( getECorrectR, postECorrectR + ) where + +import Import + +data CorrectInterfaceResponse + = CorrectInterfaceSuccess + { ciUserIdent :: CryptoUUIDUser + , ciUserDisplayName :: Text + , ciUserMatrikelnummer :: Maybe Text + , ciResults :: Map ExamPartNumber (Maybe Points) + } + | CorrectInterfaceFailure + { ciIcon :: Icon + , ciMessage :: Text + } +deriveJSON defaultOptions + { constructorTagModifier = camelToPathPiece' 2 + , fieldLabelModifier = camelToPathPiece' 1 + , sumEncoding = TaggedObject "status" "result" + } ''CorrectorInterfaceResponse +-- E.g.: { "status": "success", "user-ident": "Max Musterstudent", "user-matrikelnummer": "123", "results": { "1a": null, "2a": 7, "2c3.5": 12 } } + +data CorrectInterfaceRequest + = CorrectInterfaceRequest + { cirName :: Text + , cirResults :: Map ExamPartNumber (Maybe Points) + } +deriveJSON defaultOptions + { fieldLabelModifier = camelToPathPiece' 1 + } ''CorrectorInterfaceRequest +-- E.g.: { "name": "max", "results": { "1a": null, "2c3.5": 9001.2 }} + + +getECorrectR :: TermId -> SchoolId -> CourseShorthand -> ExamName -> Handler Html +getECorrectR = error "ECorrectR not implemented" + +postECorrectR :: TermId -> SchoolId -> CourseShorthand -> ExamName -> Handler Value +postECorrectR = error "ECorrectR not implemented" -- use returnJson & requireCheckJsonBody diff --git a/src/Handler/Exam/Form.hs b/src/Handler/Exam/Form.hs index 1604a4207..24c8000a2 100644 --- a/src/Handler/Exam/Form.hs +++ b/src/Handler/Exam/Form.hs @@ -147,7 +147,7 @@ examCorrectorsForm mPrev = wFormToAForm $ do miLayout' :: MassInputLayout ListLength (Either UserEmail UserId) () miLayout' lLength _ cellWdgts delButtons addWdgts = $(widgetFile "widgets/massinput/examCorrectors/layout") - fmap Set.fromList <$> massInputAccumW miAdd' miCell' miButtonAction' miLayout' ("correctors" :: Text) (fslI MsgExamCorrectors & setTooltip MsgMassInputTip) False (Set.toList <$> mPrev) + fmap Set.fromList <$> massInputAccumW miAdd' miCell' miButtonAction' miLayout' ("correctors" :: Text) (fslI MsgExamCorrectors & setTooltip (UniWorXMessages [MsgExamCorrectorsTip, MsgMassInputTip]) False (Set.toList <$> mPrev) examOccurrenceForm :: Maybe (Set ExamOccurrenceForm) -> AForm Handler (Set ExamOccurrenceForm) examOccurrenceForm prev = wFormToAForm $ do From cb7c9ac6dad5e248e9cf0748a871947369cd39af Mon Sep 17 00:00:00 2001 From: Sarah Vaupel Date: Wed, 8 Jan 2020 16:38:16 +0100 Subject: [PATCH 02/65] feat(exam-correct): add basic interface stub --- messages/uniworx/de-de-formal.msg | 10 +++++++++ messages/uniworx/en-eu.msg | 10 +++++++++ src/Handler/Exam/Correct.hs | 28 ++++++++++++++++++++++++- src/Model/Types/Exam.hs | 2 ++ src/Utils/Tooltip.hs | 6 ++++++ templates/widgets/exam-correct.hamlet | 30 +++++++++++++++++++++++++++ templates/widgets/text-tooltip.hamlet | 6 ++++++ 7 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 src/Utils/Tooltip.hs create mode 100644 templates/widgets/exam-correct.hamlet create mode 100644 templates/widgets/text-tooltip.hamlet diff --git a/messages/uniworx/de-de-formal.msg b/messages/uniworx/de-de-formal.msg index a82909d18..8b963d3f1 100644 --- a/messages/uniworx/de-de-formal.msg +++ b/messages/uniworx/de-de-formal.msg @@ -1359,6 +1359,16 @@ ExamRegistrationInvitationDeclined examn@ExamName: Sie haben die Einladung, Teil ExamRegistrationInviteHeading examn@ExamName: Einladung zum Teilnehmer für #{examn} ExamRegistrationInviteExplanation: Sie wurden eingeladen, Prüfungsteilnehmer zu sein. +ExamCorrectHeading examname@Text: Prüfungsergebnisse für #{examname} eintragen + +ExamCorrectHeadParticipant: Teilnehmer +ExamCorrectHeadParticipantTooltip: Geben Sie hier einen beliebigen eindeutigen Identifikator des Teilnehmers an. Definitiv eindeutig ist die Matrikelnummer des Teilnehmers, aber auch der Name oder ein Teil der Matrikelnummer können unter Umständen (je nach Liste aller Prüfungsteilnehmer) bereits eindeutig sein. +ExamCorrectHeadPart exampartnum@ExamPartNumber: #{exampartnum} +ExamCorrectHeadPartName exampartname@ExamPartName: #{exampartname} +ExamCorrectHeadStatus: Status + +ExamCorrectButtonSend: Senden + SubmissionUserInvitationAccepted shn@SheetName: Sie wurden als Mitabgebende(r) für eine Abgabe zu #{shn} eingetragen SubmissionUserInvitationDeclined shn@SheetName: Sie haben die Einladung, Mitabgebende(r) für #{shn} zu werden, abgelehnt SubmissionUserInviteHeading shn@SheetName: Einladung zu einer Abgabe für #{shn} diff --git a/messages/uniworx/en-eu.msg b/messages/uniworx/en-eu.msg index 6dc28491a..b6bf6aa6d 100644 --- a/messages/uniworx/en-eu.msg +++ b/messages/uniworx/en-eu.msg @@ -1357,6 +1357,16 @@ ExamRegistrationInvitationDeclined examn: You have declined the invitation to pa ExamRegistrationInviteHeading examn: Invitation to participate in #{examn} ExamRegistrationInviteExplanation: You were invited to register for an exam. +ExamCorrectHeading examname: Submit corrections for #{examname} + +ExamCorrectHeadParticipant: Participant +ExamCorrectHeadParticipantTooltip: Enter any string that uniquely identifies the participant. Their matriculation number is definitely unique, but also their name or a part of their matriculation number may already be unique for this participant (depending on the list of all participants). +ExamCorrectHeadPart exampartnum: #{exampartnum} +ExamCorrectHeadPartName exampartname: #{exampartname} +ExamCorrectHeadStatus: Status + +ExamCorrectButtonSend: Submit + SubmissionUserInvitationAccepted shn: You now participate in a submission for #{shn} SubmissionUserInvitationDeclined shn: You have declined the invitation to participate in a submission for #{shn} SubmissionUserInviteHeading shn: Invitation to participate in a submission for #{shn} diff --git a/src/Handler/Exam/Correct.hs b/src/Handler/Exam/Correct.hs index 39d433d23..0c4aa044c 100644 --- a/src/Handler/Exam/Correct.hs +++ b/src/Handler/Exam/Correct.hs @@ -4,6 +4,12 @@ module Handler.Exam.Correct import Import +import qualified Data.CaseInsensitive as CI (original) + +import Handler.Utils +import Handler.Utils.Exam (fetchExam) + + data CorrectInterfaceResponse = CorrectInterfaceSuccess CryptoUUIDUser Text (Maybe Text) (Map ExamPartNumber (Maybe Points)) -- { ciUserIdent :: CryptoUUIDUser @@ -34,7 +40,27 @@ deriveJSON defaultOptions getECorrectR :: TermId -> SchoolId -> CourseShorthand -> ExamName -> Handler Html -getECorrectR = error "ECorrectR not implemented" +getECorrectR tid ssh csh examn = do + MsgRenderer mr <- getMsgRenderer + + (Entity _ Exam{..}, examParts) <- runDB $ do + exam@(Entity eId Exam{..}) <- fetchExam tid ssh csh examn + examParts <- sortOn (view $ _entityVal . _examPartNumber) <$> selectList [ ExamPartExam ==. eId ] [ Asc ExamPartName ] + return (exam, entityVal <$> examParts) + + let + heading = prependCourseTitle tid ssh csh $ (mr . MsgExamCorrectHeading . CI.original) examName + + ptsInput :: ExamPartNumber -> Widget + ptsInput n = do + name <- newIdent + fieldView (pointsField :: Field Handler Points) ("exam-correct__"<>(toPathPiece n)) name [("class","exam-correct__pts-input")] (Left "") False + + participantHeadTooltip = [whamlet| _{MsgExamCorrectHeadParticipantTooltip} |] + + siteLayoutMsg heading $ do + setTitleI heading + $(widgetFile "widgets/exam-correct") postECorrectR :: TermId -> SchoolId -> CourseShorthand -> ExamName -> Handler Value postECorrectR = error "ECorrectR not implemented" -- use returnJson & requireCheckJsonBody diff --git a/src/Model/Types/Exam.hs b/src/Model/Types/Exam.hs index afd09396e..282b13931 100644 --- a/src/Model/Types/Exam.hs +++ b/src/Model/Types/Exam.hs @@ -317,6 +317,8 @@ instance PathPiece ExamPartNumber where instance ToMarkup ExamPartNumber where toMarkup = toMarkup . view _ExamPartNumber +instance ToMessage ExamPartNumber where + toMessage = toMessage . view _ExamPartNumber pathPieceCsv ''ExamPartNumber pathPieceJSON ''ExamPartNumber diff --git a/src/Utils/Tooltip.hs b/src/Utils/Tooltip.hs new file mode 100644 index 000000000..5332868b6 --- /dev/null +++ b/src/Utils/Tooltip.hs @@ -0,0 +1,6 @@ +module Utils.Tooltip where + +import ClassyPrelude.Yesod hiding (Proxy) + +textTooltip :: forall site. WidgetFor site () -> WidgetFor site () -> WidgetFor site () +textTooltip ttHandle ttContent = $(whamletFile "templates/widgets/text-tooltip.hamlet") diff --git a/templates/widgets/exam-correct.hamlet b/templates/widgets/exam-correct.hamlet new file mode 100644 index 000000000..2e177884d --- /dev/null +++ b/templates/widgets/exam-correct.hamlet @@ -0,0 +1,30 @@ +$newline never + +
+ + + + + +
+ _{MsgExamCorrectHeadParticipant} + ^{iconTooltip participantHeadTooltip Nothing True} + $forall ExamPart{examPartNumber,examPartName} <- examParts + + $maybe name <- examPartName + + + _{MsgExamCorrectHeadPart examPartNumber} + + _{MsgExamCorrectHeadPartName name} + $nothing + _{MsgExamCorrectHeadPart examPartNumber} + _{MsgExamCorrectHeadStatus} +
+ + $forall ExamPart{examPartNumber} <- examParts + + ^{ptsInput examPartNumber} + +