[bugzilla-gnome-org-extensions] Add the pages for "possible duplicates" when filing a bug.



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&amp;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]