--- loncom/interface/Attic/lonchart.pm 2002/06/28 21:12:46 1.46 +++ loncom/interface/Attic/lonchart.pm 2002/07/08 13:38:52 1.55 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # (Publication Handler # -# $Id: lonchart.pm,v 1.46 2002/06/28 21:12:46 stredwic Exp $ +# $Id: lonchart.pm,v 1.55 2002/07/08 13:38:52 stredwic Exp $ # # Copyright Michigan State University Board of Trustees # @@ -46,6 +46,59 @@ # ### +=pod + +=head1 NAME + +lonchart + +=head1 SYNOPSIS + +Quick display of students grades for a course in a compressed table format. + +=head1 DESCRIPTION + +This module process all student grades for a course and turns them into a +table like structure. + +This is part of the LearningOnline Network with CAPA project +described at http://www.lon-capa.org + +lonchart presents the user with a condensed view all a course's data. The +class title, the number of students, and the date for the last update of the +displayed data. There is also a legend that describes the chart values. + +For each valid grade for a student is linked with a submission record for that +problem. The ability to add and remove columns of data from the chart was +added for reducing the burden of having to scroll through large quantities +of data. The interface also allows for sorting of students by username, +last name, and section number of class. Active and expired students are +also available. + +The interface is controlled by three primary buttons: Recalculate Chart, +Refresh Chart, and Reset Selections. Recalculate Chart will update +the chart to the most recent data and keep the display settings for the chart +the same. Refresh Chart is used to redisplay the chart after selecting +different output formatting. Reset Selections is used to set the chart +display options back to default values. + +=head1 CODE LAYOUT DESCRIPTION + +The code is broken down into five components: formatting data for printing, +downloading data from servers, processing data, helper functions, +and the central processing functions. The module is broken into chunks +for each component. + +=head1 PACKAGES USED + + Apache::Constants qw(:common :http) + Apache::lonnet() + Apache::loncommon() + HTML::TokeParser + GDBM_File + +=cut + package Apache::lonchart; use strict; @@ -55,28 +108,102 @@ use Apache::loncommon(); use HTML::TokeParser; use GDBM_File; -my $jr; +#my $jr; + +=pod + +=head1 FORMAT DATA FOR PRINTING + +=cut + # ----- FORMAT PRINT DATA ---------------------------------------------- +=pod + +=item &FormatStudentInformation() + +This function produces a formatted string of the student's information: +username, domain, section, full name, and PID. + +=over 4 + +Input: $cache, $name, $studentInformation, $spacePadding + +$cache: This is a pointer to a hash that is tied to the cached data + +$name: The name and domain of the current student in name:domain format + +$studentInformation: A pointer to an array holding the names used to + +remove data from the hash. They represent the name of the data to be removed. + +$spacePadding: Extra spaces that represent the space between columns + +Output: $Str + +$Str: Formatted string. + +=back + +=cut + sub FormatStudentInformation { my ($cache,$name,$studentInformation,$spacePadding)=@_; - my $Str='
';
+    my $Str='';
 
-    foreach (@$studentInformation) {
-	my $data=$cache->{$name.':'.$_};
+    for(my $index=0; $index<(scalar @$studentInformation); $index++) {
+        if(!&ShouldShowColumn($cache, 'heading'.$index)) {
+            next;
+        }
+	my $data=$cache->{$name.':'.$studentInformation->[$index]};
 	$Str .= $data;
 
 	my @dataLength=split(//,$data);
 	my $length=scalar @dataLength;
-	$Str .= (' 'x($cache->{$_.'Length'}-$length));
+	$Str .= (' 'x($cache->{$studentInformation->[$index].'Length'}-
+                      $length));
 	$Str .= $spacePadding;
     }
 
     return $Str;
 }
 
+=pod
+
+=item &FormatStudentData()
+
+First, FormatStudentInformation is called and prefixes the course information.
+This function produces a formatted string of the student's course information.
+Each column of data represents all the problems for a given sequence.  For
+valid grade data, a link is created for that problem to a submission record
+for that problem.
+
+=over 4
+
+Input: $name, $studentInformation, $spacePadding, $ChartDB
+
+$name: The name and domain of the current student in name:domain format
+
+$studentInformation: A pointer to an array holding the names used to 
+remove data from the hash.  They represent 
+the name of the data to be removed.
+
+$spacePadding: Extra spaces that represent the space between columns
+
+$ChartDB: The name of the cached data database which will be tied to that 
+database.
+
+Output: $Str
+
+$Str: Formatted string that is an entire row of the chart.  It is a 
+concatenation of student information and student course information.
+
+=back
+
+=cut
+
 sub FormatStudentData {
-    my ($name,$coid,$studentInformation,$spacePadding,$ChartDB)=@_;
+    my ($name,$studentInformation,$spacePadding,$ChartDB)=@_;
     my ($sname,$sdom) = split(/\:/,$name);
     my $Str;
     my %CacheData;
@@ -91,22 +218,15 @@ sub FormatStudentData {
 
     # Handle errors
     if($CacheData{$name.':error'} =~ /environment/) {
+        $Str .= '
'; untie(%CacheData); - $Str .= '
'; return $Str; -# my $errorMessage = $CacheData{$name.':error'}; -# return ''.$sname.''.$sdom. -# ''.$errorMessage.''; } if($CacheData{$name.':error'} =~ /course/) { + $Str .= '
'; untie(%CacheData); - $Str .= ''; - return $Str; -# my $errorMessage = 'May have no course data or '. -# $CacheData{$name.':error'}; -# return ''.$sname.''.$sdom. -# ''.$errorMessage.''; + return $Str; } # Handle problem data ------------------------------------------------ @@ -116,6 +236,10 @@ sub FormatStudentData { my $problemsSolved = 0; my $numberOfParts = 0; foreach my $sequence (split(/\:/,$CacheData{'orderedSequences'})) { + if(!&ShouldShowColumn(\%CacheData, 'sequence'.$sequence)) { + next; + } + my $characterCount=0; foreach my $problemID (split(/\:/,$CacheData{$sequence.':problems'})) { my $problem = $CacheData{$problemID.':problem'}; @@ -207,18 +331,52 @@ sub FormatStudentData { $Str .= $spacePadding; } - $Str .= ''.$problemsSolved. - ' / '.$totalProblems.''; + my $outputProblemsSolved = sprintf( "%4d", $problemsSolved ); + my $outputTotalProblems = sprintf( "%4d", $totalProblems ); + $Str .= ''.$outputProblemsSolved. + ' / '.$outputTotalProblems.'
'; untie(%CacheData); return $Str; } +=pod + +=item &CreateTableHeadings() + +This function generates the column headings for the chart. + +=over 4 + +Inputs: $CacheData, $studentInformation, $headings, $spacePadding + +$CacheData: pointer to a hash tied to the cached data database + +$studentInformation: a pointer to an array containing the names of the data +held in a column and is used as part of a key into $CacheData + +$headings: The names of the headings for the student information + +$spacePadding: The spaces to go between columns + +Output: $Str + +$Str: A formatted string of the table column headings. + +=back + +=cut + sub CreateTableHeadings { my ($CacheData,$studentInformation,$headings,$spacePadding)=@_; - my $Str='
';
+    my $Str='';
 
     for(my $index=0; $index<(scalar @$headings); $index++) {
+        if(!&ShouldShowColumn($CacheData, 'heading'.$index)) {
+            next;
+        }
+
+        $Str .= '
';
 	my $data=$$headings[$index];
 	$Str .= $data;
 
@@ -227,103 +385,258 @@ sub CreateTableHeadings {
 	$Str .= (' 'x($CacheData->{$$studentInformation[$index].'Length'}-
                       $length));
 	$Str .= $spacePadding;
+        $Str .= '
'; } foreach my $sequence (split(/\:/,$CacheData->{'orderedSequences'})) { - $Str .= $CacheData->{$sequence.':title'}; + if(!&ShouldShowColumn($CacheData, 'sequence'.$sequence)) { + next; + } + + $Str .= '
';
+        my $name = $CacheData->{$sequence.':title'};
+	$Str .= $name;
 	my @titleLength=split(//,$CacheData->{$sequence.':title'});
 	my $leftover=$CacheData->{$sequence.':columnWidth'}-
                      (scalar @titleLength);
 	$Str .= (' 'x$leftover);
 	$Str .= $spacePadding;
+        $Str .= '
'; } - $Str .= 'Total Solved/Total Problems'; - $Str .= '
'; + $Str .= '
Total Solved/Total Problems
'; + $Str .= ''; return $Str; } -sub CreateColumnSelectors { - my ($CacheData,$studentInformation,$headings,$spacePadding)=@_; - my $Str=''; +=pod - $Str .= '
'."\n"; - $Str .= ''; - $Str .= '
'."\n"; - return $Str; +=item &CreateColumnSelectionBox() + +If there are columns not being displayed then this selection box is created +with a list of those columns. When selections are made and the page +refreshed, the columns will be removed from this box and the column is +put back in the chart. If there is no columns to select, no row is added +to the interface table. + +=over 4 +Input: $CacheData, $headings + + +$CacheData: A pointer to a hash tied to the cached data + +$headings: An array of the names of the columns for the student information. +They are used for displaying which columns are missing. + +Output: $notThere + +$notThere: The string contains one row of a table. The first column has the +name of the selection box. The second contains the selection box +which has a size of four. + +=back + +=cut + +sub CreateColumnSelectionBox { + my ($CacheData,$headings)=@_; + + my $missing=0; + my $notThere='Select column to view:'; + my $name; + $notThere .= ''; + $notThere .= ''; + } else { + $notThere=''; + } + + return $notThere.''; +} + +=pod + +=item &CreateColumnSelectors() + +This function generates the checkboxes above the column headings. The +column will be removed if the checkbox is unchecked. + +=over 4 + +Input: $CacheData, $headings + +$CacheData: A pointer to a hash tied to the cached data + +$headings: An array of the names of the columns for the student +information. They are used to know what are the student information columns + +Output: $present + +$present: The string contains the first row of a table. Each column contains +a checkbox which is left justified. Currently left justification is used +for consistency of location over the column in which it presides. + +=back + +=cut + +sub CreateColumnSelectors { + my ($CacheData,$headings)=@_; + + my $found=0; + my ($name, $length, $position); + + my $present = ''; + for(my $index=0; $index<(scalar @$headings); $index++) { + if(!&ShouldShowColumn($CacheData, 'heading'.$index)) { + next; + } + $present .= ''; + $present .= ''; + $present .= ''; + $found++; } foreach my $sequence (split(/\:/,$CacheData->{'orderedSequences'})) { - $Str .= $CacheData->{$sequence.':title'}; - my @titleLength=split(//,$CacheData->{$sequence.':title'}); - my $leftover=$CacheData->{$sequence.':columnWidth'}- - (scalar @titleLength); - $Str .= (' 'x$leftover); - $Str .= $spacePadding; + if(!&ShouldShowColumn($CacheData, 'sequence'.$sequence)) { + next; + } + $present .= ''; + $present .= ''; + $present .= ''; + $found++; } - return $Str; + if(!$found) { + $present = ''; + } + + return $present.''."\n";; } +=pod + +=item &CreateForm() + +The interface for this module consists primarily of the controls in this +function. The student status selection (active, expired, any) is set here. +The sort buttons: username, last name, and section are set here. The +other buttons are Recalculate Chart, Refresh Chart, and Reset Selections. +These controls are in a table to clean up the interface. + +=over 4 + +Input: $CacheData + +$CacheData is a hash pointer to tied database for cached data. + +Output: $Ptr + +$Ptr is a string containing all the html for the above mentioned buttons. + +=back + +=cut + sub CreateForm { + my ($CacheData)=@_; my $OpSel1=''; my $OpSel2=''; my $OpSel3=''; - my $Status = $ENV{'form.status'}; + my $Status = $CacheData->{'form.status'}; if ( $Status eq 'Any' ) { $OpSel3='selected'; } elsif ($Status eq 'Expired' ) { $OpSel2 = 'selected'; } else { $OpSel1 = 'selected'; } - my $Ptr = '
'."\n"; - $Ptr .= ' Sort by:   '."\n"; + my $Ptr .= ''."\n"; + $Ptr .= ''; + $Ptr .= ''; + $Ptr .= ''. ' '."\n"; - $Ptr .= '

'; - $Ptr .= ''; - $Ptr .= "\n"; - $Ptr .= '   '; - $Ptr .= ''; - $Ptr .= "\n"; - $Ptr .= '
'."\n"; + $Ptr .= ''; return $Ptr; } +=pod + +=item &CreateLegend() + +This function returns a formatted string containing the legend for the +chart. The legend describes the symbols used to represent grades for +problems. + +=cut + sub CreateLegend { - my $Str = '

'.$ENV{'course.'.$ENV{'request.course.id'}.'.description'}. - '

'.localtime(). - "

1..9: correct by student in 1..9 tries\n".
+    my $Str = "

".
+              "1..9: correct by student in 1..9 tries\n".
               "   *: correct by student in more than 9 tries\n".
 	      "   +: correct by override\n".
               "   -: incorrect by override\n".
 	      "   .: incorrect attempted\n".
 	      "   #: ungraded attempted\n".
               "    : not attempted\n".
-	      "   x: excused

"; + " x: excused". + "

"; return $Str; } +=pod + +=item &StartDocument() + +Returns a string containing the header information for the chart: title, +logo, and course title. + +=cut + sub StartDocument { my $Str = ''; $Str .= ''; @@ -333,14 +646,61 @@ sub StartDocument { $Str .= ''; $Str .= ''; $Str .= '

Assessment Chart

'; + $Str .= '

'.$ENV{'course.'.$ENV{'request.course.id'}.'.description'}; + $Str .= '

'; return $Str; } # ----- END FORMAT PRINT DATA ------------------------------------------ +=pod + +=head1 DOWNLOAD INFORMATION + +This section contains all the files that get data from other servers +and/or itself. There is one function that has a call to get remote +information but isn't included here which is ProcessTopLevelMap. The +usage was small enough to be ignored, but that portion may be moved +here in the future. + +=cut + # ----- DOWNLOAD INFORMATION ------------------------------------------- +=pod + +=item &DownloadPrerequisiteData() + +Collects lastname, generation, middlename, firstname PID, and section for each +student from their environment database. The list of students is built from +collecting a classlist for the course that is to be displayed. + +=over 4 + +Input: $courseID, $c + +$courseID: The id of the course + +$c: The connection class that can determine if the browser has aborted. It +is used to short circuit this function so that it doesn't continue to +get information when there is no need. + +Output: \%classlist + +\%classlist: A pointer to a hash containing the following data: + +-A list of student name:domain (as keys) (known below as $name) + +-A hash pointer for each student containing lastname, generation, firstname, +middlename, and PID : Key is $name.'studentInformation' + +-A hash pointer to each students section data : Key is $name.section + +=back + +=cut + sub DownloadPrerequisiteData { my ($courseID, $c)=@_; my ($courseDomain,$courseNumber)=split(/\_/,$courseID); @@ -381,6 +741,30 @@ sub DownloadPrerequisiteData { return \%classlist; } +=pod + +=item &DownloadStudentCourseInformation() + +Dump of all the course information for a single student. There is no +pruning of data, it is all stored in a hash and returned. + +=over 4 + +Input: $name, $courseID + +$name: student name:domain + +$courseID: The id of the course + +Output: \%courseData + +\%courseData: A hash pointer to the raw data from the student's course +database. + +=back + +=cut + sub DownloadStudentCourseInformation { my ($name,$courseID)=@_; my ($studentName,$studentDomain) = split(/\:/,$name); @@ -393,7 +777,23 @@ sub DownloadStudentCourseInformation { # ----- END DOWNLOAD INFORMATION --------------------------------------- -# ----- END PROCESSING FUNCTIONS --------------------------------------- +=pod + +=head1 PROCESSING FUNCTIONS + +These functions process all the data for all the students. Also, they +are the only functions that access the cache database for writing. Thus +they are the only functions that cache data. The downloading and caching +were separated to reduce problems with stopping downloading then can't +tie hash to database later. + +=cut + +# ----- PROCESSING FUNCTIONS --------------------------------------- + +=pod + +=cut sub ProcessTopResourceMap { my ($ChartDB,$c)=@_; @@ -442,6 +842,17 @@ sub ProcessTopResourceMap { push(@finishResource, $lastResourceID); $currentSequence=$hash{'map_pc_'.$hash{'src_'.$currentResourceID}}; + + # Mark sequence as containing problems. If it doesn't, then + # it will be removed when processing for this sequence is + # complete. This allows the problems in a sequence + # to be outputed before problems in the subsequences + if(!defined($CacheData{'orderedSequences'})) { + $CacheData{'orderedSequences'}=$currentSequence; + } else { + $CacheData{'orderedSequences'}.=':'.$currentSequence; + } + $lastResourceID=$hash{'map_finish_'. $hash{'src_'.$currentResourceID}}; $currentResourceID=$hash{'map_start_'. @@ -503,19 +914,16 @@ sub ProcessTopResourceMap { if(defined($CacheData{$currentSequence.':problems'})) { # Capture sequence information here - if(!defined($CacheData{'orderedSequences'})) { - $CacheData{'orderedSequences'}=$currentSequence; - } else { - $CacheData{'orderedSequences'}.=':'.$currentSequence; - } - $CacheData{$currentSequence.':title'}= $hash{'title_'.$currentResourceID}; my $totalProblems=0; - foreach (split(/\:/,$CacheData{$currentSequence. + foreach my $currentProblem (split(/\:/, + $CacheData{$currentSequence. ':problems'})) { - foreach ($CacheData{$currentSequence.':'.$_.':parts'}) { + foreach (split(/\:/,$CacheData{$currentSequence.':'. + $currentProblem. + ':parts'})) { $totalProblems++; } } @@ -531,13 +939,16 @@ sub ProcessTopResourceMap { $CacheData{$currentSequence.':columnWidth'}= (scalar @titleLength); } - } + } else { + $CacheData{'orderedSequences'}=~s/$currentSequence//; + $CacheData{'orderedSequences'}=~s/::/:/g; + $CacheData{'orderedSequences'}=~s/^:|:$//g; + } $currentSequence=pop(@sequences); if($currentSequence eq $topLevelSequenceNumber) { last; } - #else } # MOVE!!! @@ -648,7 +1059,7 @@ sub ProcessStudentInformation { } # Get student's section number - my $sec=&ProcessSection($section, $courseID, $ENV{'form.status'}); + my $sec=&ProcessSection($section, $courseID, $CacheData->{'form.status'}); if($sec != -1) { $CacheData->{$name.':section'}=$sec; } else { @@ -665,7 +1076,8 @@ sub ProcessClassList { my %CacheData; if(tie(%CacheData,'GDBM_File',$ChartDB,&GDBM_WRCREAT,0640)) { foreach my $name (keys(%$classlist)) { - if($name =~ /\:section/ || $name =~ /\:studentInformation/) { + if($name =~ /\:section/ || $name =~ /\:studentInformation/ || + $name eq '') { next; } if($c->aborted()) { @@ -680,17 +1092,155 @@ sub ProcessClassList { $name,$courseID,$c); } - $CacheData{'NamesOfStudents'}=join(":::",@names); -# $CacheData{'NamesOfStudents'}=&Apache::lonnet::arrayref2str(\@names); + # Time of download + $CacheData{'time'}=localtime(); untie(%CacheData); } return @names; } -# ----- END PROCESSING FUNCTIONS --------------------------------------- +sub ProcessStudentData { + my ($courseData, $name, $ChartDB)=@_; -# ----- HELPER FUNCTIONS ----------------------------------------------- + my %CacheData; + if(tie(%CacheData,'GDBM_File',$ChartDB,&GDBM_WRCREAT,0640)) { + my ($checkForError) = keys(%$courseData); + if($checkForError =~ /^(con_lost|error|no_such_host)/i) { + $CacheData{$name.':error'}='Could not download course data.'; + } else { + foreach my $key (keys (%$courseData)) { + $CacheData{$name.':'.$key}=$courseData->{$key}; + } + if(defined($CacheData{'NamesOfStudents'})) { + $CacheData{'NamesOfStudents'}.=':::'.$name; + } else { + $CacheData{'NamesOfStudents'}=$name; + } + } + untie(%CacheData); + } + + return; +} + +=pod + +=item &ProcessFormData() + +Cache form data and set default form data (sort, status, heading.$number, +sequence.$number, reselect, reset, recalculate, and refresh) + +=over 4 + +Input: $ChartDB, $isCached + +$ChartDB: The name of the database for cached data + +$isCached: Is there already data for this course cached. This is used in +conjunction with the absence of all form data to know to display all selection +types. + +Output: None + +=back + +=cut + +sub ProcessFormData { + my ($ChartDB, $isCached)=@_; + my %CacheData; + + if(tie(%CacheData,'GDBM_File',$ChartDB,&GDBM_WRCREAT,0640)) { + if(defined($ENV{'form.sort'})) { + $CacheData{'form.sort'}=$ENV{'form.sort'}; + } elsif(!defined($CacheData{'form.sort'})) { + $CacheData{'form.sort'}='username'; + } + + # Ignore $ENV{'form.refresh'} + # Ignore $ENV{'form.recalculate'} + + if(defined($ENV{'form.status'})) { + $CacheData{'form.status'}=$ENV{'form.status'}; + } elsif(!defined($CacheData{'form.status'})) { + $CacheData{'form.status'}='Active'; + } + + my @headings=(); + my @sequences=(); + my $found=0; + foreach (keys(%ENV)) { + if(/form\.heading/) { + $found++; + push(@headings, $_); + } elsif(/form\.sequence/) { + $found++; + push(@sequences, $_); + } elsif(/form\./) { + $found++; + } + } + + if($found) { + $CacheData{'form.headings'}=join(":::",@headings); + $CacheData{'form.sequences'}=join(":::",@sequences); + } + + if(defined($ENV{'form.reselect'})) { + my @reselected = (ref($ENV{'form.reselect'}) ? + @{$ENV{'form.reselect'}} + : ($ENV{'form.reselect'})); + foreach (@reselected) { + if(/heading/) { + $CacheData{'form.headings'}.=":::".$_; + } elsif(/sequence/) { + $CacheData{'form.sequences'}.=":::".$_; + } + } + } + + if(defined($ENV{'form.reset'}) || (!$found && !$isCached)) { + $CacheData{'form.reset'}='true'; + $CacheData{'form.status'}='Active'; + $CacheData{'form.sort'}='username'; + $CacheData{'form.headings'}='ALLHEADINGS'; + $CacheData{'form.sequences'}='ALLSEQUENCES'; + } else { + $CacheData{'form.reset'}='false'; + } + + untie(%CacheData); + } + + return; +} + +=pod + +=item &SpaceColumns() + +Determines the width of all the columns in the chart. It is based on +the max of the data for that column and its header. + +=over 4 + +Input: $students, $studentInformation, $headings, $ChartDB + +$students: An array pointer to a list of students (username:domain) + +$studentInformatin: The type of data for the student information. It is +used as part of the key in $CacheData. + +$headings: The name of the student information columns. + +$ChartDB: The name of the cache database which is opened for read/write. + +Output: None - All data stored in cache. + +=back + +=cut sub SpaceColumns { my ($students,$studentInformation,$headings,$ChartDB)=@_; @@ -719,6 +1269,28 @@ sub SpaceColumns { return; } +# ----- END PROCESSING FUNCTIONS --------------------------------------- + +=pod + +=head1 HELPER FUNCTIONS + +These are just a couple of functions do various odd and end +jobs. + +=cut + +# ----- HELPER FUNCTIONS ----------------------------------------------- + +=pod + +=item &ProcessFullName() + +Takes lastname, generation, firstname, and middlename (or some partial +set of this data) and returns the full name version as a string. + +=cut + sub ProcessFullName { my ($lastname, $generation, $firstname, $middlename)=@_; my $Str = ''; @@ -759,17 +1331,40 @@ sub ProcessFullName { return $Str; } +=pod + +=item &SortStudents() + +Determines which students to display and in which order. Which are +displayed are determined by their status(active/expired). The order +is determined by the sort button pressed (default to username). The +type of sorting is username, lastname, or section. + +=over 4 + +Input: $students, $CacheData + +$students: A array pointer to a list of students (username:domain) + +$CacheData: A pointer to the hash tied to the cached data + +Output: @order + +@order: An ordered list of students (username:domain) + +=back + +=cut + sub SortStudents { - my ($CacheData)=@_; - my @students = split(/:::/,$CacheData->{'NamesOfStudents'}); -# my @students=&Apache::lonnet::str2array($CacheData->{'NamesOfStudents'}); + my ($students,$CacheData)=@_; my @sorted1Students=(); - foreach (@students) { + foreach (@$students) { my ($end,$start)=split(/\:/,$CacheData->{$_.':date'}); my $active=1; my $now=time; - my $Status=$ENV{'form.status'}; + my $Status=$CacheData->{'form.status'}; $Status = ($Status) ? $Status : 'Active'; if((($end) && $now > $end) && (($Status eq 'Active'))) { $active=0; @@ -782,7 +1377,7 @@ sub SortStudents { } } - my $Pos = $ENV{'form.sort'}; + my $Pos = $CacheData->{'form.sort'}; my %sortData; if($Pos eq 'Last Name') { for(my $index=0; $index= 3) { + if($tieTries >= 10) { return -1; } @@ -843,27 +1464,85 @@ sub TestCacheData { return $isCached; } -sub ExtractStudentData { - my ($courseData, $name, $ChartDB)=@_; +=pod - my %CacheData; - if(tie(%CacheData,'GDBM_File',$ChartDB,&GDBM_WRCREAT,0640)) { - my ($checkForError) = keys(%$courseData); - if($checkForError =~ /^(con_lost|error|no_such_host)/i) { - $CacheData{$name.':error'}='Could not download course data.'; - } else { - foreach my $key (keys (%$courseData)) { - $CacheData{$name.':'.$key}=$courseData->{$key}; - } - } - untie(%CacheData); +=item &ShouldShowColumn() + +Determine if a specified column should be shown on the chart. + +=over 4 + +Input: $cache, $test + +$cache: A pointer to the hash tied to the cached data + +$test: The form name of the column (heading.$headingIndex) or +(sequence.$sequenceIndex) + +Output: 0 (false), 1 (true) + +=back + +=cut + +sub ShouldShowColumn { + my ($cache,$test)=@_; + + if($cache->{'form.reset'} eq 'true') { + return 1; } - return; + my $headings=$cache->{'form.headings'}; + my $sequences=$cache->{'form.sequences'}; + if($headings eq 'ALLHEADINGS' || $sequences eq 'ALLSEQUENCES' || + $headings=~/$test/ || $sequences=~/$test/) { + return 1; + } + + return 0; } # ----- END HELPER FUNCTIONS -------------------------------------------- +=pod + +=head1 Handler and main function(BuildChart) + +The handler does some initial error checking and then passes the torch to +BuildChart. BuildChart calls all the appropriate functions to get the +job done. These are the only two functions that use print ($r). All other +functions return strings to BuildChart to be printed. + +=cut + +=pod + +=item &BuildChart() + + The following is the process that BuildChart goes through to create the + html document. + + -Start the lonchart document + -Test for access to the CacheData + -Download class list information if not using cached data + -Sort students and print out table desciptive data + -Output student data + -If recalculating, store a list of students, but only if all their data was + downloaded. Leave off the others. + -End document + +=over 4 + +Input: $r + +$r: Used to print html + +Output: None + +=back + +=cut + sub BuildChart { my ($r)=@_; my $c = $r->connection; @@ -886,8 +1565,10 @@ sub BuildChart { $r->rflush(); return; } + &ProcessFormData($ChartDB, $isCached); # Download class list information if not using cached data + my %CacheData; my @students=(); my @studentInformation=('username','domain','section','id','fullname'); my @headings=('User Name','Domain','Section','PID','Full Name'); @@ -911,59 +1592,82 @@ sub BuildChart { &SpaceColumns(\@students,\@studentInformation,\@headings, $ChartDB); if($c->aborted()) { return; } + } else { + if(!$c->aborted() && tie(%CacheData,'GDBM_File',$ChartDB, + &GDBM_READER,0640)) { + @students=split(/:::/,$CacheData{'NamesOfStudents'}); + } } # Sort students and print out table desciptive data - my %CacheData; + my $downloadTime=0; if(tie(%CacheData,'GDBM_File',$ChartDB,&GDBM_READER,0640)) { - if(!$c->aborted()) { @students=&SortStudents(\%CacheData); } - if(!$c->aborted()) { $r->print(&CreateLegend()); } - if(!$c->aborted()) { $r->print(&CreateForm()); } - if(!$c->aborted()) { $r->print('

'.(scalar @students). - ' students

'); } + if(!$c->aborted()) { @students=&SortStudents(\@students,\%CacheData); } + if(defined($CacheData{'time'})) { $downloadTime=$CacheData{'time'}; } + else { $downloadTime=localtime(); } + if(!$c->aborted()) { $r->print('

'.$downloadTime.'

'); } + if(!$c->aborted()) { $r->print('

'.(scalar @students). + ' students

'); } if(!$c->aborted()) { $r->rflush(); } -# if(!$c->aborted()) { $r->print(&CreateColumnSelectors( -# \%CacheData, -# \@studentInformation, -# \@headings, -# $spacePadding)); } + if(!$c->aborted()) { $r->print(&CreateLegend()); } + if(!$c->aborted()) { $r->print(''); } + if(!$c->aborted()) { $r->print(&CreateForm(\%CacheData)); } + if(!$c->aborted()) { $r->print(&CreateColumnSelectionBox( + \%CacheData, + \@headings)); } + if(!$c->aborted()) { $r->print('
'); } + if(!$c->aborted()) { $r->print('Note: Uncheck the boxes above a'); } + if(!$c->aborted()) { $r->print(' column to remove that column from'); } + if(!$c->aborted()) { $r->print(' the display.'); } + if(!$c->aborted()) { $r->print('aborted()) { $r->print('cellspacing="0">'); } + if(!$c->aborted()) { $r->print(&CreateColumnSelectors( + \%CacheData, + \@headings)); } if(!$c->aborted()) { $r->print(&CreateTableHeadings( \%CacheData, \@studentInformation, \@headings, $spacePadding)); } + if(!$c->aborted()) { $r->print('
'); } + if(!$c->aborted()) { $r->rflush(); } untie(%CacheData); } else { $r->print("Init2: Unable to tie hash to db file"); return; } + # Output student data my @updateStudentList = (); my $courseData; + $r->print('
');
     foreach (@students) {
         if($c->aborted()) {
-            if(!$isCached && 
-               tie(%CacheData,'GDBM_File',$ChartDB,&GDBM_WRCREAT,0640)) {
-                $CacheData{'NamesOfStudents'}=join(":::", @updateStudentList);
-#		    $CacheData{'NamesOfStudents'}=
-#		            &Apache::lonnet::arrayref2str(\@updateStudentList);
-                untie(%CacheData);
-            }
             last;
         }
 
         if(!$isCached) {
             $courseData=&DownloadStudentCourseInformation($_, $cid);
-            if($c->aborted()) { next; }
+            if($c->aborted()) { last; }
             push(@updateStudentList, $_);
-            &ExtractStudentData($courseData, $_, $ChartDB);
+            &ProcessStudentData($courseData, $_, $ChartDB);
         }
-        $r->print(&FormatStudentData($_, $cid, \@studentInformation,
+        $r->print(&FormatStudentData($_, \@studentInformation,
                                      $spacePadding, $ChartDB));
         $r->rflush();
     }
 
-    $r->print('');
+    # If recalculating, store a list of students, but only if all their 
+    # data was downloaded.  Leave off the others.
+    if(!$isCached && tie(%CacheData,'GDBM_File',$ChartDB,&GDBM_WRCREAT,0640)) {
+        $CacheData{'NamesOfStudents'}=join(":::", @updateStudentList);
+#		    $CacheData{'NamesOfStudents'}=
+#		            &Apache::lonnet::arrayref2str(\@updateStudentList);
+        untie(%CacheData);
+    }
+
+    # End document
+    $r->print('
'); $r->rflush(); return; @@ -971,9 +1675,29 @@ sub BuildChart { # ================================================================ Main Handler +=pod + +=item &handler() + +The handler checks for permission to access the course data and for +initial header problem. Then it passes the torch to the work horse +function BuildChart. + +=over 4 + +Input: $r + +$r: This is the object that is used to print. + +Output: A Value (OK or HTTP_NOT_ACCEPTABLE) + +=back + +=cut + sub handler { my $r=shift; - $jr=$r; +# $jr=$r; unless(&Apache::lonnet::allowed('vgr',$ENV{'request.course.id'})) { $ENV{'user.error.msg'}= $r->uri.":vgr:0:0:Cannot view grades for complete course";