--- loncom/interface/lonstatistics.pm	2003/05/12 22:07:17	1.68
+++ loncom/interface/lonstatistics.pm	2003/11/11 22:14:28	1.90
@@ -1,6 +1,6 @@
 # The LearningOnline Network with CAPA
 #
-# $Id: lonstatistics.pm,v 1.68 2003/05/12 22:07:17 matthew Exp $
+# $Id: lonstatistics.pm,v 1.90 2003/11/11 22:14:28 matthew Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -48,6 +48,7 @@ Main handler for statistics and chart.
     use Apache::loncoursedata;
     use Apache::lonhtmlcommon;
     use Apache::lonproblemanalysis;
+    use Apache::lonsubmissiontimeanalysis;
     use Apache::lonproblemstatistics;
     use Apache::lonstudentassessment;
     use Apache::lonpercentage;
@@ -79,10 +80,12 @@ use Apache::loncommon;
 use Apache::loncoursedata;
 use Apache::lonhtmlcommon;
 use Apache::lonproblemanalysis();
+use Apache::lonsubmissiontimeanalysis();
 use Apache::lonproblemstatistics();
 use Apache::lonstudentassessment();
 use Apache::lonpercentage;
 use Apache::lonmysql;
+use Apache::lonlocal;
 use Time::HiRes;
 
 #######################################################
@@ -206,7 +209,6 @@ upon the calling context.
 #######################################################
 #######################################################
 sub PrepareClasslist {
-    my $r = shift;
     my %Sections;
     &clear_classlist_variables();
     #
@@ -223,6 +225,7 @@ sub PrepareClasslist {
             @SelectedSections = ($ENV{'form.Section'});
         }
     }
