REGRESSION (r243153): [iOS] TestWebKitAPI.FocusPreservationTests.ChangingFocusedNodeR...
[WebKit-https.git] / Websites / bugs.webkit.org / reports.cgi
1 #!/usr/bin/perl -T
2 # This Source Code Form is subject to the terms of the Mozilla Public
3 # License, v. 2.0. If a copy of the MPL was not distributed with this
4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 #
6 # This Source Code Form is "Incompatible With Secondary Licenses", as
7 # defined by the Mozilla Public License, v. 2.0.
8
9 use 5.10.1;
10 use strict;
11 use warnings;
12
13 use lib qw(. lib);
14
15 use Bugzilla;
16 use Bugzilla::Constants;
17 use Bugzilla::Util;
18 use Bugzilla::Error;
19 use Bugzilla::Status;
20
21 use File::Basename;
22 use Digest::SHA qw(hmac_sha256_base64);
23
24 # If we're using bug groups for products, we should apply those restrictions
25 # to viewing reports, as well.  Time to check the login in that case.
26 my $user = Bugzilla->login();
27 my $cgi = Bugzilla->cgi;
28 my $template = Bugzilla->template;
29 my $vars = {};
30
31 # We use a dummy product instance with ID 0, representing all products
32 my $product_all = {id => 0};
33 bless($product_all, 'Bugzilla::Product');
34
35 if (!Bugzilla->feature('old_charts')) {
36     ThrowUserError('feature_disabled', { feature => 'old_charts' });
37 }
38
39 my $dir       = bz_locations()->{'datadir'} . "/mining";
40 my $graph_dir = bz_locations()->{'graphsdir'};
41 my $graph_url = basename($graph_dir);
42 my $product_id = $cgi->param('product_id');
43
44 Bugzilla->switch_to_shadow_db();
45
46 if (! defined($product_id)) {
47     # Can we do bug charts?
48     (-d $dir && -d $graph_dir) 
49       || ThrowCodeError('chart_dir_nonexistent',
50                         {dir => $dir, graph_dir => $graph_dir});
51
52     my %default_sel = map { $_ => 1 } BUG_STATE_OPEN;
53
54     my @datasets;
55     my @data = get_data($dir);
56
57     foreach my $dataset (@data) {
58         my $datasets = {};
59         $datasets->{'value'} = $dataset;
60         $datasets->{'selected'} = $default_sel{$dataset} ? 1 : 0;
61         push(@datasets, $datasets);
62     }
63
64     $vars->{'datasets'} = \@datasets;
65
66     print $cgi->header();
67 }
68 else {
69     my $product;
70     # For security and correctness, validate the value of the "product_id" form
71     # variable. Valid values are IDs of those products for which the user has
72     # permissions which appear in the "product_id" drop-down menu on the report
73     # generation form. The product_id 0 is a special case, meaning "All
74     # Products".
75     if ($product_id) {
76         $product = Bugzilla::Product->new($product_id);
77         $product && $user->can_see_product($product->name)
78             || ThrowUserError('product_access_denied',
79                               {id => $product_id});
80     }
81     else {
82         $product = $product_all;
83     }
84
85     # Make sure there is something to plot.
86     my @datasets = $cgi->param('datasets');
87     scalar(@datasets) || ThrowUserError('missing_datasets');
88
89     if (grep { $_ !~ /^[A-Za-z0-9:_-]+$/ } @datasets) {
90         ThrowUserError('invalid_datasets', {'datasets' => \@datasets});
91     }
92
93     # Filenames must not be guessable as they can point to products
94     # you are not allowed to see. Also, different projects can have
95     # the same product IDs.
96     my $project = bz_locations()->{'project'} || '';
97     my $image_file =  join(':', ($project, $product->id, @datasets));
98     my $key = Bugzilla->localconfig->{'site_wide_secret'};
99     $image_file = hmac_sha256_base64($image_file, $key) . '.png';
100     $image_file =~ s/\+/-/g;
101     $image_file =~ s/\//_/g;
102     trick_taint($image_file);
103
104     if (! -e "$graph_dir/$image_file") {
105         generate_chart($dir, "$graph_dir/$image_file", $product, \@datasets);
106     }
107
108     $vars->{'url_image'} = "$graph_url/$image_file";
109
110     print $cgi->header(-Content_Disposition=>'inline; filename=bugzilla_report.html');
111 }
112
113 $template->process('reports/old-charts.html.tmpl', $vars)
114   || ThrowTemplateError($template->error());
115
116 #####################
117 #    Subroutines    #
118 #####################
119
120 sub get_data {
121     my $dir = shift;
122
123     my @datasets;
124     open(DATA, '<', "$dir/0")
125       || ThrowCodeError('chart_file_open_fail', {filename => "$dir/0"});
126
127     while (<DATA>) {
128         if (/^# fields?: (.+)\s*$/) {
129             @datasets = grep ! /date/i, (split /\|/, $1);
130             last;
131         }
132     }
133     close(DATA);
134     return @datasets;
135 }
136
137 sub generate_chart {
138     my ($dir, $image_file, $product, $datasets) = @_;
139     my $data_file = $dir . '/' . $product->id;
140
141     if (!open(FILE, '<', $data_file)) {
142         ThrowCodeError('chart_data_not_generated', {'product' => $product});
143     }
144
145     my $product_in_title = $product->id ? $product->name : 'All Products';
146     my @fields;
147     my @labels = qw(DATE);
148     my %datasets = map { $_ => 1 } @$datasets;
149
150     my %data = ();
151     while (<FILE>) {
152         chomp;
153         next unless $_;
154         if (/^#/) {
155             if (/^# fields?: (.*)\s*$/) {
156                 @fields = split /\||\r/, $1;
157                 $data{$_} ||= [] foreach @fields;
158                 unless ($fields[0] =~ /date/i) {
159                     ThrowCodeError('chart_datafile_corrupt', {'file' => $data_file});
160                 }
161                 push @labels, grep($datasets{$_}, @fields);
162             }
163             next;
164         }
165
166         unless (@fields) {
167             ThrowCodeError('chart_datafile_corrupt', {'file' => $data_file});
168         }
169
170         my @line = split /\|/;
171         my $date = $line[0];
172         my ($yy, $mm, $dd) = $date =~ /^\d{2}(\d{2})(\d{2})(\d{2})$/;
173         push @{$data{DATE}}, "$mm/$dd/$yy";
174         
175         for my $i (1 .. $#fields) {
176             my $field = $fields[$i];
177             if (! defined $line[$i] or $line[$i] eq '') {
178                 # no data point given, don't plot (this will probably
179                 # generate loads of Chart::Base warnings, but that's not
180                 # our fault.)
181                 push @{$data{$field}}, undef;
182             }
183             else {
184                 push @{$data{$field}}, $line[$i];
185             }
186         }
187     }
188     
189     shift @labels;
190
191     close FILE;
192
193     if (! @{$data{DATE}}) {
194         ThrowUserError('insufficient_data_points');
195     }
196
197     my $img = Chart::Lines->new (800, 600);
198     my $i = 0;
199
200     my $MAXTICKS = 20;      # Try not to show any more x ticks than this.
201     my $skip = 1;
202     if (@{$data{DATE}} > $MAXTICKS) {
203         $skip = int((@{$data{DATE}} + $MAXTICKS - 1) / $MAXTICKS);
204     }
205
206     my %settings =
207         (
208          "title" => "Status Counts for $product_in_title",
209          "x_label" => "Dates",
210          "y_label" => "Bug Counts",
211          "legend_labels" => \@labels,
212          "skip_x_ticks" => $skip,
213          "y_grid_lines" => "true",
214          "grey_background" => "false",
215          "colors" => {
216                       # default dataset colours are too alike
217                       dataset4 => [0, 0, 0], # black
218                      },
219         );
220     
221     $img->set (%settings);
222     $img->png($image_file, [ @data{('DATE', @labels)} ]);
223 }