@@ -7,10 +7,12 @@ WeBWorK::ContentGenerator::Instructor::StudentProgress - Display Student Progres
77
88=cut
99
10- use WeBWorK::Utils qw( wwRound) ;
11- use WeBWorK::Utils::FilterRecords qw( getFiltersForClass filterRecords) ;
12- use WeBWorK::Utils::JITAR qw( jitar_id_to_seq) ;
13- use WeBWorK::Utils::Sets qw( grade_set list_set_versions format_set_name_display) ;
10+ use WeBWorK::Utils qw( wwRound) ;
11+ use WeBWorK::Utils::DateTime qw( after) ;
12+ use WeBWorK::Utils::FilterRecords qw( getFiltersForClass filterRecords) ;
13+ use WeBWorK::Utils::JITAR qw( jitar_id_to_seq) ;
14+ use WeBWorK::Utils::Sets qw( grade_set list_set_versions format_set_name_display) ;
15+ use WeBWorK::Utils::ProblemProcessing qw( compute_unreduced_score) ;
1416
1517sub initialize ($c ) {
1618 my $db = $c -> db;
@@ -278,4 +280,333 @@ sub displaySets ($c) {
278280 );
279281}
280282
283+ sub displayStudentStats ($c ) {
284+ my $db = $c -> db;
285+ my $ce = $c -> ce;
286+ my $authz = $c -> authz;
287+
288+ my $studentID = $c -> {studentID };
289+ my $studentRecord = $db -> getUser($studentID );
290+ unless ($studentRecord ) {
291+ $c -> addbadmessage($c -> maketext(' Record for user [_1] not found.' , $studentID ));
292+ return ' ' ;
293+ }
294+
295+ my $courseName = $ce -> {courseName };
296+
297+ # First get all merged sets for this user ordered by set_id.
298+ my @sets = $db -> getMergedSetsWhere({ user_id => $studentID }, ' set_id' );
299+ # To be able to find the set objects later, make a handy hash of set ids to set objects.
300+ my %setsByID = (map { $_ -> set_id => $_ } @sets );
301+
302+ # Before going through the table generating loop, find all the set versions for the sets in our list.
303+ my %setVersionsCount ;
304+ my @allSetIDs ;
305+ for my $set (@sets ) {
306+ # Don't show hidden sets unless user has appropriate permissions.
307+ next unless ($set -> visible || $authz -> hasPermissions($c -> param(' user' ), ' view_hidden_sets' ));
308+
309+ my $setID = $set -> set_id();
310+
311+ # FIXME: Here, as in many other locations, we assume that there is a one-to-one matching between versioned sets
312+ # and gateways. We really should have two flags, $set->assignment_type and $set->versioned. I'm not adding
313+ # that yet, however, so this will continue to use assignment_type.
314+ if (defined $set -> assignment_type && $set -> assignment_type =~ / gateway/ ) {
315+ # We have to have the merged set versions to know what each of their assignment types are
316+ # (because proctoring can change this).
317+ my @setVersions =
318+ $db -> getMergedSetVersionsWhere({ user_id => $studentID , set_id => { like => " $setID ,v\% " } });
319+
320+ # Add the set versions to our list of sets.
321+ $setsByID { $_ -> set_id . ' ,v' . $_ -> version_id } = $_ for (@setVersions );
322+
323+ # Flag the existence of set versions for this set.
324+ $setVersionsCount {$setID } = scalar @setVersions ;
325+
326+ # Save the set names for display.
327+ push (@allSetIDs , $setID );
328+ push (@allSetIDs , map { $_ -> set_id . ' ,v' . $_ -> version_id } @setVersions );
329+
330+ } else {
331+ push (@allSetIDs , $setID );
332+ }
333+ }
334+
335+ my $fullName = join (' ' , $studentRecord -> first_name, $studentRecord -> last_name);
336+ my $effectiveUser = $studentRecord -> user_id();
337+
338+ my $max_problems = 0;
339+ my $courseTotal = 0;
340+ my $courseTotalRight = 0;
341+
342+ for my $setID (@allSetIDs ) {
343+ my $set = $db -> getGlobalSet($setID );
344+ my $num_of_problems ;
345+ # For jitar sets we only display grades for top level problems, so we need to count how many there are.
346+ if ($set && $set -> assignment_type() eq ' jitar' ) {
347+ my @problemIDs = $db -> listGlobalProblems($setID );
348+ for my $problemID (@problemIDs ) {
349+ my @seq = jitar_id_to_seq($problemID );
350+ $num_of_problems ++ if ($#seq == 0);
351+ }
352+ } else {
353+ # For other sets we just count the number of problems.
354+ $num_of_problems = $db -> countGlobalProblems($setID );
355+ }
356+ $max_problems =
357+ $set && after($set -> open_date) && $max_problems < $num_of_problems ? $num_of_problems : $max_problems ;
358+ }
359+
360+ # Variables to help compute gateway scores.
361+ my $numGatewayVersions = 0;
362+ my $bestGatewayScore = 0;
363+
364+ my $rows = $c -> c;
365+ for my $setID (@allSetIDs ) {
366+ my $act_as_student_set_url =
367+ $c -> systemLink($c -> url_for(' problem_list' , setID => $setID ), params => { effectiveUser => $effectiveUser });
368+ my $set = $setsByID {$setID };
369+
370+ # Determine if set is a test and create the test url.
371+ my $setIsVersioned = 0;
372+ my $act_as_student_test_url = ' ' ;
373+ if (defined $set -> assignment_type && $set -> assignment_type =~ / gateway/ ) {
374+ $setIsVersioned = 1;
375+ if ($set -> assignment_type eq ' proctored_gateway' ) {
376+ $act_as_student_test_url = $act_as_student_set_url =~ s / ($courseName)\/ / $1 \/ proctored_test_mode\/ / r ;
377+ } else {
378+ $act_as_student_test_url = $act_as_student_set_url =~ s / ($courseName)\/ / $1 \/ test_mode\/ / r ;
379+ }
380+ # Remove version from set url
381+ $act_as_student_set_url =~ s / ,v\d +// ;
382+ }
383+
384+ # Format set name based on set visibility.
385+ my $setName = $c -> tag(
386+ ' span' ,
387+ class => $set -> visible ? ' font-visible' : ' font-hidden' ,
388+ format_set_name_display($setID =~ s / ,v\d +$// r )
389+ );
390+
391+ # If the set is a template gateway set and there are no versions, we acknowledge that the set exists and the
392+ # student hasn't attempted it. Otherwise, we skip it and let the versions speak for themselves.
393+ if (defined $setVersionsCount {$setID }) {
394+ next if $setVersionsCount {$setID };
395+ push @$rows ,
396+ $c -> tag(
397+ ' tr' ,
398+ $c -> c(
399+ $c -> tag(
400+ ' th' ,
401+ dir => ' ltr' ,
402+ (after($set -> open_date) || $authz -> hasPermissions($c -> param(' user' ), ' view_unopened_sets' ))
403+ ? $c -> link_to($setName => $act_as_student_set_url )
404+ : $setName
405+ ),
406+ $c -> tag(
407+ ' td' ,
408+ colspan => $max_problems + 3,
409+ $c -> tag(
410+ ' em' ,
411+ after($set -> open_date) ? $c -> maketext(' No versions of this test have been taken.' )
412+ : $c -> maketext(
413+ ' Will open on [_1].' ,
414+ $c -> formatDateTime($set -> open_date, $ce -> {studentDateDisplayFormat })
415+ )
416+ )
417+ )
418+ )-> join (' ' )
419+ );
420+ next ;
421+ }
422+
423+ # If the set has hide_score set, then we need to skip printing the score as well.
424+ if (
425+ defined $set -> assignment_type
426+ && $set -> assignment_type =~ / gateway/
427+ && defined $set -> hide_score
428+ && (
429+ !$authz -> hasPermissions($c -> param(' user' ), ' view_hidden_work' )
430+ && ($set -> hide_score eq ' Y' || ($set -> hide_score eq ' BeforeAnswerDate' && time < $set -> answer_date))
431+ )
432+ )
433+ {
434+ # Add a link to the test version if the problems can be seen.
435+ my $thisSetName =
436+ $c -> link_to($setName => $act_as_student_set_url ) . ' ('
437+ . (
438+ (
439+ $set -> hide_work eq ' N'
440+ || ($set -> hide_work eq ' BeforeAnswerDate' && time >= $set -> answer_date)
441+ || $authz -> hasPermissions($c -> param(' user' ), ' view_unopened_sets' )
442+ )
443+ ? $c -> link_to($c -> maketext(' version [_1]' , $set -> version_id) => $act_as_student_test_url )
444+ : $c -> maketext(' version [_1]' , $set -> version_id)
445+ ) . ' )' ;
446+ push (
447+ @$rows ,
448+ $c -> tag(
449+ ' tr' ,
450+ $c -> c(
451+ $c -> tag(
452+ ' th' ,
453+ dir => ' ltr' ,
454+ sub {$thisSetName }
455+ ),
456+ $c -> tag(
457+ ' td' ,
458+ colspan => $max_problems + 3,
459+ $c -> tag(' em' , $c -> maketext(' Display of scores for this test is not allowed.' ))
460+ )
461+ )-> join (' ' )
462+ )
463+ );
464+ next ;
465+ }
466+
467+ my ($totalRight , $total , $problem_scores , $problem_incorrect_attempts , $problem_records ) =
468+ grade_set($db , $set , $studentID , $setIsVersioned , 1);
469+ $totalRight = wwRound(2, $totalRight );
470+
471+ my @html_prob_scores ;
472+
473+ my $show_problem_scores = 1;
474+
475+ if (defined $set -> hide_score_by_problem
476+ && !$authz -> hasPermissions($c -> param(' user' ), ' view_hidden_work' )
477+ && $set -> hide_score_by_problem eq ' Y' )
478+ {
479+ $show_problem_scores = 0;
480+ }
481+
482+ for my $i (0 .. $max_problems - 1) {
483+ my $score = defined $problem_scores -> [$i ] && $show_problem_scores ? $problem_scores -> [$i ] : ' ' ;
484+ my $is_correct = $score =~ / ^\d +$ / && compute_unreduced_score($ce , $problem_records -> [$i ], $set ) == 1;
485+ push (
486+ @html_prob_scores ,
487+ $c -> tag(
488+ ' td' ,
489+ class => ' problem-data' ,
490+ $c -> c(
491+ $c -> tag(
492+ ' span' ,
493+ class => $is_correct ? ' correct' : $score eq ' . ' ? ' unattempted' : ' ' ,
494+ $c -> b($score )
495+ ),
496+ $c -> tag(' br' ),
497+ (defined $problem_incorrect_attempts -> [$i ] && $show_problem_scores )
498+ ? $problem_incorrect_attempts -> [$i ]
499+ : $c -> b(' ' )
500+ )-> join (' ' )
501+ )
502+ );
503+ }
504+
505+ # Get percentage correct.
506+ my $totalRightPercent = 100 * wwRound(2, $total ? $totalRight / $total : 0);
507+ my $class = ' ' ;
508+ if ($totalRightPercent == 0) {
509+ $class = ' unattempted' ;
510+ } elsif ($totalRightPercent == 100) {
511+ $class = ' correct' ;
512+ }
513+
514+ # If its a gateway set, then in order to mimic the scoring done in Scoring Tools we need to use the best score a
515+ # student had. Otherwise we just add the set to the running course total.
516+ if ($setIsVersioned ) {
517+ $setID =~ / (.+),v(\d +)$ / ;
518+ my $gatewayName = $1 ;
519+ my $currentVersion = $2 ;
520+
521+ # If we are just starting a new gateway then set variables to look for the max.
522+ if ($currentVersion == 1) {
523+ $numGatewayVersions = $db -> countSetVersions($studentID , $gatewayName );
524+ }
525+
526+ if ($totalRight > $bestGatewayScore ) {
527+ $bestGatewayScore = $totalRight ;
528+ }
529+
530+ # If its the last version then add the max to the course totals and reset variables;
531+ if ($currentVersion == $numGatewayVersions ) {
532+ if (after($set -> open_date())) {
533+ $courseTotal += $total ;
534+ $courseTotalRight += $bestGatewayScore ;
535+ }
536+ $bestGatewayScore = 0;
537+ }
538+ } else {
539+ if (after($set -> open_date())) {
540+ $courseTotal += $total ;
541+ $courseTotalRight += $totalRight ;
542+ }
543+ }
544+
545+ # Only show scores for open sets, and don't link to non open sets.
546+ if (after($set -> open_date) || $authz -> hasPermissions($c -> param(' user' ), ' view_unopened_sets' )) {
547+ # Set the set name and link. If a test, don't link to the version unless the problems can be seen.
548+ my $thisSetName = $setIsVersioned
549+ ? $c -> link_to($setName => $act_as_student_set_url ) . ' ('
550+ . (
551+ (
552+ $set -> hide_work eq ' N'
553+ || ($set -> hide_work eq ' BeforeAnswerDate' && time >= $set -> answer_date)
554+ || $authz -> hasPermissions($c -> param(' user' ), ' view_unopened_sets' )
555+ )
556+ ? $c -> link_to($c -> maketext(' version [_1]' , $set -> version_id) => $act_as_student_test_url )
557+ : $c -> maketext(' version [_1]' , $set -> version_id)
558+ )
559+ . ' )'
560+ : $c -> link_to($setName => $act_as_student_set_url );
561+ push @$rows , $c -> tag(
562+ ' tr' ,
563+ $c -> c(
564+ $c -> tag(
565+ ' th' ,
566+ scope => ' row' ,
567+ dir => ' ltr' ,
568+ sub {$thisSetName }
569+ ),
570+ $c -> tag(' td' , $c -> tag(' span' , class => $class , $totalRightPercent . ' %' )),
571+ $c -> tag(' td' , sprintf (' %0.2f' , $totalRight )), # score
572+ $c -> tag(' td' , $total ), # out of
573+ @html_prob_scores # problems
574+ )-> join (' ' )
575+ );
576+ } else {
577+ push @$rows ,
578+ $c -> tag(
579+ ' tr' ,
580+ $c -> c(
581+ $c -> tag(
582+ ' th' ,
583+ dir => ' ltr' ,
584+ $setName
585+ ),
586+ $c -> tag(
587+ ' td' ,
588+ colspan => $max_problems + 3,
589+ $c -> tag(
590+ ' em' ,
591+ $c -> maketext(
592+ ' Will open on [_1].' ,
593+ $c -> formatDateTime($set -> open_date, $ce -> {studentDateDisplayFormat })
594+ )
595+ )
596+ )
597+ )-> join (' ' )
598+ );
599+ }
600+ }
601+
602+ return $c -> include(
603+ ' ContentGenerator/Instructor/StudentProgress/student_stats' ,
604+ fullName => $fullName ,
605+ max_problems => $max_problems ,
606+ rows => $rows -> join (' ' ),
607+ courseTotal => $courseTotal ,
608+ courseTotalRight => $courseTotalRight
609+ );
610+ }
611+
2816121;
0 commit comments