@@ -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) ;
1313use WeBWorK::Utils::Sets qw( grade_set format_set_name_display) ;
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,240 @@ 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+ };
550+ $allItems {$setID } = $item ;
551+ $item -> {link } =
552+ $c -> systemLink($c -> url_for(' problem_list' , setID => $setID ), params => { effectiveUser => $effectiveUser });
553+
554+ # Determine which group to put set in. Test versions are added to test template.
555+ unless ($setIsVersioned ) {
556+ my $enable_reduced_scoring =
557+ $ce -> {pg }{ansEvalDefaults }{enableReducedScoring }
558+ && $set -> enable_reduced_scoring
559+ && $set -> reduced_scoring_date;
560+ if (before($set -> open_date)) {
561+ push (@notOpen , $item );
562+ $item -> {message } = $c -> maketext(' Will open on [_1].' ,
563+ $c -> formatDateTime($set -> open_date, $ce -> {studentDateDisplayFormat }));
564+ next ;
565+ } elsif (($enable_reduced_scoring && before($set -> reduced_scoring_date)) || before($set -> due_date)) {
566+ push (@open , $item );
567+ } elsif ($enable_reduced_scoring && before($set -> due_date)) {
568+ push (@reduced , $item );
569+ } elsif ($ce -> {achievementsEnabled } && $ce -> {achievementItemsEnabled } && before($set -> due_date + TWO_DAYS))
570+ {
571+ push (@recentClosed , $item );
572+ } else {
573+ push (@closed , $item );
574+ }
575+ }
576+
577+ # Tests need their link updated. Along with template sets need to add a version list.
578+ # Also determines if grade and test problems should be shown.
579+ if ($setIsTest ) {
580+ my $act_as_student_test_url = ' ' ;
581+ if ($set -> assignment_type eq ' proctored_gateway' ) {
582+ $act_as_student_test_url = $item -> {link } =~ s / ($courseName)\/ / $1 \/ proctored_test_mode\/ / r ;
583+ } else {
584+ $act_as_student_test_url = $item -> {link } =~ s / ($courseName)\/ / $1 \/ test_mode\/ / r ;
585+ }
586+
587+ # If this is a template gateway set, determine if there are any versions, then move on.
588+ unless ($setIsVersioned ) {
589+ # Remove version from set url
590+ $item -> {link } =~ s / ,v\d +// ;
591+ if ($setVersionsCount {$setID }) {
592+ $item -> {versions } = [];
593+ # Hide score initially unless there is a version the score can be seen.
594+ $item -> {hide_score } = 1;
595+ } else {
596+ $item -> {message } = $c -> maketext(' No versions of this test have been taken.' );
597+ }
598+ next ;
599+ }
600+
601+ # This is a versioned test, add it to the appropriate template item.
602+ push (@{ $allItems {$setTemplateID }{versions } }, $item );
603+ $item -> {name } = $c -> maketext(' Version [_1]' , $set -> version_id);
604+
605+ # Only add link if the problems can be seen.
606+ if ($set -> hide_work eq ' N'
607+ || ($set -> hide_work eq ' BeforeAnswerDate' && time >= $set -> answer_date))
608+ {
609+ if ($set -> assignment_type eq ' proctored_gateway' ) {
610+ $item -> {link } =~ s / ($courseName)\/ / $1 \/ proctored_test_mode\/ / ;
611+ } else {
612+ $item -> {link } =~ s / ($courseName)\/ / $1 \/ test_mode\/ / ;
613+ }
614+ } else {
615+ $item -> {link } = ' ' ;
616+ }
617+
618+ # If the set has hide_score set, then nothing left to do.
619+ if (defined $set -> hide_score && $set -> hide_score eq ' Y'
620+ || ($set -> hide_score eq ' BeforeAnswerDate' && time < $set -> answer_date))
621+ {
622+ $item -> {hide_score } = 1;
623+ $item -> {message } = $c -> maketext(' Display of scores for this test is not allowed.' );
624+ next ;
625+ }
626+ # This is a test version, and the scores can be shown, so also show score of template set.
627+ $allItems {$setTemplateID }{hide_score } = 0;
628+ } else {
629+ # For a regular set, start out assuming it is complete until a problem says otherwise.
630+ $item -> {completed } = 1;
631+ }
632+
633+ my ($total_right , $total , $problem_scores , $problem_incorrect_attempts , $problem_records ) =
634+ grade_set($db , $set , $studentID , $setIsVersioned , 1);
635+ $total_right = wwRound(2, $total_right );
636+
637+ # Save set grades.
638+ $item -> {grade_total } = $total ;
639+ $item -> {grade_total_right } = $total_right ;
640+ $item -> {grade } = 100 * wwRound(2, $total ? $total_right / $total : 0);
641+
642+ # Only show problem scores if allowed.
643+ unless (defined $set -> hide_score_by_problem && $set -> hide_score_by_problem eq ' Y' ) {
644+ $item -> {problems } = [];
645+ for my $i (0 .. $# $problem_scores ) {
646+ my $score = $problem_scores -> [$i ];
647+ my $problem_id = $setIsVersioned ? $i + 1 : $problem_records -> [$i ]{problem_id };
648+ my $problem_link =
649+ $setIsTest
650+ ? ' '
651+ : $c -> systemLink($c -> url_for(' problem_detail' , setID => $setID , problemID => $problem_id ),
652+ params => { effectiveUser => $effectiveUser });
653+ $score = 0 unless $score =~ / ^\d +$ / ;
654+ # For jitar sets we only display grades for top level problems.
655+ if ($set -> assignment_type eq ' jitar' ) {
656+ my @seq = jitar_id_to_seq($problem_id );
657+ if ($#seq == 0) {
658+ push (@{ $item -> {problems } }, { id => $seq [0], score => $score , link => $problem_link });
659+ $item -> {completed } = 0 if $c -> can_improve_score($set , $problem_records -> [$i ]);
660+ }
661+ } else {
662+ push (@{ $item -> {problems } }, { id => $problem_id , score => $score , link => $problem_link });
663+ $item -> {completed } = 0 if !$setIsTest && $c -> can_improve_score($set , $problem_records -> [$i ]);
664+ }
665+ }
666+ }
667+
668+ # If this is a test version, update template set to the best grade a student hand.
669+ if ($setIsVersioned ) {
670+ # Compare the score to the template set and update as needed.
671+ my $templateItem = $allItems {$setTemplateID };
672+ if ($item -> {grade } > $templateItem -> {grade }) {
673+ for (' grade' , ' grade_total' , ' grade_total_right' ) {
674+ $templateItem -> {$_ } = $item -> {$_ };
675+ }
676+ }
677+ }
678+ }
679+
680+ # Compute total course grade if requested.
681+ my $courseTotal = 0;
682+ my $totalRight = 0;
683+ if ($ce -> {showCourseHomeworkTotals }) {
684+ for (@open , @reduced , @closed ) {
685+ $courseTotal += $_ -> {grade_total };
686+ $totalRight += $_ -> {grade_total_right };
687+ }
688+ }
689+
690+ return $c -> include(
691+ ' ContentGenerator/Grades/student_grades' ,
692+ effectiveUser => $effectiveUser ,
693+ fullName => join (' ' , $studentRecord -> first_name, $studentRecord -> last_name),
694+ notOpen => \@notOpen ,
695+ open => \@open ,
696+ reduced => \@reduced ,
697+ recentClosed => \@recentClosed ,
698+ closed => \@closed ,
699+ courseTotal => $courseTotal ,
700+ totalRight => $totalRight
701+ );
702+ }
703+
4537041;
0 commit comments