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