]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Landing Gerv and Adam's changes for bug #6682
authortara%tequilarista.org <>
Tue, 31 Oct 2000 07:02:41 +0000 (07:02 +0000)
committertara%tequilarista.org <>
Tue, 31 Oct 2000 07:02:41 +0000 (07:02 +0000)
collectstats.pl
reports.cgi

index 47ba0cb5efce5e30b17cd11f954192ca25af8dc3..313004fdf046c635ecdfd2586c403a8e435c3993 100755 (executable)
 use DB_File;
 use diagnostics;
 use strict;
-use vars @::legal_product,
-       @::legal_bug_status;
+use vars @::legal_product;
 
 require "globals.pl";
 
+# tidy up after graphing module
+chdir("data/mining");
+unlink <*.gif>; 
+unlink <*.png>;
+chdir("../..");
 ConnectToDatabase();
 GetVersionTable();
 
@@ -71,32 +76,36 @@ sub collect_stats {
     if (open DATA, ">>$file") {
         push my @row, &today;
 
-        foreach my $status (@::legal_bug_status) {
-           if( $product eq "-All-" ) {
-                SendSQL("select count(bug_status) from bugs where bug_status='$status'");
-           } else {
-                SendSQL("select count(bug_status) from bugs where bug_status='$status' and product='$product'");
+               foreach my $status ('NEW', 'ASSIGNED', 'REOPENED', 'UNCONFIRMED', 'RESOLVED', 'VERIFIED', 'CLOSED') {
+                   if( $product eq "-All-" ) {
+                               SendSQL("select count(bug_status) from bugs where bug_status='$status'");
+                   } else {
+                               SendSQL("select count(bug_status) from bugs where bug_status='$status' and product='$product'");
+                   }
+               
+                   push @row, FetchOneColumn();
+           }
+         
+           foreach my $resolution ('FIXED', 'INVALID', 'WONTFIX', 'LATER', 'REMIND', 'DUPLICATE', 'WORKSFORME', 'MOVED') {
+                   if( $product eq "-All-" ) {
+                               SendSQL("select count(resolution) from bugs where resolution='$resolution'");
+                   } else {
+                               SendSQL("select count(resolution) from bugs where resolution='$resolution' and product='$product'");
+                   }
+                   push @row, FetchOneColumn();
            }
-            push @row, FetchOneColumn();
-        }
 
-        if (! $exists)
-        {
-            print DATA <<FIN;
-# Bugzilla daily bug stats
+           if (! $exists) {
+                   print DATA <<FIN;
+# Bugzilla Daily Bug Stats
 #
-# do not edit me! this file is generated.
+# Do not edit me! This file is generated.
 # 
-# product: $product
-# created: $when
+# Fields: DATE|NEW|ASSIGNED|REOPENED|UNCONFIRMED|FIXED|INVALID|WONTFIX|LATER|REMIND|DUPLICATE|WORKSFORME|MOVED
+# Product: $product
+# Created: $when
 FIN
-          print DATA "# field: DATE";
-            foreach my $status (@::legal_bug_status) {
-              print DATA "|$status";
-          }
-          print DATA "\n";
-       }
-
+           }
         print DATA (join '|', @row) . "\n";
         close DATA;
     } else {
index b48ad71336d9f3af19d80db533a09d0b023c42ac..68a00e6f066b8d5389b1a4a249943e7259cf2fdf 100755 (executable)
 # Joe Robins <jmrobins@tgix.com>,
 #    If using the usebuggroups parameter, users shouldn't be able to see
 #    reports for products they don't have access to.
+# Gervase Markham <gerv@gerv.net> and Adam Spiers <adam@spiers.net>
+#    Added ability to chart any combination of resolutions/statuses.
+#    Derive the choice of resolutions/statuses from the -All- data file
+#    Removed hardcoded order of resolutions/statuses when reading from
+#    daily stats file, so now works independently of collectstats.pl
+#    version
+#    Added image caching by date and datasets
 
 use diagnostics;
 use strict;
+
 use GD;
 eval "use Chart::Lines";
 
 require "CGI.pl";
-require "globals.pl";
+use vars qw(%FORM); # globals from CGI.pl
 
-use vars @::legal_product;
+require "globals.pl";
+use vars qw(@legal_product); # globals from er, globals.pl
 
 my $dir = "data/mining";
-my $week = 60 * 60 * 24 * 7;
+
 my @status = qw (NEW ASSIGNED REOPENED);
 my %bugsperperson;
 
@@ -48,12 +57,12 @@ my %bugsperperson;
 # functions differently than the value passed in
 
 my %reports = 
-       
-       "most_doomed" => \&most_doomed,
-       "most_doomed_for_milestone" => \&most_doomed_for_milestone,
-       "most_recently_doomed" => \&most_recently_doomed,
-       "show_chart" => \&show_chart,
-       );
+    ( 
+    "most_doomed" => \&most_doomed,
+    "most_doomed_for_milestone" => \&most_doomed_for_milestone,
+    "most_recently_doomed" => \&most_recently_doomed,
+    "show_chart" => \&show_chart,
+    );
 
 # If we're using bug groups for products, we should apply those restrictions
 # to viewing reports, as well.  Time to check the login in that case.
@@ -66,14 +75,12 @@ print "Content-type: text/html\n";
 print "Content-disposition: inline; filename=bugzilla_report.html\n\n";
 
 # If we're here for the first time, give a banner.  Else respect the banner flag.
-if ( (!defined $::FORM{'product'}) || ($::FORM{'banner'})  )
-        {
-        PutHeader ("Bug Reports")
-        }
-else
-        {
-       print("<html><head><title>Bug Reports</title></head><body bgcolor=\"#FFFFFF\">");
-        }
+if ( (!defined $FORM{'product'}) || ($FORM{'banner'})  ) {
+    PutHeader ("Bug Reports")
+}
+else {
+    print("<html><head><title>Bug Reports</title></head><body bgcolor=\"#FFFFFF\">");
+}
 
 GetVersionTable();
 
@@ -81,84 +88,79 @@ GetVersionTable();
 # We only want those products that the user has permissions for.
 my @myproducts;
 if(Param("usebuggroups")) {
-  push( @myproducts, "-All-");
-  foreach my $this_product (@::legal_product) {
-    if(GroupExists($this_product) && !UserInGroup($this_product)) {
-      next;
-    } else {
-      push( @myproducts, $this_product )
+    push( @myproducts, "-All-");
+    foreach my $this_product (@legal_product) {
+        if(GroupExists($this_product) && !UserInGroup($this_product)) {
+            next;
+        } else {
+            push( @myproducts, $this_product )
+        }
     }
-  }
 } else {
-  push( @myproducts, "-All-", @::legal_product );
+    push( @myproducts, "-All-", @legal_product );
 }
 
-$::FORM{'output'} = $::FORM{'output'} || "most_doomed"; # a reasonable default
-
-if (! defined $::FORM{'product'})
-       {
-       &choose_product;
-       }
-else
-       {
-          # If usebuggroups is on, we don't want people to be able to view
-          # reports for products they don't have permissions for...
-          if(Param("usebuggroups") &&
-             GroupExists($::FORM{'product'}) &&
-             !UserInGroup($::FORM{'product'})) {
-            print "<H1>Permission denied.</H1>\n";
-            print "Sorry; you do not have the permissions necessary to view\n";
-            print "reports for this product.\n";
-            print "<P>\n";
-            PutFooter();
-            exit;
-          }
+$FORM{'output'} ||= "most_doomed"; # a reasonable default
+
+if (! defined $FORM{'product'}) {
+    &choose_product;
+}
+else {
+    # If usebuggroups is on, we don't want people to be able to view
+    # reports for products they don't have permissions for...
+    if(Param("usebuggroups") &&
+       GroupExists($FORM{'product'}) &&
+       !UserInGroup($FORM{'product'}))
+    {
+        print "<H1>Permission denied.</H1>\n";
+        print "Sorry; you do not have the permissions necessary to view\n";
+        print "reports for this product.\n";
+        print "<P>\n";
+        PutFooter();
+        exit;
+    }
           
-       # we want to be careful about what subroutines 
-       # can be called from outside. modify %reports
-       # accordingly when a new report type is added
-
-       if (! exists $reports{$::FORM{'output'}})
-               {
-               $::FORM{'output'} = "most_doomed"; # a reasonable default
-               }
-       
-       my $f = $reports{$::FORM{'output'}};
-
-       if (! defined $f)
-               {
-               print "start over, your form data was all messed up.<p>\n";
-               foreach (keys %::FORM)
-                       {
-                       print "<font color=blue>$_</font> : " . 
-                               ($::FORM{$_} ? $::FORM{$_} : "undef") . "<br>\n";
-                       }
-                PutFooter() if $::FORM{banner};
-               exit;
-               }
-
-       &{$f};
-       }
+    # we want to be careful about what subroutines 
+    # can be called from outside. modify %reports
+    # accordingly when a new report type is added
+
+    if (! exists $reports{$FORM{'output'}}) {
+        $FORM{'output'} = "most_doomed"; # a reasonable default
+    }
+    
+    my $f = $reports{$FORM{'output'}};
+
+    if (! defined $f) {
+        print "start over, your form data was all messed up.<p>\n";
+        foreach (keys %::FORM) {
+            print "<font color=blue>$_</font> : " . 
+                ($FORM{$_} ? $FORM{$_} : "undef") . "<br>\n";
+        }
+        PutFooter() if $FORM{banner};
+        exit;
+    }
+
+    &{$f};
+}
 
 print <<FIN;
 <p>
 FIN
 
-PutFooter() if $::FORM{banner};
+PutFooter() if $FORM{banner};
 
 
 ##################################
 # user came in with no form data #
 ##################################
 
-sub choose_product
-       {
-       my $product_popup = make_options (\@myproducts, $myproducts[0]);
-       my $charts = defined $Chart::Lines::VERSION && -d $dir ? "<option value=\"show_chart\">Bug Charts" : "";
-       # get rid of warning:
-       $Chart::Lines::VERSION = $Chart::Lines::VERSION;
+sub choose_product {
+    my $product_popup = make_options (\@myproducts, $myproducts[0]);
+    my $charts = defined $Chart::Lines::VERSION && -d $dir ? "<option value=\"show_chart\">Bug Charts" : "";
+    # get rid of warning:
+    $Chart::Lines::VERSION = $Chart::Lines::VERSION;
 
-       print <<FIN;
+    print <<FIN;
 <center>
 <h1>Welcome to the Bugzilla Query Kitchen</h1>
 <form method=get action=reports.cgi>
@@ -177,25 +179,59 @@ $product_popup
 <select name="output">
 <option value="most_doomed">Bug Counts
 FIN
-        if (Param('usetargetmilestone')) {
-            print "<option value=\"most_doomed_for_milestone\">Most Doomed";
-            }
-        print "<option value=\"most_recently_doomed\">Most Recently Doomed";
-       print <<FIN;
+    if (Param('usetargetmilestone')) {
+        print "<option value=\"most_doomed_for_milestone\">Most Doomed";
+    }
+    print "<option value=\"most_recently_doomed\">Most Recently Doomed";
+    print <<FIN;
 $charts
 </select>
 <tr>
+<td align=center><b>Chart datasets:</b></td>
+<td align=center>
+<select name="datasets" multiple size=5>
+FIN
+
+    my @datasets = ();
+
+    my $datafile = daily_stats_filename('-All-');
+    if (! open(DATA, "$dir/$datafile")) {
+        die_politely("Couldn't read daily statistics file");
+    }
+    
+    while (<DATA>) {
+        if (/^# fields?: (.+)\s*$/) {
+            @datasets = grep ! /date/i, (split /\|/, $1);
+            last;
+        }
+    }
+        
+    close(DATA);
+
+    my %default_sel = map { $_ => 1 }
+                          qw/UNCONFIRMED NEW ASSIGNED REOPENED/;
+    foreach my $dataset (@datasets) {
+        my $sel = $default_sel{$dataset} ? ' selected' : '';
+        print qq{<option value="$dataset:"$sel>$dataset</option>\n};
+    }
+
+    print <<FIN;
+</select>
+</td>
+</tr>
+<tr>
 <td align=center><b>Switches:</b></td>
 <td align=left>
 <input type=checkbox name=links checked value=1>&nbsp;Links to Bugs<br>
 <input type=checkbox name=banner checked value=1>&nbsp;Banner<br>
 FIN
-       if (Param('usequip')) {
-           print "<input type=checkbox name=quip value=1>&nbsp;Quip<br>";
-       } else {
-            print "<input type=hidden name=quip value=0>";
-        }
-       print <<FIN;
+  
+    if (Param('usequip')) {
+        print "<input type=checkbox name=quip value=1>&nbsp;Quip<br>";
+    } else {
+        print "<input type=hidden name=quip value=0>";
+    }
+    print <<FIN;
 </td>
 </tr>
 <tr>
@@ -209,30 +245,29 @@ FIN
 FIN
 #Add this above to get a control for showing the SQL query:
 #<input type=checkbox name=showsql value=1>&nbsp;Show SQL<br>
-        PutFooter();
-       }
+    PutFooter();
+}
 
-sub most_doomed
-       {
-       my $when = localtime (time);
+sub most_doomed {
+    my $when = localtime (time);
 
-       print <<FIN;
+    print <<FIN;
 <center>
 <h1>
-Bug Report for $::FORM{'product'}
+Bug Report for $FORM{'product'}
 </h1>
 $when<p>
 FIN
 
 # Build up $query string
-       my $query;
-       $query = <<FIN;
+    my $query;
+    $query = <<FIN;
 select 
-       bugs.bug_id, bugs.assigned_to, bugs.bug_severity,
-       bugs.bug_status, bugs.product, 
-       assign.login_name,
-       report.login_name,
-       unix_timestamp(date_format(bugs.creation_ts, '%Y-%m-%d %h:%m:%s'))
+    bugs.bug_id, bugs.assigned_to, bugs.bug_severity,
+    bugs.bug_status, bugs.product, 
+    assign.login_name,
+    report.login_name,
+    unix_timestamp(date_format(bugs.creation_ts, '%Y-%m-%d %h:%m:%s'))
 
 from   bugs,
        profiles assign,
@@ -242,78 +277,75 @@ where  bugs.assigned_to = assign.userid
 and    bugs.reporter = report.userid
 FIN
 
-       if( $::FORM{'product'} ne "-All-" ) {
-               $query .= "and    bugs.product=".SqlQuote($::FORM{'product'});
-       }
-
-       $query .= <<FIN;
-and     
-       
-       bugs.bug_status = 'NEW' or 
-       bugs.bug_status = 'ASSIGNED' or 
-       bugs.bug_status = 'REOPENED'
-       )
+    if ($FORM{'product'} ne "-All-" ) {
+        $query .= "and    bugs.product=".SqlQuote($FORM{'product'});
+    }
+
+    $query .= <<FIN;
+and      
+    ( 
+    bugs.bug_status = 'NEW' or 
+    bugs.bug_status = 'ASSIGNED' or 
+    bugs.bug_status = 'REOPENED'
+    )
 FIN
 # End build up $query string
 
-       print "<font color=purple><tt>$query</tt></font><p>\n" 
-               unless (! exists $::FORM{'showsql'});
-
-       SendSQL ($query);
-       
-       my $c = 0;
-
-       my $quip = "Summary";
-       my $bugs_count = 0;
-       my $bugs_new_this_week = 0;
-       my $bugs_reopened = 0;
-       my %bugs_owners;
-       my %bugs_summary;
-       my %bugs_status;
-       my %bugs_totals;
-       my %bugs_lookup;
-
-       #############################
-       # suck contents of database # 
-       #############################
-
-       while (my ($bid, $a, $sev, $st, $prod, $who, $rep, $ts) = FetchSQLData())
-               {
-               next if (exists $bugs_lookup{$bid});
-               
-               $bugs_lookup{$bid} ++;
-               $bugs_owners{$who} ++;
-               $bugs_new_this_week ++ if (time - $ts <= $week);
-               $bugs_status{$st} ++;
-               $bugs_count ++;
-               
-               push @{$bugs_summary{$who}{$st}}, $bid;
-               
-               $bugs_totals{$who}{$st} ++;
-               }
-
-       if ($::FORM{'quip'})
-               {
-               if (open (COMMENTS, "<data/comments")) 
-                       {
-       my @cdata;
-                       while (<COMMENTS>) 
-                               {
-                               push @cdata, $_;
-                               }
-                       close COMMENTS;
-                       $quip = "<i>" . $cdata[int(rand($#cdata + 1))] . "</i>";
-                       }
-               } 
-
-       #########################
-       # start painting report #
-       #########################
-
-        $bugs_status{'NEW'}      ||= '0';
-        $bugs_status{'ASSIGNED'} ||= '0';
-        $bugs_status{'REOPENED'} ||= '0';
-       print <<FIN;
+    print "<font color=purple><tt>$query</tt></font><p>\n" 
+        unless (! exists $FORM{'showsql'});
+
+    SendSQL ($query);
+    
+    my $c = 0;
+
+    my $quip = "Summary";
+    my $bugs_count = 0;
+    my $bugs_new_this_week = 0;
+    my $bugs_reopened = 0;
+    my %bugs_owners;
+    my %bugs_summary;
+    my %bugs_status;
+    my %bugs_totals;
+    my %bugs_lookup;
+
+    #############################
+    # suck contents of database # 
+    #############################
+
+    my $week = 60 * 60 * 24 * 7;
+    while (my ($bid, $a, $sev, $st, $prod, $who, $rep, $ts) = FetchSQLData()) {
+        next if (exists $bugs_lookup{$bid});
+        
+        $bugs_lookup{$bid} ++;
+        $bugs_owners{$who} ++;
+        $bugs_new_this_week ++ if (time - $ts <= $week);
+        $bugs_status{$st} ++;
+        $bugs_count ++;
+        
+        push @{$bugs_summary{$who}{$st}}, $bid;
+        
+        $bugs_totals{$who}{$st} ++;
+    }
+
+    if ($FORM{'quip'}) {
+        if (open (COMMENTS, "<data/comments")) {
+            my @cdata;
+            while (<COMMENTS>) {
+                push @cdata, $_;
+            }
+            close COMMENTS;
+            $quip = "<i>" . $cdata[int(rand($#cdata + 1))] . "</i>";
+        }
+    } 
+
+    #########################
+    # start painting report #
+    #########################
+
+    $bugs_status{'NEW'}      ||= '0';
+    $bugs_status{'ASSIGNED'} ||= '0';
+    $bugs_status{'REOPENED'} ||= '0';
+    print <<FIN;
 <h1>$quip</h1>
 <table border=1 cellpadding=5>
 <tr>
@@ -345,14 +377,13 @@ FIN
 <p>
 FIN
 
-       if ($bugs_count == 0)
-               {
-               print "No bugs found!\n";
-                PutFooter() if $::FORM{banner};
-               exit;
-               }
-       
-       print <<FIN;
+    if ($bugs_count == 0) {
+        print "No bugs found!\n";
+                PutFooter() if $FORM{banner};
+        exit;
+    }
+    
+    print <<FIN;
 <h1>Bug Count by Engineer</h1>
 <table border=3 cellpadding=5>
 <tr>
@@ -364,39 +395,37 @@ FIN
 </tr>
 FIN
 
-       foreach my $who (sort keys %bugs_summary)
-               {
-               my $bugz = 0;
-               print <<FIN;
+    foreach my $who (sort keys %bugs_summary) {
+        my $bugz = 0;
+        print <<FIN;
 <tr>
 <td align=left><tt>$who</tt></td>
 FIN
-               
-               foreach my $st (@status)
-                       {
-                       $bugs_totals{$who}{$st} += 0;
-                       print <<FIN;
+        
+        foreach my $st (@status) {
+            $bugs_totals{$who}{$st} += 0;
+            print <<FIN;
 <td align=center>$bugs_totals{$who}{$st}
 FIN
-                       $bugz += $#{$bugs_summary{$who}{$st}} + 1;
-                       }
-               
-               print <<FIN;
+            $bugz += $#{$bugs_summary{$who}{$st}} + 1;
+        }
+        
+        print <<FIN;
 <td align=center>$bugz</td>
 </tr>
 FIN
-               }
-       
-       print <<FIN;
+    }
+    
+    print <<FIN;
 </table>
 <p>
 FIN
 
-       ###############################
-       # individual bugs by engineer #
-       ###############################
+    ###############################
+    # individual bugs by engineer #
+    ###############################
 
-       print <<FIN;
+    print <<FIN;
 <h1>Individual Bugs by Engineer</h1>
 <table border=1 cellpadding=5>
 <tr>
@@ -407,426 +436,451 @@ FIN
 </tr>
 FIN
 
-       foreach my $who (sort keys %bugs_summary)
-               {
-               print <<FIN;
+    foreach my $who (sort keys %bugs_summary) {
+        print <<FIN;
 <tr>
 <td align=left><tt>$who</tt></td>
 FIN
 
-               foreach my $st (@status)
-                       {
-                       my @l;
-
-                       foreach (sort { $a <=> $b } @{$bugs_summary{$who}{$st}})
-                               {
-                               if ($::FORM{'links'})
-                                       {
-                                       push @l, "<a href=\"show_bug.cgi?id=$_\">$_</a>\n"; 
-                                       }
-                               else
-                                       {
-                                       push @l, $_;
-                                       }
-                               }
-                               
-                       my $bugz = join ' ', @l;
-                       $bugz = "&nbsp;" unless ($bugz);
-                       
-                       print <<FIN
+        foreach my $st (@status) {
+            my @l;
+
+            foreach (sort { $a <=> $b } @{$bugs_summary{$who}{$st}}) {
+                if ($FORM{'links'}) {
+                    push @l, "<a href=\"show_bug.cgi?id=$_\">$_</a>\n"; 
+                }
+                else {
+                    push @l, $_;
+                }
+            }
+                
+            my $bugz = join ' ', @l;
+            $bugz = "&nbsp;" unless ($bugz);
+            
+            print <<FIN
 <td align=left>$bugz</td>
 FIN
-                       }
+        }
 
-               print <<FIN;
+        print <<FIN;
 </tr>
 FIN
-               }
+    }
 
-       print <<FIN;
+    print <<FIN;
 </table>
 <p>
 FIN
-       }
+}
 
-sub is_legal_product
-       {
-       my $product = shift;
-       return grep { $_ eq $product} @myproducts;
-       }
+sub is_legal_product {
+    my $product = shift;
+    return grep { $_ eq $product} @myproducts;
+}
 
-sub show_chart
-       {
-  my $when = localtime (time);
+sub daily_stats_filename {
+    my ($prodname) = @_;
+    $prodname =~ s/\//-/gs;
+    return $prodname;
+}
 
-       if (! is_legal_product ($::FORM{'product'}))
-               {
-               &die_politely ("Unknown product: $::FORM{'product'}");
-               }
+sub show_chart {
+    if (! is_legal_product ($FORM{'product'})) {
+        &die_politely ("Unknown product: $FORM{'product'}");
+    }
+
+    if (! $FORM{datasets}) {
+        die_politely("You didn't select any datasets to plot");
+    }
 
   print <<FIN;
 <center>
 FIN
-       
-       my @dates;
-       my @open; my @assigned; my @reopened;
-        my @resolved; my @verified; my @closed;
-
-        my $prodname = $::FORM{'product'};
-
-        $prodname =~ s/\//-/gs;
-
-       my $testimg = Chart::Lines->new(2,2);
-       my $x = '$testimg->gif()';
-       eval $x;
-       my $type = ($@ =~ /Can't locate object method/) ? "png" : "gif";
-
-        my $file = join '/', $dir, $prodname;
-       my $image = "$file.$type";
-       my $url_image = $dir . "/" . url_quote($prodname) . ".$type";
-
-       if (! open FILE, $file)
-               {
-               &die_politely ("The tool which gathers bug counts has not been run yet.");
-               }
-       
-       while (<FILE>)
-               {
-               chomp;
-               next if ($_ =~ /^#/ or ! $_);
-               my ($date, $open, $assigned, $reopened,
-                       $resolved, $verified, $closed) = split /\|/, $_;
-               my ($yy, $mm, $dd) = $date =~ /^\d{2}(\d{2})(\d{2})(\d{2})$/;
-
-               push @dates, "$mm/$dd/$yy";
-               push @open, $open;
-               push @assigned, $assigned;
-               push @reopened, $reopened;
-                push @resolved, $resolved;
-                push @verified, $verified;
-                push @closed, $closed;
-               }
-       
-       close FILE;
-
-       if ($#dates < 1)
-               {
-               &die_politely ("We don't have enough data points to make a graph (yet)");
-               }
-       
-       my $img = Chart::Lines->new (800, 600);
-        my @labels = qw (New Assigned Reopened Resolved Verified Closed);
-       my @when;
-       my $i = 0;
-       my @data;
-
-       push @data, \@dates;
-       push @data, \@open;
-       push @data, \@assigned;
-       push @data, \@reopened;
-        push @data, \@resolved;
-        push @data, \@verified;
-        push @data, \@closed;
 
-    my $MAXTICKS = 20;      # Try not to show any more x ticks than this.
-    my $skip = 1;
-    if (@dates > $MAXTICKS) {
-        $skip = int((@dates + $MAXTICKS - 1) / $MAXTICKS);
-    }
-
-       my %settings =
-               (
-                 "title" => "Status Counts for $::FORM{'product'}",
-               "x_label" => "Dates",
-               "y_label" => "Bug Counts",
-               "legend_labels" => \@labels,
-                "skip_x_ticks" => $skip,
-                "y_grid_lines" => "true",
-                "grey_background" => "false"
-               );
-       
-       $img->set (%settings);
-       
-       open IMAGE, ">$image" or die "$image: $!";
-       $img->$type (*IMAGE, \@data);
-       close IMAGE;
-
-       print <<FIN;
+    my $type = chart_image_type();
+    my $data_file = daily_stats_filename($FORM{product});
+    my $image_file = chart_image_name($data_file, $type);
+    my $url_image = "$dir/" . url_quote($image_file);
+
+    if (! -e "$dir/$image_file") {
+        generate_chart("$dir/$data_file", "$dir/$image_file", $type);
+    }
+    
+    print <<FIN;
 <img src="$url_image">
 <br clear=left>
 <br>
 FIN
-       }
+}
 
-sub die_politely
-       {
-       my $msg = shift;
+sub chart_image_type {
+    # what chart type should we be generating?
+    my $testimg = Chart::Lines->new(2,2);
+    my $type = $testimg->can('gif') ? "gif" : "png";
+    undef $testimg;
+    return $type;
+}
 
-       print <<FIN;
+sub chart_image_name {
+    my ($data_file, $type) = @_;
+
+    my $id = datasets_id($FORM{datasets});
+    my $doy = day_of_year();
+
+    return "${data_file}_${id}.$type";
+}
+
+# Cache charts by generating a unique filename based on what they
+# show. Charts should be deleted by collectstats.pl nightly.
+sub datasets_id {
+       # Current method is very long filenames...      
+       my $longname = "";
+    foreach (@_) {
+         $longname .= $_;
+       }       
+    return $longname;
+}
+
+sub day_of_year {
+    my ($mday, $month, $year) = (localtime())[3 .. 5];
+    $month += 1;
+    $year += 1900;
+    my $date = sprintf "%02d%02d%04d", $mday, $month, $year;
+}
+
+sub generate_chart {
+    my ($data_file, $image_file, $type) = @_;
+    
+    if (! open FILE, $data_file) {
+        &die_politely ("The tool which gathers bug counts has not been run yet.");
+    }
+
+    my @fields;
+    my @labels = qw(DATE);
+    my %datasets = map { $_ => 1 } split /:/, $FORM{datasets};
+
+    my %data = ();
+    while (<FILE>) {
+        chomp;
+        next unless $_;
+        if (/^#/) {
+            if (/^# fields?: (.*)\s*$/) {
+                @fields = split /\|/, $1;
+                &die_politely("`# fields: ' line didn't start with DATE, but with $fields[0]")
+                  unless $fields[0] =~ /date/i;
+                push @labels, grep($datasets{$_}, @fields);
+            }
+            next;
+        }
+
+        &die_politely("`# fields: ' line was not found before start of data")
+          unless @fields;
+        
+        my @line = split /\|/;
+        my $date = $line[0];
+        my ($yy, $mm, $dd) = $date =~ /^\d{2}(\d{2})(\d{2})(\d{2})$/;
+        push @{$data{DATE}}, "$mm/$dd/$yy";
+        
+        for my $i (1 .. $#fields) {
+            my $field = $fields[$i];
+            if (! defined $line[$i] or $line[$i] eq '') {
+                # no data point given, don't plot (this will probably
+                # generate loads of Chart::Base warnings, but that's not
+                # our fault.
+                push @{$data{$field}}, undef;
+            }
+            else {
+                push @{$data{$field}}, $line[$i];
+            }
+        }
+    }
+    
+    shift @labels;
+
+    close FILE;
+
+    if (! @{$data{DATE}}) {
+        &die_politely ("We don't have enough data points to make a graph (yet)");
+    }
+    
+    my $img = Chart::Lines->new (800, 600);
+    my $i = 0;
+
+    my $MAXTICKS = 20;      # Try not to show any more x ticks than this.
+    my $skip = 1;
+    if (@{$data{DATE}} > $MAXTICKS) {
+        $skip = int((@{$data{DATE}} + $MAXTICKS - 1) / $MAXTICKS);
+    }
+
+    my %settings =
+        (
+         "title" => "Status Counts for $FORM{'product'}",
+         "x_label" => "Dates",
+         "y_label" => "Bug Counts",
+         "legend_labels" => \@labels,
+         "skip_x_ticks" => $skip,
+         "y_grid_lines" => "true",
+         "grey_background" => "false",
+         "colors" => {
+                      # default dataset colours are too alike
+                      dataset4 => [0, 0, 0], # black
+                     },
+        );
+    
+    $img->set (%settings);
+    $img->$type($image_file, [ @data{('DATE', @labels)} ]);
+}
+
+sub die_politely {
+    my $msg = shift;
+
+    print <<FIN;
 <p>
 <table border=1 cellpadding=10>
 <tr>
 <td align=center>
 <font color=blue>Sorry, but ...</font>
 <p>
-There is no graph available for <b>$::FORM{'product'}</b><p>
+There is no graph available for <b>$FORM{'product'}</b><p>
 
-<font size=-1>
 $msg
 <p>
-</font>
 </td>
 </tr>
 </table>
 <p>
 FIN
-       
-       PutFooter() if $::FORM{banner};
-       exit;
-       }
+    
+    PutFooter() if $FORM{banner};
+    exit;
+}
 
 sub bybugs {
    $bugsperperson{$a} <=> $bugsperperson{$b}
-   }
+}
 
-sub most_doomed_for_milestone
-       {
-       my $when = localtime (time);
-        my $ms = "M" . Param("curmilestone");
-        my $quip = "Summary";
+sub most_doomed_for_milestone {
+    my $when = localtime (time);
+    my $ms = "M" . Param("curmilestone");
+    my $quip = "Summary";
 
-       print "<center>\n<h1>";
-        if( $::FORM{'product'} ne "-All-" ) {
-            print "Most Doomed for $ms ($::FORM{'product'})";
-        } else {
-            print "Most Doomed for $ms";
-            }
-        print "</h1>\n$when<p>\n";
-
-       #########################
-       # start painting report #
-       #########################
-
-       if ($::FORM{'quip'})
-                {
-                if (open (COMMENTS, "<data/comments"))
-                        {
-                        my @cdata;
-                        while (<COMMENTS>)
-                                {
-                                push @cdata, $_;
-                                }
-                        close COMMENTS;
-                        $quip = "<i>" . $cdata[int(rand($#cdata + 1))] . "</i>";                        }
-                }
+    print "<center>\n<h1>";
+    if( $FORM{'product'} ne "-All-" ) {
+        print "Most Doomed for $ms ($FORM{'product'})";
+    } else {
+        print "Most Doomed for $ms";
+    }
+    print "</h1>\n$when<p>\n";
 
+    #########################
+    # start painting report #
+    #########################
 
-        # Build up $query string
-       my $query;
-       $query = "select distinct assigned_to from bugs where target_milestone=\"$ms\"";
-       if( $::FORM{'product'} ne "-All-" ) {
-               $query .= "and    bugs.product=".SqlQuote($::FORM{'product'});
-       }
-       $query .= <<FIN;
-and     
-       ( 
-       bugs.bug_status = 'NEW' or 
-       bugs.bug_status = 'ASSIGNED' or 
-       bugs.bug_status = 'REOPENED'
-       )
+    if ($FORM{'quip'}) {
+        if (open (COMMENTS, "<data/comments")) {
+            my @cdata;
+            while (<COMMENTS>) {
+                push @cdata, $_;
+            }
+            close COMMENTS;
+            $quip = "<i>" . $cdata[int(rand($#cdata + 1))] . "</i>";
+        }
+    }
+    
+    # Build up $query string
+    my $query;
+    $query = "select distinct assigned_to from bugs where target_milestone=\"$ms\"";
+    if ($FORM{'product'} ne "-All-" ) {
+        $query .= "and    bugs.product=".SqlQuote($FORM{'product'});
+    }
+    $query .= <<FIN;
+and      
+    ( 
+    bugs.bug_status = 'NEW' or 
+    bugs.bug_status = 'ASSIGNED' or 
+    bugs.bug_status = 'REOPENED'
+    )
 FIN
 # End build up $query string
 
-        SendSQL ($query);
-        my @people = ();
-        while (my ($person) = FetchSQLData())
-            {
-            push @people, $person;
-            }
+    SendSQL ($query);
+    my @people = ();
+    while (my ($person) = FetchSQLData()) {
+        push @people, $person;
+    }
 
-        #############################
-        # suck contents of database # 
-        #############################
-        my $person = "";
-        my $bugtotal = 0;
-        foreach $person (@people)
-                {
-                my $query = "select count(bug_id) from bugs,profiles where target_milestone=\"$ms\" and userid=assigned_to and userid=\"$person\"";
-               if( $::FORM{'product'} ne "-All-" ) {
-                    $query .= "and    bugs.product=".SqlQuote($::FORM{'product'});
-                    }
-               $query .= <<FIN;
-and     
-       ( 
-       bugs.bug_status = 'NEW' or 
-       bugs.bug_status = 'ASSIGNED' or 
-       bugs.bug_status = 'REOPENED'
-       )
+    #############################
+    # suck contents of database # 
+    #############################
+    my $person = "";
+    my $bugtotal = 0;
+    foreach $person (@people) {
+        my $query = "select count(bug_id) from bugs,profiles where target_milestone=\"$ms\" and userid=assigned_to and userid=\"$person\"";
+        if( $FORM{'product'} ne "-All-" ) {
+            $query .= "and    bugs.product=".SqlQuote($FORM{'product'});
+        }
+        $query .= <<FIN;
+and      
+    ( 
+    bugs.bug_status = 'NEW' or 
+    bugs.bug_status = 'ASSIGNED' or 
+    bugs.bug_status = 'REOPENED'
+    )
 FIN
-                SendSQL ($query);
-               my $bugcount = FetchSQLData();
-                $bugsperperson{$person} = $bugcount;
-                $bugtotal += $bugcount;
-                }
+        SendSQL ($query);
+        my $bugcount = FetchSQLData();
+        $bugsperperson{$person} = $bugcount;
+        $bugtotal += $bugcount;
+    }
 
-#       sort people by the number of bugs they have assigned to this milestone
-        @people = sort bybugs @people;
-        my $totalpeople = @people;
+#   sort people by the number of bugs they have assigned to this milestone
+    @people = sort bybugs @people;
+    my $totalpeople = @people;
                 
-        print "<TABLE>\n";
-        print "<TR><TD COLSPAN=2>\n";
-        print "$totalpeople engineers have $bugtotal $ms bugs and features.\n";
-        print "</TD></TR>\n";
-
-        while (@people)
-                {
-                $person = pop @people;
-                print "<TR><TD>\n";
-                SendSQL("select login_name from profiles where userid=$person");
-                my $login_name= FetchSQLData();
-                print("<A HREF=\"buglist.cgi?bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&target_milestone=$ms&assigned_to=$login_name");
-               if( $::FORM{'product'} ne "-All-" ) {
-                   print "&product=" . url_quote($::FORM{'product'});
-               }
-               print("\">\n");
-                print("$bugsperperson{$person}  bugs and features");
-                print("</A>");
-                print(" for \n");
-                print("<A HREF=\"mailto:$login_name\">");
-                print("$login_name");
-                print("</A>\n");
-                print("</TD><TD>\n");
-
-                $person = pop @people;
-                if ($person) {
-                    SendSQL("select login_name from profiles where userid=$person");
-                    my $login_name= FetchSQLData();
-                    print("<A HREF=\"buglist.cgi?bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&target_milestone=$ms&assigned_to=$login_name\">\n");
-                    print("$bugsperperson{$person}  bugs and features");
-                    print("</A>");
-                    print(" for \n");
-                    print("<A HREF=\"mailto:$login_name\">");
-                    print("$login_name");
-                    print("</A>\n");
-                    print("</TD></TR>\n\n");
-                    }
-                }
-        print "</TABLE>\n";
-
+    print "<TABLE>\n";
+    print "<TR><TD COLSPAN=2>\n";
+    print "$totalpeople engineers have $bugtotal $ms bugs and features.\n";
+    print "</TD></TR>\n";
+
+    while (@people) {
+        $person = pop @people;
+        print "<TR><TD>\n";
+        SendSQL("select login_name from profiles where userid=$person");
+        my $login_name= FetchSQLData();
+        print("<A HREF=\"buglist.cgi?bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&target_milestone=$ms&assigned_to=$login_name");
+        if ($FORM{'product'} ne "-All-" ) {
+            print "&product=" . url_quote($FORM{'product'});
+        }
+        print("\">\n");
+        print("$bugsperperson{$person}  bugs and features");
+        print("</A>");
+        print(" for \n");
+        print("<A HREF=\"mailto:$login_name\">");
+        print("$login_name");
+        print("</A>\n");
+        print("</TD><TD>\n");
+
+        $person = pop @people;
+        if ($person) {
+            SendSQL("select login_name from profiles where userid=$person");
+            my $login_name= FetchSQLData();
+            print("<A HREF=\"buglist.cgi?bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&target_milestone=$ms&assigned_to=$login_name\">\n");
+            print("$bugsperperson{$person}  bugs and features");
+            print("</A>");
+            print(" for \n");
+            print("<A HREF=\"mailto:$login_name\">");
+            print("$login_name");
+            print("</A>\n");
+            print("</TD></TR>\n\n");
         }
+    }
+    print "</TABLE>\n";
+}
 
 
+sub most_recently_doomed {
+    my $when = localtime (time);
+    my $ms = "M" . Param("curmilestone");
+    my $quip = "Summary";
+                                                     
+    print "<center>\n<h1>";
+    if( $FORM{'product'} ne "-All-" ) {
+        print "Most Recently Doomed ($FORM{'product'})";
+    } else {
+        print "Most Recently Doomed";
+    }
+    print "</h1>\n$when<p>\n";
 
-sub most_recently_doomed
-       {
-       my $when = localtime (time);
-        my $ms = "M" . Param("curmilestone");
-        my $quip = "Summary";
+    #########################
+    # start painting report #
+    #########################
 
-       print "<center>\n<h1>";
-        if( $::FORM{'product'} ne "-All-" ) {
-            print "Most Recently Doomed ($::FORM{'product'})";
-        } else {
-            print "Most Recently Doomed";
+    if ($FORM{'quip'}) {
+        if (open (COMMENTS, "<data/comments")) {
+            my @cdata;
+            while (<COMMENTS>) {
+                push @cdata, $_;
             }
-        print "</h1>\n$when<p>\n";
-
-       #########################
-       # start painting report #
-       #########################
-
-       if ($::FORM{'quip'})
-                {
-                if (open (COMMENTS, "<data/comments"))
-                        {
-                        my @cdata;
-                        while (<COMMENTS>)
-                                {
-                                push @cdata, $_;
-                                }
-                        close COMMENTS;
-                        $quip = "<i>" . $cdata[int(rand($#cdata + 1))] . "</i>";                        }
-                }
+            close COMMENTS;
+            $quip = "<i>" . $cdata[int(rand($#cdata + 1))] . "</i>";
+        }
+    }
 
 
-        # Build up $query string
-       my $query;
-        $query = "select distinct assigned_to from bugs where bugs.bug_status='NEW' and target_milestone='' and bug_severity!='enhancement' and status_whiteboard='' and (product='Browser' or product='MailNews')";
-       if( $::FORM{'product'} ne "-All-" ) {
-               $query .= "and    bugs.product=".SqlQuote($::FORM{'product'});
-       }
+    # Build up $query string
+    my $query = "select distinct assigned_to from bugs where bugs.bug_status='NEW' and target_milestone='' and bug_severity!='enhancement' and status_whiteboard='' and (product='Browser' or product='MailNews')";
+    if ($FORM{'product'} ne "-All-" ) {
+        $query .= "and    bugs.product=".SqlQuote($FORM{'product'});
+    }
 
 # End build up $query string
 
-        SendSQL ($query);
-        my @people = ();
-        while (my ($person) = FetchSQLData())
-            {
-            push @people, $person;
-            }
+    SendSQL ($query);
+    my @people = ();
+    while (my ($person) = FetchSQLData()) {
+        push @people, $person;
+    }
 
-        #############################
-        # suck contents of database # 
-        #############################
-        my $person = "";
-        my $bugtotal = 0;
-        foreach $person (@people)
-                {
-                my $query = "select count(bug_id) from bugs,profiles where bugs.bug_status='NEW' and userid=assigned_to and userid='$person' and target_milestone='' and bug_severity!='enhancement' and status_whiteboard='' and (product='Browser' or product='MailNews')";
-               if( $::FORM{'product'} ne "-All-" ) {
-                    $query .= "and    bugs.product='$::FORM{'product'}'";
-                    }
-                SendSQL ($query);
-               my $bugcount = FetchSQLData();
-                $bugsperperson{$person} = $bugcount;
-                $bugtotal += $bugcount;
-                }
+    #############################
+    # suck contents of database # 
+    #############################
+    my $person = "";
+    my $bugtotal = 0;
+    foreach $person (@people) {
+        my $query = "select count(bug_id) from bugs,profiles where bugs.bug_status='NEW' and userid=assigned_to and userid='$person' and target_milestone='' and bug_severity!='enhancement' and status_whiteboard='' and (product='Browser' or product='MailNews')";
+        if( $FORM{'product'} ne "-All-" ) {
+            $query .= "and    bugs.product='$FORM{'product'}'";
+        }
+        SendSQL ($query);
+        my $bugcount = FetchSQLData();
+        $bugsperperson{$person} = $bugcount;
+        $bugtotal += $bugcount;
+    }
 
-#       sort people by the number of bugs they have assigned to this milestone
-        @people = sort bybugs @people;
-        my $totalpeople = @people;
-        
-        if ($totalpeople > 20) {
-            splice @people, 0, $totalpeople-20;
-            }
+#   sort people by the number of bugs they have assigned to this milestone
+    @people = sort bybugs @people;
+    my $totalpeople = @people;
+    
+    if ($totalpeople > 20) {
+        splice @people, 0, $totalpeople-20;
+    }
                 
-        print "<TABLE>\n";
-        print "<TR><TD COLSPAN=2>\n";
-        print "$totalpeople engineers have $bugtotal untouched new bugs.\n";
-        if ($totalpeople > 20) {
-            print "These are the 20 most doomed.";
-            }
-        print "</TD></TR>\n";
-
-        while (@people)
-                {
-                $person = pop @people;
-                print "<TR><TD>\n";
-                SendSQL("select login_name from profiles where userid=$person");
-                my $login_name= FetchSQLData();
-                print("<A HREF=\"buglist.cgi?bug_status=NEW&email1=$login_name&emailtype1=substring&emailassigned_to1=1&product=Browser&product=MailNews&target_milestone=---&status_whiteboard=.&status_whiteboard_type=notregexp&bug_severity=blocker&bug_severity=critical&bug_severity=major&bug_severity=normal&bug_severity=minor&bug_severity=trivial\">\n"); 
-                print("$bugsperperson{$person}  bugs");
-                print("</A>");
-                print(" for \n");
-                print("<A HREF=\"mailto:$login_name\">");
-                print("$login_name");
-                print("</A>\n");
-                print("</TD><TD>\n");
-
-                $person = pop @people;
-                if ($person) {
-                    SendSQL("select login_name from profiles where userid=$person");
-                    my $login_name= FetchSQLData();
-                    print("<A HREF=\"buglist.cgi?bug_status=NEW&email1=$login_name&emailtype1=substring&emailassigned_to1=1&product=Browser&product=MailNews&target_milestone=---&status_whiteboard=.&status_whiteboard_type=notregexp&bug_severity=blocker&bug_severity=critical&bug_severity=major&bug_severity=normal&bug_severity=minor&bug_severity=trivial\">\n"); 
-                    print("$bugsperperson{$person}  bugs");
-                    print("</A>");
-                    print(" for \n");
-                    print("<A HREF=\"mailto:$login_name\">");
-                    print("$login_name");
-                    print("</A>\n");
-                    print("</TD></TR>\n\n");
-                    }
-                }
-        print "</TABLE>\n";
-
+    print "<TABLE>\n";
+    print "<TR><TD COLSPAN=2>\n";
+    print "$totalpeople engineers have $bugtotal untouched new bugs.\n";
+    if ($totalpeople > 20) {
+        print "These are the 20 most doomed.";
+    }
+    print "</TD></TR>\n";
+
+    while (@people) {
+        $person = pop @people;
+        print "<TR><TD>\n";
+        SendSQL("select login_name from profiles where userid=$person");
+        my $login_name= FetchSQLData();
+        print("<A HREF=\"buglist.cgi?bug_status=NEW&email1=$login_name&emailtype1=substring&emailassigned_to1=1&product=Browser&product=MailNews&target_milestone=---&status_whiteboard=.&status_whiteboard_type=notregexp&bug_severity=blocker&bug_severity=critical&bug_severity=major&bug_severity=normal&bug_severity=minor&bug_severity=trivial\">\n"); 
+        print("$bugsperperson{$person}  bugs");
+        print("</A>");
+        print(" for \n");
+        print("<A HREF=\"mailto:$login_name\">");
+        print("$login_name");
+        print("</A>\n");
+        print("</TD><TD>\n");
+
+        $person = pop @people;
+        if ($person) {
+            SendSQL("select login_name from profiles where userid=$person");
+            my $login_name= FetchSQLData();
+            print("<A HREF=\"buglist.cgi?bug_status=NEW&email1=$login_name&emailtype1=substring&emailassigned_to1=1&product=Browser&product=MailNews&target_milestone=---&status_whiteboard=.&status_whiteboard_type=notregexp&bug_severity=blocker&bug_severity=critical&bug_severity=major&bug_severity=normal&bug_severity=minor&bug_severity=trivial\">\n"); 
+            print("$bugsperperson{$person}  bugs");
+            print("</A>");
+            print(" for \n");
+            print("<A HREF=\"mailto:$login_name\">");
+            print("$login_name");
+            print("</A>\n");
+            print("</TD></TR>\n\n");
         }
+    }
+    print "</TABLE>\n";
+
+}