Parent Directory
|
Revision Log
Revision 3131 - (view) (download) (as text)
| 1 : | apizer | 2159 | ################################################################################ |
| 2 : | # WeBWorK Online Homework Delivery System | ||
| 3 : | # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ | ||
| 4 : | glarose | 2977 | # $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/Instructor/StudentProgress.pm,v 1.4.2.2 2004/07/26 15:11:18 gage Exp $ |
| 5 : | apizer | 2159 | # |
| 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::Utils qw(readDirectory list2hash max sortByName); | ||
| 30 : | use WeBWorK::DB::Record::Set; | ||
| 31 : | use WeBWorK::ContentGenerator::Grades; | ||
| 32 : | # The table format has been borrowed from the Grades.pm module | ||
| 33 : | sub initialize { | ||
| 34 : | my $self = shift; | ||
| 35 : | # FIXME are there args here? | ||
| 36 : | my @components = @_; | ||
| 37 : | my $r = $self->{r}; | ||
| 38 : | my $urlpath = $r->urlpath; | ||
| 39 : | my $type = $urlpath->arg("statType") || ''; | ||
| 40 : | my $db = $self->{db}; | ||
| 41 : | my $ce = $self->{ce}; | ||
| 42 : | my $authz = $self->{authz}; | ||
| 43 : | my $courseName = $urlpath->arg('courseID'); | ||
| 44 : | my $user = $r->param('user'); | ||
| 45 : | |||
| 46 : | gage | 2536 | # Check permissions |
| 47 : | return unless $authz->hasPermissions($user, "access_instructor_tools"); | ||
| 48 : | |||
| 49 : | apizer | 2159 | $self->{type} = $type; |
| 50 : | if ($type eq 'student') { | ||
| 51 : | my $studentName = $r->urlpath->arg("userID") || $user; | ||
| 52 : | $self->{studentName } = $studentName; | ||
| 53 : | |||
| 54 : | } elsif ($type eq 'set') { | ||
| 55 : | my $setName = $r->urlpath->arg("setID") || 0; | ||
| 56 : | $self->{setName} = $setName; | ||
| 57 : | my $setRecord = $db->getGlobalSet($setName); # checked | ||
| 58 : | die "global set $setName not found." unless $setRecord; | ||
| 59 : | $self->{set_due_date} = $setRecord->due_date; | ||
| 60 : | $self->{setRecord} = $setRecord; | ||
| 61 : | } | ||
| 62 : | } | ||
| 63 : | |||
| 64 : | |||
| 65 : | sub title { | ||
| 66 : | my ($self) = @_; | ||
| 67 : | gage | 2536 | my $r = $self->r; |
| 68 : | my $authz = $r->authz; | ||
| 69 : | my $user = $r->param('user'); | ||
| 70 : | |||
| 71 : | # Check permissions | ||
| 72 : | return "" unless $authz->hasPermissions($user, "access_instructor_tools"); | ||
| 73 : | |||
| 74 : | apizer | 2159 | my $type = $self->{type}; |
| 75 : | my $string = "Student Progress for ".$self->{ce}->{courseName}." "; | ||
| 76 : | if ($type eq 'student') { | ||
| 77 : | $string .= "student ".$self->{studentName}; | ||
| 78 : | } elsif ($type eq 'set' ) { | ||
| 79 : | $string .= "set ".$self->{setName}; | ||
| 80 : | $string .= ". Due ". WeBWorK::Utils::formatDateTime($self->{set_due_date}); | ||
| 81 : | } | ||
| 82 : | return $string; | ||
| 83 : | } | ||
| 84 : | sub siblings { | ||
| 85 : | my ($self) = @_; | ||
| 86 : | my $r = $self->r; | ||
| 87 : | my $db = $r->db; | ||
| 88 : | gage | 2536 | my $authz = $r->authz; |
| 89 : | my $user = $r->param('user'); | ||
| 90 : | apizer | 2159 | my $urlpath = $r->urlpath; |
| 91 : | |||
| 92 : | gage | 2536 | # Check permissions |
| 93 : | return "" unless $authz->hasPermissions($user, "access_instructor_tools"); | ||
| 94 : | apizer | 2159 | |
| 95 : | my $courseID = $urlpath->arg("courseID"); | ||
| 96 : | my $eUserID = $r->param("effectiveUser"); | ||
| 97 : | my @setIDs = sort $db->listGlobalSets; | ||
| 98 : | |||
| 99 : | apizer | 2169 | my $progress = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::StudentProgress", |
| 100 : | apizer | 2159 | courseID => $courseID); |
| 101 : | |||
| 102 : | print CGI::start_ul({class=>"LinksMenu"}); | ||
| 103 : | print CGI::start_li(); | ||
| 104 : | apizer | 2169 | print CGI::span({style=>"font-size:larger"}, CGI::a({href=>$self->systemLink($progress)}, 'Student Progress')); |
| 105 : | apizer | 2159 | print CGI::start_ul(); |
| 106 : | |||
| 107 : | foreach my $setID (@setIDs) { | ||
| 108 : | apizer | 2169 | my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::StudentProgress", |
| 109 : | apizer | 2159 | courseID => $courseID, setID => $setID,statType => 'set',); |
| 110 : | print CGI::li(CGI::a({href=>$self->systemLink($problemPage)}, "Set $setID")); | ||
| 111 : | } | ||
| 112 : | |||
| 113 : | print CGI::end_ul(); | ||
| 114 : | print CGI::end_li(); | ||
| 115 : | print CGI::end_ul(); | ||
| 116 : | |||
| 117 : | return ""; | ||
| 118 : | } | ||
| 119 : | sub body { | ||
| 120 : | my $self = shift; | ||
| 121 : | my $r = $self->r; | ||
| 122 : | my $urlpath = $r->urlpath; | ||
| 123 : | my $db = $r->db; | ||
| 124 : | my $ce = $r->ce; | ||
| 125 : | my $authz = $r->authz; | ||
| 126 : | gage | 2536 | my $user = $r->param('user'); |
| 127 : | apizer | 2159 | my $courseName = $urlpath->arg("courseID"); |
| 128 : | my $type = $self->{type}; | ||
| 129 : | gage | 2536 | |
| 130 : | # Check permissions | ||
| 131 : | return CGI::div({class=>"ResultsWithError"}, CGI::p("You are not authorized to access instructor tools")) | ||
| 132 : | unless $authz->hasPermissions($user, "access_instructor_tools"); | ||
| 133 : | apizer | 2159 | |
| 134 : | if ($type eq 'student') { | ||
| 135 : | my $studentName = $self->{studentName}; | ||
| 136 : | |||
| 137 : | my $studentRecord = $db->getUser($studentName); # checked | ||
| 138 : | die "record for user $studentName not found" unless $studentRecord; | ||
| 139 : | |||
| 140 : | my $fullName = join("", $studentRecord->first_name," ", $studentRecord->last_name); | ||
| 141 : | |||
| 142 : | my $courseHomePage = $urlpath->new(type => 'set_list', | ||
| 143 : | args => {courseID => $courseName} | ||
| 144 : | ); | ||
| 145 : | my $act_as_student_url = $self->systemLink($courseHomePage, | ||
| 146 : | params => { effectiveUser => $studentName } | ||
| 147 : | ); | ||
| 148 : | |||
| 149 : | my $email = $studentRecord->email_address; | ||
| 150 : | |||
| 151 : | CGI::a({-href=>"mailto:$email"},$email),CGI::br(), | ||
| 152 : | "Section: ", $studentRecord->section, CGI::br(), | ||
| 153 : | "Recitation: ", $studentRecord->recitation,CGI::br(), | ||
| 154 : | 'Act as: ', | ||
| 155 : | CGI::a({-href=>$act_as_student_url},$studentRecord->user_id); | ||
| 156 : | WeBWorK::ContentGenerator::Grades::displayStudentStats($self,$studentName); | ||
| 157 : | |||
| 158 : | # The table format has been borrowed from the Grades.pm module | ||
| 159 : | } elsif( $type eq 'set') { | ||
| 160 : | my $setName = $self->{setName}; | ||
| 161 : | $self->displaySets($self->{setName}); | ||
| 162 : | } elsif ($type eq '') { | ||
| 163 : | |||
| 164 : | $self->index; | ||
| 165 : | } else { | ||
| 166 : | warn "Don't recognize statistics display type: |$type|"; | ||
| 167 : | |||
| 168 : | } | ||
| 169 : | |||
| 170 : | |||
| 171 : | return ''; | ||
| 172 : | |||
| 173 : | } | ||
| 174 : | sub index { | ||
| 175 : | my $self = shift; | ||
| 176 : | my $r = $self->r; | ||
| 177 : | my $urlpath = $r->urlpath; | ||
| 178 : | my $ce = $r->ce; | ||
| 179 : | my $db = $r->db; | ||
| 180 : | my $courseName = $urlpath->arg("courseID"); | ||
| 181 : | |||
| 182 : | my @studentList = sort $db->listUsers; | ||
| 183 : | my @setList = sort $db->listGlobalSets; | ||
| 184 : | mschmitt | 2204 | |
| 185 : | ## Edit to filter out students you aren't allowed to see | ||
| 186 : | # | ||
| 187 : | my @myUsers; | ||
| 188 : | my @studentRecords = $db->getUsers; | ||
| 189 : | my $user = $r->param("user"); | ||
| 190 : | apizer | 2159 | |
| 191 : | mschmitt | 2204 | my (@viewable_sections, @viewable_recitations); |
| 192 : | if (defined @{$ce->{viewable_sections}->{$user}}) | ||
| 193 : | {@viewable_sections = @{$ce->{viewable_sections}->{$user}};} | ||
| 194 : | if (defined @{$ce->{viewable_recitations}->{$user}}) | ||
| 195 : | {@viewable_recitations = @{$ce->{viewable_recitations}->{$user}};} | ||
| 196 : | if (@viewable_sections or @viewable_recitations){ | ||
| 197 : | foreach my $studentL (@studentList){ | ||
| 198 : | my $keep = 0; | ||
| 199 : | my $student = $db->getUser($studentL); | ||
| 200 : | foreach my $sec (@viewable_sections){ | ||
| 201 : | if ($student->section() eq $sec){$keep = 1; last;} | ||
| 202 : | } | ||
| 203 : | foreach my $rec (@viewable_recitations){ | ||
| 204 : | if ($student->recitation() eq $rec){$keep = 1; last;} | ||
| 205 : | } | ||
| 206 : | if ($keep) {push @myUsers, $studentL;} | ||
| 207 : | } | ||
| 208 : | # @studentList = @myUsers; | ||
| 209 : | } | ||
| 210 : | else {@myUsers = @studentList;} | ||
| 211 : | apizer | 2159 | my @setLinks = (); |
| 212 : | my @studentLinks = (); | ||
| 213 : | foreach my $set (@setList) { | ||
| 214 : | my $setStatisticsPage = $urlpath->newFromModule($urlpath->module, | ||
| 215 : | courseID => $courseName, | ||
| 216 : | statType => 'set', | ||
| 217 : | setID => $set | ||
| 218 : | ); | ||
| 219 : | push @setLinks, CGI::a({-href=>$self->systemLink($setStatisticsPage) },"set $set" ); | ||
| 220 : | } | ||
| 221 : | |||
| 222 : | mschmitt | 2204 | foreach my $student (@myUsers) { |
| 223 : | apizer | 2159 | my $userStatisticsPage = $urlpath->newFromModule($urlpath->module, |
| 224 : | courseID => $courseName, | ||
| 225 : | statType => 'student', | ||
| 226 : | userID => $student | ||
| 227 : | ); | ||
| 228 : | push @studentLinks, CGI::a({-href=>$self->systemLink($userStatisticsPage, | ||
| 229 : | prams=>{effectiveUser => $student} | ||
| 230 : | )}," $student" ),; | ||
| 231 : | } | ||
| 232 : | print join("", | ||
| 233 : | CGI::start_table({-border=>2, -cellpadding=>20}), | ||
| 234 : | CGI::Tr( | ||
| 235 : | CGI::td({-valign=>'top'}, | ||
| 236 : | CGI::h3({-align=>'center'},'View student progress by set'), | ||
| 237 : | CGI::ul( CGI::li( [@setLinks] ) ), | ||
| 238 : | ), | ||
| 239 : | CGI::td({-valign=>'top'}, | ||
| 240 : | CGI::h3({-align=>'center'},'View student progress by student'), | ||
| 241 : | CGI::ul(CGI::li( [ @studentLinks ] ) ), | ||
| 242 : | ), | ||
| 243 : | ), | ||
| 244 : | CGI::end_table(), | ||
| 245 : | ); | ||
| 246 : | |||
| 247 : | } | ||
| 248 : | ################################################### | ||
| 249 : | sub displaySets { | ||
| 250 : | my $self = shift; | ||
| 251 : | my $r = $self->r; | ||
| 252 : | my $urlpath = $r->urlpath; | ||
| 253 : | my $db = $r->db; | ||
| 254 : | my $ce = $r->ce; | ||
| 255 : | my $authz = $r->authz; | ||
| 256 : | my $courseName = $urlpath->arg("courseID"); | ||
| 257 : | my $setName = $urlpath->arg("setID"); | ||
| 258 : | my $user = $r->param('user'); | ||
| 259 : | my $setRecord = $self->{setRecord}; | ||
| 260 : | my $root = $ce->{webworkURLs}->{root}; | ||
| 261 : | |||
| 262 : | my $setStatsPage = $urlpath->newFromModule($urlpath->module,courseID=>$courseName,statType=>'sets',setID=>$setName); | ||
| 263 : | my $sort_method_name = $r->param('sort'); | ||
| 264 : | my @studentList = $db->listUsers; | ||
| 265 : | glarose | 3130 | |
| 266 : | # another versioning/gateway change. in many cases we don't want or need | ||
| 267 : | # all of the columns that are put in here by default, so we add a set of | ||
| 268 : | # flags for which columns to show. for versioned sets we may also want to | ||
| 269 : | # only see the best score, so we include that as an option also. | ||
| 270 : | # defaults: | ||
| 271 : | my %showColumns = ( 'name' => 1, 'score' => 1, 'outof' => 1, | ||
| 272 : | 'date' => 0, 'index' => 1, 'problems' => 1, | ||
| 273 : | 'section' => 1, 'recit' => 1, 'login' => 1 ); | ||
| 274 : | my $showBestOnly = 0; | ||
| 275 : | apizer | 2159 | |
| 276 : | my @index_list = (); # list of all student index | ||
| 277 : | my @score_list = (); # list of all student total percentage scores | ||
| 278 : | my %attempts_list_for_problem = (); # a list of the number of attempts for each problem | ||
| 279 : | my %number_of_attempts_for_problem = (); # the total number of attempst for this problem (sum of above list) | ||
| 280 : | my %number_of_students_attempting_problem = (); # the number of students attempting this problem. | ||
| 281 : | my %correct_answers_for_problem = (); # the number of students correctly answering this problem (partial correctness allowed) | ||
| 282 : | my $sort_method = sub { | ||
| 283 : | my ($a,$b) = @_; | ||
| 284 : | return 0 unless defined($sort_method_name); | ||
| 285 : | return $b->{score} <=> $a->{score} if $sort_method_name eq 'score'; | ||
| 286 : | return $b->{index} <=> $a->{index} if $sort_method_name eq 'index'; | ||
| 287 : | return $a->{section} cmp $b->{section} if $sort_method_name eq 'section'; | ||
| 288 : | apizer | 2197 | return $a->{recitation} cmp $b->{recitation} if $sort_method_name eq 'recitation'; |
| 289 : | return $a->{user_id} cmp $b->{user_id} if $sort_method_name eq 'user_id'; | ||
| 290 : | apizer | 2159 | if ($sort_method_name =~/p(\d+)/) { |
| 291 : | my $left = $b->{problemData}->{$1} ||0; | ||
| 292 : | my $right = $a->{problemData}->{$1} ||0; | ||
| 293 : | return $left <=> $right; # sort by number of attempts. | ||
| 294 : | } | ||
| 295 : | |||
| 296 : | }; | ||
| 297 : | |||
| 298 : | glarose | 2977 | # get versioning information |
| 299 : | my $GlobalSet = $db->getGlobalSet($setName); | ||
| 300 : | my $setIsVersioned = | ||
| 301 : | ( defined($GlobalSet->assignment_type()) && | ||
| 302 : | $GlobalSet->assignment_type() =~ /gateway/ ) ? 1 : 0; | ||
| 303 : | |||
| 304 : | glarose | 3130 | # reset column view options based on whether the set is versioned and, if so, |
| 305 : | # input parameters | ||
| 306 : | if ( $setIsVersioned ) { | ||
| 307 : | # the returning parameter lets us set defaults for versioned sets | ||
| 308 : | my $ret = $r->param('returning'); | ||
| 309 : | $showColumns{'date'} = $ret ? $r->param('show_date') : 1; | ||
| 310 : | $showColumns{'index'} = $ret ? $r->param('show_index') : 0; | ||
| 311 : | $showColumns{'problems'} = $ret ? $r->param('show_problems') : 0; | ||
| 312 : | $showColumns{'section'} = $ret? $r->param('show_section') : 0; | ||
| 313 : | $showColumns{'recit'} = $ret ? $r->param('show_recitation') : 0; | ||
| 314 : | $showColumns{'login'} = $ret ? $r->param('show_login') : 0; | ||
| 315 : | $showBestOnly = $ret ? $r->param('show_best_only') : 0; | ||
| 316 : | } | ||
| 317 : | |||
| 318 : | apizer | 2159 | ############################################################### |
| 319 : | # Print tables | ||
| 320 : | ############################################################### | ||
| 321 : | |||
| 322 : | my $max_num_problems = 0; | ||
| 323 : | # get user records | ||
| 324 : | $WeBWorK::timer->continue("Begin obtaining user records for set $setName") if defined($WeBWorK::timer); | ||
| 325 : | my @userRecords = $db->getUsers(@studentList); | ||
| 326 : | $WeBWorK::timer->continue("End obtaining user records for set $setName") if defined($WeBWorK::timer); | ||
| 327 : | $WeBWorK::timer->continue("begin main loop") if defined($WeBWorK::timer); | ||
| 328 : | my @augmentedUserRecords = (); | ||
| 329 : | my $number_of_active_students; | ||
| 330 : | mschmitt | 2204 | |
| 331 : | ## Edit to filter out students | ||
| 332 : | # | ||
| 333 : | my @myUsers; | ||
| 334 : | my $ActiveUser = $r->param("user"); | ||
| 335 : | my (@viewable_sections, @viewable_recitations); | ||
| 336 : | if (defined @{$ce->{viewable_sections}->{$user}}) | ||
| 337 : | {@viewable_sections = @{$ce->{viewable_sections}->{$user}};} | ||
| 338 : | if (defined @{$ce->{viewable_recitations}->{$user}}) | ||
| 339 : | {@viewable_recitations = @{$ce->{viewable_recitations}->{$user}};} | ||
| 340 : | if (@viewable_sections or @viewable_recitations){ | ||
| 341 : | foreach my $student (@userRecords){ | ||
| 342 : | my $keep = 0; | ||
| 343 : | foreach my $sec (@viewable_sections){ | ||
| 344 : | if ($student->section() eq $sec){$keep = 1; last;} | ||
| 345 : | } | ||
| 346 : | foreach my $rec (@viewable_recitations){ | ||
| 347 : | if ($student->recitation() eq $rec){$keep = 1; last;} | ||
| 348 : | } | ||
| 349 : | if ($keep) {push @myUsers, $student;} | ||
| 350 : | } | ||
| 351 : | } | ||
| 352 : | else {@myUsers = @userRecords;} | ||
| 353 : | glarose | 3130 | |
| 354 : | mschmitt | 2204 | foreach my $studentRecord (@myUsers) { |
| 355 : | apizer | 2159 | next unless ref($studentRecord); |
| 356 : | my $student = $studentRecord->user_id; | ||
| 357 : | next if $studentRecord->last_name =~/^practice/i; # don't show practice users | ||
| 358 : | next if $studentRecord->status !~/C/; # don't show dropped students FIXME | ||
| 359 : | $number_of_active_students++; | ||
| 360 : | glarose | 2977 | |
| 361 : | # build list of versioned sets for this student user | ||
| 362 : | my @allSetNames = (); | ||
| 363 : | if ( $setIsVersioned ) { | ||
| 364 : | my $numVersions = $db->getUserSetVersionNumber($student,$setName); | ||
| 365 : | for ( my $i=1; $i<=$numVersions; $i++ ) { | ||
| 366 : | $allSetNames[$i-1] = "$setName,v$i"; | ||
| 367 : | } | ||
| 368 : | } else { | ||
| 369 : | @allSetNames = ( "$setName" ); | ||
| 370 : | } | ||
| 371 : | |||
| 372 : | glarose | 3130 | # for versioned sets, we might be keeping only the high score |
| 373 : | my $maxScore = -1; | ||
| 374 : | my $max_hash = {}; | ||
| 375 : | # make this global to the student loop | ||
| 376 : | my $act_as_student_url = ''; | ||
| 377 : | |||
| 378 : | glarose | 2977 | foreach my $sN ( @allSetNames ) { |
| 379 : | |||
| 380 : | apizer | 2159 | my $status = 0; |
| 381 : | my $attempted = 0; | ||
| 382 : | my $longStatus = ''; | ||
| 383 : | my $string = ''; | ||
| 384 : | my $twoString = ''; | ||
| 385 : | my $totalRight = 0; | ||
| 386 : | my $total = 0; | ||
| 387 : | my $num_of_attempts = 0; | ||
| 388 : | my %h_problemData = (); | ||
| 389 : | my $probNum = 0; | ||
| 390 : | |||
| 391 : | $WeBWorK::timer->continue("Begin obtaining problem records for user $student set $setName") if defined($WeBWorK::timer); | ||
| 392 : | |||
| 393 : | glarose | 2977 | my @problemRecords = sort {$a->problem_id <=> $b->problem_id } $db->getAllUserProblems( $student, $sN ); |
| 394 : | $WeBWorK::timer->continue("End obtaining problem records for user $student set $sN") if defined($WeBWorK::timer); | ||
| 395 : | apizer | 2159 | my $num_of_problems = @problemRecords; |
| 396 : | $max_num_problems = ($max_num_problems>= $num_of_problems) ? $max_num_problems : $num_of_problems; | ||
| 397 : | |||
| 398 : | foreach my $problemRecord (@problemRecords) { | ||
| 399 : | next unless ref($problemRecord); | ||
| 400 : | my $probID = $problemRecord->problem_id; | ||
| 401 : | |||
| 402 : | my $valid_status = 0; | ||
| 403 : | unless (defined($problemRecord) ){ | ||
| 404 : | glarose | 2977 | # warn "Can't find record for problem $prob in set $sN for $student"; |
| 405 : | apizer | 2159 | # FIXME check the legitimate reasons why a student record might not be defined |
| 406 : | next; | ||
| 407 : | } | ||
| 408 : | $status = $problemRecord->status || 0; | ||
| 409 : | $attempted = $problemRecord->attempted; | ||
| 410 : | if (!$attempted){ | ||
| 411 : | $longStatus = '. '; | ||
| 412 : | } | ||
| 413 : | elsif ($status >= 0 and $status <=1 ) { | ||
| 414 : | $valid_status = 1; | ||
| 415 : | $longStatus = int(100*$status+.5); | ||
| 416 : | if ($longStatus == 100) { | ||
| 417 : | $longStatus = 'C '; | ||
| 418 : | } | ||
| 419 : | else { | ||
| 420 : | glarose | 3130 | # change here to give a more reasonable output for gateways, |
| 421 : | # and other versioned sets with only one attempt per version | ||
| 422 : | # FIXME this isn't good in general, when versioned sets might | ||
| 423 : | # FIXME allow multiple submissions | ||
| 424 : | $longStatus = ($setIsVersioned)?'X ': | ||
| 425 : | &threeSpaceFill($longStatus); | ||
| 426 : | apizer | 2159 | } |
| 427 : | } | ||
| 428 : | else { | ||
| 429 : | $longStatus = 'X '; | ||
| 430 : | } | ||
| 431 : | |||
| 432 : | my $num_correct = $problemRecord->num_correct || 0; | ||
| 433 : | my $num_incorrect = $problemRecord->num_incorrect || 0; | ||
| 434 : | # It's possible that $incorrect is an empty or blank string instead of 0 the || clause fixes this and prevents | ||
| 435 : | # warning messages in the comparison below. | ||
| 436 : | $string .= $longStatus; | ||
| 437 : | $twoString .= threeSpaceFill($num_incorrect); | ||
| 438 : | my $probValue = $problemRecord->value; | ||
| 439 : | gage | 2536 | $probValue = 1 unless defined($probValue) and $probValue ne ""; # FIXME?? set defaults here? |
| 440 : | apizer | 2159 | $total += $probValue; |
| 441 : | $totalRight += round_score($status*$probValue) if $valid_status; | ||
| 442 : | |||
| 443 : | $num_of_attempts += $num_correct + $num_incorrect; | ||
| 444 : | |||
| 445 : | $h_problemData{$probID} = $num_incorrect; | ||
| 446 : | |||
| 447 : | $correct_answers_for_problem{$probID} = 0 unless defined($correct_answers_for_problem{$probID}); | ||
| 448 : | # add on the scores for this problem | ||
| 449 : | if (defined($attempted) and $attempted) { | ||
| 450 : | $number_of_students_attempting_problem{$probID}++; | ||
| 451 : | push( @{ $attempts_list_for_problem{$probID} } , $num_of_attempts); | ||
| 452 : | $number_of_attempts_for_problem{$probID} += $num_of_attempts; | ||
| 453 : | $correct_answers_for_problem{$probID} += $status; | ||
| 454 : | } | ||
| 455 : | |||
| 456 : | } | ||
| 457 : | glarose | 3130 | |
| 458 : | # for versioned tests we might be displaying the test date | ||
| 459 : | my $dateOfTest = ''; | ||
| 460 : | # annoyingly, this is a set property, so get the set | ||
| 461 : | if ( $setIsVersioned && $showColumns{'date'} ) { | ||
| 462 : | my @userSet = | ||
| 463 : | $db->getMergedVersionedSets( [ $studentRecord->user_id, $setName, $sN ] ); | ||
| 464 : | if ( defined( $userSet[0] ) ) { # if this isn't defined, something's wrong | ||
| 465 : | $dateOfTest = localtime( $userSet[0]->version_creation_time() ); | ||
| 466 : | } else { | ||
| 467 : | $dateOfTest = '???'; | ||
| 468 : | } | ||
| 469 : | } | ||
| 470 : | apizer | 2159 | |
| 471 : | glarose | 3130 | $act_as_student_url = $self->systemLink($urlpath->new(type=>'set_list',args=>{courseID=>$courseName}), |
| 472 : | apizer | 2159 | params=>{effectiveUser => $studentRecord->user_id} |
| 473 : | ); | ||
| 474 : | glarose | 3130 | # update for versioned sets |
| 475 : | # if ( $setIsVersioned ) { | ||
| 476 : | # $act_as_student_url =~ s/\/$courseName/\/$courseName\/quiz_mode/; | ||
| 477 : | # } | ||
| 478 : | |||
| 479 : | apizer | 2159 | my $email = $studentRecord->email_address; |
| 480 : | # FIXME this needs formatting | ||
| 481 : | glarose | 3130 | |
| 482 : | # change to give better output for gateways | ||
| 483 : | # FIXME this assumes that all versioned sets only allow one attempt | ||
| 484 : | # FIXME per version, which is the case for gateways but probably not others | ||
| 485 : | my $longtwo = ( $setIsVersioned ) ? $string : | ||
| 486 : | "$string\n$twoString"; | ||
| 487 : | apizer | 2159 | |
| 488 : | my $avg_num_attempts = ($num_of_problems) ? $num_of_attempts/$num_of_problems : 0; | ||
| 489 : | my $successIndicator = ($avg_num_attempts) ? ($totalRight/$total)**2/$avg_num_attempts : 0 ; | ||
| 490 : | my $temp_hash = { user_id => $studentRecord->user_id, | ||
| 491 : | last_name => $studentRecord->last_name, | ||
| 492 : | first_name => $studentRecord->first_name, | ||
| 493 : | score => $totalRight, | ||
| 494 : | total => $total, | ||
| 495 : | index => $successIndicator, | ||
| 496 : | section => $studentRecord->section, | ||
| 497 : | recitation => $studentRecord->recitation, | ||
| 498 : | glarose | 3130 | problemString => "<pre>$longtwo</pre>", |
| 499 : | apizer | 2159 | act_as_student => $act_as_student_url, |
| 500 : | email_address => $studentRecord->email_address, | ||
| 501 : | problemData => {%h_problemData}, | ||
| 502 : | glarose | 3130 | date => $dateOfTest, |
| 503 : | apizer | 2159 | }; |
| 504 : | glarose | 3130 | |
| 505 : | # keep track of best score | ||
| 506 : | if ( $totalRight > $maxScore ) { | ||
| 507 : | $maxScore = $totalRight; | ||
| 508 : | $max_hash = { %$temp_hash }; | ||
| 509 : | } | ||
| 510 : | |||
| 511 : | # if we're showing all records, add it in to the list | ||
| 512 : | if ( ! $showBestOnly ) { | ||
| 513 : | apizer | 2159 | # add this data to the list of total scores (out of 100) |
| 514 : | # add this data to the list of success indices. | ||
| 515 : | push( @index_list, $temp_hash->{index}); | ||
| 516 : | push( @score_list, ($temp_hash->{total}) ?$temp_hash->{score}/$temp_hash->{total} : 0 ) ; | ||
| 517 : | push( @augmentedUserRecords, $temp_hash ); | ||
| 518 : | glarose | 2977 | } |
| 519 : | glarose | 3130 | } |
| 520 : | # if we're showing only the best score, add the best score now | ||
| 521 : | if ( $showBestOnly ) { | ||
| 522 : | if ( ! %$max_hash ) { # then we have no tests---e.g., for proctors | ||
| 523 : | next; # if we could exclude proctors, etc., we | ||
| 524 : | # might want to keep these: | ||
| 525 : | $max_hash = { user_id => $studentRecord->user_id(), | ||
| 526 : | last_name => $studentRecord->last_name(), | ||
| 527 : | first_name => $studentRecord->first_name(), | ||
| 528 : | score => 0, | ||
| 529 : | total => 'n/a', | ||
| 530 : | index => 0, | ||
| 531 : | section => $studentRecord->section(), | ||
| 532 : | recitation => $studentRecord->recitation(), | ||
| 533 : | problemString => 'no attempt recorded', | ||
| 534 : | act_as_student => $act_as_student_url, | ||
| 535 : | email_address => $studentRecord->email_address(), | ||
| 536 : | problemData => {}, | ||
| 537 : | date => 'none', | ||
| 538 : | } | ||
| 539 : | } | ||
| 540 : | push( @index_list, $max_hash->{index} ); | ||
| 541 : | push( @score_list, | ||
| 542 : | ($max_hash->{total} && $max_hash->{total} ne 'n/a') ? | ||
| 543 : | $max_hash->{score}/$max_hash->{total} : 0 ); | ||
| 544 : | push( @augmentedUserRecords, $max_hash ); | ||
| 545 : | } | ||
| 546 : | |||
| 547 : | } | ||
| 548 : | |||
| 549 : | apizer | 2159 | $WeBWorK::timer->continue("end mainloop") if defined($WeBWorK::timer); |
| 550 : | |||
| 551 : | @augmentedUserRecords = sort { &$sort_method($a,$b) | ||
| 552 : | || | ||
| 553 : | lc($a->{last_name}) cmp lc($b->{last_name} ) } @augmentedUserRecords; | ||
| 554 : | |||
| 555 : | ##################################################################################### | ||
| 556 : | # construct header | ||
| 557 : | my $problem_header = ''; | ||
| 558 : | apizer | 2169 | |
| 559 : | glarose | 3130 | # changes for gateways/versioned sets here. in this case we allow instructors |
| 560 : | # to modify the appearance of output, which we do with a form. so paste in the | ||
| 561 : | # form header here, and make appropriate modifications | ||
| 562 : | my $verSelectors = ''; | ||
| 563 : | if ( $setIsVersioned ) { | ||
| 564 : | print CGI::start_form({'method' => 'post', | ||
| 565 : | 'action' => $self->systemLink($urlpath, | ||
| 566 : | authen=>0), | ||
| 567 : | 'name' => 'StudentProgress'}); | ||
| 568 : | print $self->hidden_authen_fields(); | ||
| 569 : | |||
| 570 : | $verSelectors = CGI::p({'style'=>'background-color:#eeeeee;color:black;'}, | ||
| 571 : | "Display options: Show ", | ||
| 572 : | CGI::hidden(-name=>'returning', -value=>'1'), | ||
| 573 : | CGI::checkbox(-name=>'show_best_only', -value=>'1', | ||
| 574 : | -checked=>$showBestOnly, | ||
| 575 : | -label=>' only best scores; '), | ||
| 576 : | CGI::checkbox(-name=>'show_index', -value=>'1', | ||
| 577 : | -checked=>$showColumns{'index'}, | ||
| 578 : | -label=>' success indicator; '), | ||
| 579 : | CGI::checkbox(-name=>'show_date', -value=>'1', | ||
| 580 : | -checked=>$showColumns{'date'}, | ||
| 581 : | -label=>' test date; '), | ||
| 582 : | CGI::checkbox(-name=>'show_problems', -value=>'1', | ||
| 583 : | -checked=>$showColumns{'problems'}, | ||
| 584 : | -label=>'problems;'), "\n", CGI::br(), "\n", | ||
| 585 : | CGI::checkbox(-name=>'show_section', -value=>'1', | ||
| 586 : | -checked=>$showColumns{'section'}, | ||
| 587 : | -label=>' section #; '), | ||
| 588 : | CGI::checkbox(-name=>'show_recitation', -value=>'1', | ||
| 589 : | -checked=>$showColumns{'recit'}, | ||
| 590 : | -label=>' recitation #; '), | ||
| 591 : | CGI::checkbox(-name=>'show_login', -value=>'1', | ||
| 592 : | -checked=>$showColumns{'login'}, | ||
| 593 : | -label=>'login'), "\n", CGI::br(), "\n", | ||
| 594 : | CGI::submit(-value=>'Update Display') | ||
| 595 : | ); | ||
| 596 : | } | ||
| 597 : | |||
| 598 : | # set up a column header variable to allow us to drop certain columns | ||
| 599 : | # for versioned set viewing | ||
| 600 : | my @columnHdrs = (); | ||
| 601 : | # define this for use on all of the sorts | ||
| 602 : | glarose | 3131 | my %paramList = (); |
| 603 : | if ( $setIsVersioned ) { | ||
| 604 : | my $rtng = (defined( $r->param('returning') )) ? | ||
| 605 : | $r->param('returning') : 0; | ||
| 606 : | %paramList = ( 'returning' => $rtng, | ||
| 607 : | 'show_best_only' => $showBestOnly, | ||
| 608 : | 'show_index' => $showColumns{'index'}, | ||
| 609 : | 'show_date' => $showColumns{'date'}, | ||
| 610 : | 'show_problems' => $showColumns{'problems'}, | ||
| 611 : | 'show_section' => $showColumns{'section'}, | ||
| 612 : | 'show_recitation' => $showColumns{'recit'}, | ||
| 613 : | 'show_login' => $showColumns{'login'} ); | ||
| 614 : | } | ||
| 615 : | push(@columnHdrs, (! defined($sort_method_name) || ($sort_method_name ne 'name' && $sort_method_name ne '')) ? CGI::a({'href'=>$self->systemLink($setStatsPage,params=>{'sort'=>'name', %paramList})},'Name') : 'Name') if ( $showColumns{'name'} ); | ||
| 616 : | push(@columnHdrs, (! defined($sort_method_name) || $sort_method_name ne 'score') ? CGI::a({"href"=>$self->systemLink($setStatsPage,params=>{'sort'=>'score', %paramList})},'Score') : 'Score') if ( $showColumns{'score'} ); | ||
| 617 : | glarose | 3130 | push(@columnHdrs, 'Out'.CGI::br().'Of') if ( $showColumns{'outof'} ); |
| 618 : | push(@columnHdrs, 'Date') if ( $showColumns{'date'} ); | ||
| 619 : | glarose | 3131 | push(@columnHdrs, (! defined($sort_method_name) || $sort_method_name ne 'index') ? CGI::a({"href"=>$self->systemLink($setStatsPage,params=>{'sort'=>'index', %paramList})},'Ind') : 'Ind') if ( $showColumns{'index'} ); |
| 620 : | glarose | 3130 | push(@columnHdrs, 'Problems'.CGI::br().$problem_header) if ( $showColumns{'problems'} ); |
| 621 : | glarose | 3131 | push(@columnHdrs, (! defined($sort_method_name) || $sort_method_name ne 'section') ? CGI::a({"href"=>$self->systemLink($setStatsPage,params=>{'sort'=>'section', %paramList})},'Section') : 'Section') if ( $showColumns{'section'} ); |
| 622 : | push(@columnHdrs, (! defined($sort_method_name) || $sort_method_name ne 'recitation') ? CGI::a({"href"=>$self->systemLink($setStatsPage,params=>{'sort'=>'recitation', %paramList})},'Recitation'):'Recitation') if ( $showColumns{'recit'} ); | ||
| 623 : | push(@columnHdrs, (! defined($sort_method_name) || $sort_method_name ne 'user_id') ? CGI::a({"href"=>$self->systemLink($setStatsPage,params=>{'sort'=>'user_id', %paramList})},'Login Name'):'Login Name') if ( $showColumns{'login'} ); | ||
| 624 : | glarose | 3130 | |
| 625 : | apizer | 2159 | |
| 626 : | apizer | 2169 | CGI::br(), |
| 627 : | CGI::br(), | ||
| 628 : | CGI::p('A period (.) indicates a problem has not been attempted, a "C" indicates | ||
| 629 : | a problem has been answered 100% correctly, and a number from 0 to 99 | ||
| 630 : | indicates the percentage of partial credit earned. The number on the | ||
| 631 : | second line gives the number of incorrect attempts. The success indicator,' | ||
| 632 : | ,CGI::i('Ind'),', for each student is calculated as', | ||
| 633 : | CGI::br(), | ||
| 634 : | '100*(totalNumberOfCorrectProblems / totalNumberOfProblems)^2 / (AvgNumberOfAttemptsPerProblem)',CGI::br(), | ||
| 635 : | 'or 0 if there are no attempts.' | ||
| 636 : | apizer | 2159 | ), |
| 637 : | apizer | 2169 | CGI::br(), |
| 638 : | "Click on student's name to see the student's version of the problem set; | ||
| 639 : | Click heading to sort table. ", | ||
| 640 : | CGI::br(), | ||
| 641 : | glarose | 3130 | $verSelectors, |
| 642 : | apizer | 2169 | CGI::br(), |
| 643 : | defined($sort_method_name) ?" sort method is $sort_method_name":"", | ||
| 644 : | apizer | 2159 | CGI::start_table({-border=>5,style=>'font-size:smaller'}), |
| 645 : | glarose | 3130 | CGI::Tr(CGI::td( {-align=>'left'}, [ @columnHdrs ])); |
| 646 : | glarose | 2977 | |
| 647 : | my $prevFullName = ''; | ||
| 648 : | my $vNum = 1; | ||
| 649 : | apizer | 2159 | foreach my $rec (@augmentedUserRecords) { |
| 650 : | my $fullName = join("", $rec->{first_name}," ", $rec->{last_name}); | ||
| 651 : | my $email = $rec->{email_address}; | ||
| 652 : | my $twoString = $rec->{twoString}; | ||
| 653 : | glarose | 2977 | |
| 654 : | # revision to make versioned sets' format nicer | ||
| 655 : | my $nameEntry = ''; | ||
| 656 : | if ( $fullName eq $prevFullName ) { | ||
| 657 : | $vNum++; | ||
| 658 : | glarose | 3130 | $nameEntry = CGI::span({-style=>"text-align:right;"}, |
| 659 : | "(v$vNum)"); | ||
| 660 : | glarose | 2977 | } else { |
| 661 : | glarose | 3130 | $nameEntry = |
| 662 : | CGI::a({-href=>$rec->{act_as_student}},$fullName) . | ||
| 663 : | ($setIsVersioned && ! $showBestOnly ? ' (v1)' : ' ') . | ||
| 664 : | CGI::br() . CGI::a({-href=>"mailto:$email"},$email); | ||
| 665 : | glarose | 2977 | $vNum = 1; |
| 666 : | $prevFullName = $fullName; | ||
| 667 : | } | ||
| 668 : | glarose | 3130 | # build set of columns that we want |
| 669 : | my @cols = (); | ||
| 670 : | push(@cols, $nameEntry) if ($showColumns{'name'}); | ||
| 671 : | push(@cols, sprintf("%0.2f",$rec->{score})) if ($showColumns{'score'}); | ||
| 672 : | push(@cols, $rec->{total}) if ( $showColumns{'outof'} ); | ||
| 673 : | push(@cols, $self->nbsp($rec->{date})) if ($showColumns{'date'}); | ||
| 674 : | push(@cols, sprintf("%0.0f",100*($rec->{index}))) if ($showColumns{'index'}); | ||
| 675 : | push(@cols, $rec->{problemString}) if ($showColumns{'problems'}); | ||
| 676 : | push(@cols, $self->nbsp($rec->{section})) if ($showColumns{'section'}); | ||
| 677 : | push(@cols, $self->nbsp($rec->{recitation})) if ($showColumns{'recit'}); | ||
| 678 : | push(@cols, $rec->{user_id}) if ($showColumns{'login'}); | ||
| 679 : | glarose | 2977 | |
| 680 : | glarose | 3130 | print CGI::Tr( CGI::td( [ @cols ] ) ); |
| 681 : | glarose | 2977 | # CGI::td(CGI::a({-href=>$rec->{act_as_student}},$fullName), CGI::br(), CGI::a({-href=>"mailto:$email"},$email)), |
| 682 : | glarose | 3130 | |
| 683 : | apizer | 2159 | } |
| 684 : | |||
| 685 : | print CGI::end_table(); | ||
| 686 : | |||
| 687 : | |||
| 688 : | |||
| 689 : | |||
| 690 : | return ""; | ||
| 691 : | } | ||
| 692 : | |||
| 693 : | |||
| 694 : | ################################# | ||
| 695 : | # Utility function NOT a method | ||
| 696 : | ################################# | ||
| 697 : | sub threeSpaceFill { | ||
| 698 : | my $num = shift @_ || 0; | ||
| 699 : | |||
| 700 : | if (length($num)<=1) {return "$num".' ';} | ||
| 701 : | elsif (length($num)==2) {return "$num".' ';} | ||
| 702 : | else {return "## ";} | ||
| 703 : | } | ||
| 704 : | sub round_score{ | ||
| 705 : | return shift; | ||
| 706 : | } | ||
| 707 : | 1; |
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |