[bugzilla-gnome-org-extensions] Add the pages for "possible duplicates" when filing a bug.
- From: Krzesimir Nowak <krnowak src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [bugzilla-gnome-org-extensions] Add the pages for "possible duplicates" when filing a bug.
- Date: Thu, 20 Nov 2014 22:14:27 +0000 (UTC)
commit d391a22fa2f0d84cc6838148ddc3964305b3017e
Author: Max Kanat-Alexander <mkanat everythingsolved com>
Date: Sat Aug 8 20:51:11 2009 -0500
Add the pages for "possible duplicates" when filing a bug.
lib/TraceParser/Hooks.pm | 275 ++++++++++++++------
lib/TraceParser/Trace.pm | 22 ++-
template/en/default/pages/trace.html.tmpl | 5 +-
.../en/default/trace/possible-duplicate.html.tmpl | 91 +++++++
web/style.css | 12 +-
5 files changed, 315 insertions(+), 90 deletions(-)
---
diff --git a/lib/TraceParser/Hooks.pm b/lib/TraceParser/Hooks.pm
index 887c2fa..bdfec68 100644
--- a/lib/TraceParser/Hooks.pm
+++ b/lib/TraceParser/Hooks.pm
@@ -31,6 +31,7 @@ use Bugzilla::Util qw(detaint_natural);
use TraceParser::Trace;
use List::Util;
+use POSIX qw(ceil);
our @EXPORT = qw(
bug_create
@@ -45,7 +46,9 @@ use constant DEFAULT_POPULAR_LIMIT => 20;
sub bug_create {
my %params = @_;
my $bug = $params{bug};
- my $comment = $bug->longdescs->[0];
+ my $comments = Bugzilla::Bug::GetComments($bug->id, 'oldest_to_newest',
+ '1970-01-01', $bug->creation_ts, 'raw');
+ my $comment = $comments->[0];
my $data = TraceParser::Trace->parse_from_text($comment->{body});
return if !$data;
my $trace = TraceParser::Trace->create(
@@ -55,86 +58,184 @@ sub bug_create {
sub _check_duplicate_trace {
my ($trace, $bug, $comment) = @_;
+ my $cgi = Bugzilla->cgi;
my $dbh = Bugzilla->dbh;
+ my $template = Bugzilla->template;
my $user = Bugzilla->user;
if (my $dup_to = $trace->must_dup_to) {
$dbh->bz_rollback_transaction if $dbh->bz_in_transaction;
- if ($user->can_edit_product($dup_to->product_id)
- and $user->can_see_bug($dup_to))
- {
- _handle_dup_to($trace, $dup_to, $comment);
+ _handle_dup_to($trace, $dup_to, $comment);
+ }
+
+ my @identical = grep { $_->is_visible } @{ $trace->identical_traces };
+ my @similar = grep { $_->is_visible } @{ $trace->similar_traces };
+ if (@identical or @similar) {
+ $dbh->bz_rollback_transaction if $dbh->bz_in_transaction;
+ my $product = $bug->product;
+ my @prod_traces = grep { $_->bug->product eq $product }
+ (@identical, @similar);
+ my @other_traces = grep { $_->bug->product ne $product }
+ (@identical, @similar);
+
+ my %vars = (
+ comment => $comment,
+ prod_bugs => _traces_to_bugs(\ prod_traces),
+ other_bugs => _traces_to_bugs(\ other_traces),
+ product => $product,
+ );
+ my $total_other_bugs = scalar(@{ $vars{other_bugs} });
+
+ my %by_product;
+ foreach my $bug (@{ $vars{other_bugs} }) {
+ $by_product{$bug->product} ||= [];
+ push(@{ $by_product{$bug->product} }, $bug);
}
- else {
- ThrowUserError('traceparser_dup_to_hidden',
- { dup_to => $dup_to });
+ $vars{other_bugs} = \%by_product;
+
+ if ($total_other_bugs > 10) {
+ my $total_products = scalar keys %by_product;
+ $vars{other_limit} = ceil(10.0 / $total_products);
+ }
+
+ print $cgi->header;
+ $template->process('trace/possible-duplicate.html.tmpl', \%vars)
+ or ThrowTemplateError($template->error);
+ exit;
+ }
+}
+
+sub _traces_to_bugs {
+ my $traces = shift;
+ my $user = Bugzilla->user;
+
+ my @bugs_in = map { $_->bug } @$traces;
+ my %result_bugs;
+ foreach my $bug (@bugs_in) {
+ $bug = _walk_dup_chain($bug);
+ if ($user->can_see_bug($bug)) {
+ $result_bugs{$bug->id} = $bug;
}
}
- my $identical = $trace->identical_traces;
- my $similar = $trace->similar_traces;
- my $product = $bug->product;
- my @prod_identical = grep { $_->bug->product eq $product } @$identical;
- my @prod_similar = grep { $_->bug->product eq $product } @$identical;
+ my @sorted_bugs = sort _cmp_trace_bug (values %result_bugs);
+ return \ sorted_bugs;
+}
+
+sub _walk_dup_chain {
+ my $bug = shift;
+ if (!$bug->dup_id) {
+ return $bug;
+ }
+ return _walk_dup_chain(new Bugzilla::Bug($bug->dup_id));
+}
+
+sub _cmp_trace_bug($$) {
+ my ($a, $b) = @_;
+
+ # $a should sort before $b if it has a resolution of FIXED
+ # and $b does not.
+ if ($a->resolution eq 'FIXED' and $b->resolution ne 'FIXED') {
+ return -1;
+ }
+ elsif ($a->resolution ne 'FIXED' and $b->resolution eq 'FIXED') {
+ return 1;
+ }
+
+ # Otherwise, $a should sort before $b if it is open and $b is not.
+ if ($a->isopened and !$b->isopened) {
+ return -1;
+ }
+ elsif (!$a->isopened and $b->isopened) {
+ return 1;
+ }
+
+ # But sort UNCONFIRMED later than other open statuses
+ if ($a->bug_status eq 'UNCONFIRMED' and $b->bug_status ne 'UNCONFIRMED') {
+ return 1;
+ }
+ elsif ($a->bug_status ne 'UNCONFIRMED' and $b->bug_status eq 'UNCONFIRMED') {
+ return -1;
+ }
+
+ # Otherwise, show older bugs first.
+ return $a->id <=> $b->id;
}
sub _handle_dup_to {
- my ($trace, $dup_to, $comment) = @_;
+ my ($trace, $dup_to, $comment, $allow_closed) = @_;
my $user = Bugzilla->user;
- if ($dup_to->isopened) {
- $dup_to->add_cc($user);
-
- # If this trace is higher quality than any other trace on the
- # bug, then we add the comment. Otherwise we just skip the
- # comment entirely.
- my $bug_traces = TraceParser::Trace->traces_on_bug($dup_to);
- my $higher_quality_traces;
- foreach my $t (@$bug_traces) {
- if ($t->quality >= $trace->quality) {
- $higher_quality_traces = 1;
- last;
- }
- }
+ if (!$user->can_edit_product($dup_to->product_id)
+ or !$user->can_see_bug($dup_to))
+ {
+ ThrowUserError('traceparser_dup_to_hidden',
+ { dup_to => $dup_to });
+ }
+
+ if (!$dup_to->isopened and !$allow_closed) {
+ ThrowUserError('traceparser_dup_to_closed',
+ { dup_to => $dup_to });
+ }
- if (!$higher_quality_traces) {
- $dup_to->add_comment($comment->{thetext}, $comment);
+ $dup_to->add_cc($user);
+
+ # If this trace is higher quality than any other trace on the
+ # bug, then we add the comment. Otherwise we just skip the
+ # comment entirely.
+ my $bug_traces = TraceParser::Trace->traces_on_bug($dup_to);
+ my $higher_quality_traces;
+ foreach my $t (@$bug_traces) {
+ if ($t->{quality} >= $trace->{quality}) {
+ $higher_quality_traces = 1;
+ last;
}
+ }
- $dup_to->update();
- if (Bugzilla->usage_mode == USAGE_MODE_BROWSER) {
- my $template = Bugzilla->template;
- my $cgi = Bugzilla->cgi;
- my $vars = {};
- # Do the various silly things required to display show_bug.cgi
- # in Bugzilla 3.4.
- $vars->{use_keywords} = 1 if Bugzilla::Keyword::keyword_count();
- $vars->{bugs} = [$dup_to];
- $vars->{bugids} = [$dup_to->id];
- if ($cgi->cookie("BUGLIST")) {
- $vars->{bug_list} = [split(/:/, $cgi->cookie("BUGLIST"))];
+ my $comment_added;
+ if (!$higher_quality_traces) {
+ if ($dup_to->check_can_change_field('longdesc', 0, 1)) {
+ my %comment_options = %$comment;
+ my @comment_cols = Bugzilla::Bug::UPDATE_COMMENT_COLUMNS;
+ foreach my $key (keys %comment_options) {
+ if (!grep { $_ eq $key } @comment_cols) {
+ delete $comment_options{$key};
+ }
}
- eval {
- require PatchReader;
- $vars->{'patchviewerinstalled'} = 1;
- };
- $vars->{added_comment} = !$higher_quality_traces;
- $vars->{message} = 'traceparser_dup_to';
- print $cgi->header;
- $template->process('bug/show.html.tmpl', $vars)
- or ThrowTemplateError($template->error);
- exit;
- }
- else {
- ThrowUserError('traceparser_dup_to',
- { dup_to => $dup_to,
- comment_added => !$higher_quality_traces });
+ $dup_to->add_comment($comment->{body}, \%comment_options);
+ $comment_added = 1;
}
}
- else {
- ThrowUserError('traceparser_dup_to_closed',
- { dup_to => $dup_to });
+
+ $dup_to->update();
+ if (Bugzilla->usage_mode == USAGE_MODE_BROWSER) {
+ my $template = Bugzilla->template;
+ my $cgi = Bugzilla->cgi;
+ my $vars = {};
+ # Do the various silly things required to display show_bug.cgi
+ # in Bugzilla 3.4.
+ $vars->{use_keywords} = 1 if Bugzilla::Keyword::keyword_count();
+ $vars->{bugs} = [$dup_to];
+ $vars->{bugids} = [$dup_to->id];
+ if ($cgi->cookie("BUGLIST")) {
+ $vars->{bug_list} = [split(/:/, $cgi->cookie("BUGLIST"))];
+ }
+ eval {
+ require PatchReader;
+ $vars->{'patchviewerinstalled'} = 1;
+ };
+ $vars->{comment_added} = $comment_added;
+ $vars->{message} = 'traceparser_dup_to';
+ print $cgi->header;
+ $template->process('bug/show.html.tmpl', $vars)
+ or ThrowTemplateError($template->error);
+ exit;
}
+
+ # This is what we do for all non-browser usage modes.
+ ThrowUserError('traceparser_dup_to',
+ { dup_to => $dup_to,
+ comment_added => $comment_added });
}
sub bug_update {
@@ -229,12 +330,15 @@ sub format_comment {
sub page {
my %params = @_;
my ($vars, $page) = @params{qw(vars page_id)};
- if ($page =~ '^trace\.') {
+ if ($page =~ /^trace\./) {
_page_trace($vars);
}
- elsif ($page =~ '^popular-traces\.') {
+ elsif ($page =~ /^popular-traces\./) {
_page_popular_traces($vars);
}
+ elsif ($page =~ /^post-duplicate-trace/) {
+ _page_post_duplicate_trace($vars);
+ }
}
sub _page_trace {
@@ -266,31 +370,28 @@ sub _page_trace {
if ($trace->stack_hash) {
my $identical_traces = $trace->identical_traces;
- my $similar_traces = $trace->similar_traces;
-
- my %ungrouped = ( identical => $identical_traces,
- similar => $similar_traces );
- my %by_product = ( identical => {}, similar => {} );
-
- foreach my $type (qw(identical similar)) {
- my $traces = $ungrouped{$type};
- my $grouped = $by_product{$type};
- foreach my $trace (@$traces) {
- my $product = $trace->bug->product;
- next if (!Bugzilla->user->can_see_product($product)
- or $trace->is_hidden);
- $grouped->{$product} ||= [];
- push(@{ $grouped->{$product} }, $trace);
- }
- }
-
- $vars->{similar_traces} = $by_product{similar};
- $vars->{identical_traces} = $by_product{identical};
+ my $similar_traces = $trace->similar_traces;
+ $vars->{similar_traces} = _group_by_product($similar_traces);
+ $vars->{identical_traces} = _group_by_product($identical_traces);
}
$vars->{trace} = $trace;
}
+sub _group_by_product {
+ my $traces = shift;
+
+ my %by_product;
+ foreach my $trace (@$traces) {
+ my $product = $trace->bug->product;
+ next if (!Bugzilla->user->can_see_product($product)
+ or $trace->is_hidden_comment);
+ $by_product{$product} ||= [];
+ push(@{ $by_product{$product} }, $trace);
+ }
+ return \%by_product;
+}
+
sub _page_popular_traces {
my $vars = shift;
my $limit = Bugzilla->cgi->param('limit') || DEFAULT_POPULAR_LIMIT;
@@ -320,4 +421,14 @@ sub _page_popular_traces {
$vars->{trace_count} = \%trace_count;
}
+sub _page_post_duplicate_trace {
+ my $cgi = Bugzilla->cgi;
+ my $comment = { body => scalar $cgi->param('comment'),
+ isprivate => scalar $cgi->param('isprivate'),
+ };
+ my $trace = TraceParser::Trace->parse_from_text($comment->{body});
+ my $bug = Bugzilla::Bug->check(scalar $cgi->param('bug_id'));
+ _handle_dup_to($trace, $bug, $comment, 'allow closed');
+}
+
1;
diff --git a/lib/TraceParser/Trace.pm b/lib/TraceParser/Trace.pm
index 6c20627..ec81315 100644
--- a/lib/TraceParser/Trace.pm
+++ b/lib/TraceParser/Trace.pm
@@ -103,17 +103,22 @@ sub _do_list_select {
my %unique_ids = map { $bug_ids{$_} => 1 } (keys %bug_ids);
my $bugs = Bugzilla::Bug->new_from_list([values %bug_ids]);
- # Populate "product" for each bug.
+ # Populate "product" & dup_id for each bug.
my %product_ids = map { $_->{product_id} => 1 } @$bugs;
my %products = @{ $dbh->selectcol_arrayref(
'SELECT id, name FROM products WHERE id IN('
. join(',', keys %product_ids) . ')', {Columns=>[1,2]}) };
+ my %dup_ids = @{ $dbh->selectcol_arrayref(
+ 'SELECT dupe, dupe_of FROM duplicates WHERE dupe IN ('
+ . join(',', map { $_->id } @$bugs) . ')') };
+
foreach my $bug (@$bugs) {
$bug->{product} = $products{$bug->{product_id}};
+ $bug->{dup_id} = $dup_ids{$bug->id};
}
# Pre-initialize the can_see_bug cache for these bugs.
- Bugzilla->user->visible_bugs($bugs);
+ Bugzilla->user->visible_bugs([ $bugs, values %dup_ids]);
my %bug_map = map { $_->id => $_ } @$bugs;
# And add them to each trace object.
@@ -249,7 +254,7 @@ sub identical_traces {
return $self->{identical_traces};
}
-sub is_hidden {
+sub is_hidden_comment {
my $self = shift;
if ($self->comment_is_private and !Bugzilla->user->is_insider) {
return 1;
@@ -257,10 +262,17 @@ sub is_hidden {
return 0;
}
+sub is_visible {
+ my $self = shift;
+ my $user = Bugzilla->user;
+ return ($user->can_see_bug($self->bug) and !$self->is_hidden_comment)
+ ? 1 : 0;
+}
+
sub check_is_visible {
my $self = shift;
$self->bug->check_is_visible;
- if ($self->is_hidden) {
+ if ($self->is_hidden_comment) {
ThrowUserError('traceparser_comment_private',
{ trace_id => $self->id, bug_id => $self->bug->id });
}
@@ -269,7 +281,7 @@ sub check_is_visible {
sub must_dup_to {
my $self = shift;
my $id = $self->identical_dup_id || $self->similar_dup_id;
- return new Bugzilla::Bug($id);
+ return $id ? new Bugzilla::Bug($id) : undef;
}
sub _important_stack_frames {
diff --git a/template/en/default/pages/trace.html.tmpl b/template/en/default/pages/trace.html.tmpl
index c8a18e4..35a16bc 100644
--- a/template/en/default/pages/trace.html.tmpl
+++ b/template/en/default/pages/trace.html.tmpl
@@ -56,7 +56,8 @@
[% PROCESS trace_list list = similar_traces %]
[% END %]
-<h2>Trace [% trace.id FILTER html %]</h2>
+<h2>Trace [% trace.id FILTER html %]
+ (Quality: [% trace.quality FILTER html %])</h2>
<table border="0" cellpadding="0" cellspacing="0"><tr><td><div class="trace">
<pre>[% trace.text FILTER html %]
@@ -73,7 +74,7 @@
<li>
<a href="page.cgi?id=trace.html&trace_id=
[%- this_trace.id FILTER url_quote %]">Trace
- [% this_trace.id FILTER html %]</a>
+ [%+ this_trace.id FILTER html %]</a>
(Quality: <strong>[% this_trace.quality FILTER html %]</strong>)
on
[%+ "Bug $bug.id" FILTER bug_link(bug) %]:
diff --git a/template/en/default/trace/possible-duplicate.html.tmpl
b/template/en/default/trace/possible-duplicate.html.tmpl
new file mode 100644
index 0000000..bed5844
--- /dev/null
+++ b/template/en/default/trace/possible-duplicate.html.tmpl
@@ -0,0 +1,91 @@
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla TraceParser Plugin.
+ #
+ # The Initial Developer of the Original Code is Canonical Ltd.
+ # Portions created by Canonical Ltd. are Copyright (C) 2009
+ # Canonical Ltd. All Rights Reserved.
+ #
+ # Contributor(s):
+ # Max Kanat-Alexander <mkanat bugzilla org>
+ #%]
+
+[% PROCESS "global/field-descs.none.tmpl" %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Possible Duplicate"
+%]
+
+[% USE Bugzilla %]
+
+<p>The crash you have submitted looks very similar to the following
+ [%+ terms.bugs %]. Would you like to CC yourself on one of these
+ [%+ terms.bugs %] instead of filing a new one? If so, select it
+ and then click "Add Me To CC".</p>
+
+<p>Or, if you would like to continue to file this [% terms.bug %],
+ scroll to the bottom of the page and select "Continue To File
+ This [% terms.Bug %]".</p>
+
+<h2>Similar [% terms.Bugs %]</h2>
+
+<form id="submit_dup" method="POST"
+ action="page.cgi?id=post-duplicate-trace.none">
+ <input type="hidden" name="comment" value="[% comment.body FILTER html %]">
+ <input type="hidden" name="isprivate"
+ value="[% comment.isprivate FILTER html %]">
+ [% SET shown_bugs = 0 %]
+ [% IF prod_bugs.size %]
+ [% PROCESS trace_bug_list list = prod_bugs limit = 10 %]
+ [% END %]
+
+ [% IF shown_bugs < 10 %]
+ [% FOREACH product = other_bugs.keys %]
+ [% SET shown_bugs = 0 %]
+ [% PROCESS trace_bug_list list = other_bugs.$product
+ limit = other_limit %]
+ [% END %]
+ [% END %]
+</form>
+
+[% BLOCK trace_bug_list %]
+ <p>Similar [% terms.bugs %] in <strong>[% product FILTER html %]</strong>:</p>
+ <ul class="trace_bug_list">
+ [% FOREACH bug = list %]
+ <li><input type="radio" name="bug_id" value="[% bug.id FILTER html %]">
+ [%= "$terms.Bug $bug.id" FILTER bug_link(bug) %]
+ ([% bug.bug_status FILTER html %][% ' ' IF bug.resolution %]
+ [%- bug.resolution FILTER html -%]):
+ [%= bug.short_desc FILTER html %]</li>
+ [% shown_bugs = loop.count() %]
+ [% LAST IF limit AND shown_bugs > limit %]
+ [% END %]
+ <li class="submit_container">
+ <input type="submit" id="add_cc" value="Add Me To CC Instead">
+ </li>
+ </ul>
+[% END %]
+
+<h2>Continue Filing This Bug</h2>
+
+<form id="create" method="POST" action="post_bug.cgi"
+ [%- IF Bugzilla.cgi.param("data") %] enctype="multipart/form-data"[% END %]>
+ <input type="hidden" name="ignore_token" value="[% bugid FILTER html %]">
+
+ <p>If none of the above [% terms.bugs %] look like the bug
+ you are trying to file, then
+ <input type="submit" id="file_bug_again"
+ value="Continue to File This [% terms.Bug %]"></p>
+ [% PROCESS "global/hidden-fields.html.tmpl"
+ exclude="^(Bugzilla_login|Bugzilla_password|ignore_token)$" %]
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/web/style.css b/web/style.css
index db8ee92..7ff6e6a 100644
--- a/web/style.css
+++ b/web/style.css
@@ -22,7 +22,17 @@
margin-top: 0;
}
-.frames {
+.trace_bug_list li {
+ margin-top: .3em;
+}
+.trace_bug_list li input {
+ vertical-align: bottom;
+}
+.trace_bug_list li.submit_container {
+ margin-left: 2.3em;
+}
+
+.frames, .trace_bug_list {
list-style-type: none;
margin: 0;
padding: 0;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]