File:  [LON-CAPA] / loncom / interface / statistics / lonstudentassessment.pm
Revision 1.12: download - view: text, annotated - select for diffs
Sun Sep 22 18:24:45 2002 UTC (21 years, 9 months ago) by minaeibi
Branches: MAIN
CVS tags: HEAD
Fixed bug #805.

    1: # The LearningOnline Network with CAPA
    2: # (Publication Handler
    3: #
    4: # $Id: lonstudentassessment.pm,v 1.12 2002/09/22 18:24:45 minaeibi Exp $
    5: #
    6: # Copyright Michigan State University Board of Trustees
    7: #
    8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
    9: #
   10: # LON-CAPA is free software; you can redistribute it and/or modify
   11: # it under the terms of the GNU General Public License as published by
   12: # the Free Software Foundation; either version 2 of the License, or
   13: # (at your option) any later version.
   14: #
   15: # LON-CAPA is distributed in the hope that it will be useful,
   16: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   18: # GNU General Public License for more details.
   19: #
   20: # You should have received a copy of the GNU General Public License
   21: # along with LON-CAPA; if not, write to the Free Software
   22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   23: #
   24: # /home/httpd/html/adm/gpl.txt
   25: #
   26: # http://www.lon-capa.org/
   27: #
   28: # (Navigate problems for statistical reports
   29: # YEAR=2001
   30: # 5/5,7/9,7/25/1,8/11,9/13,9/26,10/5,10/9,10/22,10/26 Behrouz Minaei
   31: # 11/1,11/4,11/16,12/14,12/16,12/18,12/20,12/31 Behrouz Minaei
   32: # YEAR=2002
   33: # 1/22,2/1,2/6,2/25,3/2,3/6,3/17,3/21,3/22,3/26,4/7,5/6 Behrouz Minaei
   34: # 5/12,5/14,5/15,5/19,5/26,7/16  Behrouz Minaei
   35: #
   36: ###
   37: 
   38: package Apache::lonstudentassessment; 
   39: 
   40: use strict;
   41: use Apache::lonhtmlcommon;
   42: use Apache::loncoursedata;
   43: use GDBM_File;
   44: 
   45: #my $jr;
   46: 
   47: sub BuildStudentAssessmentPage {
   48:     my ($cacheDB,$students,$courseID,$formName,$headings,$spacing,
   49:         $studentInformation,$r,$c)=@_;
   50: #    $jr = $r;
   51:     my %cache;
   52:     unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) {
   53:         $r->print('<html><body>Unable to tie database.</body></html>');
   54:         return;
   55:     }
   56: 
   57:     # Remove students who don't have the proper section.
   58:     my @sectionsSelected = split(':',$cache{'sectionsSelected'});
   59:     for(my $studentIndex=((scalar @$students)-1); $studentIndex>=0;
   60:         $studentIndex--) {
   61:         my $value = $cache{$students->[$studentIndex].':section'};
   62:         my $found = 0;
   63:         foreach (@sectionsSelected) {
   64:             if($_ eq 'none') {
   65:                 if($value eq '' || !defined($value) || $value eq ' ') {
   66:                     $found = 1;
   67:                     last;
   68:                 }
   69:             } else {
   70:                 if($value eq $_) {
   71:                     $found = 1;
   72:                     last;
   73:                 }
   74:             }
   75:         }
   76:         if($found == 0) {
   77:             splice(@$students, $studentIndex, 1);
   78:         }
   79:     }
   80:     my ($infoHeadings, $infoKeys, $sequenceHeadings, $sequenceKeys,
   81:         $doNotShow) = 
   82:         &ShouldShowColumns(\%cache, $headings, $studentInformation);
   83: 
   84:     my $selectedName = &FindSelectedStudent(\%cache, 
   85:                                             $cache{'StudentAssessmentStudent'},
   86:                                             $students);
   87:     $r->print(&CreateInterface(\%cache, $selectedName, $students, $formName,
   88:                                $doNotShow));
   89:     $r->rflush();
   90: 
   91:     my $Str = '';
   92:     if($selectedName eq 'No Student Selected') {
   93: 	$Str .= '<h3><font color=blue>WARNING: ';
   94:         $Str .= 'Please select a student</font></h3>';
   95:         $r->print($Str);
   96:         return;
   97:     }
   98: 
   99:     $r->print(&CreateTableHeadings(\%cache, $spacing, $infoKeys, $infoHeadings,
  100:                                    $sequenceKeys, $sequenceHeadings));
  101:     untie(%cache);
  102:     if($c->aborted()) {  return $Str; }
  103: 
  104:     my $selected=0;
  105:     $r->print('<pre>'."\n");
  106:     foreach (@$students) {
  107:         if($c->aborted()) { return $Str; }
  108:         next if ($_ ne $selectedName && 
  109:                  $selectedName ne 'All Students');
  110:         $selected = 1;
  111: 
  112:         my @who = ($_);
  113:         next if(&Apache::loncoursedata::DownloadStudentCourseData(\@who, 'true', 
  114:                                                              $cacheDB, 'true', 
  115:                                                              'false', $courseID,
  116:                                                              $r, $c) ne 'OK');
  117:         next if($c->aborted());
  118: 
  119:         if(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) {
  120:             my @before=();
  121:             my @after=();
  122:             my @updateColumn=();
  123:             my $foundUpdate = 0;
  124:             foreach(@$infoKeys) {
  125:                 if(/updateTime/) {
  126:                     $foundUpdate=1;
  127:                     push(@updateColumn, $_);
  128:                     next;
  129:                 }
  130:                 if($foundUpdate) {
  131:                     push(@after, $_);
  132:                 } else {
  133:                     push(@before, $_);
  134:                 }
  135:             }
  136:             my $displayString = 'DISPLAYDATA'.$spacing;
  137:             $r->print(&Apache::lonhtmlcommon::FormatStudentInformation(
  138:                                                          \%cache, $_,
  139:                                                          \@before,
  140:                                                          $displayString,
  141:                                                          'preformatted'));
  142: 
  143:             if($foundUpdate) {
  144:                 $displayString = '';
  145:                 $displayString .= '<a href="/adm/statistics?reportSelected=';
  146:                 $displayString .= &Apache::lonnet::escape('Student Assessment');
  147:                 $displayString .= '&download='.$_.'">';
  148:                 $displayString .= 'DISPLAYDATA</a>'.$spacing;
  149:                 $r->print(&Apache::lonhtmlcommon::FormatStudentInformation(
  150:                                                                    \%cache, $_,
  151:                                                                    \@updateColumn,
  152:                                                                    $displayString,
  153:                                                                    'preformatted'));
  154:             }
  155: 
  156:             $displayString = 'DISPLAYDATA'.$spacing;
  157:             $r->print(&Apache::lonhtmlcommon::FormatStudentInformation(
  158:                                                          \%cache, $_,
  159:                                                          \@after,
  160:                                                          $displayString,
  161:                                                          'preformatted'));
  162:             $r->print(&StudentReport(\%cache, $_, $spacing, $sequenceKeys));
  163:             $r->print("\n");
  164:             $r->rflush();
  165:             untie(%cache);
  166:         }
  167:     }
  168:     $r->print('</pre>'."\n");
  169:     if($selected == 0) {
  170: 	$Str .= '<h3><font color=blue>WARNING: ';
  171:         $Str .= 'Please select a student</font></h3>';
  172:         $r->print($Str);
  173:     }
  174: 
  175:     return;
  176: }
  177: 
  178: #---- Student Assessment Web Page --------------------------------------------
  179: 
  180: sub CreateInterface {
  181:     my($cache,$selectedName,$students,$formName,$doNotShow)=@_;
  182: 
  183:     my $Str = '';
  184:     $Str .= &CreateLegend();
  185:     $Str .= '<table><tr><td>'."\n";
  186:     $Str .= '<input type="submit" name="PreviousStudent" ';
  187:     $Str .= 'value="Previous Student" />'."\n";
  188:     $Str .= '&nbsp&nbsp&nbsp'."\n";
  189:     $Str .= &Apache::lonhtmlcommon::StudentOptions($cache, $students, 
  190:                                                    $selectedName, 
  191:                                                    'StudentAssessment', 
  192:                                                    $formName);
  193:     $Str .= "\n".'&nbsp&nbsp&nbsp'."\n";
  194:     $Str .= '<input type="submit" name="NextStudent" ';
  195:     $Str .= 'value="Next Student" />'."\n";
  196:     $Str .= '</td></tr></table>'."\n";
  197:     $Str .= '<table cellspacing="5"><tr>'."\n";
  198:     $Str .= '<td align="center"><b>Select Sections</b>'."\n";
  199:     $Str .= '</td>'."\n";
  200:     $Str .= '<td align="center"><b>Select column to view:</b></td>'."\n";
  201:     $Str .= '<td></td></tr>'."\n";
  202: 
  203:     $Str .= '<tr><td align="center">'."\n";
  204:     my @sections = split(':',$cache->{'sectionList'});
  205:     my @selectedSections = split(':',$cache->{'sectionsSelected'});
  206:     $Str .= &Apache::lonhtmlcommon::MultipleSectionSelect(\@sections,
  207:                                                           \@selectedSections,
  208:                                                           'Statistics');
  209:     $Str .= '</td><td align="center">';
  210:     $Str .= &CreateColumnSelectionBox($doNotShow);
  211:     $Str .= '</td><td>'."\n";
  212:     $Str .= '<input type="submit" name="DefaultColumns" ';
  213:     $Str .= 'value="Default Column Display" />'."\n";
  214:     $Str .= '</td></tr></table>'."\n";
  215: 
  216:     return $Str;
  217: }
  218: 
  219: sub CreateTableHeadings {
  220:     my($cache,$spacing,$infoKeys,$infoHeadings,$sequenceKeys,
  221:        $sequenceHeadings)=@_;
  222: 
  223:     my $Str = '';
  224:     $Str .= '<table border="0" cellpadding="0" cellspacing="0">'."\n";
  225: 
  226:     $Str .= '<tr>'."\n";
  227:     $Str .= &CreateColumnSelectors($infoHeadings, $sequenceHeadings,
  228:                                    $sequenceKeys);
  229:     $Str .= '<td></td></tr>'."\n";
  230: 
  231:     $Str .= '<tr>'."\n";
  232:     my $displayString = '<td align="left"><pre><a href="/adm/statistics?';
  233:     $displayString .= 'sort=LINKDATA">DISPLAYDATA</a>FORMATTING';
  234:     $displayString .= $spacing.'</pre></td>'."\n";
  235:     $Str .= &Apache::lonhtmlcommon::CreateHeadings($cache, 
  236:                                                    $infoKeys,
  237:                                                    $infoHeadings,
  238:                                                    $displayString,
  239:                                                    'preformatted');
  240: 
  241:     $displayString  = '<td align="left"><pre>DISPLAYDATAFORMATTING'.$spacing;
  242:     $displayString .= '</pre></td>'."\n";
  243:     $Str .= &Apache::lonhtmlcommon::CreateHeadings($cache,
  244:                                                    $sequenceKeys,
  245:                                                    $sequenceHeadings,
  246:                                                    $displayString,
  247:                                                    'preformatted');
  248: 
  249:     $Str .= '<td><pre>Total Solved/Total Problems</pre></td>';
  250:     $Str .= '</tr></table>'."\n";
  251: 
  252:     return $Str;
  253: }
  254: 
  255: =pod
  256: 
  257: =item &FormatStudentData()
  258: 
  259: First, FormatStudentInformation is called and prefixes the course information.
  260: This function produces a formatted string of the student's course information.
  261: Each column of data represents all the problems for a given sequence.  For
  262: valid grade data, a link is created for that problem to a submission record
  263: for that problem.
  264: 
  265: =over 4
  266: 
  267: Input: $name, $studentInformation, $ChartDB
  268: 
  269: $name: The name and domain of the current student in name:domain format
  270: 
  271: $studentInformation: A pointer to an array holding the names used to 
  272: remove data from the hash.  They represent 
  273: the name of the data to be removed.
  274: 
  275: $ChartDB: The name of the cached data database which will be tied to that 
  276: database.
  277: 
  278: Output: $Str
  279: 
  280: $Str: Formatted string that is an entire row of the chart.  It is a 
  281: concatenation of student information and student course information.
  282: 
  283: =back
  284: 
  285: =cut
  286: 
  287: sub StudentReport {
  288:     my ($cache,$name,$spacing,$showSequences)=@_;
  289:     my ($username,$domain)=split(':',$name);
  290: 
  291:     my $Str = '';
  292:     if(defined($cache->{$name.':error'})) {
  293:         return $Str;
  294:     }
  295:     if($cache->{$name.':error'} =~ /course/) {
  296:         $Str .= '<b><font color="blue">No course data for student </font>';
  297:         $Str .= '<font color="red">'.$username.'.</font></b><br>';
  298:         return $Str;
  299:     }
  300: 
  301:     my $hasVersion = 'false';
  302:     my $hasFinalData = 'false';
  303:     foreach my $sequence (@$showSequences) {
  304:         my $hasData = 'false';
  305:         my $characterCount=0;
  306:         foreach my $problemID (split(':', $cache->{$sequence.':problems'})) {
  307:             my $problem = $cache->{$problemID.':problem'};
  308:             # All grades (except for versionless parts) are displayed as links
  309:             # to their submission record.  Loop through all the parts for the
  310:             # current problem in the correct order and prepare the output links
  311:             foreach(split(/\:/,$cache->{$sequence.':'.$problemID.
  312:                                         ':parts'})) {
  313:                 if($cache->{$name.':'.$problemID.':NoVersion'} eq 'true' ||
  314:                    $cache->{$name.':'.$problemID.':'.$_.':code'} eq ' ' ||
  315:                    $cache->{$name.':'.$problemID.':'.$_.':code'} eq '') {
  316:                     $Str .= ' ';
  317:                     $characterCount++;
  318:                     next;
  319:                 }
  320:                 $hasVersion = 'true';
  321:                 $hasData = 'true';
  322:                 $Str .= '<a href="/adm/grades?symb=';
  323:                 $Str .= &Apache::lonnet::escape($problem);
  324:                 $Str .= '&student='.$username.'&domain='.$domain;
  325:                 $Str .= '&command=submission">'; 
  326:                 my $code = $cache->{$name.':'.$problemID.':'.$_.':code'};
  327:                 my $tries = $cache->{$name.':'.$problemID.':'.$_.':tries'};
  328:                 if($code eq '*' && $tries < 10 && $tries ne '') {
  329:                     $code = $tries;
  330:                 }
  331:                 $Str .= $code;
  332:                 $Str .= '</a>';
  333:                 $characterCount++;
  334:             }
  335:         }
  336: 
  337:         # Output the number of correct answers for the current sequence.
  338:         # This part takes up 6 character slots, but is formated right 
  339:         # justified.
  340:         my $spacesNeeded=$cache->{$sequence.':columnWidth'}-$characterCount;
  341:         $spacesNeeded -= 3;
  342:         $Str .= (' 'x$spacesNeeded);
  343: 
  344: 	my $outputProblemsCorrect = sprintf("%3d", $cache->{$name.':'.$sequence.
  345:                                                             ':problemsCorrect'});
  346:         if($hasData eq 'true') {
  347:             $Str .= '<font color="#007700">'.$outputProblemsCorrect.'</font>';
  348:             $hasFinalData = 'true';
  349:         } else {
  350:             $Str .= '<font color="#007700">   </font>';
  351:         }
  352:         $Str .= $spacing;
  353:     }
  354: 
  355:     # Output the total correct problems over the total number of problems.
  356:     # I don't like this type of formatting, but it is a solution.  Need
  357:     # a way to dynamically determine the space requirements.
  358:     my $outputProblemsSolved = sprintf("%4d", $cache->{$name.':problemsSolved'});
  359:     my $outputTotalProblems  = sprintf("%4d", $cache->{$name.':totalProblems'});
  360:     if($hasFinalData eq 'true') {
  361:         $Str .= '<font color="#000088">'.$outputProblemsSolved.
  362: 	    ' / '.$outputTotalProblems.'</font>';
  363:     } else {
  364:         $Str .= '<font color="#000088">           </font>';
  365:     }
  366: 
  367:     if($hasVersion eq 'false') {
  368:         $Str = '<b><font color="blue">No course data.</font></b>';
  369:     }
  370: 
  371:     return $Str;
  372: }
  373: 
  374: =pod
  375: 
  376: =item &CreateLegend()
  377: 
  378: This function returns a formatted string containing the legend for the
  379: chart.  The legend describes the symbols used to represent grades for
  380: problems.
  381: 
  382: =cut
  383: 
  384: sub CreateLegend {
  385:     my $Str = "<p><pre>".
  386:               "1..9  correct by student in 1..9 tries\n".
  387:               "   *  correct by student in more than 9 tries\n".
  388: 	      "   +  correct by override\n".
  389:               "   -  incorrect by override\n".
  390: 	      "   .  incorrect attempted\n".
  391: 	      "   #  ungraded attempted\n".
  392:               "      not attempted\n".
  393: 	      "   x  excused".
  394:               "</pre><p>"; 
  395:     return $Str;
  396: }
  397: 
  398: =pod
  399: 
  400: =item &CreateColumnSelectionBox()
  401: 
  402: If there are columns not being displayed then this selection box is created
  403: with a list of those columns.  When selections are made and the page 
  404: refreshed, the columns will be removed from this box and the column is
  405: put back in the chart.  If there is no columns to select, no row is added
  406: to the interface table.
  407: 
  408: =over 4
  409: Input: $CacheData, $headings
  410: 
  411: 
  412: $CacheData: A pointer to a hash tied to the cached data
  413: 
  414: $headings:  An array of the names of the columns for the student information.  
  415: They are used for displaying which columns are missing.
  416: 
  417: Output: $notThere
  418: 
  419: $notThere: The string contains one row of a table.  The first column has the 
  420: name of the selection box.  The second contains the selection box 
  421: which has a size of four.
  422: 
  423: =back
  424: 
  425: =cut
  426: 
  427: sub CreateColumnSelectionBox {
  428:     my ($doNotShow)=@_;
  429: 
  430:     my $notThere = '';
  431:     $notThere .= '<select name="ReselectColumns" size="4" ';
  432:     $notThere .= 'multiple="true">'."\n";
  433: 
  434:     for(my $index=0; $index<$doNotShow->{'count'}; $index++) {
  435:         my $name = $doNotShow->{$index.':name'};
  436:         $notThere .= '<option value="';
  437:         $notThere .= $doNotShow->{$index.':id'}.'">';
  438:         $notThere .= $name.'</option>'."\n";
  439:     }
  440: 
  441:     $notThere .= '</select>';
  442: 
  443:     return $notThere;
  444: }
  445: 
  446: =pod
  447: 
  448: =item &CreateColumnSelectors()
  449: 
  450: This function generates the checkboxes above the column headings.  The 
  451: column will be removed if the checkbox is unchecked.
  452: 
  453: =over 4
  454: 
  455: Input: $CacheData, $headings
  456: 
  457: $CacheData: A pointer to a hash tied to the cached data
  458: 
  459: $headings:  An array of the names of the columns for the student 
  460: information.  They are used to know what are the student information columns
  461: 
  462: Output: $present
  463: 
  464: $present: The string contains the first row of a table.  Each column contains
  465: a checkbox which is left justified.  Currently left justification is used
  466: for consistency of location over the column in which it presides.
  467: 
  468: =back
  469: 
  470: =cut
  471: 
  472: sub CreateColumnSelectors {
  473:     my ($infoHeadings, $sequenceHeadings, $sequenceKeys)=@_;
  474: 
  475:     my $present = '';
  476:     for(my $index=0; $index<(scalar @$infoHeadings); $index++) {
  477:         $present .= '<td align="left">';
  478:         $present .= '<input type="checkbox" checked="on" ';
  479:         $present .= 'name="HeadingColumn'.$infoHeadings->[$index].'" />';
  480:         $present .= '</td>'."\n";
  481:     }
  482: 
  483:     for(my $index=0; $index<(scalar @$sequenceHeadings); $index++) {
  484:         $present .= '<td align="left">';
  485:         $present .= '<input type="checkbox" checked="on" ';
  486:         $present .= 'name="SequenceColumn'.$sequenceKeys->[$index].'" />';
  487:         $present .= '</td>'."\n";
  488:     }
  489: 
  490:     return $present;
  491: }
  492: 
  493: #---- END Student Assessment Web Page ----------------------------------------
  494: 
  495: #---- Student Assessment Worker Functions ------------------------------------
  496: 
  497: sub FindSelectedStudent {
  498:     my($cache, $selectedName, $students)=@_;
  499: 
  500:     if($selectedName eq 'All Students' || 
  501:        $selectedName eq 'No Student Selected') {
  502:         return $selectedName;
  503:     }
  504: 
  505:     for(my $index=0; $index<(scalar @$students); $index++) {
  506:         my $fullname = $cache->{$students->[$index].':fullname'};
  507:         if($fullname eq $selectedName) {
  508:             if($cache->{'StudentAssessmentMove'} eq 'next') {
  509:                 if($index == ((scalar @$students) - 1)) {
  510:                     $selectedName = $students->[0];
  511:                     return $selectedName;
  512:                 } else {
  513:                     $selectedName = $students->[$index+1];
  514:                     return $selectedName;
  515:                 }
  516:             } elsif($cache->{'StudentAssessmentMove'} eq 'previous') {
  517:                 if($index == 0) {
  518:                     $selectedName = $students->[-1];
  519:                     return $selectedName;
  520:                 } else {
  521:                     $selectedName = $students->[$index-1];
  522:                     return $selectedName;
  523:                 }
  524:             } else {
  525:                 $selectedName = $students->[$index];
  526:                 return $selectedName;
  527:             }
  528:             last;
  529:         }
  530:     }
  531: 
  532:     return 'No Student Selected';
  533: }
  534: 
  535: =pod
  536: 
  537: =item &ShouldShowColumn()
  538: 
  539: Determine if a specified column should be shown on the chart.
  540: 
  541: =over 4
  542: 
  543: Input: $cache, $test
  544: 
  545: $cache: A pointer to the hash tied to the cached data
  546: 
  547: $test: The form name of the column (heading.$headingIndex) or 
  548: (sequence.$sequenceIndex)
  549: 
  550: Output: 0 (false), 1 (true)
  551: 
  552: =back
  553: 
  554: =cut
  555: 
  556: sub ShouldShowColumns {
  557:     my ($cache,$headings,$cacheKey)=@_;
  558: 
  559:     my @infoKeys=();
  560:     my @infoHeadings=();
  561: 
  562:     my @sequenceKeys=();
  563:     my @sequenceHeadings=();
  564: 
  565:     my %doNotShow;
  566: 
  567:     my $index;
  568:     my $count = 0;
  569:     my $check = '';
  570:     for($index=0; $index < scalar @$headings; $index++) {
  571:         $check = 'HeadingColumn'.$headings->[$index];
  572:         if($cache->{'HeadingsFound'} =~ /$check/) {
  573:             push(@infoHeadings, $headings->[$index]);
  574:             push(@infoKeys, $cacheKey->[$index]);
  575:         } else {
  576:             $doNotShow{$count.':name'} = $headings->[$index];
  577:             $doNotShow{$count.':id'} = 'HeadingColumn'.$headings->[$index];
  578:             $count++;
  579:         }
  580:     }
  581: 
  582:     foreach my $sequence (split(/\:/,$cache->{'orderedSequences'})) {
  583:         $check = 'SequenceColumn'.$sequence;
  584:         if($cache->{'SequencesFound'} eq 'All Sequences' || 
  585:            $cache->{'SequencesFound'} =~ /$check/) {
  586:             push(@sequenceHeadings, $cache->{$sequence.':title'});
  587:             push(@sequenceKeys, $sequence);
  588:         } else {
  589:             $doNotShow{$count.':name'} = $cache->{$sequence.':title'};
  590:             $doNotShow{$count.':id'} = 'SequenceColumn'.$sequence;
  591:             $count++;
  592:         }
  593:     }
  594: 
  595:     $doNotShow{'count'} = $count;
  596: 
  597:     return (\@infoHeadings, \@infoKeys, \@sequenceHeadings, 
  598:             \@sequenceKeys, \%doNotShow);
  599: }
  600: 
  601: #---- END Student Assessment Worker Functions --------------------------------
  602: 
  603: 1;
  604: __END__

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>