--- loncom/interface/statistics/lonstudentassessment.pm 2006/01/22 02:10:03 1.126 +++ loncom/interface/statistics/lonstudentassessment.pm 2006/12/21 02:53:42 1.144 @@ -1,6 +1,6 @@ # The LearningOnline Network with CAPA # -# $Id: lonstudentassessment.pm,v 1.126 2006/01/22 02:10:03 bowersj2 Exp $ +# $Id: lonstudentassessment.pm,v 1.144 2006/12/21 02:53:42 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -50,15 +50,20 @@ Presents assessment data about a student package Apache::lonstudentassessment; use strict; -use Apache::lonstatistics; -use Apache::lonhtmlcommon; +use Apache::lonstatistics(); +use Apache::lonhtmlcommon(); use Apache::loncommon(); use Apache::loncoursedata; use Apache::lonnet; # for logging porpoises use Apache::lonlocal; +use Apache::grades(); +use Apache::lonmsgdisplay(); use Time::HiRes; use Spreadsheet::WriteExcel; use Spreadsheet::WriteExcel::Utility(); +use lib '/home/httpd/lib/perl/'; +use LONCAPA; + ####################################################### ####################################################### @@ -145,6 +150,7 @@ sub BuildStudentAssessmentPage { 'chartoutputmode' => 'scalar', 'chartoutputdata' => 'scalar', 'Section' => 'array', + 'Groups' => 'array', 'StudentData' => 'array', 'Maps' => 'array'); &Apache::loncommon::store_course_settings('chart',\%Saveable_Parameters); @@ -312,14 +318,24 @@ the chart page. ####################################################### sub CreateInterface { my $Str = ''; - $Str .= &Apache::lonhtmlcommon::breadcrumbs(undef,'Chart'); + $Str .= &Apache::lonhtmlcommon::breadcrumbs('Chart','Chart_Description:Chart_Sections:Chart_Student_Data:Chart_Enrollment_Status:Chart_Sequences:Chart_Output_Formats:Chart_Output_Data'); # $Str .= &CreateLegend(); $Str .= '<table cellspacing="5">'."\n"; $Str .= '<tr>'; - $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>'; - $Str .= '<td align="center"><b>'.&mt('Student Data</b>').'</td>'; - $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>'; - $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b></td>'; + $Str .= '<td align="center"><b>'.&mt('Sections').'</b>'. + &Apache::loncommon::help_open_topic("Chart_Sections"). + '</td>'; + $Str .= '<td align="center"><b>'.&mt('Groups').'</b>'. + '</td>'; + $Str .= '<td align="center"><b>'.&mt('Student Data</b>'). + &Apache::loncommon::help_open_topic("Chart_Student_Data"). + '</td>'; + $Str .= '<td align="center"><b>'.&mt('Access Status').'</b>'. + &Apache::loncommon::help_open_topic("Chart_Enrollment_Status"). + '</td>'; + $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b>'. + &Apache::loncommon::help_open_topic("Chart_Sequences"). + '</td>'; $Str .= '<td align="center"><b>'.&mt('Output Format').'</b>'. &Apache::loncommon::help_open_topic("Chart_Output_Formats"). '</td>'; @@ -331,6 +347,8 @@ sub CreateInterface { $Str .= '<tr><td align="center">'."\n"; $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5); $Str .= '</td><td align="center">'; + $Str .= &Apache::lonstatistics::GroupSelect('Group','multiple',5); + $Str .= '</td><td align="center">'; $Str .= &Apache::lonstatistics::StudentDataSelect('StudentData','multiple', 5,undef); $Str .= '</td><td>'."\n"; @@ -472,6 +490,7 @@ my @OutputDataOptions = grand_maximum => 1, summary_table => 1, maximum_row => 1, + ignore_weight => 0, shortdesc => 'Total Score and Maximum Possible for each '. 'Sequence or Folder', longdesc => 'The score of each student as well as the '. @@ -490,6 +509,7 @@ my @OutputDataOptions = grand_maximum => 1, summary_table => 1, maximum_row => 1, + ignore_weight => 0, shortdesc => 'Score on each Problem Part', longdesc =>'The students score on each problem part, computed as'. 'the part weight * part awarded', @@ -507,6 +527,7 @@ my @OutputDataOptions = grand_maximum => 0, summary_table => 0, maximum_row => 0, + ignore_weight => 0, shortdesc => 'Number of Tries before success on each Problem Part', longdesc =>'The number of tries before success on each problem part.', non_html_notes => 'negative values indicate an incorrect problem', @@ -524,6 +545,7 @@ my @OutputDataOptions = grand_maximum => 1, summary_table => 1, maximum_row => 0, + ignore_weight => 1, shortdesc => 'Number of Problem Parts completed successfully.', longdesc => 'The Number of Problem Parts completed successfully and '. 'the maximum possible for each student', @@ -645,6 +667,18 @@ sub html_initialize { &mt('Unable to retrieve course information.'). '</h3>'); } + + # If we're showing links, show a checkbox to open in new + # windows. + if ($show_links ne 'no') { + $r->print(<<NEW_WINDOW_CHECKBOX); +<script type="text/javascript">new_window = true;</script> +<p><label>Show links in new window: +<input type="checkbox" checked="1" onclick="new_window=this.checked" /> +</label></p> +NEW_WINDOW_CHECKBOX + } + # $r->print("<h3>".$env{'course.'.$env{'request.course.id'}.'.description'}. " ".localtime(time)."</h3>"); @@ -714,6 +748,58 @@ sub html_initialize { $Str .= "<pre>"; $r->print($Str); $r->rflush(); + + $r->print(<<JS); +<script type="text/javascript"> +// get the left offset of a given widget as an absolute position +function getLeftOffset (element) { + return collect(element, "offsetLeft"); +} + +// get the top offset of a given widget as an absolute position +function getTopOffset (element) { + return collect(element, "offsetTop"); +} + +function collect(element, att) { + var val = 0; + while(element) { + val += element[att]; + element = element.offsetParent; + } + return val; +} + +var currentDiv; +var currentElement; +function popup_score(element, score) { + popdown_score(); + var left = getLeftOffset(element); + var top = getTopOffset(element); + var div = document.createElement("div"); + div.className = "LC_chrt_popup"; + div.appendChild(document.createTextNode(score)); + div.style.position = "absolute"; + div.style.top = (top - 25) + "px"; + div.style.left = (left - 10) + "px"; + currentDiv = div; + document.body.insertBefore(div, document.body.childNodes[0]); + element.className = "LC_chrt_popup_up"; + currentElement = element; +} + +function popdown_score() { + if (currentDiv) { + document.body.removeChild(currentDiv); + } + if (currentElement) { + currentElement.className = 'LC_chrt_popup_exists'; + } + currentDiv = undefined; +} +</script> +JS + # # Let the user know what we are doing my $studentcount = scalar(@Apache::lonstatistics::Students); @@ -757,12 +843,17 @@ sub html_outputstudent { my @tmp = &Apache::loncoursedata::get_current_state ($student->{'username'},$student->{'domain'},undef, $env{'request.course.id'}); - if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) { + if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:(.*)/)) { %StudentsData = @tmp; - } - if (scalar(@tmp) < 1) { + } else { + my $error = $1; + if (scalar(@tmp) < 1) { + $Str .= '<font color="blue">No Course Data</font>'."\n"; + } else { + $Str .= '<span class="LC_error">Error getting student data ('. + $error.') </span>'."\n"; + } $nodata_count++; - $Str .= '<font color="blue">No Course Data</font>'."\n"; $r->print($Str); $r->rflush(); return; @@ -781,7 +872,8 @@ sub html_outputstudent { } else { ($performance,$performance_length,$score,$seq_max,$rawdata) = &student_performance_on_sequence($student,\%StudentsData, - $navmap,$seq,$show_links); + $navmap,$seq,$show_links, + $chosen_output->{ignore_weight}); } my $ratio=''; if ($chosen_output->{'every_problem'} && @@ -1144,9 +1236,11 @@ sub excel_initialize { ! defined($formula_data{$symb}->{'Excel:endcell'})) { $formula_data{$symb}->{'Excel:endcell'} = $formula_data{$symb}->{'Excel:startcell'}; } + + my $start = $formula_data{$symb}->{'Excel:startcell'}; + my $end = $formula_data{$symb}->{'Excel:endcell'}; $formula_data{$symb}->{'Excel:sum'}= $excel_sheet->store_formula - ('=SUM('.$formula_data{$symb}->{'Excel:startcell'}. - ':'.$formula_data{$symb}->{'Excel:endcell'}.')'); + ("=IF(COUNT($start\:$end),SUM($start\:$end),\"\")"); # Determine cell the score is held in $formula_data{$symb}->{'Excel:scorecell'} = &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell @@ -1231,7 +1325,7 @@ sub excel_initialize { $weight = 1; if ($chosen_output->{'scores'}) { $weight = &Apache::lonnet::EXT - ('resource.'.$part.'.weight',$resource->{'symb'}, + ('resource.'.$part.'.weight',$resource->symb, undef,undef,undef); if (!defined($weight) || ($weight eq '')) { $weight=1; @@ -1255,7 +1349,7 @@ sub excel_initialize { &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell($maximum_data_row,$formula_data{$symb}->{'Excel:endcol'})); $excel_sheet->repeat_formula($maximum_data_row,$cols_output++, $formula_data{$symb}->{'Excel:sum'},undef, - %replaceCells); + %replaceCells, %replaceCells); } elsif ($chosen_output->{'sequence_sum'}) { $excel_sheet->write($maximum_data_row,$cols_output++,$max); @@ -1366,7 +1460,7 @@ sub excel_outputstudent { foreach my $field (@to_show) { my $value = $student->{$field}; if ($field eq 'comments') { - $value = &Apache::lonmsg::retrieve_instructor_comments + $value = &Apache::lonmsgdisplay::retrieve_instructor_comments ($student->{'username'},$student->{'domain'}); } $excel_sheet->write($rows_output,$cols_output++,$value); @@ -1405,7 +1499,8 @@ sub excel_outputstudent { } else { ($performance,$performance_length,$score,$seq_max,$rawdata) = &student_performance_on_sequence($student,\%StudentsData, - $navmap,$seq,'no'); + $navmap,$seq,'no', + $chosen_output->{ignore_weight}); } if ($chosen_output->{'every_problem'}) { if ($chosen_output->{'correct'}) { @@ -1439,7 +1534,7 @@ sub excel_outputstudent { # The undef is for the format $excel_sheet->repeat_formula($rows_output,$cols_output++, $formula_data{$symb}->{'Excel:sum'},undef, - %replaceCells); + %replaceCells, %replaceCells); } elsif ($chosen_output->{'sequence_sum'}) { if ($score eq ' ') { $cols_output++; @@ -1647,7 +1742,7 @@ sub csv_outputstudent { foreach my $field (@to_show) { my $value = $student->{$field}; if ($field eq 'comments') { - $value = &Apache::lonmsg::retrieve_instructor_comments + $value = &Apache::lonmsgdisplay::retrieve_instructor_comments ($student->{'username'},$student->{'domain'}); } $Str .= '"'.&Apache::loncommon::csv_translate($value).'",'; @@ -1675,7 +1770,8 @@ sub csv_outputstudent { } else { ($performance,$performance_length,$score,$seq_max,$rawdata) = &student_performance_on_sequence($student,\%StudentsData, - $navmap,$seq,'no'); + $navmap,$seq,'no', + $chosen_output->{ignore_weight}); } if ($chosen_output->{'every_problem'}) { if ($chosen_output->{'correct'}) { @@ -1738,6 +1834,18 @@ sub csv_finish { } +# This function will return an HTML string including a star, with +# a mouseover popup showing the "real" value. An optional second +# argument lets you show something other than a star. +sub show_star { + my $popup = shift; + my $symbol = shift || '*'; + # Escape the popup for JS. + $popup =~ s/([^-a-zA-Z0-9:;,._ ()|!\/?=&*])/'\\' . sprintf("%lo", ord($1))/ge; + + return "<span class=\"LC_chrt_popup_exists\" onmouseover='popup_score(this, \"$popup\");return false;' onmouseout='popdown_score();return false;'>$symbol</span>"; +} + ####################################################### ####################################################### @@ -1803,12 +1911,12 @@ sub student_tries_on_sequence { if ($status eq 'excused') { $symbol = 'x'; $max--; - } elsif ($status eq 'correct_by_override') { + } elsif ($status eq 'correct_by_override' && !$resource->is_task()) { $symbol = '+'; $sum++; } elsif ($tries > 0) { if ($tries > 9) { - $symbol = '*'; + $symbol = show_star($tries); } else { $symbol = $tries; } @@ -1851,11 +1959,12 @@ sub student_tries_on_sequence { if (length($symbol) > 1) { &Apache::lonnet::logthis('length of symbol "'.$symbol.'" > 1'); } - $symbol = '<a href="/adm/grades'. - '?symb='.&Apache::lonnet::escape($resource->symb). + my $link = '/adm/grades'. + '?symb='.&escape($resource->symb). '&student='.$student->{'username'}. '&userdom='.$student->{'domain'}. - '&command=submission">'.$symbol.'</a>'; + '&command=submission'; + $symbol = &link($symbol, $link); } $value .= $symbol; } @@ -1870,6 +1979,37 @@ sub student_tries_on_sequence { return ($Str,$performance_length,$sum,$max,\@TriesData); } +=pod + +=item &link + +Inputs: + +=over 4 + +=item $text + +=item $target + +=back + +Takes the text and creates a link to the $text that honors +the value of 'new window' if clicked on, but uses a real +'href' so middle and right clicks still work. + +$target and $text are assumed to be already correctly escaped; i.e., it +can be dumped out directly into the output stream as-is. + +=cut + +sub link { + my ($text,$target) = @_; + return + "<a href='$target' onclick=\"t=this.href;if(new_window)" + ."{window.open(t)}else{return void(window." + ."location=t)};return false;\">$text</a>"; +} + ####################################################### ####################################################### @@ -1896,7 +2036,7 @@ Inputs: ####################################################### ####################################################### sub student_performance_on_sequence { - my ($student,$studentdata,$navmap,$seq,$links) = @_; + my ($student,$studentdata,$navmap,$seq,$links,$awarded_only) = @_; $links = 'no' if (! defined($links)); my $Str = ''; # final result string my ($score,$max) = (0,0); @@ -1910,11 +2050,14 @@ sub student_performance_on_sequence { my $resource_data = $studentdata->{$symb}; foreach my $part (@{$resource->parts()}) { $partscore = undef; - my $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight', - $symb, - $student->{'domain'}, - $student->{'username'}, - $student->{'section'}); + my $weight; + if (!$awarded_only){ + $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight', + $symb, + $student->{'domain'}, + $student->{'username'}, + $student->{'section'}); + } if (!defined($weight) || ($weight eq '')) { $weight=1; } @@ -1930,7 +2073,7 @@ sub student_performance_on_sequence { $hasdata = 1; } # - $partscore = $weight*$awarded; + $partscore = &Apache::grades::compute_points($weight,$awarded); if (! defined($awarded)) { $partscore = undef; } @@ -1940,7 +2083,7 @@ sub student_performance_on_sequence { $symbol = sprintf("%.0f",$symbol); } if (length($symbol) > 1) { - $symbol = '*'; + $symbol = show_star($symbol); } if (exists($resource_data->{'resource.'.$part.'.solved'}) && $resource_data->{'resource.'.$part.'.solved'} ne '') { @@ -1971,11 +2114,12 @@ sub student_performance_on_sequence { push (@ScoreData,$partscore); # if ( ($links eq 'yes' && $symbol ne ' ') || ($links eq 'all')) { - $symbol = '<a href="/adm/grades'. - '?symb='.&Apache::lonnet::escape($symb). + my $link = '/adm/grades' . + '?symb='.&escape($symb). '&student='.$student->{'username'}. '&userdom='.$student->{'domain'}. - '&command=submission">'.$symbol.'</a>'; + '&command=submission'; + $symbol = &link($symbol, $link); } $Str .= $symbol; }