Rubber-stamped by Brady Eidson.
[webbrowser.git] / BugsSite / duplicates.cgi
blob590bb573c786993f53518219d8e11b39b8a8e160
1 #!/usr/bin/env perl -wT
2 # -*- Mode: perl; indent-tabs-mode: nil -*-
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/
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.
14 # The Original Code is the Bugzilla Bug Tracking System.
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.
21 # Contributor(s): Gervase Markham <gerv@gerv.net>
23 # Generates mostfreq list from data collected by collectstats.pl.
26 use strict;
28 use AnyDBM_File;
30 use lib qw(. lib);
32 use Bugzilla;
33 use Bugzilla::Constants;
34 use Bugzilla::Util;
35 use Bugzilla::Error;
36 use Bugzilla::Search;
37 use Bugzilla::Product;
39 my $cgi = Bugzilla->cgi;
40 my $template = Bugzilla->template;
41 my $vars = {};
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);
49 else {
50 Bugzilla->login();
53 my $dbh = Bugzilla->switch_to_shadow_db();
55 my %dbmcount;
56 my %count;
57 my %before;
59 # Get params from URL
60 sub formvalue {
61 my ($name, $default) = (@_);
62 return Bugzilla->cgi->param($name) || $default || "";
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")));
74 # Make sure all products are valid.
75 foreach my $p (@query_products) {
76 Bugzilla::Product::check_product($p);
79 # Small backwards-compatibility hack, dated 2002-04-10.
80 $sortby = "count" if $sortby eq "dup_count";
82 # Open today's record of dupes
83 my $today = days_ago(0);
84 my $yesterday = days_ago(1);
86 # We don't know the exact file name, because the extension depends on the
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
92 use Errno;
93 use Fcntl;
95 my $datadir = bz_locations()->{'datadir'};
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);
110 } else {
111 ThrowUserError("no_dupe_stats_error_today",
112 { error_msg => $! });
116 # Copy hash (so we don't mess up the on-disk file when we remove entries)
117 %count = %dbmcount;
119 # Remove all those dupes under the threshold parameter.
120 # We do this, before the sorting, for performance reasons.
121 my $threshold = Bugzilla->params->{"mostfreqthreshold"};
123 while (my ($key, $value) = each %count) {
124 delete $count{$key} if ($value < $threshold);
126 # If there's a buglist, restrict the bugs to that list.
127 delete $count{$key} if $sortvisible && (lsearch(\@buglist, $key) == -1);
130 my $origmaxrows = $maxrows;
131 detaint_natural($maxrows)
132 || ThrowUserError("invalid_maxrows", { maxrows => $origmaxrows});
134 my $origchangedsince = $changedsince;
135 detaint_natural($changedsince)
136 || ThrowUserError("invalid_changedsince",
137 { changedsince => $origchangedsince });
139 # Try and open the database from "changedsince" days ago
140 my $dobefore = 0;
141 my %delta;
142 my $whenever = days_ago($changedsince);
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,
154 } else {
155 # Calculate the deltas
156 ($delta{$_} = $count{$_} - ($before{$_} || 0)) foreach (keys(%count));
158 $dobefore = 1;
161 my @bugs;
162 my @bug_ids;
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] });
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
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.
181 # a)
182 $params->param('field0-0-0', 'bug_status');
183 $params->param('type0-0-0', 'notequals');
184 $params->param('value0-0-0', 'CLOSED');
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');
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');
197 # Restrict to product if requested
198 if ($cgi->param('product')) {
199 $params->param('product', join(',', @query_products));
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
212 'params' => $params,
215 my $results = $dbh->selectall_arrayref($query->getSQL());
217 foreach my $result (@$results) {
218 # Note: maximum row count is dealt with in the template.
220 my ($id, $component, $bug_severity, $op_sys, $target_milestone,
221 $short_desc, $bug_status, $resolution) = @$result;
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);
237 $vars->{'bugs'} = \@bugs;
238 $vars->{'bug_ids'} = \@bug_ids;
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;
249 $vars->{'products'} = Bugzilla->user->get_selectable_products;
252 my $format = $template->get_format("reports/duplicates",
253 scalar($cgi->param('format')),
254 scalar($cgi->param('ctype')));
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') : () )
264 # Generate and return the UI (HTML page) from the appropriate template.
265 $template->process($format->{'template'}, $vars)
266 || ThrowTemplateError($template->error());
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;