ddkilzer@apple.com | f573e63 | 2009-07-03 02:14:39 +0000 | [diff] [blame] | 1 | #!/usr/bin/env perl -wT |
timothy@apple.com | f42518d | 2008-02-06 20:19:16 +0000 | [diff] [blame] | 2 | # -*- Mode: perl; indent-tabs-mode: nil -*- |
| 3 | # |
| 4 | # The contents of this file are subject to the Mozilla Public |
| 5 | # License Version 1.1 (the "License"); you may not use this file |
| 6 | # except in compliance with the License. You may obtain a copy of |
| 7 | # the License at http://www.mozilla.org/MPL/ |
| 8 | # |
| 9 | # Software distributed under the License is distributed on an "AS |
| 10 | # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or |
| 11 | # implied. See the License for the specific language governing |
| 12 | # rights and limitations under the License. |
| 13 | # |
| 14 | # The Original Code is the Bugzilla Bug Tracking System. |
| 15 | # |
| 16 | # The Initial Developer of the Original Code is Netscape Communications |
| 17 | # Corporation. Portions created by Netscape are |
| 18 | # Copyright (C) 1998 Netscape Communications Corporation. All |
| 19 | # Rights Reserved. |
| 20 | # |
| 21 | # Contributor(s): Gervase Markham <gerv@gerv.net> |
| 22 | # |
| 23 | # Generates mostfreq list from data collected by collectstats.pl. |
| 24 | |
| 25 | |
| 26 | use strict; |
| 27 | |
| 28 | use AnyDBM_File; |
| 29 | |
ddkilzer@apple.com | 097da08 | 2009-07-03 02:14:25 +0000 | [diff] [blame] | 30 | use lib qw(. lib); |
timothy@apple.com | f42518d | 2008-02-06 20:19:16 +0000 | [diff] [blame] | 31 | |
timothy@apple.com | f42518d | 2008-02-06 20:19:16 +0000 | [diff] [blame] | 32 | use Bugzilla; |
timothy@apple.com | f42518d | 2008-02-06 20:19:16 +0000 | [diff] [blame] | 33 | use Bugzilla::Constants; |
ddkilzer@apple.com | f3615fc | 2009-07-03 02:13:41 +0000 | [diff] [blame] | 34 | use Bugzilla::Util; |
| 35 | use Bugzilla::Error; |
| 36 | use Bugzilla::Search; |
| 37 | use Bugzilla::Product; |
timothy@apple.com | f42518d | 2008-02-06 20:19:16 +0000 | [diff] [blame] | 38 | |
| 39 | my $cgi = Bugzilla->cgi; |
ddkilzer@apple.com | f3615fc | 2009-07-03 02:13:41 +0000 | [diff] [blame] | 40 | my $template = Bugzilla->template; |
| 41 | my $vars = {}; |
timothy@apple.com | f42518d | 2008-02-06 20:19:16 +0000 | [diff] [blame] | 42 | |
| 43 | # collectstats.pl uses duplicates.cgi to generate the RDF duplicates stats. |
| 44 | # However, this conflicts with requirelogin if it's enabled; so we make |
| 45 | # logging-in optional if we are running from the command line. |
| 46 | if ($::ENV{'GATEWAY_INTERFACE'} eq "cmdline") { |
| 47 | Bugzilla->login(LOGIN_OPTIONAL); |
| 48 | } |
| 49 | else { |
| 50 | Bugzilla->login(); |
| 51 | } |
| 52 | |
ddkilzer@apple.com | f3615fc | 2009-07-03 02:13:41 +0000 | [diff] [blame] | 53 | my $dbh = Bugzilla->switch_to_shadow_db(); |
timothy@apple.com | f42518d | 2008-02-06 20:19:16 +0000 | [diff] [blame] | 54 | |
| 55 | my %dbmcount; |
| 56 | my %count; |
| 57 | my %before; |
| 58 | |
| 59 | # Get params from URL |
| 60 | sub formvalue { |
| 61 | my ($name, $default) = (@_); |
ddkilzer@apple.com | f3615fc | 2009-07-03 02:13:41 +0000 | [diff] [blame] | 62 | return Bugzilla->cgi->param($name) || $default || ""; |
timothy@apple.com | f42518d | 2008-02-06 20:19:16 +0000 | [diff] [blame] | 63 | } |
| 64 | |
| 65 | my $sortby = formvalue("sortby"); |
| 66 | my $changedsince = formvalue("changedsince", 7); |
| 67 | my $maxrows = formvalue("maxrows", 100); |
| 68 | my $openonly = formvalue("openonly"); |
| 69 | my $reverse = formvalue("reverse") ? 1 : 0; |
| 70 | my @query_products = $cgi->param('product'); |
| 71 | my $sortvisible = formvalue("sortvisible"); |
| 72 | my @buglist = (split(/[:,]/, formvalue("bug_id"))); |
| 73 | |
ddkilzer@apple.com | f3615fc | 2009-07-03 02:13:41 +0000 | [diff] [blame] | 74 | # Make sure all products are valid. |
timothy@apple.com | f42518d | 2008-02-06 20:19:16 +0000 | [diff] [blame] | 75 | foreach my $p (@query_products) { |
ddkilzer@apple.com | f3615fc | 2009-07-03 02:13:41 +0000 | [diff] [blame] | 76 | Bugzilla::Product::check_product($p); |
timothy@apple.com | f42518d | 2008-02-06 20:19:16 +0000 | [diff] [blame] | 77 | } |
| 78 | |
| 79 | # Small backwards-compatibility hack, dated 2002-04-10. |
| 80 | $sortby = "count" if $sortby eq "dup_count"; |
| 81 | |
| 82 | # Open today's record of dupes |
| 83 | my $today = days_ago(0); |
| 84 | my $yesterday = days_ago(1); |
| 85 | |
ddkilzer@apple.com | f3615fc | 2009-07-03 02:13:41 +0000 | [diff] [blame] | 86 | # We don't know the exact file name, because the extension depends on the |
timothy@apple.com | f42518d | 2008-02-06 20:19:16 +0000 | [diff] [blame] | 87 | # underlying dbm library, which could be anything. We can't glob, because |
| 88 | # perl < 5.6 considers if (<*>) { ... } to be tainted |
| 89 | # Instead, just check the return value for today's data and yesterday's, |
| 90 | # and ignore file not found errors |
| 91 | |
| 92 | use Errno; |
| 93 | use Fcntl; |
| 94 | |
ddkilzer@apple.com | f3615fc | 2009-07-03 02:13:41 +0000 | [diff] [blame] | 95 | my $datadir = bz_locations()->{'datadir'}; |
| 96 | |
timothy@apple.com | f42518d | 2008-02-06 20:19:16 +0000 | [diff] [blame] | 97 | if (!tie(%dbmcount, 'AnyDBM_File', "$datadir/duplicates/dupes$today", |
| 98 | O_RDONLY, 0644)) { |
| 99 | if ($!{ENOENT}) { |
| 100 | if (!tie(%dbmcount, 'AnyDBM_File', "$datadir/duplicates/dupes$yesterday", |
| 101 | O_RDONLY, 0644)) { |
| 102 | my $vars = { today => $today }; |
| 103 | if ($!{ENOENT}) { |
| 104 | ThrowUserError("no_dupe_stats", $vars); |
| 105 | } else { |
| 106 | $vars->{'error_msg'} = $!; |
| 107 | ThrowUserError("no_dupe_stats_error_yesterday", $vars); |
| 108 | } |
| 109 | } |
| 110 | } else { |
| 111 | ThrowUserError("no_dupe_stats_error_today", |
| 112 | { error_msg => $! }); |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | # Copy hash (so we don't mess up the on-disk file when we remove entries) |
| 117 | %count = %dbmcount; |
| 118 | |
| 119 | # Remove all those dupes under the threshold parameter. |
| 120 | # We do this, before the sorting, for performance reasons. |
ddkilzer@apple.com | f3615fc | 2009-07-03 02:13:41 +0000 | [diff] [blame] | 121 | my $threshold = Bugzilla->params->{"mostfreqthreshold"}; |
timothy@apple.com | f42518d | 2008-02-06 20:19:16 +0000 | [diff] [blame] | 122 | |
| 123 | while (my ($key, $value) = each %count) { |
| 124 | delete $count{$key} if ($value < $threshold); |
| 125 | |
| 126 | # If there's a buglist, restrict the bugs to that list. |
| 127 | delete $count{$key} if $sortvisible && (lsearch(\@buglist, $key) == -1); |
| 128 | } |
| 129 | |
| 130 | my $origmaxrows = $maxrows; |
| 131 | detaint_natural($maxrows) |
| 132 | || ThrowUserError("invalid_maxrows", { maxrows => $origmaxrows}); |
| 133 | |
| 134 | my $origchangedsince = $changedsince; |
| 135 | detaint_natural($changedsince) |
| 136 | || ThrowUserError("invalid_changedsince", |
| 137 | { changedsince => $origchangedsince }); |
| 138 | |
| 139 | # Try and open the database from "changedsince" days ago |
| 140 | my $dobefore = 0; |
| 141 | my %delta; |
| 142 | my $whenever = days_ago($changedsince); |
| 143 | |
| 144 | if (!tie(%before, 'AnyDBM_File', "$datadir/duplicates/dupes$whenever", |
| 145 | O_RDONLY, 0644)) { |
| 146 | # Ignore file not found errors |
| 147 | if (!$!{ENOENT}) { |
| 148 | ThrowUserError("no_dupe_stats_error_whenever", |
| 149 | { error_msg => $!, |
| 150 | changedsince => $changedsince, |
| 151 | whenever => $whenever, |
| 152 | }); |
| 153 | } |
| 154 | } else { |
| 155 | # Calculate the deltas |
| 156 | ($delta{$_} = $count{$_} - ($before{$_} || 0)) foreach (keys(%count)); |
| 157 | |
| 158 | $dobefore = 1; |
| 159 | } |
| 160 | |
| 161 | my @bugs; |
| 162 | my @bug_ids; |
| 163 | |
| 164 | if (scalar(%count)) { |
| 165 | # use Bugzilla::Search so that we get the security checking |
| 166 | my $params = new Bugzilla::CGI({ 'bug_id' => [keys %count] }); |
| 167 | |
| 168 | if ($openonly) { |
| 169 | $params->param('resolution', '---'); |
| 170 | } else { |
| 171 | # We want to show bugs which: |
| 172 | # a) Aren't CLOSED; and |
| 173 | # b) i) Aren't VERIFIED; OR |
| 174 | # ii) Were resolved INVALID/WONTFIX |
| 175 | |
| 176 | # The rationale behind this is that people will eventually stop |
| 177 | # reporting fixed bugs when they get newer versions of the software, |
| 178 | # but if the bug is determined to be erroneous, people will still |
| 179 | # keep reporting it, so we do need to show it here. |
| 180 | |
| 181 | # a) |
| 182 | $params->param('field0-0-0', 'bug_status'); |
| 183 | $params->param('type0-0-0', 'notequals'); |
| 184 | $params->param('value0-0-0', 'CLOSED'); |
| 185 | |
| 186 | # b) i) |
| 187 | $params->param('field0-1-0', 'bug_status'); |
| 188 | $params->param('type0-1-0', 'notequals'); |
| 189 | $params->param('value0-1-0', 'VERIFIED'); |
| 190 | |
| 191 | # b) ii) |
| 192 | $params->param('field0-1-1', 'resolution'); |
| 193 | $params->param('type0-1-1', 'anyexact'); |
| 194 | $params->param('value0-1-1', 'INVALID,WONTFIX'); |
| 195 | } |
| 196 | |
| 197 | # Restrict to product if requested |
| 198 | if ($cgi->param('product')) { |
| 199 | $params->param('product', join(',', @query_products)); |
| 200 | } |
| 201 | |
| 202 | my $query = new Bugzilla::Search('fields' => [qw(bugs.bug_id |
| 203 | map_components.name |
| 204 | bugs.bug_severity |
| 205 | bugs.op_sys |
| 206 | bugs.target_milestone |
| 207 | bugs.short_desc |
| 208 | bugs.bug_status |
| 209 | bugs.resolution |
| 210 | ) |
| 211 | ], |
| 212 | 'params' => $params, |
| 213 | ); |
| 214 | |
ddkilzer@apple.com | f3615fc | 2009-07-03 02:13:41 +0000 | [diff] [blame] | 215 | my $results = $dbh->selectall_arrayref($query->getSQL()); |
timothy@apple.com | f42518d | 2008-02-06 20:19:16 +0000 | [diff] [blame] | 216 | |
ddkilzer@apple.com | f3615fc | 2009-07-03 02:13:41 +0000 | [diff] [blame] | 217 | foreach my $result (@$results) { |
timothy@apple.com | f42518d | 2008-02-06 20:19:16 +0000 | [diff] [blame] | 218 | # Note: maximum row count is dealt with in the template. |
| 219 | |
| 220 | my ($id, $component, $bug_severity, $op_sys, $target_milestone, |
ddkilzer@apple.com | f3615fc | 2009-07-03 02:13:41 +0000 | [diff] [blame] | 221 | $short_desc, $bug_status, $resolution) = @$result; |
timothy@apple.com | f42518d | 2008-02-06 20:19:16 +0000 | [diff] [blame] | 222 | |
| 223 | push (@bugs, { id => $id, |
| 224 | count => $count{$id}, |
| 225 | delta => $delta{$id}, |
| 226 | component => $component, |
| 227 | bug_severity => $bug_severity, |
| 228 | op_sys => $op_sys, |
| 229 | target_milestone => $target_milestone, |
| 230 | short_desc => $short_desc, |
| 231 | bug_status => $bug_status, |
| 232 | resolution => $resolution }); |
| 233 | push (@bug_ids, $id); |
| 234 | } |
| 235 | } |
| 236 | |
| 237 | $vars->{'bugs'} = \@bugs; |
| 238 | $vars->{'bug_ids'} = \@bug_ids; |
| 239 | |
| 240 | $vars->{'dobefore'} = $dobefore; |
| 241 | $vars->{'sortby'} = $sortby; |
| 242 | $vars->{'sortvisible'} = $sortvisible; |
| 243 | $vars->{'changedsince'} = $changedsince; |
| 244 | $vars->{'maxrows'} = $maxrows; |
| 245 | $vars->{'openonly'} = $openonly; |
| 246 | $vars->{'reverse'} = $reverse; |
| 247 | $vars->{'format'} = $cgi->param('format'); |
| 248 | $vars->{'query_products'} = \@query_products; |
ddkilzer@apple.com | f3615fc | 2009-07-03 02:13:41 +0000 | [diff] [blame] | 249 | $vars->{'products'} = Bugzilla->user->get_selectable_products; |
timothy@apple.com | f42518d | 2008-02-06 20:19:16 +0000 | [diff] [blame] | 250 | |
| 251 | |
ddkilzer@apple.com | f3615fc | 2009-07-03 02:13:41 +0000 | [diff] [blame] | 252 | my $format = $template->get_format("reports/duplicates", |
| 253 | scalar($cgi->param('format')), |
| 254 | scalar($cgi->param('ctype'))); |
timothy@apple.com | f42518d | 2008-02-06 20:19:16 +0000 | [diff] [blame] | 255 | |
ddkilzer@apple.com | f3615fc | 2009-07-03 02:13:41 +0000 | [diff] [blame] | 256 | # We set the charset in Bugzilla::CGI, but CGI.pm ignores it unless the |
| 257 | # Content-Type is a text type. In some cases, such as when we are |
| 258 | # generating RDF, it isn't, so we specify the charset again here. |
| 259 | print $cgi->header( |
| 260 | -type => $format->{'ctype'}, |
| 261 | (Bugzilla->params->{'utf8'} ? ('charset', 'utf8') : () ) |
| 262 | ); |
timothy@apple.com | f42518d | 2008-02-06 20:19:16 +0000 | [diff] [blame] | 263 | |
| 264 | # Generate and return the UI (HTML page) from the appropriate template. |
| 265 | $template->process($format->{'template'}, $vars) |
| 266 | || ThrowTemplateError($template->error()); |
| 267 | |
| 268 | |
| 269 | sub days_ago { |
| 270 | my ($dom, $mon, $year) = (localtime(time - ($_[0]*24*60*60)))[3, 4, 5]; |
| 271 | return sprintf "%04d-%02d-%02d", 1900 + $year, ++$mon, $dom; |
| 272 | } |