+    &Apache::lonnet::logthis('@SelectedSections = '.join(',',@SelectedSections));
     @SelectedSections = ('all') if (! @SelectedSections);
     foreach (@SelectedSections) {
         if ($_ eq 'all') {
@@ -230,6 +233,11 @@ sub PrepareClasslist {
         }
     }
     #
+    # Deal with instructors with restricted section access
+    if ($ENV{'request.course.sec'} !~ /^\s*$/) {
+        @SelectedSections = ($ENV{'request.course.sec'});
+    }
+    #
     # Set up %StudentData
     @StudentDataOrder = qw/fullname username domain id section status/;
     foreach my $field (@StudentDataOrder) {
@@ -238,7 +246,6 @@ sub PrepareClasslist {
         $StudentData{$field}->{'width'} = 
                                $StudentData{$field}->{'base_width'};
     }
-
     #
     # get the status requested
     my $requested_status = 'Active';
@@ -282,8 +289,12 @@ sub PrepareClasslist {
     }
     #
     # Put the consolidated section data in the right place
-    @Sections = sort {$a cmp $b} keys(%Sections);
-    unshift(@Sections,'all'); # Put 'all' at the front of the list
+    if ($ENV{'request.course.sec'} !~ /^\s*$/) {
+        @Sections = ($ENV{'request.course.sec'});
+    } else {
+        @Sections = sort {$a cmp $b} keys(%Sections);
+        unshift(@Sections,'all'); # Put 'all' at the front of the list
+    }
     #
     # Sort the Students
     my $sortby = 'fullname';
@@ -293,9 +304,10 @@ sub PrepareClasslist {
     @Students = @TmpStudents;
     # 
     # Now deal with that current student thing....
-    if (exists($ENV{'form.StudentAssessmentStudent'})) {
+    $curr_student = undef;
+    if (exists($ENV{'form.SelectedStudent'})) {
         my ($current_uname,$current_dom) = 
-            split(':',$ENV{'form.StudentAssessmentStudent'});
+            split(':',$ENV{'form.SelectedStudent'});
         my $i;
         for ($i = 0; $i<=$#Students; $i++) {
             next if (($Students[$i]->{'username'} ne $current_uname) || 
@@ -303,15 +315,17 @@ sub PrepareClasslist {
             $curr_student = $Students[$i];
             last; # If we get here, we have our student.
         }
-        if ($i == 0) {
-            $prev_student = 'none';
-        } else {
-            $prev_student = $Students[$i-1];
-        }
-        if ($i == $#Students) {
-            $next_student = 'none';
-        } else {
-            $next_student = $Students[$i+1];
+        if (defined($curr_student)) {
+            if ($i == 0) {
+                $prev_student = undef;
+            } else {
+                $prev_student = $Students[$i-1];
+            }
+            if ($i == $#Students) {
+                $next_student = undef;
+            } else {
+                $next_student = $Students[$i+1];
+            }
         }
     }
     #
@@ -322,7 +336,7 @@ sub PrepareClasslist {
             @SelectedStudentData = ($ENV{'form.StudentData'});
         }
     } else {
-        @SelectedStudentData = ('fullname');
+        @SelectedStudentData = ('username');
     }
     foreach (@SelectedStudentData) {
         if ($_ eq 'all') {
@@ -334,6 +348,27 @@ sub PrepareClasslist {
     return;
 }
 
+
+#######################################################
+#######################################################
+
+=pod
+
+=item get_students
+
+Returns a list of the selected students
+
+=cut
+
+#######################################################
+#######################################################
+sub get_students {
+    if (! @Students) {
+        &PrepareClasslist()
+    }
+    return @Students;
+}
+
 #######################################################
 #######################################################
 
@@ -349,11 +384,7 @@ selected student.
 #######################################################
 #######################################################
 sub current_student { 
-    if (defined($curr_student)) {
-        return $curr_student;
-    } else {
-        return 'All Students';
-    }
+    return $curr_student;
 }
 
 #######################################################
@@ -371,11 +402,7 @@ in the list of students.  Or something.
 #######################################################
 #######################################################
 sub previous_student { 
-    if (defined($prev_student)) {
-        return $prev_student;
-    } else {
-        return 'No Student Selected';
-    }
+    return $prev_student;
 }
 
 #######################################################
@@ -393,11 +420,7 @@ to be viewed.
 #######################################################
 #######################################################
 sub next_student { 
-    if (defined($next_student)) {
-        return $next_student;
-    } else {
-        return 'No Student Selected';
-    }
+    return $next_student;
 }
 
 #######################################################
@@ -500,10 +523,8 @@ sub PrepareCourseData {
         my $name_length = length($seq->{'title'});
         my $num_parts = $seq->{'num_assess_parts'};
         #
-        # The number of columns needed for the summation text: 
-        #    " 1/5" = 1+3 columns, " 10/99" = 1+5 columns
-        my $sum_length = 1+1+2*(length($num_parts));
-        my $num_col = $num_parts+$sum_length;
+        # Use 3 digits for each the sum and total, which means 7 total...
+        my $num_col = $num_parts+7;
         if ($num_col < $name_length) {
             $num_col = $name_length;
         }
@@ -731,6 +752,7 @@ sub MapSelect {
 Returns html for a selection box allowing the user to choose one (or more) 
 of the sections in the course.  
 
+Uses the package variables @Sections and @SelectedSections
 =over 4
 
 =item $elementname The name of the HTML form element
@@ -739,13 +761,6 @@ of the sections in the course.
 
 =item $numvisible The number of options to be visible
 
-=item $selected Array ref to the names of the already selected sections.
-If undef, $ENV{'form.'.$elementname} is used.  
-If $ENV{'form.'.$elementname} is also empty, none will be selected.
-
-=item $restriction Code reference to subroutine which returns true or 
-false.  The code must expect a reference to a sequence data structure.
-
 =back
 
 =cut
@@ -758,6 +773,11 @@ sub SectionSelect {
         return;
     }
     #
+    # Make sure we have the data we need to continue
+    if (! @Sections) {
+        &PrepareClasslist()
+    }
+    #
     # Build the form element
     my $Str = "\n";
     $Str .= '<select name="'.$elementname.'" ';
@@ -781,6 +801,150 @@ sub SectionSelect {
     return $Str;
 }
 
+#######################################################
+#######################################################
+
+=pod
+
+=item &CreateAndParseOutputSelector()
+
+Construct a selection list of options for output and parse output selections.
+
+=cut
+
+#######################################################
+#######################################################
+sub OutputDescriptions {
+    my (@OutputOptions) = @_;
+    my $Str = '';
+    $Str .= "<h2>Output Modes</h2>\n";
+    $Str .= "<dl>\n";
+    foreach my $outputmode (@OutputOptions) {
+	$Str .="    <dt>".$outputmode->{'name'}."</dt>\n";
+	$Str .="        <dd>".$outputmode->{'description'}."</dd>\n";
+    }
+    $Str .= "</dl>\n";
+    return $Str;
+}
+
+sub CreateAndParseOutputSelector {
+    my ($elementname,$default,@OutputOptions) = @_;
+    my $output_mode;
+    my $show;
+    my $Str = '';
+    #
+    # Format for output options is 'mode, restrictions';
+    my $selected = $default;
+    if (exists($ENV{'form.'.$elementname})) {
+        if (ref($ENV{'form.'.$elementname} eq 'ARRAY')) {
+            $selected = $ENV{'form.'.$elementname}->[0];
+        } else {
+            $selected = $ENV{'form.'.$elementname};
+        }
+    }
+    #
+    # Set package variables describing output mode
+    $output_mode = 'html';
+    $show        = 'all';
+    foreach my $option (@OutputOptions) {
+        next if ($option->{'value'} ne $selected);
+        $output_mode = $option->{'mode'};
+        $show        = $option->{'show'};
+    }
+    #
+    # Build the form element
+    $Str = qq/<select size="5" name="$elementname">/;
+    foreach my $option (@OutputOptions) {
+        if (exists($option->{'special'}) && 
+            $option->{'special'} =~ /do not show/) {
+            next;
+        }
+        $Str .= "\n".'    <option value="'.$option->{'value'}.'"';
+        $Str .= " selected " if ($option->{'value'} eq $selected);
+        $Str .= ">".&mt($option->{'name'})."<\/option>";
+    }
+    $Str .= "\n</select>";
+    return ($Str,$output_mode,$show);
+}
+
+###############################################
+###############################################
+
+=pod 
+
+=item &Gather_Student_Data()
+
+Ensures all student data is up to date.
+
+=cut
+
+###############################################
+###############################################
+sub Gather_Student_Data {
+    my ($r) = @_;
+    my $c = $r->connection();
+    #
+    &Apache::loncoursedata::clear_internal_caches();
+    #
+    my @Sequences = &Apache::lonstatistics::Sequences_with_Assess();
+    #
+    my @Students = @Apache::lonstatistics::Students;
+    #
+    # Open the progress window
+    my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
+        ($r,'Statistics Compilation Status',
+         'Statistics Compilation Progress', scalar(@Students));
+    #
+    while (my $student = shift @Students) {
+        return if ($c->aborted());
+        my ($status,undef) = &Apache::loncoursedata::ensure_current_data
+            ($student->{'username'},$student->{'domain'},
+             $ENV{'request.course.id'});
+        &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
+                                                 &mt('last student'));
+    }
+    &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
+    $r->rflush();
+}
+
+###############################################
+###############################################
+
+=pod 
+
+=item &Gather_Full_Student_Data()
+
+Ensures all student data is up to date.
+
+=cut
+
+###############################################
+###############################################
+sub Gather_Full_Student_Data {
+    my ($r) = @_;
+    my $c = $r->connection();
+    #
+    &Apache::loncoursedata::clear_internal_caches();
+    #
+    my @Students = @Apache::lonstatistics::Students;
+    #
+    # Open the progress window
+    my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
+        ($r,&mt('Student Data Compilation Status'),
+         &mt('Student Data Compilation Progress'), scalar(@Students));
+    #
+    while (my $student = shift @Students) {
+        return if ($c->aborted());
+        my ($status,undef) = &Apache::loncoursedata::ensure_current_full_data
+            ($student->{'username'},$student->{'domain'},
+             $ENV{'request.course.id'});
+        &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
+                                                 &mt('last student'));
+    }
+    &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
+    $r->rflush();
+}
+
 ##################################################
 ##################################################
 sub DisplayClasslist {
@@ -789,6 +953,46 @@ sub DisplayClasslist {
     my @Fields = ('fullname','username','domain','id','section');
     #
     my $Str='';
+    if (! @Students) {
+        if ($SelectedSections[0] eq 'all') { 
+            if (lc($ENV{'form.Status'}) eq 'any') {
+                $Str .= '<h2>There are no students in the course.</h2>';
+            } elsif (lc($ENV{'form.Status'}) eq 'active') {
+                $Str .= '<h2>There are no currently enrolled students in '.
+                    'the course.</h2>';
+            } elsif (lc($ENV{'form.Status'}) eq 'expired') {
+                $Str .= '<h2>There are no previously enrolled '.
+                    'students in the course.</h2>';
+            }
+        } else { 
+            my $sections;
+            if (@SelectedSections == 1) {
+                $sections = 'section '.$SelectedSections[0];
+            } elsif (@SelectedSections > 2) {
+                $sections = 'sections '.join(', ',@SelectedSections);
+                $sections =~ s/, ([^,])*$/, and $1/;
+            } else {
+                $sections = 'sections '.join(' and ',@SelectedSections);
+            }
+            if (lc($ENV{'form.Status'}) eq 'any') {
+                $Str .= '<h2>There are no students in '.$sections.'.</h2>';
+            } elsif (lc($ENV{'form.Status'}) eq 'active') {
+                $Str .= '<h2>There are no currently enrolled students '.
+                    'in '.$sections.'.</h2>';
+            } elsif (lc($ENV{'form.Status'}) eq 'expired') {
+                $Str .= '<h2>There are no previously enrolled students '.
+                    'in '.$sections.'.</h2>';
+            }
+        }
+        $Str.= '<a href="/adm/statistics?reportSelected=student_assessment">'.
+            'Return to the chart.</a>';
+        $r->print($Str);
+        $r->rflush();
+        return;
+    }
+
+    # "Click" is asinine but it is probably not my place to change the world.
+    $Str .= '<h2>Click on a students name or username to view their chart</h2>';
     $Str .= '<table border="0"><tr><td bgcolor="#777777">'."\n";
     $Str .= '<table border="0" cellpadding="3"><tr bgcolor="#e6ffff">'."\n";
     foreach my $field (@Fields) {
@@ -809,10 +1013,11 @@ sub DisplayClasslist {
         #
         foreach my $field (@Fields) {
             $Str .= '<td>';
-            if ($field eq 'fullname') {
+            if ($field eq 'fullname' || $field eq 'username') {
                 $Str .= '<a href="/adm/statistics?reportSelected=';
                 $Str .= &Apache::lonnet::escape('student_assessment');
-                $Str .= '&StudentAssessmentStudent=';
+                $Str .= '&sort='.&Apache::lonnet::escape($ENV{'form.sort'});
+                $Str .= '&SelectedStudent=';
                 $Str .= &Apache::lonnet::escape($sname).'">';
                 $Str .= $student->{$field}.'&nbsp';
                 $Str .= '</a>';
@@ -834,36 +1039,43 @@ sub DisplayClasslist {
 ##############################################
 ##############################################
 sub CreateMainMenu {
-    my ($status,$reports,$current)=@_;
     #
-    my $Str = '';
+    # Define menu data
+    my @reports = ({ internal_name => 'problem_statistics',
+                     name => &mt('Overall Problem Statistics'),
+                     short_description => 
+    &mt('Student performance statistics on all problems.'),
+                 },
+                   { internal_name => 'problem_analysis',
+                     name => &mt('Detailed Problem Analysis'),
+                     short_description => 
+    &mt('Detailed statistics and graphs of student performance on problems.'),
+                 },
+                   { internal_name => 'submissiontime_analysis',
+                     name => &mt('Submission Time Analysis'),
+                     short_description => 
+    &mt('Display and analysis of submission times on assessments.'),
+                 },
+#                   { internal_name => 'student_assessment',
+#                     name => &mt('Problem Status Chart'),
+#                     short_description => 
+#    &mt('Brief view of each students performance in course.'),
+#                 },
+                   # 'percentage'  => 'Correct-problems Plot',
+                   # 'activitylog' => 'Activity Log',
+                   );
     #
-    $Str .= '<table border="0"><tbody><tr>'."\n";
-    $Str .= '<td></td>'."\n";
-    $Str .= '<td></td>'."\n";
-    $Str .= '<td align="center"><b>Select a Report</b></td>'."\n";
-    $Str .= '<tr>'."\n";
-    #
-    $Str .= '<td align="center">'.
-        '<input type="submit" name="Refresh" value="Update Display" />'.
-            "</td>\n";
-    $Str .= '<td align="center">'.
-        '<input type="submit" name="ClearCache" value="Clear Caches" />'.
-            "</td>\n";
-    #
-    $Str .= '<td align="center">';
-    $Str .= '<select name="reportSelected" >'."\n";
-    foreach (sort(keys(%$reports))) {
-        $Str .= '<option value="'.$_.'"';
-        if($current eq $_) {
-            $Str .= ' selected';
-        }
-        $Str .= '>'.$reports->{$_}.'</option>'."\n";
+    # Create the menu
+    my $Str;
+    $Str .= '<h1>'.&mt('Please select a report to generate').'</h1>';
+    foreach my $reportdata (@reports) {
+        $Str .='    <h3><a href="/adm/statistics?reportSelected='.
+            $reportdata->{'internal_name'}.'" >'.
+            $reportdata->{'name'}."</a></h3>\n";
+        $Str .= '    '.('&nbsp;'x8).$reportdata->{'short_description'}.
+            "\n";
     }
-    $Str .= '</select></td>'."\n";
-    #
-    $Str .= '</tr></tbody></table>'."\n";
-    $Str .= '<hr>'."\n";
+    $Str .="</dl>\n";
     #
     return $Str;
 }
@@ -883,10 +1095,15 @@ sub handler {
     if ($loaderror) { return $loaderror; }
     #
     # Check for access
-    unless(&Apache::lonnet::allowed('vgr',$ENV{'request.course.id'})) {
+    if (! &Apache::lonnet::allowed('vgr',$ENV{'request.course.id'})) {
         $ENV{'user.error.msg'}=
-        $r->uri.":vgr:0:0:Cannot view grades for complete course";
-        return HTTP_NOT_ACCEPTABLE;
+            $r->uri.":vgr:0:0:Cannot view grades for complete course";
+        if (! &Apache::lonnet::allowed('vgr',
+                      $ENV{'request.course.id'}.'/'.$ENV{'request.course.sec'})) {
+            $ENV{'user.error.msg'}=
+                $r->uri.":vgr:0:0:Cannot view grades with given role";
+            return HTTP_NOT_ACCEPTABLE;
+        }
     }
     #
     # Set document type for header only
@@ -908,74 +1125,70 @@ sub handler {
     # Extract form elements from query string
     &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},
                                             ['sort','reportSelected',
-                                             'StudentAssessmentStudent']);
-    if (! exists($ENV{'form.reportSelected'})) {
-        $ENV{'form.reportSelected'} = 'student_assessment';
-    }
+                                             'SelectedStudent']);
     #
     # Give the LON-CAPA page header
     $r->print(&Apache::lonhtmlcommon::Title('Course Statistics and Charts'));
     $r->rflush();
+    # 
+    # Either print out a menu for them or send them to a report
+    if (! exists($ENV{'form.reportSelected'}) || 
+        $ENV{'form.reportSelected'} eq '') {
+        $r->print(&CreateMainMenu());
+    } else {
     #
-    if (! &Apache::lonmysql::verify_sql_connection()) {
-        my $serveradmin = $r->dir_config('lonAdmEMail');
-        $r->print(<<END);
-<h2><font color="Red">Unable to connect to database!</font></h2>
-<p>
-Please notify the server administrator <b>$serveradmin</b>.
-</p><p>
-Course Statistics and Charts cannot be retrieved until the database is
-restarted.  Your data is intact but cannot be displayed at this time.
-</p>
-</body>
-</html>
-END
-        return;
-    }
-    #
-    # Clean out the caches
-    if (exists($ENV{'form.ClearCache'})) {
-        &Apache::loncoursedata::delete_caches($ENV{'requres.course.id'});
-    }
-    #
-    # Set up the statistics and chart environment
-    &PrepareClasslist($r);
-    &PrepareCourseData($r);
-    #
-    # Begin form output
-    $r->print('<form name="Statistics" ');
-    $r->print('method="post" action="/adm/statistics">');
-    #
-    # Print main menu
-    my %reports = ('classlist'          => 'Class list',
-                   'problem_statistics' => 'Problem Statistics',
-                   'student_assessment' => 'Problem Status Chart',
-                   'percentage'         => 'Correct-problems Plot',
-                   'option_response'    => 'Option Response Analysis',
-#                   'activitylog'        => 'Activity Log',
-                   );
-    $r->print(&CreateMainMenu($ENV{'form.status'},
-                              \%reports,$ENV{'form.reportSelected'}));
-    $r->rflush();
-    #
-    my $GoToPage = $ENV{'form.reportSelected'};
-    if($GoToPage eq 'activitylog') {
+        if (! &Apache::lonmysql::verify_sql_connection()) {
+            my $serveradmin = $r->dir_config('lonAdmEMail');
+            $r->print('<h2><font color="Red">'.
+                      &mt('Unable to connect to database!').
+                      '</font></h2>');
+            $r->print('<p>'.
+                      &mt('Please notify the server administrator ').
+                      '<b>'.$serveradmin.'</b></p>');
+            $r->print('<p>'.
+                      &mt('Course Statistics and Charts cannot be '.
+                          'retrieved until the database is restarted.  '.
+                          'Your data is intact but cannot be displayed '.
+                          'at this time.').'</p>');
+            $r->print('</body></html>');
+            return;
+        }
+        #
+        # Clean out the caches
+        if (exists($ENV{'form.ClearCache'})) {
+            &Apache::loncoursedata::delete_caches($ENV{'requres.course.id'});
+        }
+        #
+        # Begin form output
+        $r->print('<form name="Statistics" ');
+        $r->print('method="post" action="/adm/statistics">');
+        $r->rflush();
+        #
+        my $GoToPage = $ENV{'form.reportSelected'};
+        #
+        # Set up the statistics and chart environment
+        &PrepareCourseData($r);
+        #
+        $r->print('<input type="hidden" name="reportSelected" value="'.
+                  $GoToPage.'">');
+        if($GoToPage eq 'activitylog') {
 #        &Apache::lonproblemstatistics::Activity();
-    } elsif($GoToPage eq 'problem_statistics') {
-        &Apache::lonproblemstatistics::BuildProblemStatisticsPage($r,$c);
-    } elsif($GoToPage eq 'option_response') {
-#        &Apache::lonproblemanalysis::BuildProblemAnalysisPage($r,$c);
-    } elsif($GoToPage eq 'student_assessment') {
-        &Apache::lonstudentassessment::BuildStudentAssessmentPage($r,$c);
-    } elsif($GoToPage eq 'DoDiffGraph' || $GoToPage eq 'PercentWrongGraph') {
+        } elsif($GoToPage eq 'problem_statistics') {
+            &Apache::lonproblemstatistics::BuildProblemStatisticsPage($r,$c);
+        } elsif($GoToPage eq 'problem_analysis') {
+            &Apache::lonproblemanalysis::BuildProblemAnalysisPage($r,$c);
+        } elsif($GoToPage eq 'submissiontime_analysis') {
+            &Apache::lonsubmissiontimeanalysis::BuildSubmissionTimePage($r,$c);
+        } elsif($GoToPage eq 'student_assessment') {
+            &Apache::lonstudentassessment::BuildStudentAssessmentPage($r,$c);
+        } elsif($GoToPage eq 'DoDiffGraph' || $GoToPage eq 'PercentWrongGraph') {
 #        &Apache::lonproblemstatistics::BuildGraphicChart($r,$c);
-    } elsif($GoToPage eq 'classlist') {
-        &DisplayClasslist($r);
-    } elsif($GoToPage eq 'Correct-problems Plot') {
-#	&Apache::lonpercentage::BuildPercentageGraph($r,$c);
+        } elsif($GoToPage eq 'Correct-problems Plot') {
+            #	&Apache::lonpercentage::BuildPercentageGraph($r,$c);
+        }
+        #
+        $r->print("</form>\n");
     }
-    #
-    $r->print("</form>\n");
     $r->print("</body>\n</html>\n");
     $r->rflush();
     #