[system] / branches / rel-2-3-dev / webwork-modperl / lib / WeBWorK / ContentGenerator / Instructor / StudentProgress.pm Repository:
ViewVC logotype

View of /branches/rel-2-3-dev/webwork-modperl/lib/WeBWorK/ContentGenerator/Instructor/StudentProgress.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 4615 - (download) (as text) (annotate)
Fri Nov 3 20:21:11 2006 UTC (6 years, 6 months ago) by sh002i
File size: 34936 byte(s)
backport (glarose): Gateway update: report tests graded in the grace
period after the due time as having been completed at exactly the
maximum allowed time. Thus, if a test allows 20 min and there is a grace
period (defined in global.conf) of 2 min, a test submitted after 21 min
will be reported here as having been completed in 20 min, not 21.  This
avoids some confusion for instructors who aren't aware of the grace
period.

This update doesn't have any effect on the presentation of non-
versioned sets.

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2006 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader$
    5 #
    6 # This program is free software; you can redistribute it and/or modify it under
    7 # the terms of either: (a) the GNU General Public License as published by the
    8 # Free Software Foundation; either version 2, or (at your option) any later
    9 # version, or (b) the "Artistic License" which comes with this package.
   10 #
   11 # This program is distributed in the hope that it will be useful, but WITHOUT
   12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   13 # FOR A PARTICULAR PURPOSE.  See either the GNU General Public License or the
   14 # Artistic License for more details.
   15 ################################################################################
   16 
   17 package WeBWorK::ContentGenerator::Instructor::StudentProgress;
   18 use base qw(WeBWorK::ContentGenerator::Instructor);
   19 
   20 =head1 NAME
   21 
   22 WeBWorK::ContentGenerator::Instructor::StudentProgress - Display Student Progress.
   23 
   24 =cut
   25 
   26 use strict;
   27 use warnings;
   28 #use CGI qw(-nosticky );
   29 use WeBWorK::CGI;
   30 use WeBWorK::Debug;
   31 use WeBWorK::ContentGenerator::Grades;
   32 use WeBWorK::DB::Record::Set;
   33 use WeBWorK::Utils qw(readDirectory list2hash max sortByName);
   34 use WeBWorK::Utils::SortRecords qw/sortRecords/;
   35 
   36 
   37 # The table format has been borrowed from the Grades.pm module
   38 sub initialize {
   39   my $self     = shift;
   40   # FIXME  are there args here?
   41   my @components = @_;
   42   my $r          = $self->{r};
   43   my $urlpath    = $r->urlpath;
   44   my $type       = $urlpath->arg("statType") || '';
   45   my $db         = $self->{db};
   46   my $ce         = $self->{ce};
   47   my $authz      = $self->{authz};
   48   my $courseName = $urlpath->arg('courseID');
   49   my $user       = $r->param('user');
   50 
   51   # Check permissions
   52   return unless $authz->hasPermissions($user, "access_instructor_tools");
   53 
   54   $self->{type}  = $type;
   55   if ($type eq 'student') {
   56     my $studentName = $r->urlpath->arg("userID") || $user;
   57     $self->{studentName } = $studentName;
   58 
   59   } elsif ($type eq 'set') {
   60     my $setName = $r->urlpath->arg("setID") || 0;
   61     $self->{setName}     = $setName;
   62     my $setRecord  = $db->getGlobalSet($setName); # checked
   63     die "global set $setName  not found." unless $setRecord;
   64     $self->{set_due_date} = $setRecord->due_date;
   65     $self->{setRecord}   = $setRecord;
   66   }
   67 }
   68 
   69 
   70 sub title {
   71   my ($self) = @_;
   72   my $r = $self->r;
   73   my $authz = $r->authz;
   74   my $user = $r->param('user');
   75 
   76   # Check permissions
   77   return "" unless $authz->hasPermissions($user, "access_instructor_tools");
   78 
   79   my $type                = $self->{type};
   80   my $string              = "Student Progress for ".$self->{ce}->{courseName}." ";
   81   if ($type eq 'student') {
   82     $string             .= "student ".$self->{studentName};
   83   } elsif ($type eq 'set' ) {
   84     $string             .= "set   ".$self->{setName};
   85     $string             .= ".    Due ". $self->formatDateTime($self->{set_due_date});
   86   }
   87   return $string;
   88 }
   89 sub siblings {
   90   my ($self) = @_;
   91   my $r = $self->r;
   92   my $db = $r->db;
   93   my $authz = $r->authz;
   94   my $user = $r->param('user');
   95   my $urlpath = $r->urlpath;
   96 
   97   # Check permissions
   98   return "" unless $authz->hasPermissions($user, "access_instructor_tools");
   99 
  100   my $courseID = $urlpath->arg("courseID");
  101   my $eUserID  = $r->param("effectiveUser");
  102   my @setIDs   = sort  $db->listGlobalSets;
  103 
  104   my $progress     = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::StudentProgress",
  105                                           courseID => $courseID);
  106 
  107   print CGI::start_div({class=>"info-box", id=>"fisheye"});
  108   print CGI::h2("Student Progress");
  109   #print CGI::start_ul({class=>"LinksMenu"});
  110   #print CGI::start_li();
  111   #print CGI::span({style=>"font-size:larger"}, CGI::a({href=>$self->systemLink($stats)}, 'Statistics'));
  112   print CGI::start_ul();
  113   foreach my $setID (@setIDs) {
  114     my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::StudentProgress",
  115       courseID => $courseID, setID => $setID,statType => 'set',);
  116     print CGI::li({},CGI::a({href=>$self->systemLink($problemPage)}, WeBWorK::ContentGenerator::underscore2nbsp($setID)));
  117   }
  118 
  119   print CGI::end_ul();
  120   #print CGI::end_li();
  121   #print CGI::end_ul();
  122   print CGI::end_div();
  123 
  124   return "";
  125 }
  126 sub body {
  127   my $self       = shift;
  128   my $r          = $self->r;
  129   my $urlpath    = $r->urlpath;
  130   my $db         = $r->db;
  131   my $ce         = $r->ce;
  132   my $authz      = $r->authz;
  133   my $user       = $r->param('user');
  134   my $courseName = $urlpath->arg("courseID");
  135   my $type       = $self->{type};
  136 
  137   # Check permissions
  138   return CGI::div({class=>"ResultsWithError"}. CGI::p("You are not authorized to access instructor tools"))
  139     unless $authz->hasPermissions($user, "access_instructor_tools");
  140 
  141   if ($type eq 'student') {
  142     my $studentName = $self->{studentName};
  143     my $studentRecord = $db->getUser($studentName) # checked
  144       or die "record for user $studentName not found";
  145     my $fullName = $studentRecord->full_name;
  146         my $courseHomePage = $urlpath->new(type  => 'set_list',
  147           args => {courseID=>$courseName});
  148     my $email = $studentRecord->email_address;
  149 
  150     print CGI::a({-href=>"mailto:$email"}, $email), CGI::br(),
  151       "Section: ", $studentRecord->section, CGI::br(),
  152       "Recitation: ", $studentRecord->recitation, CGI::br();
  153 
  154     if ($authz->hasPermissions($user, "become_student")) {
  155       my $act_as_student_url = $self->systemLink($courseHomePage,
  156         params => {effectiveUser=>$studentName});
  157 
  158       print 'Act as: ', CGI::a({-href=>$act_as_student_url},$studentRecord->user_id);
  159     }
  160 
  161     print WeBWorK::ContentGenerator::Grades::displayStudentStats($self,$studentName);
  162   } elsif( $type eq 'set') {
  163     $self->displaySets($self->{setName});
  164   } elsif ($type eq '') {
  165 
  166     $self->index;
  167   } else {
  168     warn "Don't recognize statistics display type: |$type|";
  169 
  170   }
  171 
  172 
  173   return '';
  174 
  175 }
  176 sub index {
  177   my $self          = shift;
  178   my $r             = $self->r;
  179   my $urlpath       = $r->urlpath;
  180   my $ce            = $r->ce;
  181   my $db            = $r->db;
  182   my $courseName    = $urlpath->arg("courseID");
  183   my @studentList   = sort $db->listUsers;
  184   my @setList       = sort  $db->listGlobalSets;
  185 
  186 ## Edit to filter out students you aren't allowed to see
  187 #
  188   my @myUsers;
  189 # my @studentRecords = $db->getUsers;  #this is never used
  190   my $user = $r->param("user");
  191 
  192   my (@viewable_sections, @viewable_recitations);
  193   if (defined @{$ce->{viewable_sections}->{$user}})
  194     {@viewable_sections = @{$ce->{viewable_sections}->{$user}};}
  195   if (defined @{$ce->{viewable_recitations}->{$user}})
  196     {@viewable_recitations = @{$ce->{viewable_recitations}->{$user}};}
  197   if (@viewable_sections or @viewable_recitations){
  198     foreach my $studentL (@studentList){
  199       my $keep = 0;
  200       my $student = $db->getUser($studentL);
  201       foreach my $sec (@viewable_sections){
  202         if ($student->section() eq $sec){$keep = 1; last;}
  203       }
  204       foreach my $rec (@viewable_recitations){
  205         if ($student->recitation() eq $rec){$keep = 1; last;}
  206       }
  207       if ($keep) {push @myUsers, $studentL;}
  208     }
  209 # @studentList = @myUsers;
  210   }
  211   else {@myUsers = @studentList;}
  212 
  213   my @studentRecords = $db->getUsers(@myUsers);
  214   my @sortedStudentRecords = sortRecords({fields=>[qw/last_name first_name user_id/]}, @studentRecords);
  215 
  216   my @setLinks      = ();
  217   my @studentLinks  = ();
  218   foreach my $set (@setList) {
  219       my $setStatisticsPage   = $urlpath->newFromModule($urlpath->module,
  220                                                         courseID => $courseName,
  221                                                         statType => 'set',
  222                                                         setID    => $set
  223       );
  224     push @setLinks, CGI::a({-href=>$self->systemLink($setStatisticsPage) }, WeBWorK::ContentGenerator::underscore2nbsp($set));
  225   }
  226 
  227   foreach my $studentRecord (@sortedStudentRecords) {
  228     my $first_name = $studentRecord->first_name;
  229     my $last_name = $studentRecord->last_name;
  230     my $user_id = $studentRecord->user_id;
  231     my $userStatisticsPage  = $urlpath->newFromModule($urlpath->module,
  232                                                         courseID => $courseName,
  233                                                         statType => 'student',
  234                                                         userID   => $user_id
  235       );
  236 
  237     push @studentLinks, CGI::a({-href=>$self->systemLink($userStatisticsPage,
  238                                                          prams=>{effectiveUser => $studentRecord->user_id}
  239                                                          )},"  $last_name, $first_name  ($user_id)" ),;
  240   }
  241   print join("",
  242     CGI::start_table({-border=>2, -cellpadding=>20}),
  243     CGI::Tr({},
  244       CGI::td({-valign=>'top'},
  245         CGI::h3({-align=>'center'},'View student progress by set'),
  246         CGI::ul(  CGI::li( [@setLinks] ) ),
  247       ),
  248       CGI::td({-valign=>'top'},
  249         CGI::h3({-align=>'center'},'View student progress by student'),
  250         CGI::ul(CGI::li( [ @studentLinks ] ) ),
  251       ),
  252     ),
  253     CGI::end_table(),
  254   );
  255 
  256 }
  257 ###################################################
  258 sub displaySets {
  259   my $self             = shift;
  260   my $r                = $self->r;
  261   my $urlpath          = $r->urlpath;
  262   my $db               = $r->db;
  263   my $ce               = $r->ce;
  264   my $authz            = $r->authz;
  265   my $courseName       = $urlpath->arg("courseID");
  266   my $setName          = $urlpath->arg("setID");
  267   my $user             = $r->param('user');
  268   my $setRecord        = $self->{setRecord};
  269   my $root             = $ce->{webworkURLs}->{root};
  270 
  271   my $setStatsPage     = $urlpath->newFromModule($urlpath->module,courseID=>$courseName,statType=>'sets',setID=>$setName);
  272   my $primary_sort_method_name = $r->param('primary_sort');
  273   my $secondary_sort_method_name = $r->param('secondary_sort');
  274   my $ternary_sort_method_name = $r->param('ternary_sort');
  275 
  276   my @studentList      = $db->listUsers;
  277 
  278 # another versioning/gateway change.  in many cases we don't want or need
  279 # all of the columns that are put in here by default, so we add a set of
  280 # flags for which columns to show.  for versioned sets we may also want to
  281 # only see the best score, so we include that as an option also.
  282 # these are ignored for non-versioned sets
  283   my %showColumns = ( 'name' => 1, 'score' => 1, 'outof' => 1,
  284           'date' => 0, 'testtime' => 0, 'index' => 1,
  285           'problems' => 1, 'section' => 1, 'recit' => 1,
  286           'login' => 1 );
  287   my $showBestOnly = 0;
  288 
  289     my @index_list                           = ();  # list of all student index
  290   my @score_list                           = ();  # list of all student total percentage scores
  291     my %attempts_list_for_problem            = ();  # a list of the number of attempts for each problem
  292     my %number_of_attempts_for_problem       = ();  # the total number of attempst for this problem (sum of above list)
  293     my %number_of_students_attempting_problem = ();  # the number of students attempting this problem.
  294     my %correct_answers_for_problem          = ();  # the number of students correctly answering this problem (partial correctness allowed)
  295   my $sort_method = sub {
  296     my ($a,$b,$sort_method_name) = @_;
  297     return 0 unless defined($sort_method_name);
  298     return  lc($a->{last_name}) cmp lc($b->{last_name}) if $sort_method_name eq 'last_name';
  299     return  lc($a->{first_name}) cmp lc($b->{first_name}) if $sort_method_name eq 'first_name';
  300     return  lc($a->{email_address}) cmp lc($b->{email_address}) if $sort_method_name eq 'email_address';
  301     return $b->{score} <=> $a->{score} if $sort_method_name eq 'score';
  302     return $b->{index} <=> $a->{index} if $sort_method_name eq 'index';
  303     return lc($a->{section}) cmp lc($b->{section}) if $sort_method_name eq 'section';
  304     return lc($a->{recitation}) cmp lc($b->{recitation}) if $sort_method_name eq 'recitation';
  305     return lc($a->{user_id}) cmp lc($b->{user_id}) if $sort_method_name eq 'user_id';
  306     if ($sort_method_name =~/p(\d+)/) {
  307         my $left  =  $b->{problemData}->{$1} ||0;
  308         my $right =  $a->{problemData}->{$1} ||0;
  309       return $left <=> $right;  # sort by number of attempts.
  310     }
  311 
  312   };
  313   my %display_sort_method_name = (
  314     last_name => 'last name',
  315     first_name => 'first name',
  316     email_address => 'email address',
  317     score => 'score',
  318     index => 'success indicator',
  319     section => 'section',
  320     recitation => 'recitation',
  321     user_id => 'login name',
  322   );
  323 
  324 # get versioning information
  325     my $GlobalSet = $db->getGlobalSet($setName);
  326     my $setIsVersioned =
  327   ( defined($GlobalSet->assignment_type()) &&
  328     $GlobalSet->assignment_type() =~ /gateway/ ) ? 1 : 0;
  329 
  330 # reset column view options based on whether the set is versioned and, if so,
  331 # the input parameters
  332     if ( $setIsVersioned ) {
  333   # the returning parameter lets us set defaults for versioned sets
  334   my $ret = $r->param('returning');
  335   $showColumns{'date'} = $ret ? $r->param('show_date') : 1;
  336   $showColumns{'testtime'} = $ret ? $r->param('show_testtime') : 1;
  337   $showColumns{'index'} = $ret ? $r->param('show_index') : 0;
  338   $showColumns{'problems'} = $ret ? $r->param('show_problems') : 0;
  339   $showColumns{'section'} = $ret? $r->param('show_section') : 0;
  340   $showColumns{'recit'} = $ret ? $r->param('show_recitation') : 0;
  341   $showColumns{'login'} = $ret ? $r->param('show_login') : 0;
  342   $showBestOnly = $ret ? $r->param('show_best_only') : 0;
  343     }
  344 
  345 ###############################################################
  346 #  Print tables
  347 ###############################################################
  348 
  349   my $max_num_problems  = 0;
  350   # get user records
  351   debug("Begin obtaining user records for set $setName");
  352   my @userRecords  = $db->getUsers(@studentList);
  353   debug("End obtaining user records for set $setName");
  354     debug("begin main loop");
  355   my @augmentedUserRecords    = ();
  356   my $number_of_active_students;
  357 
  358 ## Edit to filter out students
  359 #
  360   my @myUsers;
  361   my $ActiveUser = $r->param("user");
  362   my (@viewable_sections, @viewable_recitations);
  363   if (defined @{$ce->{viewable_sections}->{$user}})
  364     {@viewable_sections = @{$ce->{viewable_sections}->{$user}};}
  365   if (defined @{$ce->{viewable_recitations}->{$user}})
  366     {@viewable_recitations = @{$ce->{viewable_recitations}->{$user}};}
  367   if (@viewable_sections or @viewable_recitations){
  368     foreach my $student (@userRecords){
  369       my $keep = 0;
  370       foreach my $sec (@viewable_sections){
  371         if ($student->section() eq $sec){$keep = 1; last;}
  372       }
  373       foreach my $rec (@viewable_recitations){
  374         if ($student->recitation() eq $rec){$keep = 1; last;}
  375       }
  376       if ($keep) {push @myUsers, $student;}
  377     }
  378   }
  379   else {@myUsers = @userRecords;}
  380   foreach my $studentRecord (@myUsers)   {
  381     next unless ref($studentRecord);
  382     my $student = $studentRecord->user_id;
  383     next if $studentRecord->last_name =~/^practice/i;  # don't show practice users
  384     next unless $ce->status_abbrev_has_behavior($studentRecord->status, "include_in_stats");
  385     $number_of_active_students++;
  386 
  387 # build list of versioned sets for this student user
  388       my @allSetNames = ();
  389       if ( $setIsVersioned ) {
  390     my $numVersions = $db->getUserSetVersionNumber($student,$setName);
  391     for ( my $i=1; $i<=$numVersions; $i++ ) {
  392         $allSetNames[$i-1] = "$setName,v$i";
  393     }
  394       } else {
  395     @allSetNames = ( "$setName" );
  396       }
  397 
  398   # for versioned sets, we might be keeping only the high score
  399     my $maxScore = -1;
  400     my $max_hash = {};
  401   # make this global to the student loop (there was a reason for this for
  402   # versioned sets, at least, though I'm not seeing it now -glr)
  403     my $act_as_student_url = '';
  404 
  405     foreach my $sN ( @allSetNames ) {
  406 
  407       my $status          = 0;
  408       my $attempted       = 0;
  409       my $longStatus      = '';
  410       my $string          = '';
  411       my $twoString       = '';
  412       my $totalRight      = 0;
  413       my $total           = 0;
  414     my $total_num_of_attempts_for_set = 0;
  415     my %h_problemData   = ();
  416     my $probNum         = 0;
  417 
  418     debug("Begin obtaining problem records for user $student set $setName");
  419 
  420     my @problemRecords = sort {$a->problem_id <=> $b->problem_id } $db->getAllMergedUserProblems( $student, $sN );
  421     debug("End obtaining problem records for user $student set $setName");
  422     my $num_of_problems = @problemRecords;
  423     $max_num_problems = ($max_num_problems>= $num_of_problems) ? $max_num_problems : $num_of_problems;
  424      ########################################
  425     # Notes for factoring the calculation in this loop.
  426     #
  427     # Inputs include:
  428     #
  429     #
  430     # @problemRecords
  431     # returns
  432     #       $num_of_attempts
  433     #       $status
  434     # updates
  435     #       $number_of_students_attempting_problem{$probID}++;
  436     #     @{ $attempts_list_for_problem{$probID} }
  437     #     $number_of_attempts_for_problem{$probID}
  438     #     $total_num_of_attempts_for_set
  439     #     $correct_answers_for_problem{$probID}
  440     #
  441     #       $string (formatting output)
  442     #       $twoString (more formatted output)
  443                 #       $longtwo (a combination of $string and $twostring)
  444     #       $total
  445     #       $totalRight
  446     ###################################
  447 
  448     foreach my $problemRecord (@problemRecords) {
  449       next unless ref($problemRecord);
  450         # warn "Can't find record for problem $prob in set $setName for $student";
  451         # FIXME check the legitimate reasons why a student record might not be defined
  452       ####################################################################
  453       # Grab data from the database
  454       ####################################################################
  455       # It's possible that $problemRecord->num_correct or $problemRecord->num_correct
  456       # or $problemRecord->status is an empty
  457       # or blank string instead of 0.  The || clause fixes this and prevents
  458       # warning messages in the comparisons below.
  459 
  460       my $probID             = $problemRecord->problem_id;
  461       my $attempted          = $problemRecord->attempted;
  462       my $num_correct        = $problemRecord->num_correct     || 0;
  463       my $num_incorrect      = $problemRecord->num_incorrect   || 0;
  464       my $num_of_attempts    = $num_correct + $num_incorrect;
  465 
  466         # initialize the number of correct answers for this problem
  467         # if the value has not been defined.
  468           $correct_answers_for_problem{$probID}  = 0 unless defined($correct_answers_for_problem{$probID});
  469 
  470 
  471       my $probValue          = $problemRecord->value;   ## This doesn't work - Fix it
  472       # set default problem value here
  473       $probValue             = 1 unless defined($probValue) and $probValue ne "";  # FIXME?? set defaults here?
  474 
  475       my $status             = $problemRecord->status          || 0;
  476 
  477       # sanity check that the status (score) is between 0 and 1
  478           my $valid_status       = ($status >= 0 and $status <=1 ) ? 1 : 0;
  479 
  480           ###################################################################
  481           # Determine the string $longStatus which will display the student's current score
  482           ###################################################################
  483           my $longStatus = '';
  484       if (!$attempted){
  485         $longStatus     = '.';
  486       } elsif   ($valid_status) {
  487         $longStatus     = int(100*$status+.5);
  488                  # we change $longStatus to give more reasonable output for
  489                  #   gateways (actually all versioned sets; this might get us
  490                  #   into trouble at some later date).
  491         if ( $longStatus == 100 ) {
  492             $longStatus = 'C';
  493         } elsif ( $setIsVersioned ) {
  494             $longStatus = ( $longStatus == 0 ) ?
  495           'X' : $longStatus;
  496         }
  497       } else  {
  498         $longStatus   = 'X';
  499       }
  500 
  501       $string          .= threeSpaceFill($longStatus);
  502       $twoString       .= threeSpaceFill($num_incorrect);
  503 
  504       $total           += $probValue;
  505       $totalRight      += round_score($status*$probValue) if $valid_status;
  506 
  507        # add on the scores for this problem
  508       if (defined($attempted) and $attempted) {
  509         $number_of_students_attempting_problem{$probID}++;
  510         push( @{ $attempts_list_for_problem{$probID} } ,     $num_of_attempts);
  511         $number_of_attempts_for_problem{$probID}             += $num_of_attempts;
  512         $h_problemData{$probID}                               = $num_incorrect;
  513         $total_num_of_attempts_for_set                       += $num_of_attempts;
  514         $correct_answers_for_problem{$probID}                += $status;
  515       }
  516 
  517         }  # end of problem record loop
  518 
  519     # for versioned tests we might be displaying the test date and test time
  520       my $dateOfTest = '';
  521       my $testTime = '';
  522         # annoyingly, this is a set property, so get the set
  523       if ( $setIsVersioned &&
  524      ( $showColumns{'date'} || $showColumns{'testtime'} ) ) {
  525     my @userSet =
  526         $db->getMergedVersionedSets( [ $studentRecord->user_id, $setName, $sN ] );
  527     if ( defined( $userSet[0] ) ) {  # if this isn't defined, something's wrong
  528         $dateOfTest =
  529       localtime( $userSet[0]->version_creation_time() );
  530         if ( defined( $userSet[0]->version_last_attempt_time() ) &&
  531        $userSet[0]->version_last_attempt_time() ) {
  532       $testTime = ( $userSet[0]->version_last_attempt_time() -
  533               $userSet[0]->version_creation_time() ) /
  534               60;
  535     # clean-up: if the testTime is > allowed (that is, the test was submitted in
  536     # the grace period), report just that the testTime is the maximum allowed
  537       my $timeLimit = $userSet[0]->version_time_limit()/60;
  538       $testTime = $timeLimit if ( $testTime > $timeLimit );
  539       $testTime = sprintf("%3.1f min", $testTime);
  540         } else {
  541       $testTime = 'time limit exceeded';
  542         }
  543     } else {
  544         $dateOfTest = '???';
  545         $testTime = '???';
  546     }
  547       }
  548 
  549 
  550     $act_as_student_url = $self->systemLink($urlpath->new(type=>'set_list',args=>{courseID=>$courseName}),
  551                                                params=>{effectiveUser => $studentRecord->user_id}
  552     );
  553     my $email              = $studentRecord->email_address;
  554     # FIXME  this needs formatting
  555 
  556     # change to give better output for gateways; this just reports the result
  557     # for each problem, not the number of attempts.  if versioned sets are
  558     # used where multiple attempts are allowed per version this may not be
  559     # as desirable
  560           my $longtwo = ( $setIsVersioned ) ? $string :
  561         "$string\n$twoString";
  562 
  563     my $avg_num_attempts = ($num_of_problems) ? $total_num_of_attempts_for_set/$num_of_problems : 0;
  564     my $successIndicator = ($avg_num_attempts && $total) ? ($totalRight/$total)**2/$avg_num_attempts : 0 ;
  565 
  566     my $temp_hash         = {         user_id        => $studentRecord->user_id,
  567                                       last_name      => $studentRecord->last_name,
  568                                       first_name     => $studentRecord->first_name,
  569                                       score          => $totalRight,
  570                                       total          => $total,
  571                                       index          => $successIndicator,
  572                                       section        => $studentRecord->section,
  573                                       recitation     => $studentRecord->recitation,
  574                                       problemString  => "<pre>$longtwo</pre>",
  575                                       act_as_student => $act_as_student_url,
  576                                       email_address  => $studentRecord->email_address,
  577                                       problemData    => {%h_problemData},
  578               date           => $dateOfTest,
  579               testtime       => $testTime,
  580     };
  581 
  582     # keep track of best score
  583       if ( $totalRight > $maxScore ) {
  584     $maxScore = $totalRight;
  585     $max_hash = { %$temp_hash };
  586       }
  587 
  588     # if we're showing all records, add it in to the list
  589       if ( ! $showBestOnly ) {
  590     # add this data to the list of total scores (out of 100)
  591     # add this data to the list of success indices.
  592     push( @index_list, $temp_hash->{index});
  593     push( @score_list, ($temp_hash->{total}) ?$temp_hash->{score}/$temp_hash->{total} : 0 ) ;
  594     push( @augmentedUserRecords, $temp_hash );
  595       }
  596 
  597   } # this closes the loop through all set versions
  598 
  599     # if we're showing only the best score, add the best score now
  600   if ( $showBestOnly ) {
  601       if ( ! %$max_hash ) {  # then we have no tests---e.g., for proctors
  602     next;
  603         # if we could exclude proctors and instructors, we might want to keep
  604         # these, e.g., with something like the following
  605   # $max_hash = { user_id => $studentRecord->user_id(),
  606   #         last_name => $studentRecord->last_name(),
  607   #         first_name => $studentRecord->first_name(),
  608   #         score => 0,
  609   #         total => 'n/a',
  610   #         index => 0,
  611   #         section => $studentRecord->section(),
  612   #         recitation => $studentRecord->recitation(),
  613   #         problemString => 'no attempt recorded',
  614   #         act_as_student => $act_as_student_url,
  615   #         email_address => $studentRecord->email_address(),
  616   #         problemData => {},
  617   #         date => 'none',
  618   #         testtime => 'none',
  619   #     }
  620       }
  621 
  622       push( @index_list, $max_hash->{index} );
  623       push( @score_list,
  624       ($max_hash->{total} && $max_hash->{total} ne 'n/a') ?
  625       $max_hash->{score}/$max_hash->{total} : 0 );
  626       push( @augmentedUserRecords, $max_hash );
  627 
  628   }
  629 
  630         } # this closes the loop through all student records
  631 
  632   debug("end mainloop");
  633 
  634   @augmentedUserRecords = sort {
  635     &$sort_method($a,$b,$primary_sort_method_name)
  636       ||
  637     &$sort_method($a,$b,$secondary_sort_method_name)
  638       ||
  639     &$sort_method($a,$b,$ternary_sort_method_name)
  640       ||
  641     lc($a->{last_name}) cmp lc($b->{last_name})
  642       ||
  643     lc($a->{first_name}) cmp lc($b->{first_name})
  644       ||
  645     lc($a->{user_id}) cmp lc($b->{user_id})
  646     }
  647     @augmentedUserRecords;
  648 
  649 
  650   # construct header
  651   my $problem_header = '';
  652   my @list_problems = sort {$a<=> $b } $db->listGlobalProblems($setName );
  653   $problem_header = '<pre>'.join("", map {&threeSpaceFill($_)}  @list_problems  ).'</pre>';
  654 
  655 # changes for gateways/versioned sets here.  in this case we allow instructors
  656 # to modify the appearance of output, which we do with a form.  so paste in the
  657 # form header here, and make appropriate modifications
  658         my $verSelectors = '';
  659   if ( $setIsVersioned ) {
  660       print CGI::start_form({'method' => 'post',
  661            'action' => $self->systemLink($urlpath,
  662                  authen=>0),
  663            'name' => 'StudentProgress'});
  664       print $self->hidden_authen_fields();
  665 
  666 #     $verSelectors = CGI::p({'style'=>'background-color:#eeeeee;color:black;'},
  667       print CGI::p({'style'=>'background-color:#eeeeee;color:black;'},
  668           "Display options: Show ",
  669     CGI::hidden(-name=>'returning', -value=>'1'),
  670     CGI::checkbox(-name=>'show_best_only', -value=>'1',
  671             -checked=>$showBestOnly,
  672             -label=>' only best scores; '),
  673     CGI::checkbox(-name=>'show_index', -value=>'1',
  674             -checked=>$showColumns{'index'},
  675             -label=>' success indicator; '),
  676     CGI::checkbox(-name=>'show_date', -value=>'1',
  677             -checked=>$showColumns{'date'},
  678             -label=>' test date; '),
  679     CGI::checkbox(-name=>'show_testtime', -value=>'1',
  680             -checked=>$showColumns{'testtime'},
  681             -label=>' test time; '),
  682     CGI::checkbox(-name=>'show_problems', -value=>'1',
  683             -checked=>$showColumns{'problems'},
  684             -label=>'problems;'), "\n", CGI::br(), "\n",
  685     CGI::checkbox(-name=>'show_section', -value=>'1',
  686             -checked=>$showColumns{'section'},
  687             -label=>' section #; '),
  688     CGI::checkbox(-name=>'show_recitation', -value=>'1',
  689             -checked=>$showColumns{'recit'},
  690             -label=>' recitation #; '),
  691     CGI::checkbox(-name=>'show_login', -value=>'1',
  692             -checked=>$showColumns{'login'},
  693             -label=>'login'), "\n", CGI::br(), "\n",
  694     CGI::submit(-value=>'Update Display'),
  695       );
  696       print CGI::end_form();
  697   }
  698 
  699 #####################################################################################
  700   print
  701 #   CGI::br(),
  702     CGI::br(),
  703     CGI::p({},'A period (.) indicates a problem has not been attempted, a &quot;C&quot; indicates
  704     a problem has been answered 100% correctly, and a number from 0 to 99
  705     indicates the percentage of partial credit earned. The number on the
  706     second line gives the number of incorrect attempts.  The success indicator,'
  707     ,CGI::i('Ind'),', for each student is calculated as',
  708     CGI::br(),
  709     '100*(totalNumberOfCorrectProblems / totalNumberOfProblems)^2 / (AvgNumberOfAttemptsPerProblem)',CGI::br(),
  710     'or 0 if there are no attempts.'
  711     ),
  712     CGI::br(),
  713     "Click on student's name to see the student's version of the homework set. &nbsp; &nbsp;&nbsp;
  714     Click heading to sort table. ",
  715     CGI::br(),
  716     CGI::br(),
  717     defined($primary_sort_method_name) ?" Entries are sorted by $display_sort_method_name{$primary_sort_method_name}":'',
  718     defined($secondary_sort_method_name) ?", then by $display_sort_method_name{$secondary_sort_method_name}":'',
  719     defined($ternary_sort_method_name) ?", then by $display_sort_method_name{$ternary_sort_method_name}":'',
  720     defined($primary_sort_method_name) ?'.':'',
  721   ;
  722   # calculate secondary and ternary sort methods parameters if appropriate
  723   my %past_sort_methods = ();
  724   %past_sort_methods = (secondary_sort => "$primary_sort_method_name",) if defined($primary_sort_method_name);
  725   %past_sort_methods = (%past_sort_methods, ternary_sort => "$secondary_sort_method_name",) if defined($secondary_sort_method_name);
  726 
  727   # continue with outputing of table
  728   if ( ! $setIsVersioned ) {
  729       print
  730     CGI::start_table({-border=>5,style=>'font-size:smaller'}),
  731     CGI::Tr(CGI::td(  {-align=>'left'},
  732       ['Name'.CGI::br().CGI::a({"href"=>$self->systemLink($setStatsPage,params=>{primary_sort=>'first_name', %past_sort_methods})},'First').
  733          '&nbsp;&nbsp;&nbsp;'.CGI::a({"href"=>$self->systemLink($setStatsPage,params=>{primary_sort=>'last_name', %past_sort_methods })},'Last').CGI::br().
  734          CGI::a({"href"=>$self->systemLink($setStatsPage,params=>{primary_sort=>'email_address', %past_sort_methods })},'Email'),
  735       CGI::a({"href"=>$self->systemLink($setStatsPage,params=>{primary_sort=>'score', %past_sort_methods})},'Score'),
  736       'Out'.CGI::br().'Of',
  737       CGI::a({"href"=>$self->systemLink($setStatsPage,params=>{primary_sort=>'index', %past_sort_methods})},'Ind'),
  738       'Problems'.CGI::br().$problem_header,
  739       CGI::a({"href"=>$self->systemLink($setStatsPage,params=>{primary_sort=>'section', %past_sort_methods})},'Section'),
  740       CGI::a({"href"=>$self->systemLink($setStatsPage,params=>{primary_sort=>'recitation', %past_sort_methods})},'Recitation'),
  741       CGI::a({"href"=>$self->systemLink($setStatsPage,params=>{primary_sort=>'user_id', %past_sort_methods})},'Login Name'),
  742       ])
  743 
  744     ),
  745     ;
  746   } else {
  747     # we need to preserve display options when the sort headers are clicked
  748     my %display_options = (
  749       returning       => 1,
  750       show_best_only  => $showBestOnly,
  751       show_index      => $showColumns{index},
  752       show_date       => $showColumns{date},
  753       show_testtime   => $showColumns{testtime},
  754       show_problems   => $showColumns{problems},
  755       show_section    => $showColumns{section},
  756       show_recitation => $showColumns{recitation},
  757       show_login      => $showColumns{login},
  758     );
  759     my %params = (%past_sort_methods, %display_options);
  760       my @columnHdrs = ();
  761       push( @columnHdrs, 'Name'.CGI::br().CGI::a({"href"=>$self->systemLink($setStatsPage,params=>{primary_sort=>'first_name', %params})},'First').
  762       '&nbsp;&nbsp;&nbsp;'.CGI::a({"href"=>$self->systemLink($setStatsPage,params=>{primary_sort=>'last_name', %params })},'Last') );
  763       push( @columnHdrs, CGI::a({"href"=>$self->systemLink($setStatsPage,params=>{primary_sort=>'score', %params})},'Score') );
  764       push( @columnHdrs, 'Out'.CGI::br().'Of' );
  765       push( @columnHdrs, 'Date' ) if ( $showColumns{ 'date' } );
  766       push( @columnHdrs, 'TestTime' ) if ( $showColumns{ 'testtime' } );
  767       push( @columnHdrs, CGI::a({"href"=>$self->systemLink($setStatsPage,params=>{primary_sort=>'index', %params})},'Ind') )
  768     if ( $showColumns{ 'index' } );
  769       push( @columnHdrs, 'Problems'.CGI::br().$problem_header )
  770     if ( $showColumns{ 'problems' } );
  771       push( @columnHdrs, CGI::a({"href"=>$self->systemLink($setStatsPage,params=>{primary_sort=>'section', %params})},'Section') )
  772     if ( $showColumns{ 'section' } );
  773       push( @columnHdrs, CGI::a({"href"=>$self->systemLink($setStatsPage,params=>{primary_sort=>'recitation', %params})},'Recitation') )
  774     if ( $showColumns{ 'recit' } );
  775       push( @columnHdrs, CGI::a({"href"=>$self->systemLink($setStatsPage,params=>{primary_sort=>'user_id', %params})},'Login Name') )
  776     if ( $showColumns{ 'login' } );
  777 
  778       print CGI::start_table({-border=>5,style=>'font-size:smaller'}),
  779           CGI::Tr(CGI::td(  {-align=>'left'},
  780         [ @columnHdrs ] ) ),
  781       ;
  782   }
  783 
  784     # variables to keep track of versioned sets
  785   my $prevFullName = '';
  786   my $vNum = 1;
  787 
  788   foreach my $rec (@augmentedUserRecords) {
  789     my $fullName = join("", $rec->{first_name}," ", $rec->{last_name});
  790     my $email    = $rec->{email_address};
  791     my $twoString  = $rec->{twoString};
  792     if ( ! $setIsVersioned ) {
  793         print CGI::Tr({},
  794       CGI::td({},CGI::a({-href=>$rec->{act_as_student}},$fullName), CGI::br(), CGI::a({-href=>"mailto:$email"},$email)),
  795       CGI::td( sprintf("%0.2f",$rec->{score}) ), # score
  796       CGI::td($rec->{total}), # out of
  797       CGI::td(sprintf("%0.0f",100*($rec->{index}) )),   # indicator
  798       CGI::td($rec->{problemString}), # problems
  799       CGI::td($self->nbsp($rec->{section})),
  800       CGI::td($self->nbsp($rec->{recitation})),
  801       CGI::td($rec->{user_id}),
  802         );
  803     } else {
  804             # separate versioned sets so that we can restrict what columns
  805             # we show
  806         my @cols = ();
  807             # revise names to make versioned sets' format nicer
  808         my $nameEntry = '';
  809         if ( $fullName eq $prevFullName ) {
  810       $vNum++;
  811       $nameEntry = CGI::span({-style=>"text-align:right;"},
  812                  "(v$vNum)");
  813         } else {
  814       $nameEntry =
  815           CGI::a({-href=>$rec->{act_as_student}},$fullName) .
  816           ($setIsVersioned && ! $showBestOnly ? ' (v1)':' ') .
  817           CGI::br() . CGI::a({-href=>"mailto:$email"},$email);
  818       $vNum = 1;
  819       $prevFullName = $fullName;
  820         }
  821 
  822       # build columns to show
  823         push(@cols, $nameEntry, sprintf("%0.2f",$rec->{score}),
  824        $rec->{total});
  825         push(@cols, $self->nbsp($rec->{date}))
  826       if ($showColumns{'date'});
  827         push(@cols, $self->nbsp($rec->{testtime}))
  828       if ($showColumns{'testtime'});
  829         push(@cols, sprintf("%0.0f",$rec->{index}))
  830       if ($showColumns{'index'});
  831         push(@cols, $self->nbsp($rec->{problemString}))
  832       if ($showColumns{'problems'});
  833         push(@cols, $self->nbsp($rec->{section}))
  834       if ($showColumns{'section'});
  835         push(@cols, $self->nbsp($rec->{recitation}))
  836       if ($showColumns{'recit'});
  837         push(@cols, $rec->{user_id}) if ($showColumns{'login'});
  838 
  839         print CGI::Tr( CGI::td( [ @cols ] ) );
  840     }
  841   }
  842 
  843   print CGI::end_table();
  844 
  845   return "";
  846 }
  847 
  848 
  849 #################################
  850 # Utility function NOT a method
  851 #################################
  852 sub threeSpaceFill {
  853   my $num = shift @_ || 0;
  854 
  855   if (length($num)<=1) {return "$num".'&nbsp;&nbsp;';}
  856   elsif (length($num)==2) {return "$num".'&nbsp;';}
  857   else {return "## ";}
  858 }
  859 sub round_score{
  860   return shift;
  861 }
  862 
  863 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9