--- loncom/interface/statistics/lonstudentsubmissions.pm 2004/09/08 14:58:33 1.17 +++ loncom/interface/statistics/lonstudentsubmissions.pm 2004/11/08 15:21:38 1.27 @@ -1,6 +1,6 @@ # The LearningOnline Network with CAPA # -# $Id: lonstudentsubmissions.pm,v 1.17 2004/09/08 14:58:33 matthew Exp $ +# $Id: lonstudentsubmissions.pm,v 1.27 2004/11/08 15:21:38 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -38,12 +38,7 @@ use HTML::Entities(); use Time::Local(); use Spreadsheet::WriteExcel(); -my @SubmitButtons = ({ name => 'PrevProblem', - text => 'Previous Problem' }, - { name => 'NextProblem', - text => 'Next Problem' }, - { name => 'break'}, - { name => 'SelectAnother', +my @SubmitButtons = ({ name => 'SelectAnother', text => 'Choose a different Problem' }, { name => 'Generate', text => 'Generate Report'}, @@ -72,7 +67,8 @@ sub BuildStudentSubmissionsPage { } # my @CacheButtonHTML = - &Apache::lonstathelpers::manage_caches($r,'Statistics','stats_status'); + &Apache::lonstathelpers::manage_caches($r,'Statistics','stats_status', + '

'.&mt('Loading student data').'

'); $r->rflush(); # if (exists($ENV{'form.problemchoice'}) && @@ -90,56 +86,50 @@ sub BuildStudentSubmissionsPage { $r->print($html.(' 'x5)); } # - $r->print('
'); + $r->print('
'.$/); $r->rflush(); # - # Determine which problem we are to analyze - my $current_problem = &Apache::lonstathelpers::get_target_from_id - ($ENV{'form.problemchoice'}); - # - my ($prev,$curr,$next) = - &Apache::lonstathelpers::get_prev_curr_next($current_problem, - '.', - 'response', - ); - if (exists($ENV{'form.PrevProblem'}) && defined($prev)) { - $current_problem = $prev; - } elsif (exists($ENV{'form.NextProblem'}) && defined($next)) { - $current_problem = $next; - } else { - $current_problem = $curr; + # Determine which problems we are to analyze + my @Symbs = + &Apache::lonstathelpers::get_selected_symbs('problemchoice'); + foreach my $selected (@Symbs) { + $r->print(''.$/); } # - # Store the current problem choice and send it out in the form - $ENV{'form.problemchoice'} = - &Apache::lonstathelpers::make_target_id($current_problem); - $r->print(''); - # - if (! defined($current_problem->{'resource'})) { + # Get resource objects + my $navmap = Apache::lonnavmaps::navmap->new(); + if (!defined($navmap)) { + $r->print('

'.&mt("Internal error").'

'); + return; + } + my %already_seen; + my @Problems; + foreach my $symb (@Symbs) { + my $resource = $navmap->getBySymb($symb); + push(@Problems,$resource); + } + # + if (! scalar(@Problems) || ! defined($Problems[0])) { $r->print('resource is undefined'); } else { - my $resource = $current_problem->{'resource'}; - $r->print('

'.$resource->{'title'}.'

'); - $r->print('

'.$resource->{'src'}.'

