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