[bugzilla-gnome-org-extensions] Add automatic duplicate handling.
- From: Krzesimir Nowak <krnowak src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [bugzilla-gnome-org-extensions] Add automatic duplicate handling.
- Date: Thu, 20 Nov 2014 22:13:57 +0000 (UTC)
commit a45320a1f89c20a9b5d6cdf851eb8243cbabaeb1
Author: Max Kanat-Alexander <mkanat everythingsolved com>
Date: Fri Aug 7 18:51:35 2009 -0500
Add automatic duplicate handling.
code/db_schema-abstract_schema.pl | 15 +++
code/install-before_final_checks.pl | 33 ++++++
lib/TraceParser/Hooks.pm | 126 +++++++++++++++++++++--
lib/TraceParser/Trace.pm | 103 +++++++++++++++++++-
template/en/default/pages/trace.html.tmpl | 23 +++++
template/en/global/messages-messages.html.tmpl | 7 ++
template/en/global/user-error-errors.html.tmpl | 24 +++++
7 files changed, 319 insertions(+), 12 deletions(-)
---
diff --git a/code/db_schema-abstract_schema.pl b/code/db_schema-abstract_schema.pl
index fd3a04d..4fe22b4 100644
--- a/code/db_schema-abstract_schema.pl
+++ b/code/db_schema-abstract_schema.pl
@@ -46,3 +46,18 @@ $schema->{trace} = {
trace_comment_id_idx => {TYPE => 'UNIQUE', FIELDS => ['comment_id']},
],
};
+
+$schema->{trace_dup} = {
+ FIELDS => [
+ hash => {TYPE => 'char(22)', NOTNULL => 1},
+ identical => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0},
+ bug_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'bugs',
+ COLUMN => 'bug_id'}},
+ ],
+ INDEXES => [
+ trace_dup_hash_idx => {TYPE => 'UNIQUE',
+ FIELDS => [qw(hash identical)]},
+ trace_bug_id_idx => ['bug_id'],
+ ],
+};
diff --git a/code/install-before_final_checks.pl b/code/install-before_final_checks.pl
new file mode 100644
index 0000000..736ddb7
--- /dev/null
+++ b/code/install-before_final_checks.pl
@@ -0,0 +1,33 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# 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 Example 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>
+
+
+use strict;
+use warnings;
+use Bugzilla;
+use Bugzilla::Group;
+
+if (!new Bugzilla::Group({ name => 'traceparser_edit' })) {
+ Bugzilla::Group->create({
+ name => 'traceparser_edit',
+ description => 'Can edit properties of traces',
+ isbuggroup => 0 });
+}
diff --git a/lib/TraceParser/Hooks.pm b/lib/TraceParser/Hooks.pm
index 65a93c3..dc9ece5 100644
--- a/lib/TraceParser/Hooks.pm
+++ b/lib/TraceParser/Hooks.pm
@@ -22,11 +22,16 @@
package TraceParser::Hooks;
use strict;
use base qw(Exporter);
+use Bugzilla::Bug;
+use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Install::Util qw(indicate_progress);
use Bugzilla::Util qw(detaint_natural);
+
use TraceParser::Trace;
+use List::Util;
+
our @EXPORT = qw(
bug_create
bug_update
@@ -43,7 +48,93 @@ sub bug_create {
my $comment = $bug->longdescs->[0];
my $data = TraceParser::Trace->parse_from_text($comment->{body});
return if !$data;
- TraceParser::Trace->create({ %$data, comment_id => $comment->{id} });
+ my $trace = TraceParser::Trace->create(
+ { %$data, comment_id => $comment->{id} });
+ _check_duplicate_trace($trace, $bug, $comment);
+}
+
+sub _check_duplicate_trace {
+ my ($trace, $bug, $comment) = @_;
+ my $dbh = Bugzilla->dbh;
+ 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);
+ }
+ else {
+ ThrowUserError('traceparser_dup_to_hidden',
+ { dup_to => $dup_to });
+ }
+ }
+
+ 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;
+}
+
+sub _handle_dup_to {
+ my ($trace, $dup_to, $comment) = @_;
+ 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 (!$higher_quality_traces) {
+ $dup_to->add_comment($comment->{thetext}, $comment);
+ }
+
+ $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->{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 });
+ }
+ }
+ else {
+ ThrowUserError('traceparser_dup_to_closed',
+ { dup_to => $dup_to });
+ }
}
sub bug_update {
@@ -148,21 +239,34 @@ sub page {
sub _page_trace {
my $vars = shift;
+ my $cgi = Bugzilla->cgi;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
- my $trace_id = Bugzilla->cgi->param('trace_id');
+ my $trace_id = $cgi->param('trace_id');
my $trace = TraceParser::Trace->check({ id => $trace_id });
$trace->bug->check_is_visible;
+ my $action = $cgi->param('action') || '';
+ if ($action eq 'update') {
+ $user->in_group('traceparser_edit')
+ or ThrowUserError('auth_failure',
+ { action => 'modify', group => 'traceparser_edit',
+ object => 'settings' });
+ if (!$trace->stack_hash) {
+ ThrowUserError('traceparser_trace_too_short');
+ }
+ my $ident_dup = $cgi->param('identical_dup');
+ my $similar_dup = $cgi->param('similar_dup');
+ $dbh->bz_start_transaction();
+ $trace->update_identical_dup($ident_dup);
+ $trace->update_similar_dup($similar_dup);
+ $dbh->bz_commit_transaction();
+ }
+
if ($trace->stack_hash) {
- my $identical_traces = TraceParser::Trace->match(
- { stack_hash => $trace->stack_hash });
- my $similar_traces = TraceParser::Trace->match(
- { short_hash => $trace->short_hash });
- # Remove identical traces.
- my %identical = map { $_->id => 1 } @$identical_traces;
- @$similar_traces = grep { !$identical{$_->id} } @$similar_traces;
- # Remove this trace from the identical traces.
- @$identical_traces = grep { $_->id != $trace->id } @$identical_traces;
+ my $identical_traces = $trace->identical_traces;
+ my $similar_traces = $trace->similar_traces;
my %ungrouped = ( identical => $identical_traces,
similar => $similar_traces );
diff --git a/lib/TraceParser/Trace.pm b/lib/TraceParser/Trace.pm
index 9efbfc7..735f87c 100644
--- a/lib/TraceParser/Trace.pm
+++ b/lib/TraceParser/Trace.pm
@@ -26,6 +26,7 @@ use base qw(Bugzilla::Object);
use Bugzilla::Bug;
use Bugzilla::Error;
use Bugzilla::Util;
+use Scalar::Util qw(blessed);
use Parse::StackTrace;
use Digest::MD5 qw(md5_base64);
@@ -173,11 +174,24 @@ sub parse_from_text {
}
sub _hash {
- my $str = shift;
+ my ($str) = @_;
utf8::encode($str) if utf8::is_utf8($str);
return md5_base64($str);
}
+#################
+# Class Methods #
+#################
+
+sub traces_on_bug {
+ my ($class, $bug) = @_;
+ my $bug_id = blessed $bug ? $bug->id : $bug;
+ my $comment_ids = Bugzilla->dbh->selectcol_arrayref(
+ 'SELECT comment_id FROM longdescs WHERE bug_id = ?',
+ undef, $bug_id);
+ return $class->match({ comment_id => $comment_ids });
+}
+
###############################
#### Accessors ######
###############################
@@ -216,6 +230,22 @@ sub crash_thread {
return $st->thread_with_crash || $st->threads->[0];
}
+sub identical_traces {
+ my $self = shift;
+ return $self->{identical_traces} if exists $self->{identical_traces};
+ my $class = ref $self;
+ my $identical ||= $class->match({ stack_hash => $self->stack_hash });
+ @$identical = grep { $_->id != $self->id } @$identical;
+ $self->{identical_traces} = $identical;
+ return $self->{identical_traces};
+}
+
+sub must_dup_to {
+ my $self = shift;
+ my $id = $self->identical_dup_id || $self->similar_dup_id;
+ return new Bugzilla::Bug($id);
+}
+
sub _important_stack_frames {
my ($invocant, $st) = @_;
$st ||= $invocant->stack;
@@ -268,6 +298,18 @@ sub short_stack {
return \ short_stack;
}
+# Gets similar traces without also listing identical traces in the list.
+sub similar_traces {
+ my $self = shift;
+ return $self->{similar_traces} if exists $self->{similar_traces};
+ my $class = ref $self;
+ my $similar = $class->match({ short_hash => $self->short_hash });
+ my %identical = map { $_->id => 1 } @{ $self->identical_traces };
+ @$similar = grep { !$identical{$_->id} and $_->id != $self->id } @$similar;
+ $self->{similar_traces} = $similar;
+ return $similar;
+}
+
sub stack {
my $self = shift;
my $type = $self->type;
@@ -276,6 +318,65 @@ sub stack {
return $self->{stack};
}
+###########################
+# Trace Duplicate Methods #
+###########################
+
+sub identical_dup_id {
+ my $self = shift;
+ return $self->{identical_dup_id} if exists $self->{identical_dup_id};
+ $self->{identical_dup_id} = Bugzilla->dbh->selectrow_array(
+ 'SELECT bug_id FROM trace_dup WHERE hash = ? AND identical = 1',
+ undef, $self->stack_hash);
+ return $self->{identical_dup_id};
+}
+
+sub similar_dup_id {
+ my $self = shift;
+ return $self->{similar_dup_id} if exists $self->{similar_dup_id};
+ $self->{similar_dup_id} = Bugzilla->dbh->selectrow_array(
+ 'SELECT bug_id FROM trace_dup WHERE hash = ? AND identical = 0',
+ undef, $self->short_hash);
+ return $self->{similar_dup_id};
+}
+
+sub update_identical_dup {
+ my ($self, $bug_id) = @_;
+ _update_dup($self->stack_hash, 1, $bug_id);
+}
+
+sub update_similar_dup {
+ my ($self, $bug_id) = @_;
+ _update_dup($self->short_hash, 0, $bug_id);
+}
+
+sub _update_dup {
+ my ($hash, $identical, $bug_id) = @_;
+ my $dbh = Bugzilla->dbh;
+ if (!$bug_id) {
+ $dbh->do("DELETE FROM trace_dup WHERE hash = ? AND identical = ?",
+ undef, $hash, $identical);
+ return;
+ }
+
+ my $bug = Bugzilla::Bug->check($bug_id);
+ $bug_id = $bug->id; # detaint $bug_id
+
+ my $exists = $dbh->selectrow_array(
+ 'SELECT 1 FROM trace_dup WHERE hash = ? AND identical = ?',
+ undef, $hash, $identical);
+ if ($exists) {
+ $dbh->do('UPDATE trace_dup SET bug_id = ?
+ WHERE hash = ? AND identical = ?',
+ undef, $bug_id, $hash, $identical);
+ }
+ else {
+ $dbh->do('INSERT INTO trace_dup (bug_id, hash, identical)
+ VALUES (?,?,?)', undef, $bug_id, $hash, $identical);
+ }
+}
+
+
###############################
### Validators ###
###############################
diff --git a/template/en/default/pages/trace.html.tmpl b/template/en/default/pages/trace.html.tmpl
index 8b87813..7e1b2cb 100644
--- a/template/en/default/pages/trace.html.tmpl
+++ b/template/en/default/pages/trace.html.tmpl
@@ -23,6 +23,29 @@
title = "Trace $trace.id From Bug $trace.bug.id"
%]
+[% IF user.in_group('traceparser_edit') %]
+ <h2>Properties of trace [% trace.id FILTER html %]</h2>
+ <form action="page.cgi?id=trace.html&trace_id=
+ [%- trace.id FILTER url_quote %]"
+ method="POST">
+ <div>
+ <p>If a trace with an <em>identical</em> function stack is submitted,
+ automatically refer the user to this [% terms.bug %]:
+ <input type="text" size="5" id="identical_dup" name="identical_dup"
+ value="[% trace.identical_dup_id FILTER html %]"></p>
+
+ <p>If a trace with a <em>similar</em> (but <strong>not</strong>
+ identical) function stack is submitted, automatically refer
+ to user to this [% terms.bug %]:
+ <input type="text" size="5" id="similar_dup" name="similar_dup"
+ value="[% trace.similar_dup_id FILTER html %]"></p>
+
+ <input type="hidden" name="action" value="update">
+ <input type="submit" value="Submit" id="submit_trace">
+ </div>
+ </form>
+[% END %]
+
[% IF identical_traces.size %]
<h2>Traces with an identical stack:</h2>
[% PROCESS trace_list list = identical_traces %]
diff --git a/template/en/global/messages-messages.html.tmpl b/template/en/global/messages-messages.html.tmpl
new file mode 100644
index 0000000..23265ae
--- /dev/null
+++ b/template/en/global/messages-messages.html.tmpl
@@ -0,0 +1,7 @@
+[% IF message_tag == "traceparser_dup_to" %]
+ Your crash is a duplicate of the [% terms.bug %] you see below.
+ You have been added to the CC list of this [% terms.bug %]
+ [%~ IF comment_added ~%]
+ , and your crash information has been added to it.
+ [%~ END %].
+[% END %]
diff --git a/template/en/global/user-error-errors.html.tmpl b/template/en/global/user-error-errors.html.tmpl
new file mode 100644
index 0000000..8fc273a
--- /dev/null
+++ b/template/en/global/user-error-errors.html.tmpl
@@ -0,0 +1,24 @@
+[% IF error == "traceparser_dup_to" %]
+ [% title = "Stack Trace Is a Duplicate" %]
+ Thank you for submitting your crash. This crash is a duplicate of
+ [%+ "$terms.bug $dup_to.id" FILTER bug_link(dup_to) %].
+ You have been added to the CC list of that [% terms.bug %]
+ [%~ IF comment_added ~%]
+ , and your crash information has been added to it.
+ [%~ END %].
+
+[% ELSIF error == "traceparser_dup_to_closed" %]
+ [% title = "Stack Trace Is a Duplicate" %]
+ The crash you submitted appears to be a duplicate of
+ [%+ "$terms.bug $dup_to.id" FILTER bug_link(dup_to) %].
+
+ [% IF dup_to.resolution == 'FIXED' %]
+ This [% terms.bug %] has already been fixed.
+ [% END %]
+
+ See [% "the $terms.bug" FILTER bug_link(dup_to) %] for more information.
+
+[% ELSIF error == "traceparser_trace_too_short" %]
+ [% title = "Trace Too Short To Edit" %]
+ This trace is too short to set properties for it.
+[% END %]
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]