'); - 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'}}; + if (scalar(@Problems) == 1) { + my $resource = $Problems[0]; + $r->print('

'.$resource->title.'

'); + $r->print('

'.$resource->src.'

'); + if ($ENV{'form.renderprob'} eq 'true') { + $r->print( + &Apache::lonstathelpers::render_resource({src => $resource->src}) + ); + $r->rflush(); + } + } if ($ENV{'form.output'} eq 'excel') { - &prepare_excel_output($r,$current_problem, - $ProblemData,\@Students); + &prepare_excel_output($r,\@Problems,\@Students); } elsif ($ENV{'form.output'} eq 'csv') { - &prepare_csv_output($r,$current_problem, - $ProblemData,\@Students); + &prepare_csv_output($r,\@Problems,\@Students); } else { - &prepare_html_output($r,$current_problem, - $ProblemData,\@Students); + &prepare_html_output($r,\@Problems,\@Students); } } $r->print('
'); @@ -147,160 +137,236 @@ sub BuildStudentSubmissionsPage { $r->print(''); $r->print(' 'x5); - $r->print('

'.&mt('Please select a problem to analyze').'

'); - $r->print(&Apache::lonstathelpers::ProblemSelector('.')); - } -} - -######################################################### -######################################################### - -my @DefaultColumns = - ( - {name=>'username', - display=>'Student'}, - {name=>'domain', - display=>'Domain'}, - {name => 'id', - display => 'Id'}, - {name => 'time', - display =>'Time'}, - {name => 'attempt', - display =>'Attempt'}, - {name => 'awarddetail', - display =>'Awarddetail'}, - {name => 'awarded', - display =>'Award'}, - # FIXME: Probably need to add score - ); - -sub get_response_type { - my ($resource,$partid,$respid) = @_; - my $response_type = ''; - for (my $i=0; - $i{'partdata'}->{$partid}->{'ResponseIds'}}); - $i++) { - if($resource->{'partdata'}->{$partid}->{'ResponseIds'}->[$i] eq $respid){ - $response_type = - $resource->{'partdata'}->{$partid}->{'ResponseTypes'}->[$i]; - last; - } + $r->print('

'. + &mt('Computing correct answers greatly increasese the amount of time required to prepare a report.'). + '

'); + $r->print('

'. + &mt('please select problems and use the Prepare Report button to continue.'). + '

'); + $r->print(&Apache::lonstathelpers::MultipleProblemSelector + (undef,'problemchoice','Statistics')); } - return $response_type; } - ######################################################### ######################################################### ## -## prepare_html_output +## HTML Output Routines ## ######################################################### ######################################################### sub prepare_html_output { - my ($r,$problem,$ProblemData,$Students) = @_; + my ($r,$problems,$students) = @_; my $c = $r->connection(); - my ($resource,$respid,$partid) = ($problem->{'resource'}, - $problem->{'respid'}, - $problem->{'part'}); # - if ($ENV{'form.correctans'} eq 'true') { - $r->print('

'.&mt('Generating Correct Answers').'

'); - &Apache::lonstathelpers::GetStudentAnswers($r,$problem,$Students, - 'Statistics', - 'stats_status'); + # Set a flag for the case when there is just one problem + my $single_response = 0; + if (scalar(@$problems) == 1 && + $problems->[0]->countResponses == 1) { + $single_response = 1; } # - $r->print('

'.&mt('Student Responses').'

'); - # - $r->rflush(); - my $response_type = &get_response_type($resource,$partid,$respid); - if (! defined($response_type)) { - $r->print('

'.&mt('Unable to determine response type').'

'); - return; - } - my $count = 0; - my @Columns; - if (exists($ENV{'form.concise'}) && $ENV{'form.concise'} eq 'true') { - foreach (@DefaultColumns) { - if ($_->{'name'} =~ /^(username|domain|id)$/){ - push(@Columns,$_); + # Compute the number of columns per response + my @response_headers = ('Submission'); + if ($ENV{'form.correctans'} eq 'true') { + push(@response_headers,'Correct'); + } + if ($ENV{'form.prob_status'} eq 'true') { + push(@response_headers,'Award Detail'); + push(@response_headers,'Time'); + push(@response_headers,'Attempt'); + push(@response_headers,'Awarded'); + } + my $response_multiplier = scalar(@response_headers); + # + # Create the table header + my @student_columns = ('username','domain','id'); + # + my %headers; + my $student_column_count = scalar(@student_columns); + $headers{'problem'} = qq{\ }; + foreach (@student_columns) { + $headers{'student'}.= ''.ucfirst($_).''; + } + # + # we put the headers into the %headers hash + my $total_col = scalar(@student_columns); + my $nonempty_part_headers = 0; + foreach my $prob (@$problems) { + my $prob_span = 0; + my $single_part = 0; + if (scalar(@{$prob->parts}) == 1) { + $single_part = 1; + } + foreach my $partid (@{$prob->parts}) { + my $part_span = 0; + my $responses = [$prob->responseIds($partid)]; + my $resptypes = [$prob->responseType($partid)]; + for (my $i=0;$i[$i] eq 'essay') { + # do something clever, like shut up. + } else { + $total_col += scalar(@response_headers); + $headers{'response'} .= + ''. + &mt('Response [_1]',$responses->[$i]).''; + $headers{'student'}.= ''.join('', + @response_headers). + ''; + $part_span += scalar(@response_headers); + } + } + if (! $single_part) { + my $tmpname = $partid; + if ($partid =~/^\d+$/) { + $tmpname = $prob->part_display($partid); + } + $headers{'part'} .= qq{$tmpname}; + $nonempty_part_headers = 1; + } else { + $headers{'part'} .= qq{ }; } + $prob_span += $part_span; } - } else { - @Columns = @DefaultColumns; - } - my $header = ''.$/.&html_headers(\@Columns); - if ($response_type eq 'essay') { - $header .= &html_essay_headers(); - } elsif ($response_type eq 'option') { - $header .= &html_option_headers(); - } else { - $header .= &html_generic_headers(); - } - $header = ''.$header.''; - # - $r->print($/.$header.$/); - foreach my $student (@$Students) { - if ($count >= 50) { - $r->print('
'.$/.$header.$/); + my $title = $prob->compTitle; + if ($prob_span > 0) { + $headers{'problem'}.= qq{$title}; + } elsif ($single_response) { + $prob_span = scalar(@student_columns); + $headers{'problem'} = qq{$title}; + } + } + if (exists($headers{'part'})) { + $headers{'part'} = qq{\ }. + $headers{'part'}; + } + if (exists($headers{'response'})) { + $headers{'response'}= + qq{\ }. + $headers{'response'}; + } + my $full_header = $/.''.$/; + $full_header .= ''.$headers{'problem'}.''.$/; + if ($nonempty_part_headers) { + $full_header .= ''.$headers{'part'}.''.$/; + } + $full_header .= ''.$headers{'response'}.''.$/; + $full_header .= ''.$headers{'student'}.''.$/; + # + # Main loop + my $count; + $r->print($/.$full_header.$/); + my $row_class = 'odd'; # css + foreach my $student (@$students) { + my $student_row_data; + if ($count++ >= 30) { + $r->print('
'.$/.$full_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[$i]; - if ($ENV{'form.last_sub_only'} eq 'true' && - $i < (scalar(@$results)-1)) { - next; - } - my $data; - $data->{'username'} = $student->{'username'}; - $data->{'domain'} = $student->{'domain'}; - $data->{'id'} = $student->{'id'}; - $data->{'fullname'} = $student->{'fullanem'}; - $data->{'status'} = $student->{'status'}; - $data->{'time'} = &Apache::lonlocal::locallocaltime - ($response->[&Apache::loncoursedata::RDs_timestamp()]); - $data->{'attempt'} = - $response->[&Apache::loncoursedata::RDs_tries()]; - $data->{'awarded'} = - $response->[&Apache::loncoursedata::RDs_awarded()]; - $data->{'awarddetail'} = - $response->[&Apache::loncoursedata::RDs_awarddetail()]; - my $rowextra = 'bgcolor="#CCCCCC"'; - if ($count % 2 == 1) { - $rowextra = 'bgcolor="#EEEEEE"'; - } - my $row = ''; - foreach my $col (@Columns) { - $row .= ''. - $data->{$col->{'name'}}.''; - } - if ($response_type eq 'essay') { - $row .= &html_essay_results - ($response->[&Apache::loncoursedata::RDs_submission()], - $student->{'answer'}, - scalar(@Columns),$rowextra); - } elsif ($response_type eq 'option') { - $row .= &html_option_results - ($response->[&Apache::loncoursedata::RDs_submission()], - $student->{'answer'}, - scalar(@Columns),$rowextra); - } else { - $row .= &html_generic_results - ($response->[&Apache::loncoursedata::RDs_submission()], - $student->{'answer'}, - scalar(@Columns),$rowextra); + foreach my $field (@student_columns) { + $student_row_data .= + ''.$student->{$field}.''; + } + # + # Figure out what it is we need to output for this student + my @essays; + my %problem_data; + my $maxrow; + foreach my $prob (@$problems) { + my $symb = $prob->symb; + $problem_data{$symb}={}; + foreach my $partid (@{$prob->parts}) { + my @responses = $prob->responseIds($partid); + my @response_type = $prob->responseType($partid); + for (my $i=0;$i<=$#responses;$i++) { + my $respid = $responses[$i]; + my $resptype = $response_type[$i]; + my $width = scalar(@response_headers); + $problem_data{$symb}->{$partid}->{$respid}={}; + my $resp_data = $problem_data{$symb}->{$partid}->{$respid}; + $resp_data->{'fake'} = qq{ }; + my $results = + &Apache::loncoursedata::get_response_data_by_student + ($student,$prob->symb(),$respid); + if (! defined($results)) { + $results = []; + } + # + if (scalar(@$results) > $maxrow && $resptype ne 'essay') { + $maxrow = scalar(@$results); + } + for (my $j=scalar(@$results)-1;$j>=0;$j--) { + my $response = $results->[$j]; + if ($ENV{'form.all_sub'} ne 'true') { + next if ($j ne scalar(@$results)-1); + } + if ($resptype eq 'essay') { + push(@essays, + &html_essay_results($student, + $prob,$partid,$respid, + $response, + $single_response). + ''); + } else { + push(@{$resp_data->{'real'}}, + &html_results($student, + $prob,$partid,$respid, + $response,$resptype)); + } + } + } # end of $i loop + } # end of partid loop + } # end of prob loop + # + # if there is no data, skip this student. + next if (! $maxrow && ! scalar(@essays)); + # + # Go through the problem data and output a row. + if ($row_class eq 'even') { + $row_class = 'odd'; + } else { + $row_class = 'even'; + } + my $printed_something; + for (my $rows_output = 0;$rows_output<$maxrow;$rows_output++) { + my $html; + my $no_data = 1; + foreach my $prob (@$problems) { + my $symb = $prob->symb; + foreach my $partid (@{$prob->parts}) { + my @responses = $prob->responseIds($partid); + my @response_type = $prob->responseType($partid); + for (my $i=0;$i<=$#responses;$i++) { + my $respid = $responses[$i]; + my $resp_data = + $problem_data{$symb}->{$partid}->{$respid}; + next if ($response_type[$i] eq 'essay'); + if (defined($resp_data->{'real'}->[$rows_output])) { + $html .= $resp_data->{'real'}->[$rows_output]; + $no_data = 0; + } else { + $html .= $resp_data->{'fake'}; + } + } + } } - $row .= ''; - $r->print($row.$/); - $count++; + if (! $no_data) { + $r->print(qq{$student_row_data$html}.$/); + $printed_something=1; + } + } + if (@essays) { + my $tr = qq{}; + my $td = qq{}; + if (! $printed_something) { + $r->print($tr.$student_row_data.''.$/); + } + $r->print($tr.$td. + join(''.$/.$tr.$td,@essays).''.$/); + undef(@essays); } - } - $r->print(''.$/); + } # end of student loop return; } @@ -309,471 +375,532 @@ sub prepare_html_output { ## HTML helper routines ## ##################################################### -sub html_headers { - my ($Columns) = @_; - my $Str; - foreach my $column (@$Columns) { - $Str .= ''.$column->{'display'}.''; - } - return $Str; -} - -sub html_essay { - my ($submission,$correct,$tablewidth,$rowextra)=@_; +sub html_essay_results { + my ($student,$prob,$partid,$respid,$response,$single_response)=@_; # - $submission =~ s|\\r\\n|$/|g; - $submission = &HTML::Entities::encode($submission,'<>&"'); - $submission =~ s|$/\s*$/|$/

$/|g; - $submission =~ s|\\||g; - $submission = '

'.$submission.'

'; + my $submission =$response->[&Apache::loncoursedata::RDs_submission()]; + $submission = &html_format_sub($submission,'essay'); # - my $Str = ''. - ''.$submission.''; + my $correct = ''; if ($ENV{'form.correctans'} eq 'true') { - $Str .= ''; - if (defined($correct) && $correct !~ /^\s*$/) { - $Str .= ''. - ''.&mt('Correct Answer:').''.$correct.''; - } + $correct = &Apache::lonstathelpers::get_student_answer + ($prob,$student->{'username'},$student->{'domain'}, + $partid,$respid); + $correct = &html_format_sub($correct,'essay'); + } + my $Str; + if (! $single_response) { + my $id = $prob->compTitle; + if (defined($partid) && $partid ne '0') { + $id .= ' '.$prob->part_display($partid); + } + if (defined($respid)) { + $id .= ' '.$respid; + } + $Str .= ''.$id.''.(' 'x4); + } + if ($ENV{'form.prob_status'} eq 'true') { + $Str .= ''; + $Str .= $response->[&Apache::loncoursedata::RDs_awarddetail()]. + (' 'x4); + $Str .= &mt('Attempt [_1]', + $response->[&Apache::loncoursedata::RDs_tries()]). + (' 'x4); + $Str .= &Apache::lonlocal::locallocaltime + ($response->[&Apache::loncoursedata::RDs_timestamp()]). + (' 'x4); + $Str .= &mt('Awarded: [_1]', + $response->[&Apache::loncoursedata::RDs_awarded()]). + (' 'x4); + $Str .= '
'; + } + $Str .= $submission; + if (defined($correct) && $correct !~ /^\s*$/) { + $Str .= '
'.&mt('Correct').''.$correct } - $Str .= ''; - # return $Str; } -sub html_essay_headers { - return ''; -} - -sub html_generic_headers { - my $header =''.&mt('Submission').''; +sub html_results { + my ($student,$prob,$partid,$respid,$response,$resptype) = @_; + my $submission =$response->[&Apache::loncoursedata::RDs_submission()]; + $submission = &html_format_sub($submission,$resptype); + my $correct = ''; if ($ENV{'form.correctans'} eq 'true') { - $header .= ''.&mt('Correct').''; + $correct = &Apache::lonstathelpers::get_student_answer + ($prob,$student->{'username'},$student->{'domain'}, + $partid,$respid); + $correct = &html_format_sub($correct,$resptype); } - return $header; -} - -sub html_option_headers { - return &html_generic_headers(); -} - -sub html_radiobutton_results { - my ($submission,$correct,$tablewidth,$rowclass)=@_; - $submission =~ s/=([^=])$//; - return &html_generic_results($submission,$correct,$tablewidth,$rowclass); -} - -sub html_generic_results { - my ($submission,$correct,$tablewidth,$rowclass)=@_; - my $Str .= ''.$submission.''; + my $Str; + $Str .= ''.$submission.''; if ($ENV{'form.correctans'} eq 'true') { - $Str .= ''.$correct.''; + $Str .= ''.$correct.''; + } + if ($ENV{'form.prob_status'} eq 'true') { + $Str .= ''. + $response->[&Apache::loncoursedata::RDs_awarddetail()]. + ''; + $Str .= ''. + &Apache::lonlocal::locallocaltime + ($response->[&Apache::loncoursedata::RDs_timestamp()]). + ''; + $Str .= ''. + $response->[&Apache::loncoursedata::RDs_tries()]. + ''; + $Str .= ''. + $response->[&Apache::loncoursedata::RDs_awarded()]. + ''; } - $Str .= ''; return $Str; } -sub html_option_results { - my ($submission,$correct,$tablewidth,$rowclass)=@_; - $submission = - '
    '. - '
  • '.join('
  • ', - map { - &Apache::lonnet::unescape($_) ; - } sort split('&',$submission) - ). - '
    • '; - if (defined($correct) && $correct !~ /^\s*$/) { - $correct = '
        '. +sub html_format_sub { + my ($submission,$resptype) = @_; + return '' if (! defined($submission) || $submission eq ''); + $submission = &HTML::Entities::decode($submission); + $submission =~ s/\\\"/\"/g; + $submission =~ s/\\\'/\'/g; + if ($resptype eq 'essay') { + $submission =~ s|\\r\\n|$/|g; + $submission = &HTML::Entities::encode($submission,'<>&"'); + $submission =~ s|$/\s*$/|$/

        $/|g; + $submission =~ s|\\||g; + $submission = '

        '.$submission.'

        '; + } elsif ($resptype eq 'radiobutton') { + $submission = &HTML::Entities::encode($submission,'<>&"'); + $submission =~ s/=([^=])$//; + } elsif ($resptype =~ /^(option|match|rank)$/) { + $submission = + '
          '. '
        • '.join('
        • ', - map { - &Apache::lonnet::unescape($_) ; - } sort split('&',$correct) + map { &HTML::Entities::encode($_,'<>&"'); + } map { + &Apache::lonnet::unescape($_) ; + } sort split('&',$submission) ). - '
        '; + '
          '; + } else { + $submission = &HTML::Entities::encode($submission,'<>&"'); } - # - return &html_generic_results($submission,$correct,$tablewidth,$rowclass); + return $submission; } ######################################################### ######################################################### ## -## CSV output of student answers +## Excel Output Routines ## ######################################################### ######################################################### -sub prepare_csv_output { - my ($r,$problem,$ProblemData,$Students) = @_; - # +sub prepare_excel_output { + my ($r,$Problems,$Students) = @_; my $c = $r->connection(); - my ($resource,$respid,$partid) = ($problem->{'resource'}, - $problem->{'respid'}, - $problem->{'part'}); # - if ($ENV{'form.correctans'} eq 'true') { - $r->print('

          '.&mt('Generating Correct Answers').'

          '); - &Apache::lonstathelpers::GetStudentAnswers($r,$problem,$Students, - 'Statistics', - 'stats_status'); - } # - $r->print('

          '. - &mt('Generating CSV report of student responses').'

          '); + # Determine the number of columns in the spreadsheet + my $columncount = 3; # username, domain, id + my $response_multiplier = 1; + $response_multiplier ++ if ($ENV{'form.correctans'} eq 'true'); + $response_multiplier += 4 if ($ENV{'form.prob_status'} eq 'true'); + my $lastprob; + foreach my $prob (@$Problems) { + $columncount += ($response_multiplier * $prob->countResponses); + last if ($columncount > 255); + $lastprob = $prob; + } + if ($columncount > 255) { + $r->print('

          '.&mt('Unable to complete request').'

          '.$/. + '

          '.&mt('LON-CAPA is unable to produce your Excel spreadsheet because your selections will result in more than 255 columns. Excel allows only 255 columns in a spreadsheet.').'

          '.$/. + '

          '.&mt('Consider selecting fewer problems to generate reports on, or reducing the number of items per problem. Or use HTML or CSV output.').'

          '.$/. + '

          '.&mt('The last problem that will fit in the current spreadsheet is [_1].',$lastprob->compTitle).'

          '); + $r->rflush(); + return; + } # - # Progress window - my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin - ($r,'CSV File Compilation Status', - 'CSV File Compilation Progress', - scalar(@$Students),'inline',undef,'Statistics','stats_status'); - + # Print out a message telling them what we are doing + if (scalar(@$Problems) > 1) { + $r->print('

          '. + &mt('Preparing Excel spreadsheet of student responses to [_1] problems', + scalar(@$Problems)). + '

          '); + } else { + $r->print('

          '. + &mt('Preparing Excel spreadsheet of student responses'). + '

          '); + } $r->rflush(); # - # Open a file - my $outputfile; + # Create the excel spreadsheet my $filename = '/prtspool/'. $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'. - time.'_'.rand(1000000000).'.csv'; - unless ($outputfile = Apache::File->new('>/home/httpd'.$filename)) { - $r->log_error("Couldn't open $filename for output $!"); - $r->print("Problems occured in writing the csv file. ". - "This error has been logged. ". - "Please alert your LON-CAPA administrator."); - $outputfile = undef; + 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('

          '.&mt("Unable to create new Excel file. ". + "This error has been logged. ". + "Please alert your LON-CAPA administrator"). + '

          '); + return undef; } # + $workbook->set_tempdir('/home/httpd/perl/tmp'); + # + my $format = &Apache::loncommon::define_excel_formats($workbook); + my $worksheet = $workbook->addworksheet('Student Submission Data'); # - my @Columns; - if (exists($ENV{'form.concise'}) && $ENV{'form.concise'} eq 'true') { - foreach (@DefaultColumns) { - if ($_->{'name'} =~ /^(username|domain|id)$/){ - push(@Columns,$_); + # Add headers to the worksheet + my $rows_output = 0; + $worksheet->write($rows_output++,0, + $ENV{'course.'.$ENV{'request.course.id'}.'.description'}, + $format->{'h1'}); + $rows_output++; + my $cols_output = 0; + my $title_row = $rows_output++; + my $partid_row = $rows_output++; + my $respid_row = $rows_output++; + my $header_row = $rows_output++; + $worksheet->write($title_row ,0,'Problem Title',$format->{'bold'}); + $worksheet->write($partid_row,0,'Part ID',$format->{'bold'}); + $worksheet->write($respid_row,0,'Response ID',$format->{'bold'}); + # Student headers + my @StudentColumns = ('username','domain','id'); + foreach (@StudentColumns) { + $worksheet->write($header_row,$cols_output++,ucfirst($_), + $format->{'bold'}); + } + # Problem headers + foreach my $prob (@$Problems) { + my $title = $prob->compTitle; + $worksheet->write($title_row,$cols_output, + $title,$format->{'h3'}); + foreach my $partid (@{$prob->parts}) { + $worksheet->write($partid_row,$cols_output, + $prob->part_display($partid)); + my $responses = [$prob->responseIds($partid)]; + my $resptypes = [$prob->responseType($partid)]; + for (my $i=0;$iwrite($respid_row,$cols_output, + $resptypes->[$i].', '.$responses->[$i]); + $worksheet->write($header_row,$cols_output,'Submission'); + $cols_output++; + if ($ENV{'form.correctans'} eq 'true') { + $worksheet->write($header_row,$cols_output,'Correct'); + $cols_output++; + } + if ($ENV{'form.prob_status'} eq 'true') { + $worksheet->write($header_row,$cols_output++, + 'Award Detail'); + $worksheet->write($header_row,$cols_output++,'Attempt'); + $worksheet->write($header_row,$cols_output++,'Time'); + $worksheet->write($header_row,$cols_output++,'Awarded'); + } } } - } else { - @Columns = @DefaultColumns; - } - # - my $response_type = &get_response_type($resource,$partid,$respid); - if (! defined($response_type) || $response_type eq '') { - $r->print('

          '.&mt('Unable to determine response type').'

          '); - return; } # - my $header = &csv_headers(\@Columns).','.&csv_generic_headers(); - print $outputfile $header.$/; - # + # Populate the worksheet with the student data + my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin + ($r,'Excel File Compilation Status', + 'Excel File Compilation Progress', + scalar(@$Students),'inline',undef,'Statistics','stats_status'); + my $max_row = $rows_output; foreach my $student (@$Students) { 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[$i]; - if ($ENV{'form.last_sub_only'} eq 'true' && - $i < (scalar(@$results)-1)) { - next; - } - my $data; - $data->{'username'} = $student->{'username'}; - $data->{'domain'} = $student->{'domain'}; - $data->{'id'} = $student->{'id'}; - $data->{'fullname'} = $student->{'fullanem'}; - $data->{'status'} = $student->{'status'}; - $data->{'time'} = &Apache::lonlocal::locallocaltime - ($response->[&Apache::loncoursedata::RDs_timestamp()]); - $data->{'attempt'} = - $response->[&Apache::loncoursedata::RDs_tries()]; - $data->{'awarded'} = - $response->[&Apache::loncoursedata::RDs_awarded()]; - $data->{'awarddetail'} = - $response->[&Apache::loncoursedata::RDs_awarddetail()]; - my $rowextra = ''; - my $row; - foreach my $col (@Columns) { - $row .= '"'. - &Apache::loncommon::csv_translate($data->{$col->{'name'}}).'",'; - } - if ($response_type eq 'option') { - $row .= &csv_option_results - ($response->[&Apache::loncoursedata::RDs_submission()], - $student->{'answer'}, - scalar(@Columns),$rowextra); - } elsif ($response_type eq 'radiobutton') { - $row .= &csv_radiobutton_results - ($response->[&Apache::loncoursedata::RDs_submission()], - $student->{'answer'}, - scalar(@Columns),$rowextra); - } else { - $row .= &csv_generic_results - ($response->[&Apache::loncoursedata::RDs_submission()], - $student->{'answer'}, - scalar(@Columns),$rowextra); + $cols_output = 0; + my $student_row = $max_row; + foreach my $field (@StudentColumns) { + $worksheet->write($student_row,$cols_output++, + $student->{$field}); + } + my $last_student_col = $cols_output-1; + my $response_count; + foreach my $prob (@$Problems) { + foreach my $partid (@{$prob->parts}) { + my @Response = $prob->responseIds($partid); + my @ResponseType = $prob->responseType($partid); + for (my $i=0;$i<=$#Response;$i++) { + my $response_start_col = $last_student_col + + $response_count * $response_multiplier + 1; + $response_count++; + my $respid = $Response[$i]; + my $resptype = $ResponseType[$i]; + my $results = + &Apache::loncoursedata::get_response_data_by_student + ($student,$prob->symb(),$respid); + if (! defined($results)) { + $results = []; + } + # + $rows_output = $student_row; + # + for (my $j=scalar(@$results)-1;$j>=0;$j--) { + $cols_output = $response_start_col; + my $response = $results->[$j]; + if ($ENV{'form.all_sub'} ne 'true') { + next if ($j ne scalar(@$results)-1); + } + my $cols_output = &write_excel_row + ($worksheet, + $rows_output++,$cols_output, + $response,$student, + $prob,$partid,$respid, + $format,$resptype); + if ($rows_output > $max_row) { + $max_row = $rows_output; + } + } + } } - print $outputfile $row.$/; } + $rows_output++; &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state, 'last student'); } - close($outputfile); - # - # Close the progress window &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); # - # Tell the user where to get their csv file - $r->print('
          '. - ''.&mt('Your csv file.').''."\n"); + # Close the excel file + $workbook->close(); + # + # Write a link to allow them to download it + $r->print('

          '. + &mt('Your Excel spreadsheet.'). + '

          '."\n"); + $r->print(''); $r->rflush(); return; } -sub csv_headers { - my ($Columns) = @_; - my $Str; - foreach my $column (@$Columns) { - $Str .= - '"'.&Apache::loncommon::csv_translate($column->{'display'}).'",'; - } - chop($Str); - return $Str; -} - -sub csv_generic_headers { - my ($title) = @_; - if (! defined($title)) { - $title = &mt('Submission'); - } - my $header = '"'.&Apache::loncommon::csv_translate($title).'"'; +sub write_excel_row { + my ($worksheet,$row,$col,$response,$student,$prob,$partid,$respid, + $format,$resptype) = @_; + # + my $submission =$response->[&Apache::loncoursedata::RDs_submission()]; + $submission = &excel_format_response($submission,$resptype); + $worksheet->write($row,$col++,$submission); if ($ENV{'form.correctans'} eq 'true') { - $header .= ',"'.&Apache::loncommon::csv_translate(&mt('Correct')).'"'; - } - return $header; -} - -#------------------------------------------ -sub csv_essay_results { - my ($submission,$correct,$tablewidth,$rowextra)=@_; - # - $submission =~ s|\\r|\\\\r|g; - $submission =~ s|\\n|\\\\n|g; - # - return &csv_generic_results($submission,$correct,$tablewidth); -} - -#------------------------------------------ -sub csv_radiobutton_results { - my ($submission,$correct,$tablewidth,$rowclass)=@_; - $submission =~ s/=[^=]*$//; - return &csv_generic_results($submission,$correct,$tablewidth,$rowclass); -} - -#------------------------------------------ -sub csv_option_results { - my ($submission,$correct,$tablewidth,$rowclass)=@_; - $submission = join(',', + my $correct = &Apache::lonstathelpers::get_student_answer + ($prob,$student->{'username'},$student->{'domain'}, + $partid,$respid); + $correct =&excel_format_response($correct,$resptype); + $worksheet->write($row,$col++,$correct); + } + if ($ENV{'form.prob_status'} eq 'true') { + $worksheet->write + ($row,$col++, + $response->[&Apache::loncoursedata::RDs_awarddetail()]); + $worksheet->write + ($row,$col++,$response->[&Apache::loncoursedata::RDs_tries()]); + $worksheet->write + ($row,$col++, + &Apache::lonstathelpers::calc_serial + ($response->[&Apache::loncoursedata::RDs_timestamp()]), + $format->{'date'}); + $worksheet->write + ($row,$col++,$response->[&Apache::loncoursedata::RDs_awarded()]); + } + return $col; +} + +sub excel_format_response { + my ($answer,$responsetype) = @_; + if ($responsetype eq 'radiobutton') { + $answer =~ s/=([^=])$//; + } elsif ($responsetype =~ /^(option|match)$/) { + $answer = join("\n", map { &Apache::lonnet::unescape($_) ; - } sort split('&',$submission) + } sort split('&',$answer) ); - if (defined($correct) && $correct !~ /^\s*$/) { - $correct =join(',', - map { - &Apache::lonnet::unescape($_) ; - } sort split('&',$submission)); + } elsif ($responsetype eq 'string') { + $answer =~ s/\\(n|r)/\n/g; + $answer =~ s/(\s*$|^\s*)//g; + $answer =~ s/\\\'/\'/g; } - return &csv_generic_results($submission,$correct,$tablewidth,$rowclass); -} - -#------------------------------------------ -sub csv_generic_results { - my ($submission,$correct,$tablewidth,$rowclass)=@_; - my $Str .= - '"'.&Apache::loncommon::csv_translate($submission).'"'; - if ($ENV{'form.correctans'} eq 'true') { - $Str .= ',"'.&Apache::loncommon::csv_translate($correct).'"'; + if ($answer =~ m/^=/) { + $answer = ' '.$answer; } - return $Str; + return $answer; } ######################################################### ######################################################### ## -## Excel output of student answers and correct answers +## CSV output of student answers ## ######################################################### ######################################################### -sub prepare_excel_output { - my ($r,$problem,$ProblemData,$Students) = @_; +sub prepare_csv_output { + my ($r,$problems,$students) = @_; my $c = $r->connection(); - my ($resource,$respid,$partid) = ($problem->{'resource'}, - $problem->{'respid'}, - $problem->{'part'}); - $r->print('

          '. - &mt('Preparing Excel spreadsheet of student responses'). - '

          '. - '

          '. - &mt('See the status bar above for student answer computation progress'). - '

          '); # - if ($ENV{'form.correctans'} eq 'true') { - &Apache::lonstathelpers::GetStudentAnswers($r,$problem,$Students, - 'Statistics', - 'stats_status'); - $r->print(''); - } + $r->print('

          '. + &mt('Generating CSV report of student responses').'

          '); # + # Progress window + my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin + ($r,'CSV File Compilation Status', + 'CSV File Compilation Progress', + scalar(@$students),'inline',undef,'Statistics','stats_status'); + $r->rflush(); - my @Columns; - if (exists($ENV{'form.concise'}) && $ENV{'form.concise'} eq 'true') { - foreach (@DefaultColumns) { - if ($_->{'name'} =~ /^(username|domain|id)$/){ - push(@Columns,$_); - } - } - } else { - @Columns = @DefaultColumns; - } - my ($awarded_col,$weight_col); - for (my $i=0;$i<=$#Columns;$i++) { - if ($Columns[$i]->{'name'} eq 'weight' ) { $weight_col = $i; } - if ($Columns[$i]->{'name'} eq 'awarded') { $awarded_col = $i; } - } # - # Create excel worksheet + # Open a file + my $outputfile; 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('

          '.&mt("Unable to create new Excel file. ". - "This error has been logged. ". - "Please alert your LON-CAPA administrator"). - '

          '); - return undef; + time.'_'.rand(1000000000).'.csv'; + unless ($outputfile = Apache::File->new('>/home/httpd'.$filename)) { + $r->log_error("Couldn't open $filename for output $!"); + $r->print("Problems occured in writing the csv file. ". + "This error has been logged. ". + "Please alert your LON-CAPA administrator."); + $outputfile = 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(); + # Compute the number of columns per response + my @response_headers = ('Submission'); + if ($ENV{'form.correctans'} eq 'true') { + push(@response_headers,'Correct'); + } + if ($ENV{'form.prob_status'} eq 'true') { + push(@response_headers,'Award Detail'); + push(@response_headers,'Time'); + push(@response_headers,'Attempt'); + push(@response_headers,'Awarded'); + } + my $response_multiplier = scalar(@response_headers); + # + # Create the table header + my @student_columns = ('username','domain','id'); + # + my %headers; + push(@{$headers{'student'}},@student_columns); + # Pad for the student data + foreach my $row ('problem','part','response') { + foreach (@student_columns) { + push(@{$headers{$row}},''); + } + } # - # 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, - [map {$_->{'display'}} @Columns], - $format->{'bold'}); + # we put the headers into the %headers hash + my $prob_start_idx = 0; + foreach my $prob (@$problems) { + $headers{'problem'}->[$prob_start_idx] = $prob->compTitle; + my $part_start_idx = $prob_start_idx; + foreach my $partid (@{$prob->parts}) { + $headers{'part'}->[$part_start_idx] = $prob->part_display($partid); + my $responses = [$prob->responseIds($partid)]; + for (my $i=0;$i[$resp_idx]= + &mt('Response [_1]',$responses->[$i]); + for (my $j=0;$j<=$#response_headers;$j++) { + $headers{'student'}->[$resp_idx+$j]=$response_headers[$j]; + } + } + $part_start_idx += scalar(@$responses)*$response_multiplier; + } + $prob_start_idx += $prob->countResponses * $response_multiplier; + } + foreach my $row ('problem','part','response','student') { + print $outputfile '"'.join('","',map { ''; } @student_columns).'","'. + join('","', + map { + &Apache::loncommon::csv_translate($_); + } @{$headers{$row}}).'"'.$/; + } # - # Populate the worksheet with the student data - my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin - ($r,'Excel File Compilation Status', - 'Excel File Compilation Progress', - scalar(@$Students),'inline',undef,'Statistics','stats_status'); - foreach my $student (@$Students) { + # Main loop + 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{'id'} = $student->{'id'}; - $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->{'name'}})) { - $cols_output++; - next; + my @rows; + my $prob_start_idx = 0; + foreach my $prob (@$problems) { + my $part_start_idx = 0; + foreach my $partid (@{$prob->parts}) { + my @responses = $prob->responseIds($partid); + my @response_type = $prob->responseType($partid); + for (my $i=0;$i<=$#responses;$i++) { + my $resp_start_idx = $response_multiplier * $i; + my $respid = $responses[$i]; + my $results = + &Apache::loncoursedata::get_response_data_by_student + ($student,$prob->symb(),$respid); + if (! defined($results)) { + $results = []; + } + for (my $j=0; $j[$idx]; + my @data = &compile_response_data($response,$student, + $prob,$partid, + $respid); + for (my $k=0;$k<=$#data;$k++) { + $rows[$j]->[$prob_start_idx + $part_start_idx + + $resp_start_idx + $k] = $data[$k]; + } + } } - $worksheet->write($rows_output,$cols_output++, - $row{$col->{'name'}}); + $part_start_idx += scalar(@responses)*$response_multiplier; } - $rows_output++; - } else { - for (my $i=0;$i[$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{'awarddetail'} = $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->{'name'}}); - } - $rows_output++; + $prob_start_idx += $prob->countResponses * $response_multiplier; + } + foreach my $row (@rows) { + print $outputfile '"'.join('","', + map { $student->{$_}; } + @student_columns).'"'; + + for (my $i=0;$i<$prob_start_idx;$i++) { + my $value = &Apache::loncommon::csv_translate($row->[$i]); + $value ||=''; + print $outputfile ',"'.$value.'"'; } - } # End of else clause on if (! defined($results) .... + print $outputfile $/; + } + undef(@rows); &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state, 'last student'); } - &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); + close($outputfile); # - # Close the excel file - $workbook->close(); + # Close the progress window + &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); # - # Write a link to allow them to download it - $r->print('

          '. - &mt('Your Excel spreadsheet.'). - '

          '."\n"); - $r->print(''); + # Tell the user where to get their csv file + $r->print('
          '. + ''.&mt('Your csv file.').''."\n"); $r->rflush(); return; } +sub compile_response_data { + my ($response,$student,$prob,$partid,$respid) = @_; + my @rowdata; + push(@rowdata,$response->[&Apache::loncoursedata::RDs_submission()]); + if ($ENV{'form.correctans'} eq 'true') { + my $correct = &Apache::lonstathelpers::get_student_answer + ($prob,$student->{'username'},$student->{'domain'}, + $partid,$respid); + push(@rowdata,$correct); + } + if ($ENV{'form.prob_status'}) { + push(@rowdata,$response->[&Apache::loncoursedata::RDs_awarddetail()]); + push(@rowdata,&Apache::lonlocal::locallocaltime + ($response->[&Apache::loncoursedata::RDs_timestamp()])); + push(@rowdata,$response->[&Apache::loncoursedata::RDs_tries()]); + push(@rowdata,$response->[&Apache::loncoursedata::RDs_awarded()]); + } + return @rowdata; +} + ######################################################### ######################################################### ## @@ -784,15 +911,15 @@ sub prepare_excel_output { sub CreateInterface { ## ## Output Selection - my $OutputSelector = $/.''.$/; foreach ('HTML','Excel','CSV') { - $OutputSelector .= ' '.$/; } - $OutputSelector .= ''.$/; + $output_selector .= ''.$/; ## ## Environment variable initialization my $Str = ''; @@ -803,7 +930,7 @@ sub CreateInterface { $Str .= ''; $Str .= ''.&mt('Sections').''; $Str .= ''.&mt('Enrollment Status').''; - $Str .= ''.&mt('Output Options').''; + $Str .= ''.&mt('Output as [_1]',$output_selector).''; $Str .= ''."\n"; # $Str .= ''."\n"; @@ -828,31 +955,33 @@ sub CreateInterface { } $ans_checkbox .= 'value="true" />'; # - # Only show last submission checkbox - my $last_sub_checkbox = ''.&mt('Output Format: [_1]',$OutputSelector).'
          '.$/. '
          '. + &mt('Show problem [_1]',$prob_checkbox).'
          '. '
          '. + &mt('Show correct answers [_1]',$ans_checkbox).'
          '. '
          '. + &mt('Show all submissions [_1]',$all_sub_checkbox). + '
          '. '
          '. + &mt('Show problem grading [_1]',$prob_status_checkbox). + '
          '. ''; # $Str .= ''."\n";