--- loncom/interface/statistics/lonstudentsubmissions.pm 2004/02/20 16:24:20 1.2 +++ loncom/interface/statistics/lonstudentsubmissions.pm 2004/09/01 21:13:04 1.15 @@ -1,6 +1,6 @@ # The LearningOnline Network with CAPA # -# $Id: lonstudentsubmissions.pm,v 1.2 2004/02/20 16:24:20 matthew Exp $ +# $Id: lonstudentsubmissions.pm,v 1.15 2004/09/01 21:13:04 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -43,10 +43,6 @@ my @SubmitButtons = ({ name => 'PrevProb { name => 'NextProblem', text => 'Next Problem' }, { name => 'break'}, - { name => 'ClearCache', - text => 'Clear Caches' }, - { name => 'updatecaches', - text => 'Update Student Data' }, { name => 'SelectAnother', text => 'Choose a different Problem' }, { name => 'Generate', @@ -75,18 +71,8 @@ sub BuildStudentSubmissionsPage { $r->print('<h2>There are no students in the sections selected</h2>'); } # - &Apache::loncoursedata::clear_internal_caches(); - if (exists($ENV{'form.ClearCache'}) || - exists($ENV{'form.updatecaches'}) || - (exists($ENV{'form.firstanalysis'}) && - $ENV{'form.firstanalysis'} ne 'no')) { - &Apache::lonstatistics::Gather_Full_Student_Data($r); - } - if (! exists($ENV{'form.firstanalysis'})) { - $r->print('<input type="hidden" name="firstanalysis" value="yes" />'); - } else { - $r->print('<input type="hidden" name="firstanalysis" value="no" />'); - } + my @CacheButtonHTML = + &Apache::lonstathelpers::manage_caches($r,'Statistics','stats_status'); $r->rflush(); # if (exists($ENV{'form.problemchoice'}) && @@ -100,6 +86,9 @@ sub BuildStudentSubmissionsPage { $r->print(' 'x5); } } + foreach my $html (@CacheButtonHTML) { + $r->print($html.(' 'x5)); + } # $r->print('<hr />'); $r->rflush(); @@ -133,15 +122,19 @@ sub BuildStudentSubmissionsPage { my $resource = $current_problem->{'resource'}; $r->print('<h1>'.$resource->{'title'}.'</h1>'); $r->print('<h3>'.$resource->{'src'}.'</h3>'); - $r->print(&Apache::lonstathelpers::render_resource($resource)); + if ($ENV{'form.renderprob'} eq 'true') { + $r->print(&Apache::lonstathelpers::render_resource($resource)); + } $r->rflush(); my %Data = &Apache::lonstathelpers::get_problem_data ($resource->{'src'}); my $ProblemData = $Data{$current_problem->{'part'}. '.'. $current_problem->{'respid'}}; - &prepare_excel_output($r,$current_problem, + &prepare_html_output($r,$current_problem, $ProblemData,\@Students); +# &prepare_excel_output($r,$current_problem, +# $ProblemData,\@Students); } $r->print('<hr />'); } else { @@ -153,30 +146,308 @@ sub BuildStudentSubmissionsPage { } } - ######################################################### ######################################################### ## -## Excel output of student answers and correct answers +## prepare_html_output ## ######################################################### ######################################################### -sub prepare_excel_output { +sub prepare_html_output { my ($r,$problem,$ProblemData,$Students) = @_; + my $c = $r->connection(); my ($resource,$respid,$partid) = ($problem->{'resource'}, $problem->{'respid'}, $problem->{'part'}); - $r->print('<h2>'. - &mt('Preparing Excel spreadsheet of student responses'). - '</h2>'); + $r->print('<h2>'.&mt('Student Responses').'</h2>'); + # + if ($ENV{'form.correctans'} eq 'true') { + &Apache::lonstathelpers::GetStudentAnswers($r,$problem,$Students, + 'Statistics', + 'stats_status'); + } + # + $r->rflush(); + my $response_type; + for (my $i=0; + $i<scalar(@{$resource->{'partdata'}->{$partid}->{'ResponseIds'}}); + $i++) { + if($resource->{'partdata'}->{$partid}->{'ResponseIds'}->[$i] eq $respid){ + $response_type = + $resource->{'partdata'}->{$partid}->{'ResponseTypes'}->[$i]; + last; + } + } + if (! defined($response_type)) { + $r->print('<h2>'.&mt('Unable to determine response type').'</h2>'); + } else { + my $count = 0; + my $header; + if ($response_type eq 'essay') { + $header = &html_essay_header(); + } elsif ($response_type eq 'radiobutton') { + $header = &html_radiobutton_header(); + } elsif ($response_type eq 'option') { + $header = &html_option_header(); + } else { + $header = &html_generic_header(); + } + $header = '<tr>'.$header.'</tr>'; + # + $r->print($/.'<table>'.$/.$header.$/); + foreach my $student (@$Students) { + if ($count >= 50) { + $r->print('</table>'.$/.'<table>'.$/.$header.$/); + $count = 0; + } + last if ($c->aborted()); + my $results = &Apache::loncoursedata::get_response_data_by_student + ($student,$resource->{'symb'},$respid); + next if (! defined($results) || ref($results) ne 'ARRAY'); + for (my $i=0;$i<scalar(@$results);$i++) { + my $response = $results->[$i]; + if ($ENV{'form.last_sub_only'} eq 'true' && + $i < (scalar(@$results)-1)) { + next; + } + my $data; + $data->{'sname'} = $student->{'username'}.'@'. + $student->{'domain'}; + $data->{'time'} = &Apache::lonlocal::locallocaltime + ($response->[&Apache::loncoursedata::RDs_timestamp()]); + $data->{'attempt'} = + $response->[&Apache::loncoursedata::RDs_tries()]; + $data->{'submission'} = + $response->[&Apache::loncoursedata::RDs_submission()]; + $data->{'correct'} = $student->{'answer'}; + my $row; + if ($response_type eq 'essay') { + $row = &html_essay($data); + } elsif ($response_type eq 'radiobutton') { + $row = &html_radiobutton($data); + } elsif ($response_type eq 'option') { + $row = &html_option($data); + } else { + $row = &html_generic($data); + } + $r->print($row.$/); + $count++; + } + } + $r->print('</table>'.$/); + } + return; +} + +##################################################### +## +## HTML helper routines +## +##################################################### +my @FullHeaders = ( + {name=>'sname', + display=>'Student'}, + {name => 'id', + display => 'Id'}, + {name => 'time', + display =>'Time'}, + {name => 'attempt', + display =>'Attempt'}, + {name => 'grading', + display =>'Grading'}, + ); + +sub html_essay_header { + my $header; + if ($ENV{'form.subdata'} eq 'true') { + foreach (@FullHeaders) { + $header .= '<th>'.&mt($_->{'display'}).'</th>'; + } + } else { + $header = '<th>'.&mt('Username and Submission').'</th>'; + } + return $header; +} + +sub html_essay { + my ($data)=@_; # - &GetStudentAnswers($r,$problem,$Students); + $data->{'submission'} =~ s|\\r\\n|$/|g; + $data->{'submission'} = + &HTML::Entities::encode($data->{'submission'},'<>&"'); + $data->{'submission'} =~ s|$/\s*$/|$/</p><p>$/|g; + $data->{'submission'} =~ s|\\||g; + $data->{'submission'} = '<p>'.$data->{'submission'}.'</p>'; + # + my $Str = '<tr>'; + if ($ENV{'form.subdata'} eq 'true') { + $Str .= + '<td><b>'.$data->{'sname'}.'</b></td>'. + '<td>'.$data->{'time'}.'</td>'. + '<td>'.$data->{'attempt'}.'</td>'; + $Str .= '</tr>'; + $Str .= '<tr><td colspan="'.scalar(@FullHeaders).'">'. + $data->{'submission'}.'</td>'; + } else { + $Str .= '<td><b>'.$data->{'sname'}.'</b>'. + $data->{'submission'}.'</td>'; + } + if ($ENV{'form.correctans'} eq 'true') { + $Str .= '</tr>'; + if (defined($data->{'correct'}) && $data->{'correct'} !~ /^\s*$/) { + $Str .= '<tr><td colspan="'.scalar(@FullHeaders).'">'. + '<b>'.&mt('Correct Answer:').'</b>'.$data->{'correct'}.'</td>'; + } + } + $Str .= '</tr>'; + # + return $Str; +} + +sub html_radiobutton_header { + my $header; + if ($ENV{'form.subdata'} eq 'true') { + foreach (@FullHeaders) { + $header .= '<th>'.&mt($_->{'display'}).'</th>'; + } + } else { + $header = + '<th>'.&mt('Username').'</th>'; + } + $header .='<th>'.&mt('Submission').'</th>'; + if ($ENV{'form.correctans'} eq 'true') { + $header .= '<th>'.&mt('Correct').'</th>'; + } + return $header; +} + +sub html_radiobutton { + my ($data)=@_; + # + $data->{'submission'} =~ s/=([^=])$//; + # + my $Str = '<tr>'; + if ($ENV{'form.subdata'} eq 'true') { + $Str .= + '<td>'.'<b>'.$data->{'sname'}.'</b></td>'. + '<td>'.$data->{'time'}.'</td>'. + '<td>'.$data->{'attempt'}.'</td>'; + $Str .= '<td>'.$data->{'submission'}.'</td>'; + if ($ENV{'form.correctans'} eq 'true') { + $Str .= '<td>'.$data->{'correct'}.'</td>'; + } + } else { + $Str .= '<td><b>'.$data->{'sname'}.'</b></td>'; + $Str .= '<td>'.$data->{'submission'}.'</td>'; + if ($ENV{'form.correctans'} eq 'true') { + $Str .= '<td>'.$data->{'correct'}.'</td>'; + } + } + $Str .= '</tr>'; # - my @Columns = ( 'username','domain','attempt','time', - 'submission','correct', 'grading','awarded','weight', - 'score'); - my $awarded_col = 7; - my $weight_col = 8; + return $Str; +} + +sub html_generic_header { + my $header; + if ($ENV{'form.subdata'} eq 'true') { + foreach (@FullHeaders) { + $header .= '<th>'.&mt($_->{'display'}).'</th>'; + } + } else { + $header = + '<th>'.&mt('Username').'</th>'; + } + $header .= '<th>'.&mt('Submission').'</th>'; + if ($ENV{'form.correctans'} eq 'true') { + $header .= '<th>'.&mt('Correct').'</th>'; + } + return $header; +} + +sub html_generic { + my ($data)=@_; + $data->{'submission'} = &Apache::lonnet::unescape($data->{'submission'}); + my $Str = '<tr>'; + if ($ENV{'form.subdata'} eq 'true') { + $Str .= + '<td><b>'.$data->{'sname'}.'</b></td>'. + '<td>'.$data->{'time'}.'</td>'. + '<td>'.$data->{'attempt'}.'</td>'; + } else { + $Str .= '<td><b>'.$data->{'sname'}.'</b></td>'; + } + $Str .= '<td>'.$data->{'submission'}.'</td>'; + if ($ENV{'form.correctans'} eq 'true') { + $Str .= '<td>'.$data->{'correct'}.'</td>'; + } + $Str .= '</tr>'; + return $Str; +} + +sub html_option_header { + my $header; + if ($ENV{'form.subdata'} eq 'true') { + foreach (@FullHeaders) { + $header .= '<th>'.&mt($_->{'display'}).'</th>'; + } + } else { + $header = + '<th>'.&mt('Username').'</th>'; + } + $header .= '<th>'.&mt('Submission').'</th>'; + if ($ENV{'form.correctans'} eq 'true') { + $header .= '<th>'.&mt('Correct').'</th>'; + } + return $header; +} + +sub html_option { + my ($data)=@_; + $data->{'submission'} = '<ul class="studentans">'. + '<li>'.join('</li><li>', + map { + &Apache::lonnet::unescape($_) ; + } sort split('&',$data->{'submission'})). + '</li><ul>'; + $data->{'correct'} = '<ul class="correctans">'. + '<li>'.join('</li><li>', + map { + &Apache::lonnet::unescape($_) ; + } sort split('&',$data->{'correct'})).'</li></ul>'; + my $Str = '<tr>'; + if ($ENV{'form.subdata'} eq 'true') { + $Str .= + '<td><b>'.$data->{'sname'}.'</b></td>'. + '<td>'.$data->{'time'}.'</td>'. + '<td>'.$data->{'attempt'}.'</td>'; + } else { + $Str .= '<td><b>'.$data->{'sname'}.'</b></td>'; + } + $Str .= '<td>'.$data->{'submission'}.'</td>'; + if ($ENV{'form.correctans'} eq 'true') { + $Str .= '<td>'.$data->{'correct'}.'</td>'; + } + $Str .= '</tr>'; + return $Str; +} + + +=pod + + my @Columns; + push(@Columns,'username'); + push(@Columns,'domain'); + push(@Columns,'attempt'); + push(@Columns,'time'); + push(@Columns,'submission'); + if ($ENV{'form.correctans'} eq 'true') { push(@Columns,'correct'); } + push(@Columns,'grading'); + push(@Columns,'awarded'); + my $awarded_col = $#Columns; + push(@Columns,'weight'); + my $weight_col = $#Columns; + push(@Columns,'score'); # # Create excel worksheet my $filename = '/prtspool/'. @@ -209,12 +480,13 @@ sub prepare_excel_output { # # Populate the worksheet with the student data foreach my $student (@$Students) { + last if ($c->aborted()); my $results = &Apache::loncoursedata::get_response_data_by_student ($student,$resource->{'symb'},$respid); my %row; $row{'username'} = $student->{'username'}; $row{'domain'} = $student->{'domain'}; - $row{'correct'} = $student->{'answer'}; + $row{'correct'} = $student->{'answer'}; $row{'weight'} = &Apache::lonnet::EXT ('resource.'.$partid.'.weight',$resource->{'symb'}, undef,undef,undef); @@ -235,7 +507,12 @@ sub prepare_excel_output { } $rows_output++; } else { - foreach my $response (@$results) { + for (my $i=0;$i<scalar(@$results);$i++) { + my $response = $results->[$i]; + if ($ENV{'form.last_sub_only'} eq 'true' && + $i < (scalar(@$results)-1)) { + next; + } delete($row{'time'}); delete($row{'attempt'}); delete($row{'submission'}); @@ -284,38 +561,182 @@ sub prepare_excel_output { $r->print('<p><a href="'.$filename.'">'. &mt('Your Excel spreadsheet.'). '</a></p>'."\n"); + $r->print('<script>'. + 'window.document.Statistics.stats_status.value="'. + 'Done compiling spreadsheet. See link below to download.'. + '";</script>'); + $r->rflush(); + return; + } -sub GetStudentAnswers { - my ($r,$problem,$Students) = @_; - my %Answers; - my ($resource,$partid,$respid) = ($problem->{'resource'}, - $problem->{'part'}, - $problem->{'respid'}); - # Open progress window - my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin - ($r,'Student Answer Compilation Status', - 'Student Answer Compilation Progress', scalar(@$Students)); - $r->print("<table>\n"); +=cut + +######################################################### +######################################################### +## +## Excel output of student answers and correct answers +## +######################################################### +######################################################### +sub prepare_excel_output { + my ($r,$problem,$ProblemData,$Students) = @_; + my $c = $r->connection(); + my ($resource,$respid,$partid) = ($problem->{'resource'}, + $problem->{'respid'}, + $problem->{'part'}); + $r->print('<h2>'. + &mt('Preparing Excel spreadsheet of student responses'). + '</h2>'. + '<p>'. + &mt('See the status bar above for student answer computation progress'). + '</p>'); + # + if ($ENV{'form.correctans'} eq 'true') { + &Apache::lonstathelpers::GetStudentAnswers($r,$problem,$Students, + 'Statistics', + 'stats_status'); + } + # + $r->print('<script>'. + 'window.document.Statistics.stats_status.value="'. + 'Done computing student answers. Compiling spreadsheet.'. + '";</script>'); $r->rflush(); + my @Columns; + push(@Columns,'username'); + push(@Columns,'domain'); + push(@Columns,'attempt'); + push(@Columns,'time'); + push(@Columns,'submission'); + if ($ENV{'form.correctans'} eq 'true') { push(@Columns,'correct'); } + push(@Columns,'grading'); + push(@Columns,'awarded'); + my $awarded_col = $#Columns; + push(@Columns,'weight'); + my $weight_col = $#Columns; + push(@Columns,'score'); + # + # Create excel worksheet + my $filename = '/prtspool/'. + $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'. + time.'_'.rand(1000000000).'.xls'; + my $workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename); + if (! defined($workbook)) { + $r->log_error("Error creating excel spreadsheet $filename: $!"); + $r->print('<p>'.&mt("Unable to create new Excel file. ". + "This error has been logged. ". + "Please alert your LON-CAPA administrator"). + '</p>'); + return undef; + } + # + $workbook->set_tempdir('/home/httpd/perl/tmp'); + # + my $format = &Apache::loncommon::define_excel_formats($workbook); + my $worksheet = $workbook->addworksheet('Student Submission Data'); + # + # Make sure we get new weight data instead of data on a 10 minute delay + &Apache::lonnet::clear_EXT_cache_status(); + # + # Put on the standard headers and whatnot + my $rows_output=0; + $worksheet->write($rows_output++,0,$resource->{'title'},$format->{'h1'}); + $worksheet->write($rows_output++,0,$resource->{'src'},$format->{'h3'}); + $rows_output++; + $worksheet->write_row($rows_output++,0,\@Columns,$format->{'bold'}); + # + # Populate the worksheet with the student data foreach my $student (@$Students) { - my $sname = $student->{'username'}; - my $sdom = $student->{'domain'}; - my $answer = &Apache::lonstathelpers::analyze_problem_as_student - ($resource,$sname,$sdom,$partid,$respid); - &Apache::lonnet::logthis('answer = "'.$answer.'"'); - &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state, - &mt('last student')); - $student->{'answer'} = $answer; + last if ($c->aborted()); + my $results = &Apache::loncoursedata::get_response_data_by_student + ($student,$resource->{'symb'},$respid); + my %row; + $row{'username'} = $student->{'username'}; + $row{'domain'} = $student->{'domain'}; + $row{'correct'} = $student->{'answer'}; + $row{'weight'} = &Apache::lonnet::EXT + ('resource.'.$partid.'.weight',$resource->{'symb'}, + undef,undef,undef); + if (! defined($results) || ref($results) ne 'ARRAY') { + $row{'score'} = '='. + &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell + ($rows_output,$awarded_col) + .'*'. + &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell + ($rows_output,$weight_col); + my $cols_output = 0; + foreach my $col (@Columns) { + if (! exists($row{$col})) { + $cols_output++; + next; + } + $worksheet->write($rows_output,$cols_output++,$row{$col}); + } + $rows_output++; + } else { + for (my $i=0;$i<scalar(@$results);$i++) { + my $response = $results->[$i]; + if ($ENV{'form.last_sub_only'} eq 'true' && + $i < (scalar(@$results)-1)) { + next; + } + delete($row{'time'}); + delete($row{'attempt'}); + delete($row{'submission'}); + delete($row{'awarded'}); + delete($row{'grading'}); + delete($row{'score'}); + my %row_format; + # + # Time is handled differently + $row{'time'} = &Apache::lonstathelpers::calc_serial + ($response->[&Apache::loncoursedata::RDs_timestamp()]); + $row_format{'time'}=$format->{'date'}; + # + $row{'attempt'} = $response->[ + &Apache::loncoursedata::RDs_tries()]; + $row{'submission'} = $response->[ + &Apache::loncoursedata::RDs_submission()]; + if ($row{'submission'} =~ m/^=/) { + # This will be interpreted as a formula. That is bad! + $row{'submission'} = " ".$row{'submission'}; + } + $row{'grading'} = $response->[ + &Apache::loncoursedata::RDs_awarddetail()]; + $row{'awarded'} = $response->[ + &Apache::loncoursedata::RDs_awarded()]; + $row{'score'} = '='. + &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell + ($rows_output,$awarded_col) + .'*'. + &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell + ($rows_output,$weight_col); + my $cols_output = 0; + foreach my $col (@Columns) { + $worksheet->write($rows_output,$cols_output++,$row{$col}, + $row_format{$col}); + } + $rows_output++; + } + } # End of else clause on if (! defined($results) .... } - $r->print("</table>\n"); + # + # Close the excel file + $workbook->close(); + # + # Write a link to allow them to download it + $r->print('<p><a href="'.$filename.'">'. + &mt('Your Excel spreadsheet.'). + '</a></p>'."\n"); + $r->print('<script>'. + 'window.document.Statistics.stats_status.value="'. + 'Done compiling spreadsheet. See link below to download.'. + '";</script>'); $r->rflush(); - # close progress window - &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); return; } - ######################################################### ######################################################### ## @@ -329,14 +750,14 @@ sub CreateInterface { my $Str = ''; $Str .= &Apache::lonhtmlcommon::breadcrumbs (undef,'Student Submission Reports'); + $Str .= '<p>'; $Str .= '<table cellspacing="5">'."\n"; $Str .= '<tr>'; $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>'; $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>'; - $Str .= '<td align="center"> </td>'; + $Str .= '<td> </td>'; $Str .= '</tr>'."\n"; - ## - ## + # $Str .= '<tr><td align="center">'."\n"; $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5); $Str .= '</td>'; @@ -345,29 +766,57 @@ sub CreateInterface { $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5); $Str .= '</td>'; # - my $only_seq_with_assessments = sub { - my $s=shift; - if ($s->{'num_assess'} < 1) { - return 0; - } else { - return 1; - } - }; - ## - ## + # Render problem checkbox + my $prob_checkbox = '<input type="checkbox" name="renderprob" '; + if (exists($ENV{'form.renderprob'}) && $ENV{'form.renderprob'} eq 'true') { + $prob_checkbox .= 'checked '; + } + $prob_checkbox .= 'value="true" />'; + # + # Compute correct answers checkbox + my $ans_checkbox = '<input type="checkbox" name="correctans" '; + if (exists($ENV{'form.correctans'}) && $ENV{'form.correctans'} eq 'true') { + $ans_checkbox .= 'checked '; + } + $ans_checkbox .= 'value="true" />'; + # + # Only show last submission checkbox + my $last_sub_checkbox = '<input type="checkbox" name="last_sub_only" '; + if (exists($ENV{'form.last_sub_only'}) && + $ENV{'form.last_sub_only'} eq 'true') { + $last_sub_checkbox .= 'checked '; + } + $last_sub_checkbox.= 'value="true" />'; + # + # extra submission data checkbox + my $subdata_checkbox = '<input type="checkbox" name="subdata" '; + if (exists($ENV{'form.subdata'}) && + $ENV{'form.subdata'} eq 'true') { + $subdata_checkbox .= 'checked '; + } + $subdata_checkbox.= 'value="true" />'; + # + $Str .= '<td align="right" halign="top">'.'<label><b>'. + &mt('show problem [_1]',$prob_checkbox).'</b></label><br />'. + '<label><b>'. + &mt('compute correct answers [_1]',$ans_checkbox).'</b></label><br />'. + '<label><b>'. + &mt('final answer only [_1]',$last_sub_checkbox).'</b></label><br />'. + '<label><b>'.&mt('show extra submission data [_1]',$subdata_checkbox). + '</b></label><br />'. + '</td>'; + # $Str .= '</tr>'."\n"; $Str .= '</table>'."\n"; # - # We do this to make sure the sequence information is initialized - &Apache::lonstatistics::MapSelect('Maps','multiple,all',5, - $only_seq_with_assessments); - - # + $Str .= '<nobr>'.&mt('Status: [_1]', + '<input type="text" '. + 'name="stats_status" size="60" value="" />'). + '</nobr>'.'</p>'; + ## return $Str; } - - 1; __END__