[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 4903 - (download) (as text) (annotate)
Mon Mar 26 20:28:41 2007 UTC (6 years, 1 month ago) by sh002i
File size: 34976 byte(s)
backport (sh002i): replace set ID underscores with spaces, not
nonbreaking spaces (for IE6)

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9