@@ -8,17 +8,32 @@ WeBWorK::ContentGenerator::Grades - Display statistics by user.
88=cut
99
1010use WeBWorK::Utils qw( wwRound) ;
11- use WeBWorK::Utils::DateTime qw( after) ;
11+ use WeBWorK::Utils::DateTime qw( after before ) ;
1212use WeBWorK::Utils::JITAR qw( jitar_id_to_seq) ;
13- use WeBWorK::Utils::Sets qw( grade_set format_set_name_display) ;
13+ use WeBWorK::Utils::Sets qw( grade_set format_set_name_display restricted_set_message ) ;
1414use WeBWorK::Utils::ProblemProcessing qw( compute_unreduced_score) ;
15+ use WeBWorK::HTML::StudentNav qw( studentNav) ;
1516use WeBWorK::Localize;
1617
18+ use constant TWO_DAYS => 172800;
19+
1720sub initialize ($c ) {
1821 $c -> {studentID } = $c -> param(' effectiveUser' ) // $c -> param(' user' );
1922 return ;
2023}
2124
25+ sub nav ($c , $args ) {
26+ return ' ' unless $c -> authz-> hasPermissions($c -> param(' user' ), ' become_student' );
27+
28+ return $c -> tag(
29+ ' div' ,
30+ class => ' row sticky-nav' ,
31+ role => ' navigation' ,
32+ ' aria-label' => ' student grades navigation' ,
33+ studentNav($c , undef )
34+ );
35+ }
36+
2237sub scoring_info ($c ) {
2338 my $db = $c -> db;
2439 my $ce = $c -> ce;
@@ -450,4 +465,251 @@ sub displayStudentStats ($c, $studentID) {
450465 );
451466}
452467
468+ # Determine if the grade can be improved by testing if the unreduced score
469+ # less than 1 and there are more attempts available.
470+ sub can_improve_score ($c , $set , $problem_record ) {
471+ my $unreduced_score = compute_unreduced_score($c -> ce, $problem_record , $set );
472+ return $unreduced_score < 1
473+ && ($problem_record -> max_attempts < 0
474+ || $problem_record -> num_correct + $problem_record -> num_incorrect < $problem_record -> max_attempts);
475+ }
476+
477+ # Note, this is meant to be a student view. Instructors will see the same information
478+ # as the student they are acting as. For an instructor to see hidden grades, they
479+ # can use the student progress report in instructor tools.
480+ sub displayStudentGrades ($c , $studentID ) {
481+ my $db = $c -> db;
482+ my $ce = $c -> ce;
483+ my $authz = $c -> authz;
484+
485+ my $studentRecord = $db -> getUser($studentID );
486+ unless ($studentRecord ) {
487+ $c -> addbadmessage($c -> maketext(' Record for user [_1] not found.' , $studentID ));
488+ return ' ' ;
489+ }
490+ my $effectiveUser = $studentRecord -> user_id;
491+
492+ my $courseName = $ce -> {courseName };
493+
494+ # First get all merged sets for this user ordered by set_id.
495+ my @sets = $db -> getMergedSetsWhere({ user_id => $studentID }, ' set_id' );
496+ # To be able to find the set objects later, make a handy hash of set ids to set objects.
497+ my %setsByID = (map { $_ -> set_id => $_ } @sets );
498+
499+ # Before going through the table generating loop, find all the set versions for the sets in our list.
500+ my %setVersionsCount ;
501+ my @allSetIDs ;
502+ for my $set (@sets ) {
503+ # Don't show hidden sets.
504+ next unless $set -> visible;
505+
506+ my $setID = $set -> set_id;
507+
508+ # FIXME: Here, as in many other locations, we assume that there is a one-to-one matching between versioned sets
509+ # and gateways. We really should have two flags, $set->assignment_type and $set->versioned. I'm not adding
510+ # that yet, however, so this will continue to use assignment_type.
511+ if (defined $set -> assignment_type && $set -> assignment_type =~ / gateway/ ) {
512+ # We have to have the merged set versions to know what each of their assignment types are
513+ # (because proctoring can change this).
514+ my @setVersions =
515+ $db -> getMergedSetVersionsWhere({ user_id => $studentID , set_id => { like => " $setID ,v\% " } });
516+
517+ # Add the set versions to our list of sets.
518+ $setsByID { $_ -> set_id . ' ,v' . $_ -> version_id } = $_ for (@setVersions );
519+
520+ # Flag the existence of set versions for this set.
521+ $setVersionsCount {$setID } = scalar @setVersions ;
522+
523+ # Save the set names for display.
524+ push (@allSetIDs , $setID );
525+ push (@allSetIDs , map { $_ -> set_id . ' ,v' . $_ -> version_id } @setVersions );
526+ } else {
527+ push (@allSetIDs , $setID );
528+ }
529+ }
530+
531+ # Set groups.
532+ my (@notOpen , @open , @reduced , @recentClosed , @closed , %allItems );
533+
534+ for my $setID (@allSetIDs ) {
535+ my $set = $setsByID {$setID };
536+
537+ # Determine if set is a test and if it is a test template or version.
538+ my $setIsTest = defined $set -> assignment_type && $set -> assignment_type =~ / gateway/ ;
539+ my $setIsVersioned = $setIsTest && !defined $setVersionsCount {$setID };
540+ my $setTemplateID = $setID =~ s / ,v\d +$// r ;
541+
542+ # Initialize set item. Define link here. It will be adjusted for versioned tests later.
543+ my $item = {
544+ name => format_set_name_display($setTemplateID ),
545+ grade => 0,
546+ grade_total => 0,
547+ grade_total_right => 0,
548+ is_test => $setIsTest ,
549+ link => $c -> systemLink(
550+ $c -> url_for(' problem_list' , setID => $setID ),
551+ params => { effectiveUser => $effectiveUser }
552+ )
553+ };
554+ $allItems {$setID } = $item ;
555+
556+ # Determine which group to put set in. Test versions are added to test template.
557+ unless ($setIsVersioned ) {
558+ my $enable_reduced_scoring =
559+ $ce -> {pg }{ansEvalDefaults }{enableReducedScoring }
560+ && $set -> enable_reduced_scoring
561+ && $set -> reduced_scoring_date;
562+ if (before($set -> open_date)) {
563+ push (@notOpen , $item );
564+ $item -> {message } = $c -> maketext(' Will open on [_1].' ,
565+ $c -> formatDateTime($set -> open_date, $ce -> {studentDateDisplayFormat }));
566+ next ;
567+ } elsif (($enable_reduced_scoring && before($set -> reduced_scoring_date)) || before($set -> due_date)) {
568+ push (@open , $item );
569+ } elsif ($enable_reduced_scoring && before($set -> due_date)) {
570+ push (@reduced , $item );
571+ } elsif ($ce -> {achievementsEnabled } && $ce -> {achievementItemsEnabled } && before($set -> due_date + TWO_DAYS))
572+ {
573+ push (@recentClosed , $item );
574+ } else {
575+ push (@closed , $item );
576+ }
577+ }
578+
579+ # Tests need their link updated. Along with template sets need to add a version list.
580+ # Also determines if grade and test problems should be shown.
581+ if ($setIsTest ) {
582+ my $act_as_student_test_url = ' ' ;
583+ if ($set -> assignment_type eq ' proctored_gateway' ) {
584+ $act_as_student_test_url = $item -> {link } =~ s / ($courseName)\/ / $1 \/ proctored_test_mode\/ / r ;
585+ } else {
586+ $act_as_student_test_url = $item -> {link } =~ s / ($courseName)\/ / $1 \/ test_mode\/ / r ;
587+ }
588+
589+ # If this is a template gateway set, determine if there are any versions, then move on.
590+ unless ($setIsVersioned ) {
591+ # Remove version from set url
592+ $item -> {link } =~ s / ,v\d +// ;
593+ if ($setVersionsCount {$setID }) {
594+ $item -> {versions } = [];
595+ # Hide score initially unless there is a version the score can be seen.
596+ $item -> {hide_score } = 1;
597+ } else {
598+ $item -> {message } = $c -> maketext(' No versions of this test have been taken.' );
599+ }
600+ next ;
601+ }
602+
603+ # This is a versioned test, add it to the appropriate template item.
604+ push (@{ $allItems {$setTemplateID }{versions } }, $item );
605+ $item -> {name } = $c -> maketext(' Version [_1]' , $set -> version_id);
606+
607+ # Only add link if the problems can be seen.
608+ if ($set -> hide_work eq ' N'
609+ || ($set -> hide_work eq ' BeforeAnswerDate' && time >= $set -> answer_date))
610+ {
611+ if ($set -> assignment_type eq ' proctored_gateway' ) {
612+ $item -> {link } =~ s / ($courseName)\/ / $1 \/ proctored_test_mode\/ / ;
613+ } else {
614+ $item -> {link } =~ s / ($courseName)\/ / $1 \/ test_mode\/ / ;
615+ }
616+ } else {
617+ $item -> {link } = ' ' ;
618+ }
619+
620+ # If the set has hide_score set, then nothing left to do.
621+ if (defined $set -> hide_score && $set -> hide_score eq ' Y'
622+ || ($set -> hide_score eq ' BeforeAnswerDate' && time < $set -> answer_date))
623+ {
624+ $item -> {hide_score } = 1;
625+ $item -> {message } = $c -> maketext(' Display of scores for this test is not allowed.' );
626+ next ;
627+ }
628+ # This is a test version, and the scores can be shown, so also show score of template set.
629+ $allItems {$setTemplateID }{hide_score } = 0;
630+ } else {
631+ # For a regular set, start out assuming it is complete until a problem says otherwise.
632+ $item -> {completed } = 1;
633+ }
634+
635+ my ($total_right , $total , $problem_scores , $problem_incorrect_attempts , $problem_records ) =
636+ grade_set($db , $set , $studentID , $setIsVersioned , 1);
637+ $total_right = wwRound(2, $total_right );
638+
639+ # Save set grades.
640+ $item -> {grade_total } = $total ;
641+ $item -> {grade_total_right } = $total_right ;
642+ $item -> {grade } = 100 * wwRound(2, $total ? $total_right / $total : 0);
643+
644+ # Only show problem scores if allowed.
645+ unless (defined $set -> hide_score_by_problem && $set -> hide_score_by_problem eq ' Y' ) {
646+ $item -> {problems } = [];
647+
648+ # Create a direct link to the problems unless the set is a test, or there is a set
649+ # restriction preventing the student from accessing the set problems.
650+ my $noProblemLink =
651+ $setIsTest
652+ || restricted_set_message($c , $set , ' lti' )
653+ || restricted_set_message($c , $set , ' conditional' )
654+ || $authz -> invalidIPAddress($set );
655+
656+ for my $i (0 .. $# $problem_scores ) {
657+ my $score = $problem_scores -> [$i ];
658+ my $problem_id = $setIsVersioned ? $i + 1 : $problem_records -> [$i ]{problem_id };
659+ my $problem_link =
660+ $noProblemLink
661+ ? ' '
662+ : $c -> systemLink($c -> url_for(' problem_detail' , setID => $setID , problemID => $problem_id ),
663+ params => { effectiveUser => $effectiveUser });
664+ $score = 0 unless $score =~ / ^\d +$ / ;
665+ # For jitar sets we only display grades for top level problems.
666+ if ($set -> assignment_type eq ' jitar' ) {
667+ my @seq = jitar_id_to_seq($problem_id );
668+ if ($#seq == 0) {
669+ push (@{ $item -> {problems } }, { id => $seq [0], score => $score , link => $problem_link });
670+ $item -> {completed } = 0 if $c -> can_improve_score($set , $problem_records -> [$i ]);
671+ }
672+ } else {
673+ push (@{ $item -> {problems } }, { id => $problem_id , score => $score , link => $problem_link });
674+ $item -> {completed } = 0 if !$setIsTest && $c -> can_improve_score($set , $problem_records -> [$i ]);
675+ }
676+ }
677+ }
678+
679+ # If this is a test version, update template set to the best grade a student hand.
680+ if ($setIsVersioned ) {
681+ # Compare the score to the template set and update as needed.
682+ my $templateItem = $allItems {$setTemplateID };
683+ if ($item -> {grade } > $templateItem -> {grade }) {
684+ for (' grade' , ' grade_total' , ' grade_total_right' ) {
685+ $templateItem -> {$_ } = $item -> {$_ };
686+ }
687+ }
688+ }
689+ }
690+
691+ # Compute total course grade if requested.
692+ my $courseTotal = 0;
693+ my $totalRight = 0;
694+ if ($ce -> {showCourseHomeworkTotals }) {
695+ for (@open , @reduced , @recentClosed , @closed ) {
696+ $courseTotal += $_ -> {grade_total };
697+ $totalRight += $_ -> {grade_total_right };
698+ }
699+ }
700+
701+ return $c -> include(
702+ ' ContentGenerator/Grades/student_grades' ,
703+ effectiveUser => $effectiveUser ,
704+ fullName => join (' ' , $studentRecord -> first_name, $studentRecord -> last_name),
705+ notOpen => \@notOpen ,
706+ open => \@open ,
707+ reduced => \@reduced ,
708+ recentClosed => \@recentClosed ,
709+ closed => \@closed ,
710+ courseTotal => $courseTotal ,
711+ totalRight => $totalRight
712+ );
713+ }
714+
4537151;
0 commit comments