From bebb833fa34621e2a0a3d5aff424a45b0eb93abb Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Mon, 18 Sep 2023 08:35:23 -0500 Subject: [PATCH] Remove the attempts table. It is now provided by PG. PG also generates the results summary so the AttemptsTable module is not needed at all. Without the attempts table the problem page top matter looked too spaced out, so the styles were tweaked to improve spacing. --- htdocs/js/RenderProblem/renderproblem.js | 2 +- htdocs/themes/math4/math4.scss | 34 +- lib/FormatRenderedProblem.pm | 63 ++- lib/WeBWorK/ContentGenerator/GatewayQuiz.pm | 86 +--- lib/WeBWorK/ContentGenerator/Problem.pm | 117 ++--- lib/WeBWorK/HTML/AttemptsTable.pm | 466 ------------------ lib/WeBWorK/Utils/Rendering.pm | 11 +- lib/WebworkWebservice/RenderProblem.pm | 10 +- .../ContentGenerator/GatewayQuiz.html.ep | 18 +- templates/RPCRenderFormats/default.html.ep | 4 +- templates/RPCRenderFormats/default.json.ep | 4 +- 11 files changed, 134 insertions(+), 681 deletions(-) delete mode 100644 lib/WeBWorK/HTML/AttemptsTable.pm diff --git a/htdocs/js/RenderProblem/renderproblem.js b/htdocs/js/RenderProblem/renderproblem.js index 726ff789a7..3041912cf1 100644 --- a/htdocs/js/RenderProblem/renderproblem.js +++ b/htdocs/js/RenderProblem/renderproblem.js @@ -28,7 +28,7 @@ send_pg_flags: 1, extra_header_text: '', ...renderOptions }; diff --git a/htdocs/themes/math4/math4.scss b/htdocs/themes/math4/math4.scss index 87494e47b8..07844b815b 100644 --- a/htdocs/themes/math4/math4.scss +++ b/htdocs/themes/math4/math4.scss @@ -687,14 +687,6 @@ ul.courses-list { } } -div.AuthorComment { - background-color: #00e0e0; - color: black; - padding: 0.25rem; - border: 1px solid transparent; - border-radius: 0.25rem; -} - /* Footer */ #footer { font-size: 0.8em; @@ -901,22 +893,22 @@ input.changed[type=text] { /* orange */ min-width: 2.5em; text-align: center; } -} -span { - &.correct { - color: inherit; /* green */ - background-color: #8f8; - } + span { + &.correct { + color: inherit; + background-color: #8f8; + } - &.incorrect { - color: #bf5454; /* red */ - background-color: inherit; - } + &.incorrect { + color: #bf5454; /* red */ + background-color: inherit; + } - &.unattempted { - color: inherit; - background-color: #88ecff; + &.unattempted { + color: inherit; + background-color: #88ecff; + } } } diff --git a/lib/FormatRenderedProblem.pm b/lib/FormatRenderedProblem.pm index 005b9c2c37..34ff77b035 100644 --- a/lib/FormatRenderedProblem.pm +++ b/lib/FormatRenderedProblem.pm @@ -29,7 +29,6 @@ use Digest::SHA qw(sha1_base64); use Mojo::Util qw(xml_escape); use Mojo::DOM; -use WeBWorK::HTML::AttemptsTable; use WeBWorK::Utils qw(getAssetURL); use WeBWorK::Utils::LanguageAndDirection; @@ -145,33 +144,30 @@ sub formatRenderedProblem { my $showCorrectMode = defined($ws->{inputs_ref}{WWcorrectAns}) || 0; # A problemUUID should be added to the request as a parameter. It is used by PG to create a proper UUID for use in # aliases for resources. It should be unique for a course, user, set, problem, and version. - my $problemUUID = $ws->{inputs_ref}{problemUUID} // ''; - my $problemResult = $rh_result->{problem_result} // {}; - my $showSummary = $ws->{inputs_ref}{showSummary} // 1; - my $showAnswerNumbers = $ws->{inputs_ref}{showAnswerNumbers} // 1; - - # Attempts table - my $answerTemplate = ''; - - # Do not produce an AttemptsTable when we had a rendering error. - if (!$renderErrorOccurred) { - my $tbl = WeBWorK::HTML::AttemptsTable->new( - $rh_result->{answers} // {}, $ws->c, - answersSubmitted => $ws->{inputs_ref}{answersSubmitted} // 0, - answerOrder => $rh_result->{flags}{ANSWER_ENTRY_ORDER} // [], - displayMode => $displayMode, - showAnswerNumbers => $showAnswerNumbers, - ce => $ce, - showAttemptPreviews => $previewMode || $submitMode || $showCorrectMode, - showAttemptResults => $submitMode || $showCorrectMode, - showCorrectAnswers => $showCorrectMode, - showMessages => $previewMode || $submitMode || $showCorrectMode, - showSummary => (($showSummary && ($submitMode || $showCorrectMode)) // 0) ? 1 : 0, - maketext => WeBWorK::Localize::getLoc($formLanguage), - summary => $problemResult->{summary} // '', # can be set by problem grader - ); - $answerTemplate = $tbl->answerTemplate; - $tbl->imgGen->render(refresh => 1) if $tbl->displayMode eq 'images'; + my $problemUUID = $ws->{inputs_ref}{problemUUID} // ''; + my $problemResult = $rh_result->{problem_result} // {}; + my $showSummary = $ws->{inputs_ref}{showSummary} // 1; + + # Result summary + my $resultSummary = ''; + + my $lh = WeBWorK::Localize::getLangHandle($formLanguage); + + # Do not produce a result summary when we had a rendering error. + if (!$renderErrorOccurred + && $showSummary + && !$previewMode + && ($submitMode || $showCorrectMode) + && $problemResult->{summary}) + { + $resultSummary = $ws->c->c( + $ws->c->tag( + 'h2', + class => 'fs-3 mb-2', + $ws->c->maketext('Results for this submission') + ) + . $ws->c->tag('div', role => 'alert', $ws->c->b($problemResult->{summary})) + )->join(''); } # Answer hash in XML format used by the PTX format. @@ -214,7 +210,7 @@ sub formatRenderedProblem { $output->{input} = $ws->{input}; # The following could be constructed from the above, but this is a convenience - $output->{answerTemplate} = $answerTemplate->to_string if $answerTemplate; + $output->{resultSummary} = $resultSummary->to_string if $resultSummary; $output->{lang} = $PROBLEM_LANG_AND_DIR{lang}; $output->{dir} = $PROBLEM_LANG_AND_DIR{dir}; $output->{extra_css_files} = \@extra_css_files; @@ -242,7 +238,7 @@ sub formatRenderedProblem { formatName => $formatName, ws => $ws, ce => $ce, - lh => WeBWorK::Localize::getLangHandle($ws->{inputs_ref}{language} // 'en'), + lh => $lh, rh_result => $rh_result, SITE_URL => $SITE_URL, FORM_ACTION_URL => $SITE_URL . $ws->c->webwork_url . '/render_rpc', @@ -263,7 +259,7 @@ sub formatRenderedProblem { extra_js_files => \@extra_js_files, problemText => $problemText, extra_header_text => $ws->{inputs_ref}{extra_header_text} // '', - answerTemplate => $answerTemplate, + resultSummary => $resultSummary, showScoreSummary => $submitMode && !$renderErrorOccurred && $problemResult, answerhashXML => $answerhashXML, LTIGradeMessage => $LTIGradeMessage, @@ -275,9 +271,8 @@ sub formatRenderedProblem { isInstructor => $ws->{inputs_ref}{isInstructor} // '', forceScaffoldsOpen => $ws->{inputs_ref}{forceScaffoldsOpen} // '', showSummary => $showSummary, - showHints => $ws->{inputs_ref}{showHints} // '', - showSolutions => $ws->{inputs_ref}{showSolutions} // '', - showAnswerNumbers => $showAnswerNumbers, + showHints => $ws->{inputs_ref}{showHints} // '', + showSolutions => $ws->{inputs_ref}{showSolutions} // '', showPreviewButton => $ws->{inputs_ref}{showPreviewButton} // '', showCheckAnswersButton => $ws->{inputs_ref}{showCheckAnswersButton} // '', showCorrectAnswersButton => $ws->{inputs_ref}{showCorrectAnswersButton} // '', diff --git a/lib/WeBWorK/ContentGenerator/GatewayQuiz.pm b/lib/WeBWorK/ContentGenerator/GatewayQuiz.pm index bf49e59896..f30e552205 100644 --- a/lib/WeBWorK/ContentGenerator/GatewayQuiz.pm +++ b/lib/WeBWorK/ContentGenerator/GatewayQuiz.pm @@ -38,7 +38,6 @@ use WeBWorK::Utils::Tasks qw(fake_set fake_set_version fake_problem); use WeBWorK::Debug; use WeBWorK::Authen::LTIAdvanced::SubmitGrade; use WeBWorK::Authen::LTIAdvantage::SubmitGrade; -use WeBWorK::HTML::AttemptsTable; use PGrandom; use Caliper::Sensor; use Caliper::Entity; @@ -238,42 +237,10 @@ sub can_useMathQuill ($c) { } # Output utility -sub attemptResults ($c, $pg, $showCorrectAnswers, $showAttemptResults, $showSummary) { - my $ce = $c->ce; - - # Create AttemptsTable object - my $tbl = WeBWorK::HTML::AttemptsTable->new( - $pg->{answers}, - $c, - answersSubmitted => 1, - answerOrder => $pg->{flags}{ANSWER_ENTRY_ORDER}, - displayMode => $c->{displayMode}, - showHeadline => 0, - showAnswerNumbers => 0, - showAttemptAnswers => $ce->{pg}{options}{showEvaluatedAnswers}, - showAttemptPreviews => 1, - showAttemptResults => $showAttemptResults, - showCorrectAnswers => $showCorrectAnswers, - showMessages => 1, - showSummary => $showSummary, - imgGen => WeBWorK::PG::ImageGenerator->new( - tempDir => $ce->{webworkDirs}{tmp}, - latex => $ce->{externalPrograms}{latex}, - dvipng => $ce->{externalPrograms}{dvipng}, - useCache => 1, - cacheDir => $ce->{webworkDirs}{equationCache}, - cacheURL => $ce->{webworkURLs}{equationCache}, - cacheDB => $ce->{webworkFiles}{equationCacheDB}, - useMarkers => 1, - dvipng_align => $ce->{pg}{displayModeOptions}{images}{dvipng_align}, - dvipng_depth_db => $ce->{pg}{displayModeOptions}{images}{dvipng_depth_db}, - ), - ); - - my $answerTemplate = $tbl->answerTemplate; - $tbl->imgGen->render(body_text => $answerTemplate) if $tbl->displayMode eq 'images'; - - return $answerTemplate; +sub attemptResults ($c, $pg) { + return ($c->{can}{showProblemScores} && $pg->{result}{summary}) + ? $c->tag('div', role => 'alert', $c->b($pg->{result}{summary})) + : ''; } sub get_instructor_comment ($c, $problem) { @@ -1482,18 +1449,6 @@ sub warningMessage ($c) { # hash of parameters from the input form that need to be passed to the translator, and $mergedProblem # is what we'd expect. async sub getProblemHTML ($c, $effectiveUser, $set, $formFields, $mergedProblem) { - my $setID = $set->set_id; - my $setVersionNumber = $set->version_id; - - # Figure out solutions are allowed and call renderPG accordingly. - my $showCorrectAnswers = $c->{will}{showCorrectAnswers}; - my $showHints = $c->{will}{showHints}; - my $showSolutions = $c->{will}{showSolutions}; - my $processAnswers = $c->{will}{checkAnswers}; - - # FIXME: I'm not sure that problem_id is what we want here. - my $problemNumber = $mergedProblem->problem_id; - my $pg = await renderPG( $c, $effectiveUser, @@ -1502,17 +1457,26 @@ async sub getProblemHTML ($c, $effectiveUser, $set, $formFields, $mergedProblem) $set->psvn, $formFields, { - displayMode => $c->{displayMode}, - showHints => $showHints, - showSolutions => $showSolutions, - refreshMath2img => $showHints || $showSolutions, - processAnswers => 1, - QUIZ_PREFIX => 'Q' . sprintf('%04d', $problemNumber) . '_', - useMathQuill => $c->{will}{useMathQuill}, - useMathView => $c->{will}{useMathView}, - forceScaffoldsOpen => 1, - isInstructor => $c->authz->hasPermissions($c->{userID}, 'view_answers'), - debuggingOptions => getTranslatorDebuggingOptions($c->authz, $c->{userID}) + displayMode => $c->{displayMode}, + showHints => $c->{will}{showHints}, + showSolutions => $c->{will}{showSolutions}, + refreshMath2img => $c->{will}{showHints} || $c->{will}{showSolutions}, + processAnswers => 1, + QUIZ_PREFIX => 'Q' . sprintf('%04d', $mergedProblem->problem_id) . '_', + useMathQuill => $c->{will}{useMathQuill}, + useMathView => $c->{will}{useMathView}, + forceScaffoldsOpen => 1, + isInstructor => $c->authz->hasPermissions($c->{userID}, 'view_answers'), + showFeedback => $c->{submitAnswers} || $c->{previewAnswers} || $c->{will}{checkAnswers}, + showAttemptAnswers => $c->ce->{pg}{options}{showEvaluatedAnswers}, + showAttemptPreviews => 1, + showAttemptResults => !$c->{previewAnswers} && $c->{can}{showProblemScores}, + forceShowAttemptResults => $c->{will}{showProblemGrader}, + showMessages => 1, + showCorrectAnswers => ($c->{submitAnswers} || $c->{will}{checkAnswers} || $c->{will}{showProblemGrader}) + ? $c->{will}{showCorrectAnswers} + : 0, + debuggingOptions => getTranslatorDebuggingOptions($c->authz, $c->{userID}) }, ); @@ -1523,7 +1487,7 @@ async sub getProblemHTML ($c, $effectiveUser, $set, $formFields, $mergedProblem) if ($pg->{flags}{error_flag}) { push @{ $c->{errors} }, { - set => "$setID,v$setVersionNumber", + set => $set->set_id . ',v' . $set->version_id, problem => $mergedProblem->problem_id, message => $pg->{errors}, context => $pg->{body_text}, diff --git a/lib/WeBWorK/ContentGenerator/Problem.pm b/lib/WeBWorK/ContentGenerator/Problem.pm index 3cbf75816b..9fe1a03f02 100644 --- a/lib/WeBWorK/ContentGenerator/Problem.pm +++ b/lib/WeBWorK/ContentGenerator/Problem.pm @@ -36,7 +36,6 @@ use WeBWorK::Localize; use WeBWorK::Utils::Tasks qw(fake_set fake_problem); use WeBWorK::Utils::LanguageAndDirection qw(get_problem_lang_and_dir); use WeBWorK::AchievementEvaluator; -use WeBWorK::HTML::AttemptsTable; # GET/POST Parameters for this module # @@ -246,42 +245,11 @@ sub can_showMeAnother ($c, $user, $effectiveUser, $set, $problem, $submitAnswers return 0; } -sub attemptResults ($c, $pg, $showCorrectAnswers, $showAttemptResults, $showSummary) { - my $ce = $c->ce; - - # Create AttemptsTable object - my $tbl = WeBWorK::HTML::AttemptsTable->new( - $pg->{answers}, - $c, - answersSubmitted => 1, - answerOrder => $pg->{flags}{ANSWER_ENTRY_ORDER}, - displayMode => $c->{displayMode}, - showAnswerNumbers => 0, - showAttemptAnswers => $ce->{pg}{options}{showEvaluatedAnswers}, - showAttemptPreviews => 1, - showAttemptResults => $showAttemptResults, - showCorrectAnswers => $showCorrectAnswers, - showMessages => 1, - showSummary => $showSummary, - imgGen => WeBWorK::PG::ImageGenerator->new( - tempDir => $ce->{webworkDirs}{tmp}, - latex => $ce->{externalPrograms}{latex}, - dvipng => $ce->{externalPrograms}{dvipng}, - useCache => 1, - cacheDir => $ce->{webworkDirs}{equationCache}, - cacheURL => $ce->{webworkURLs}{equationCache}, - cacheDB => $ce->{webworkFiles}{equationCacheDB}, - useMarkers => 1, - dvipng_align => $ce->{pg}{displayModeOptions}{images}{dvipng_align}, - dvipng_depth_db => $ce->{pg}{displayModeOptions}{images}{dvipng_depth_db}, - ), - ); - - # Render equation images - my $answerTemplate = $tbl->answerTemplate; - $tbl->imgGen->render(body_text => \$answerTemplate) if $tbl->displayMode eq 'images'; - - return $answerTemplate; +sub attemptResults ($c, $pg) { + return $pg->{result}{summary} + ? $c->c($c->tag('h2', class => 'fs-3 mb-2', $c->maketext('Results for this submission')) + . $c->tag('div', role => 'alert', $c->b($pg->{result}{summary})))->join('') + : ''; } async sub pre_header_initialize ($c) { @@ -407,8 +375,8 @@ async sub pre_header_initialize ($c) { } $c->addmessage($c->{set}->visible - ? $c->tag('p', class => 'font-visible', $c->maketext('This set is visible to students.')) - : $c->tag('p', class => 'font-hidden', $c->maketext('This set is hidden from students.'))); + ? $c->tag('p', class => 'font-visible m-0', $c->maketext('This set is visible to students.')) + : $c->tag('p', class => 'font-hidden m-0', $c->maketext('This set is hidden from students.'))); } else { # Test for additional problem validity if it's not already invalid. @@ -451,6 +419,7 @@ async sub pre_header_initialize ($c) { { $c->{submitAnswers} = 0; $c->{resubmitDetected} = 1; + delete $formFields->{submitAnswers}; } $c->{displayMode} = $displayMode; @@ -581,6 +550,24 @@ async sub pre_header_initialize ($c) { # Final values for options my %will = map { $_ => $can{$_} && ($want{$_} || $must{$_}) } keys %must; + if ($prEnabled && $problem->{prCount} >= $rerandomizePeriod && !after($c->{set}->due_date, $c->submitTime)) { + $showMeAnother{active} = 0; + $must{requestNewSeed} = 1; + $can{requestNewSeed} = 1; + $want{requestNewSeed} = 1; + $will{requestNewSeed} = 1; + $c->{showCorrectOnRandomize} = $ce->{pg}{options}{showCorrectOnRandomize}; + # If this happens, it means that the page was refreshed. So prevent the answers from + # being recorded and the number of attempts from being increased. + if ($problem->{prCount} > $rerandomizePeriod) { + $c->{resubmitDetected} = 1; + $must{recordAnswers} = 0; + $can{recordAnswers} = 0; + $want{recordAnswers} = 0; + $will{recordAnswers} = 0; + } + } + # Sticky answers if (!($c->{submitAnswers} || $previewAnswers || $checkAnswers) && $will{showOldAnswers}) { my %oldAnswers = decodeAnswers($problem->last_answer); @@ -615,7 +602,16 @@ async sub pre_header_initialize ($c) { useMathView => $will{useMathView}, forceScaffoldsOpen => 0, isInstructor => $authz->hasPermissions($userID, 'view_answers'), - debuggingOptions => getTranslatorDebuggingOptions($authz, $userID) + showFeedback => $c->{submitAnswers} || $c->{previewAnswers}, + showAttemptAnswers => $ce->{pg}{options}{showEvaluatedAnswers}, + showAttemptPreviews => 1, + showAttemptResults => $c->{submitAnswers}, + forceShowAttemptResults => $will{checkAnswers} || $will{showProblemGrader}, + showMessages => 1, + showCorrectAnswers => $c->{submitAnswers} ? ($c->{showCorrectOnRandomize} // $will{showCorrectAnswers}) + : $will{checkAnswers} || $will{showProblemGrader} ? $will{showCorrectAnswers} + : 0, + debuggingOptions => getTranslatorDebuggingOptions($authz, $userID) } ); @@ -630,24 +626,6 @@ async sub pre_header_initialize ($c) { id => 'num_attempts' ); - if ($prEnabled && $problem->{prCount} >= $rerandomizePeriod && !after($c->{set}->due_date, $c->submitTime)) { - $showMeAnother{active} = 0; - $must{requestNewSeed} = 1; - $can{requestNewSeed} = 1; - $want{requestNewSeed} = 1; - $will{requestNewSeed} = 1; - $c->{showCorrectOnRandomize} = $ce->{pg}{options}{showCorrectOnRandomize}; - # If this happens, it means that the page was refreshed. So prevent the answers from - # being recorded and the number of attempts from being increased. - if ($problem->{prCount} > $rerandomizePeriod) { - $c->{resubmitDetected} = 1; - $must{recordAnswers} = 0; - $can{recordAnswers} = 0; - $want{recordAnswers} = 0; - $will{recordAnswers} = 0; - } - } - # Update and fix hint/solution options after PG processing $can{showHints} &&= $pg->{flags}{hintExists}; $can{showSolutions} &&= $pg->{flags}{solutionExists}; @@ -1484,37 +1462,26 @@ sub output_summary ($c) { my $output = $c->c; # Attempt summary - if (defined $pg->{flags}{showPartialCorrectAnswers} - && $pg->{flags}{showPartialCorrectAnswers} >= 0 - && $c->{submitAnswers}) - { - push( - @$output, - $c->attemptResults( - $pg, - $c->{showCorrectOnRandomize} // $will{showCorrectAnswers}, - $pg->{flags}{showPartialCorrectAnswers}, 1 - ) - ); + if ($c->{submitAnswers}) { + push(@$output, $c->attemptResults($pg)); } elsif ($will{checkAnswers} || $c->{will}{showProblemGrader}) { push( @$output, $c->tag( 'div', - class => 'ResultsWithError d-inline-block mb-3', + class => 'ResultsWithError d-inline-block mb-2', $c->maketext('ANSWERS ONLY CHECKED -- ANSWERS NOT RECORDED') ), - $c->attemptResults($pg, $will{showCorrectAnswers}, 1, 1) + $c->attemptResults($pg) ); } elsif ($c->{previewAnswers}) { push( @$output, $c->tag( 'div', - class => 'ResultsWithError d-inline-block mb-3', + class => 'ResultsWithError d-inline-block mb-2', $c->maketext('PREVIEW ONLY -- ANSWERS NOT RECORDED') ), - $c->attemptResults($pg, 0, 0, 0) ); } @@ -1522,7 +1489,7 @@ sub output_summary ($c) { @$output, $c->tag( 'div', - class => 'ResultsWithError d-inline-block mb-3', + class => 'ResultsWithError d-inline-block mb-2', $c->maketext( 'ATTEMPT NOT ACCEPTED -- Please submit answers again (or request new version if neccessary).') ) diff --git a/lib/WeBWorK/HTML/AttemptsTable.pm b/lib/WeBWorK/HTML/AttemptsTable.pm deleted file mode 100644 index 15103e0638..0000000000 --- a/lib/WeBWorK/HTML/AttemptsTable.pm +++ /dev/null @@ -1,466 +0,0 @@ -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the -# Artistic License for more details. -################################################################################ - -=head1 NAME - - AttemptsTable - -=head1 SYNPOSIS - - my $tbl = WeBWorK::HTML::AttemptsTable->new( - $answers, - answersSubmitted => 1, - answerOrder => $pg->{flags}{ANSWER_ENTRY_ORDER}, - displayMode => 'MathJax', - showAnswerNumbers => 0, - showAttemptAnswers => $showAttemptAnswers && $showEvaluatedAnswers, - showAttemptPreviews => $showAttemptPreview, - showAttemptResults => $showAttemptResults, - showCorrectAnswers => $showCorrectAnswers, - showMessages => $showAttemptAnswers, # internally checks for messages - showSummary => $showSummary, - imgGen => $imgGen, # not needed if ce is present , - ce => '', # not needed if $imgGen is present - maketext => WeBWorK::Localize::getLoc("en"), - ); - $tbl->{imgGen}->render(refresh => 1) if $tbl->displayMode eq 'images'; - my $answerTemplate = $tbl->answerTemplate; - - -=head1 DESCRIPTION - -This module handles the formatting of the table which presents the results of analyzing a student's -answer to a WeBWorK problem. It is used in Problem.pm, OpaqueServer.pm, standAlonePGproblemRender - -=head2 new - - my $tbl = WeBWorK::HTML::AttemptsTable->new( - $answers, - answersSubmitted => 1, - answerOrder => $pg->{flags}{ANSWER_ENTRY_ORDER}, - displayMode => 'MathJax', - showHeadline => 1, - showAnswerNumbers => 0, - showAttemptAnswers => $showAttemptAnswers && $showEvaluatedAnswers, - showAttemptPreviews => $showAttemptPreview, - showAttemptResults => $showAttemptResults, - showCorrectAnswers => $showCorrectAnswers, - showMessages => $showAttemptAnswers, # internally checks for messages - showSummary => $showSummary, - imgGen => $imgGen, # not needed if ce is present , - ce => '', # not needed if $imgGen is present - maketext => WeBWorK::Localize::getLoc("en"), - summary =>'', - ); - - $answers -- a hash of student answers e.g. $pg->{answers} - answersSubmitted if 0 then then the attemptsTable is not displayed (???) - answerOrder -- an array indicating the order the answers appear on the page. - displayMode 'MathJax' and 'images' are the most common - - showHeadline Show the header line 'Results for this submission' - - showAnswerNumbers, showAttemptAnswers, showAttemptPreviews,showAttemptResults, - showCorrectAnswers and showMessages control the display of each column in the table. - - attemptAnswers the student's typed in answer (possibly simplified numerically) - attemptPreview the student's answer after typesetting - attemptResults "correct", "_% correct", "incorrect" or "ungraded"- links to the answer blank - correctAnswers typeset version (untypeset versions are available via popups) - messages warns of formatting typos in the answer, or - more detailed messages about a wrong answer - summary is obtained from $pg->{result}{summary}. - If this is empty then a (localized) - version of "all answers are correct" - or "at least one answer is not coorrect" - imgGen points to a prebuilt image generator objectfor "images" mode - ce points to the CourseEnvironment -- it is needed if AttemptsTable - is required to build its own imgGen object - maketext points to a localization subroutine - -=head2 Methods - -=over 4 - -=item answerTemplate - -Returns HTML which formats the analysis of the student's answers to the problem. - -=back - -=head2 Read/Write Properties - -=over 4 - -=item showMessages, - -This can be switched on or off before exporting the answerTemplate, perhaps -under instructions from the PG problem. - -=item summary - -The contents of the summary can be defined when the attemptsTable object is created. - -The summary can be defined by the PG problem grader usually returned as -$pg->{result}{summary}. - -If the summary is not explicitly defined then (localized) versions -of the default summaries are created: - - "The answer above is correct.", - "Some answers will be graded later.", - "All of the [gradeable] answers above are correct.", - "[N] of the questions remain unanswered.", - "At least one of the answers above is NOT [fully] correct.', - -Note that if this is set after initialization, you must ensure that it is a -Mojo::ByteStream object if it contains html or characters that need escaping. - -=back - -=cut - -package WeBWorK::HTML::AttemptsTable; -use Mojo::Base 'Class::Accessor', -signatures; - -use Scalar::Util 'blessed'; -use WeBWorK::Utils 'wwRound'; - -# %options may contain: displayMode, submitted, imgGen, ce -# At least one of imgGen or ce must be provided if displayMode is 'images'. -sub new ($class, $rh_answers, $c, %options) { - $class = ref $class || $class; - ref($rh_answers) =~ /HASH/ or die 'The first entry to AttemptsTable must be a hash of answers'; - $c->isa('WeBWorK::Controller') or die 'The second entry to AttemptsTable must be a WeBWorK::Controller'; - my $self = bless { - answers => $rh_answers, - c => $c, - answerOrder => $options{answerOrder} // [], - answersSubmitted => $options{answersSubmitted} // 0, - summary => undef, # summary provided by problem grader (set in _init) - displayMode => $options{displayMode} || 'MathJax', - showHeadline => $options{showHeadline} // 1, - showAnswerNumbers => $options{showAnswerNumbers} // 1, - showAttemptAnswers => $options{showAttemptAnswers} // 1, # show student answer as entered and parsed - showAttemptPreviews => $options{showAttemptPreviews} // 1, # show preview of student answer - showAttemptResults => $options{showAttemptResults} // 1, # show results of grading student answer - showMessages => $options{showMessages} // 1, # show messages generated by evaluation - showCorrectAnswers => $options{showCorrectAnswers} // 0, # show the correct answers - showSummary => $options{showSummary} // 1, # show result summary - imgGen => undef, # set or created in _init method - }, $class; - - # Create accessors/mutators - $self->mk_ro_accessors(qw(answers c answerOrder answersSubmitted displayMode imgGen showAnswerNumbers - showAttemptAnswers showHeadline showAttemptPreviews showAttemptResults showCorrectAnswers showSummary)); - $self->mk_accessors(qw(showMessages summary)); - - # Sanity check and initialize imgGenerator. - $self->_init(%options); - - return $self; -} - -# Verify the display mode, and build imgGen if it is not supplied. -sub _init ($self, %options) { - $self->{submitted} = $options{submitted} // 0; - $self->{displayMode} = $options{displayMode} || 'MathJax'; - - # Only show message column if there is at least one message. - my @reallyShowMessages = grep { $self->answers->{$_}{ans_message} } @{ $self->answerOrder }; - $self->showMessages($self->showMessages && !!@reallyShowMessages); - - # Only used internally. Accessors are not needed. - $self->{numCorrect} = 0; - $self->{numBlanks} = 0; - $self->{numEssay} = 0; - - if ($self->displayMode eq 'images') { - if (blessed($options{imgGen}) && $options{imgGen}->isa('WeBWorK::PG::ImageGenerator')) { - $self->{imgGen} = $options{imgGen}; - } elsif (blessed($options{ce}) && $options{ce}->isa('WeBWorK::CourseEnvironment')) { - my $ce = $options{ce}; - - $self->{imgGen} = WeBWorK::PG::ImageGenerator->new( - tempDir => $ce->{webworkDirs}{tmp}, - latex => $ce->{externalPrograms}{latex}, - dvipng => $ce->{externalPrograms}{dvipng}, - useCache => 1, - cacheDir => $ce->{webworkDirs}{equationCache}, - cacheURL => $ce->{server_root_url} . $ce->{webworkURLs}{equationCache}, - cacheDB => $ce->{webworkFiles}{equationCacheDB}, - dvipng_align => $ce->{pg}{displayModeOptions}{images}{dvipng_align}, - dvipng_depth_db => $ce->{pg}{displayModeOptions}{images}{dvipng_depth_db}, - ); - } else { - warn 'Must provide image Generator (imgGen) or a course environment (ce) to build attempts table.'; - } - } - - # Make sure that the provided summary is a Mojo::ByteStream object. - $self->summary(blessed($options{summary}) - && $options{summary}->isa('Mojo::ByteStream') ? $options{summary} : $self->c->b($options{summary} // '')); - - return; -} - -sub formatAnswerRow ($self, $rh_answer, $ans_id, $answerNumber) { - my $c = $self->c; - - my $answerString = $rh_answer->{student_ans} // ''; - my $answerPreview = $self->previewAnswer($rh_answer) // ' '; - my $correctAnswer = $rh_answer->{correct_ans} // ''; - my $correctAnswerPreview = $self->previewCorrectAnswer($rh_answer) // ' '; - - my $answerMessage = $rh_answer->{ans_message} // ''; - $answerMessage =~ s/\n/
/g; - my $answerScore = $rh_answer->{score} // 0; - $self->{numCorrect} += $answerScore >= 1; - $self->{numEssay} += ($rh_answer->{type} // '') eq 'essay'; - $self->{numBlanks}++ unless $answerString =~ /\S/ || $answerScore >= 1; - - my $feedbackMessageClass = ($answerMessage eq '') ? '' : $c->maketext('FeedbackMessage'); - - my $resultString; - my $resultStringClass; - if ($answerScore >= 1) { - $resultString = $c->maketext('correct'); - $resultStringClass = 'ResultsWithoutError'; - } elsif (($rh_answer->{type} // '') eq 'essay') { - $resultString = $c->maketext('Ungraded'); - $self->{essayFlag} = 1; - } elsif ($answerScore == 0) { - $resultStringClass = 'ResultsWithError'; - $resultString = $c->maketext('incorrect'); - } else { - $resultString = $c->maketext('[_1]% correct', wwRound(0, $answerScore * 100)); - } - my $attemptResults = $c->tag( - 'td', - class => $resultStringClass, - $c->tag('a', href => '#', data => { answer_id => $ans_id }, $self->nbsp($resultString)) - ); - - return $c->c( - $self->showAnswerNumbers ? $c->tag('td', $answerNumber) : '', - $self->showAttemptAnswers ? $c->tag('td', dir => 'auto', $self->nbsp($answerString)) : '', - $self->showAttemptPreviews - ? (((defined $answerPreview && $answerPreview ne '') || $self->showAttemptAnswers) - ? $self->formatToolTip($answerString, $answerPreview) - : $c->tag('td', dir => 'auto', $self->nbsp($answerString))) - : '', - $self->showAttemptResults ? $attemptResults : '', - $self->showCorrectAnswers ? $self->formatToolTip($correctAnswer, $correctAnswerPreview) : '', - $self->showMessages ? $c->tag('td', class => $feedbackMessageClass, $self->nbsp($answerMessage)) : '' - )->join(''); -} - -# Determine whether any answers were submitted and create answer template if they have been. -sub answerTemplate ($self) { - my $c = $self->c; - - return '' unless $self->answersSubmitted; # Only print if there is at least one non-blank answer - - my $tableRows = $c->c; - - push( - @$tableRows, - $c->tag( - 'tr', - $c->c( - $self->showAnswerNumbers ? $c->tag('th', '#') : '', - $self->showAttemptAnswers ? $c->tag('th', $c->maketext('Entered')) : '', - $self->showAttemptPreviews ? $c->tag('th', $c->maketext('Answer Preview')) : '', - $self->showAttemptResults ? $c->tag('th', $c->maketext('Result')) : '', - $self->showCorrectAnswers ? $c->tag('th', $c->maketext('Correct Answer')) : '', - $self->showMessages ? $c->tag('th', $c->maketext('Message')) : '' - )->join('') - ) - ); - - my $answerNumber = 0; - for (@{ $self->answerOrder() }) { - push @$tableRows, $c->tag('tr', $self->formatAnswerRow($self->{answers}{$_}, $_, ++$answerNumber)); - } - - return $c->c( - $self->showHeadline - ? $c->tag('h2', class => 'attemptResultsHeader', $c->maketext('Results for this submission')) - : '', - $c->tag( - 'div', - class => 'table-responsive', - $c->tag('table', class => 'attemptResults table table-sm table-bordered', $tableRows->join('')) - ), - $self->showSummary ? $self->createSummary : '' - )->join(''); -} - -sub previewAnswer ($self, $answerResult) { - my $displayMode = $self->displayMode; - my $imgGen = $self->imgGen; - - my $tex = $answerResult->{preview_latex_string}; - - return '' unless defined $tex and $tex ne ''; - - return $tex if $answerResult->{non_tex_preview}; - - if ($displayMode eq 'plainText') { - return $tex; - } elsif (($answerResult->{type} // '') eq 'essay') { - return $tex; - } elsif ($displayMode eq 'images') { - return $imgGen->add($tex); - } elsif ($displayMode eq 'MathJax') { - return $self->c->tag('script', type => 'math/tex; mode=display', $self->c->b($tex)); - } -} - -sub previewCorrectAnswer ($self, $answerResult) { - my $displayMode = $self->displayMode; - my $imgGen = $self->imgGen; - - my $tex = $answerResult->{correct_ans_latex_string}; - - # Some answers don't have latex strings defined return the raw correct answer - # unless defined $tex and $tex contains non whitespace characters; - return $answerResult->{correct_ans} - unless defined $tex and $tex =~ /\S/; - - return $tex if $answerResult->{non_tex_preview}; - - if ($displayMode eq 'plainText') { - return $tex; - } elsif ($displayMode eq 'images') { - return $imgGen->add($tex); - } elsif ($displayMode eq 'MathJax') { - return $self->c->tag('script', type => 'math/tex; mode=display', $self->c->b($tex)); - } -} - -# Create summary -sub createSummary ($self) { - my $c = $self->c; - - my $numCorrect = $self->{numCorrect}; - my $numBlanks = $self->{numBlanks}; - my $numEssay = $self->{numEssay}; - - my $summary; - - unless (defined($self->summary) and $self->summary =~ /\S/) { - # Default messages - $summary = $c->c; - my @answerNames = @{ $self->answerOrder() }; - if (scalar @answerNames == 1) { - if ($numCorrect == scalar @answerNames) { - push( - @$summary, - $c->tag( - 'div', - class => 'ResultsWithoutError mb-2', - $c->maketext('The answer above is correct.') - ) - ); - } elsif ($self->{essayFlag}) { - push(@$summary, $c->tag('div', $c->maketext('Some answers will be graded later.'))); - } else { - push( - @$summary, - $c->tag( - 'div', - class => 'ResultsWithError mb-2', - $c->maketext('The answer above is NOT correct.') - ) - ); - } - } else { - if ($numCorrect + $numEssay == scalar @answerNames) { - if ($numEssay) { - push( - @$summary, - $c->tag( - 'div', - class => 'ResultsWithoutError mb-2', - $c->maketext('All of the gradeable answers above are correct.') - ) - ); - } else { - push( - @$summary, - $c->tag( - 'div', - class => 'ResultsWithoutError mb-2', - $c->maketext('All of the answers above are correct.') - ) - ); - } - } elsif ($numBlanks + $numEssay != scalar(@answerNames)) { - push( - @$summary, - $c->tag( - 'div', - class => 'ResultsWithError mb-2', - $c->maketext('At least one of the answers above is NOT correct.') - ) - ); - } - if ($numBlanks > $numEssay) { - my $s = ($numBlanks > 1) ? '' : 's'; - push( - @$summary, - $c->tag( - 'div', - class => 'ResultsAlert mb-2', - $c->maketext( - '[quant,_1,of the questions remains,of the questions remain] unanswered.', $numBlanks - ) - ) - ); - } - } - $summary = $summary->join(''); - } else { - $summary = $self->summary; # Summary defined by grader - } - $summary = $c->tag('div', role => 'alert', class => 'attemptResultsSummary', $summary); - $self->summary($summary); - return $summary; -} - -# Utility subroutine that prevents unwanted line breaks, and ensures that the return value is a Mojo::ByteStream object. -sub nbsp ($self, $str) { - return $self->c->b(defined $str && $str =~ /\S/ ? $str : ' '); -} - -# Note that formatToolTip output includes the wrapper. -sub formatToolTip ($self, $answer, $formattedAnswer) { - return $self->c->tag( - 'td', - $self->c->tag( - 'div', - class => 'answer-preview', - data => { - bs_toggle => 'popover', - bs_content => $answer, - bs_placement => 'bottom', - }, - $self->nbsp($formattedAnswer) - ) - ); -} - -1; diff --git a/lib/WeBWorK/Utils/Rendering.pm b/lib/WeBWorK/Utils/Rendering.pm index b80c6c96c4..8413c151c8 100644 --- a/lib/WeBWorK/Utils/Rendering.pm +++ b/lib/WeBWorK/Utils/Rendering.pm @@ -152,6 +152,15 @@ sub constructPGOptions ($ce, $user, $set, $problem, $psvn, $formFields, $transla $options{inputs_ref} = $formFields; $options{processAnswers} = $translationOptions->{processAnswers}; + # Attempt Results + $options{showFeedback} = $translationOptions->{showFeedback}; + $options{showAttemptAnswers} = $translationOptions->{showAttemptAnswers}; + $options{showAttemptPreviews} = $translationOptions->{showAttemptPreviews}; + $options{forceShowAttemptResults} = $translationOptions->{forceShowAttemptResults}; + $options{showAttemptResults} = $translationOptions->{showAttemptResults}; + $options{showMessages} = $translationOptions->{showMessages}; + $options{showCorrectAnswers} = $translationOptions->{showCorrectAnswers}; + # External Data $options{external_data} = decode_json($set->{external_data} || '{}'); @@ -245,7 +254,7 @@ sub renderPG ($c, $effectiveUser, $set, $problem, $psvn, $formFields, $translati flags => $pg->{flags}, }; - if (ref $pg->{pgcore}) { + if (ref($pg->{pgcore}) eq 'PGcore') { $ret->{internal_debug_messages} = $pg->{pgcore}->get_internal_debug_messages; $ret->{warning_messages} = $pg->{pgcore}->get_warning_messages(); $ret->{debug_messages} = $pg->{pgcore}->get_debug_messages(); diff --git a/lib/WebworkWebservice/RenderProblem.pm b/lib/WebworkWebservice/RenderProblem.pm index ac2f5ef6ab..f039322613 100644 --- a/lib/WebworkWebservice/RenderProblem.pm +++ b/lib/WebworkWebservice/RenderProblem.pm @@ -228,7 +228,15 @@ async sub renderProblem { isInstructor => $rh->{isInstructor} // 0, forceScaffoldsOpen => $rh->{forceScaffoldsOpen} // 0, QUIZ_PREFIX => $rh->{answerPrefix}, - debuggingOptions => { + showFeedback => $rh->{preview} || $rh->{WWsubmit} || $rh->{WWcorrectAns}, + showAttemptAnswers => $rh->{showAttemptAnswers} // 1, + showAttemptPreviews => $rh->{showAttemptPreviews} + // ($rh->{preview} || $rh->{WWsubmit} || $rh->{WWcorrectAns}), + showAttemptResults => $rh->{showAttemptResults} // ($rh->{WWsubmit} || $rh->{WWcorrectAns}), + forceShowAttemptResults => $rh->{forceShowAttemptResults}, + showMessages => $rh->{showMessages} // ($rh->{preview} || $rh->{WWsubmit} || $rh->{WWcorrectAns}), + showCorrectAnswers => $rh->{showCorrectAnswers} // $rh->{WWcorrectAns}, + debuggingOptions => { show_resource_info => $rh->{show_resource_info} // 0, view_problem_debugging_info => $rh->{view_problem_debugging_info} // 0, show_pg_info => $rh->{show_pg_info} // 0, diff --git a/templates/ContentGenerator/GatewayQuiz.html.ep b/templates/ContentGenerator/GatewayQuiz.html.ep index f85113404c..9751ee722a 100644 --- a/templates/ContentGenerator/GatewayQuiz.html.ep +++ b/templates/ContentGenerator/GatewayQuiz.html.ep @@ -490,7 +490,6 @@ <%= $jumpLinks->() =%> % % # Print out problems and attempt results, as appropriate. - % # Note: Usage is $c->attemptResults($pg, $showCorrectAnswers, $showAttemptResults, $showSummary) % for my $i (0 .. $#$pg_results) { % my $pg = $pg_results->[ $probOrder->[$i] ]; % @@ -498,34 +497,23 @@ % my $recordMessage = ''; % my $resultsTable = ''; % - % if ($pg->{flags}{showPartialCorrectAnswers} >= 0 && $c->{submitAnswers}) { + % if ($c->{submitAnswers}) { % if ($c->{scoreRecordedMessage}[ $probOrder->[$i] ] ne 'recorded') { % $recordMessage = tag('div', class => 'ResultsWithError d-inline-block mb-2', % maketext('ANSWERS NOT RECORDED -- [_1]', $c->{scoreRecordedMessage}[ $probOrder->[$i] ]) % ); % } - % $resultsTable = $c->attemptResults( - % $pg, - % $c->{will}{showCorrectAnswers}, - % $pg->{flags}{showPartialCorrectAnswers} && $c->{can}{showProblemScores}, - % $c->{can}{showProblemScores} - % ); + % $resultsTable = $c->attemptResults($pg); % } elsif ($c->{will}{checkAnswers} || $c->{will}{showProblemGrader}) { % $recordMessage = tag('div', class => 'ResultsWithError d-inline-block mb-2', % maketext('ANSWERS ONLY CHECKED -- ANSWERS NOT RECORDED') % ); % - % $resultsTable = $c->attemptResults( - % $pg, - % $c->{will}{showCorrectAnswers}, - % $pg->{flags}{showPartialCorrectAnswers} && $c->{can}{showProblemScores}, - % $c->{can}{showProblemScores} - % ); + % $resultsTable = $c->attemptResults($pg); % } elsif ($c->{previewAnswers}) { % $recordMessage = tag('div', class => 'ResultsWithError d-inline-block mb-2', % maketext('PREVIEW ONLY -- ANSWERS NOT RECORDED') % ); - % $resultsTable = $c->attemptResults($pg, 0, 0, 0); % } %
diff --git a/templates/RPCRenderFormats/default.html.ep b/templates/RPCRenderFormats/default.html.ep index fbccee5544..71e4f68543 100644 --- a/templates/RPCRenderFormats/default.html.ep +++ b/templates/RPCRenderFormats/default.html.ep @@ -37,7 +37,7 @@
- %== $answerTemplate + %== $resultSummary <%= form_for $FORM_ACTION_URL, id => 'problemMainForm', class => 'problem-main-form', name => 'problemMainForm', method => 'POST', begin %>
> @@ -66,7 +66,6 @@ % } %== $LTIGradeMessage % - %= hidden_field answersSubmitted => 1 %= hidden_field sourceFilePath => $sourceFilePath %= hidden_field problemSource => $problemSource %= hidden_field uriEncodedProblemSource => $uriEncodedProblemSource @@ -87,7 +86,6 @@ %= hidden_field showSummary => $showSummary %= hidden_field showHints => $showHints %= hidden_field showSolutions => $showSolutions - %= hidden_field showAnswerNumbers => $showAnswerNumbers %= hidden_field showPreviewButton => $showPreviewButton %= hidden_field showCheckAnswersButton => $showCheckAnswersButton %= hidden_field showCorrectAnswersButton => $showCorrectAnswersButton diff --git a/templates/RPCRenderFormats/default.json.ep b/templates/RPCRenderFormats/default.json.ep index 38a8c504d9..c25764aadd 100644 --- a/templates/RPCRenderFormats/default.json.ep +++ b/templates/RPCRenderFormats/default.json.ep @@ -19,7 +19,7 @@ % % body_part001 => '', % body_part100 => '
', - % body_part300 => $answerTemplate, + % body_part300 => $resultSummary, % body_part500 => '
', % body_part530 => qq{
}, @@ -46,7 +46,6 @@ % . '}', % % hidden_input_field => { - % answersSubmitted => '1', % sourceFilePath => $sourceFilePath, % problemSource => $problemSource, % problemSeed => $problemSeed, @@ -64,7 +63,6 @@ % showSummary => $showSummary, % showHints => $showHints, % showSolutions => $showSolutions, - % showAnswerNumbers => $showAnswerNumbers, % showPreviewButton => $showPreviewButton, % showCheckAnswersButton => $showCheckAnswersButton, % showCorrectAnswersButton => $showCorrectAnswersButton,