Add Bug status table in the detailed report.
Initial support for Bugzilla and Redmine
--- a/extras/crash_test.conf Sun Nov 27 17:17:43 2016 +0100
+++ b/extras/crash_test.conf Sun Dec 04 01:16:39 2016 +0100
@@ -54,10 +54,15 @@
},
BugTrackerLinks => {
MyRedmine => {
- type => 'Redmine',
- match => qr/\d{1,8}/,
+ Type => 'Redmine',
+ API => { URL => 'https://redmine.example.org/' },
url_pattern => 'http://redmine.example.org/issues/<%= $bug_key =%>',
},
+ MyBugzilla => {
+ Type => 'Bugzilla',
+ API => { URL => 'https://bugzilla.example.org/' },
+ url_pattern => 'https://bugzilla.example.org/show_bug.cgi?id=<%= $bug_key =%>',
+ },
},
ExtraColumns => {
Index => [
--- a/lib/CrashTest.pm Sun Nov 27 17:17:43 2016 +0100
+++ b/lib/CrashTest.pm Sun Dec 04 01:16:39 2016 +0100
@@ -6,6 +6,7 @@
use CrashTest::Model::CrashReport;
use CrashTest::Model::CrashGroup;
use CrashTest::Model::CrashProcessor;
+use CrashTest::Model::BugLink;
# This method will run once at server start
sub startup {
@@ -37,13 +38,15 @@
$self->helper(crash_groups => sub { state $crash_groups = CrashTest::Model::CrashGroup->new (app => $self); });
$self->helper(crash_processor => sub { state $crash_processor = CrashTest::Model::CrashProcessor->new (app => $self, config => $self->config); });
- $self->helper(stackfilter => sub { state $crash_reports = CrashTest::Model::StackFilter->new (app => $self, config => $self->config); });
+ $self->helper(stackfilter => sub { state $stackfilter = CrashTest::Model::StackFilter->new (app => $self, config => $self->config); });
$self->helper(storage => sub { state $storage = CrashTest::Model::Storage->new (app => $self, config => $self->config); });
+ $self->helper(bug_link => sub { state $bug_link = CrashTest::Model::BugLink->new (app => $self, config => $self->config->{WebInterface}->{BugTrackerLinks}); });
$self->plugin('Minion', $self->config->{Processor}->{JobQueue}->{Backend}->{Minion});
$self->storage->load_plugins();
$self->crash_processor->load_plugins();
+ $self->bug_link->load_plugins();
# Router
my $r = $self->routes;
--- a/lib/CrashTest/Controller/CrashReports.pm Sun Nov 27 17:17:43 2016 +0100
+++ b/lib/CrashTest/Controller/CrashReports.pm Sun Dec 04 01:16:39 2016 +0100
@@ -61,6 +61,14 @@
}
$self->stash(threads => \@threads);
+ my $report = $self->app->crash_reports->get($uuid);
+ $self->stash(crash => $report);
+
+ my $bugs_status = {};
+ if(defined($report) && defined($report->{bug_links})) {
+ $bugs_status = $self->app->bug_link->get_statuses($report->{bug_links});
+ }
+ $self->stash(bugs_status => $bugs_status);
my $group = $self->app->crash_groups->get($uuid);
$self->stash(crash_group => $group);
$self->stash(extra_columns => $self->app->config->{WebInterface}->{ExtraColumns}->{Index});
--- a/lib/CrashTest/Helper/BugLinks.pm Sun Nov 27 17:17:43 2016 +0100
+++ b/lib/CrashTest/Helper/BugLinks.pm Sun Dec 04 01:16:39 2016 +0100
@@ -23,6 +23,7 @@
$self->app($app);
$app->helper(bug_links => sub { $self->_bug_links(@_) } );
+ $app->helper(bug_status => sub { $self->_bug_status(@_) } );
}
sub _link_template {
@@ -74,4 +75,34 @@
return Mojo::ByteStream->new(join ", ", @links);
}
+sub _bug_status {
+ my ($self, $c, $bug, $bugs_status) = @_;
+
+ my $bug_status = $bugs_status->{$bug->{bugtracker_key}}->{$bug->{bug_key}};
+ my $link;
+ my $template = $self->_link_template($bug->{bugtracker_key});
+ if(defined($template)) {
+ # build url using the configured template
+ my $url = $template->process({ bug_key => $bug->{bug_key} });
+ # and create a link to it
+ $link = $self->app->t(tr => sub { join "", (
+ $self->app->t(td => sub { $self->app->link_to($bug->{bug_key} => $url) }),
+ $self->app->t(td => sub { $bug_status->{status} }),
+ $self->app->t(td => sub { $bug_status->{resolution} }),
+ $self->app->t(td => sub { $bug_status->{summary} }),
+ )}
+ );
+ } else {
+ $link = $self->app->t(tr => sub { join "", (
+ $self->app->t(td => $bug->{bug_key}),
+ $self->app->t(td => ""),
+ $self->app->t(td => ""),
+ $self->app->t(td => ""),
+ )}
+ );
+ }
+
+ return Mojo::ByteStream->new($link);
+}
+
1;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/CrashTest/Model/BugLink.pm Sun Dec 04 01:16:39 2016 +0100
@@ -0,0 +1,57 @@
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package CrashTest::Model::BugLink;
+use Mojo::Base -base;
+use Mojo::Util qw/dumper/;
+
+has [ qw/app config/ ];
+has bugtrackers => sub { return {} };
+
+sub load_plugins {
+ my $self = shift;
+
+ foreach my $bt_key (keys %{$self->config}) {
+ my $bt_config = $self->config->{$bt_key};
+
+ if(defined($bt_config->{Type})) {
+ $self->app->plugin("CrashTest::Plugin::BugLink::" . $bt_config->{Type}, $bt_config);
+ }
+ $self->bugtrackers->{$bt_key} = "CrashTest::Plugin::BugLink::$bt_config->{Type}"->new(app => $self->app, config => $bt_config);
+ }
+}
+
+sub get_status {
+ my ($self, $bugtracker_key, $bug_key) = @_;
+
+ return $self->bugtrackers->{$bugtracker_key}->get_status($bug_key);
+}
+
+sub get_statuses {
+ my ($self, $bugs) = @_;
+
+ my %bugs_by_tracker;
+ foreach my $bug(@$bugs) {
+ my $tracker = $bug->{bugtracker_key};
+ push @{$bugs_by_tracker{$tracker}}, $bug->{bug_key};
+ }
+
+ my $statuses = {};
+ foreach my $tracker(keys %bugs_by_tracker) {
+ $statuses->{$tracker} = $self->bugtrackers->{$tracker}->get_statuses($bugs_by_tracker{$tracker});
+ }
+
+ return $statuses;
+}
+
+1;
--- a/lib/CrashTest/Model/CrashReport.pm Sun Nov 27 17:17:43 2016 +0100
+++ b/lib/CrashTest/Model/CrashReport.pm Sun Dec 04 01:16:39 2016 +0100
@@ -34,6 +34,12 @@
return $self->app->storage->each("CrashReport::update", $uuid, $pjson, $client_info, $dump);
}
+sub get {
+ my ($self, $uuid) = @_;
+
+ return $self->app->storage->first("CrashReport::get", $uuid);
+}
+
sub get_processed_data {
my ($self, $uuid) = @_;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/CrashTest/Plugin/BugLink/Bugzilla.pm Sun Dec 04 01:16:39 2016 +0100
@@ -0,0 +1,76 @@
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package CrashTest::Plugin::BugLink::Bugzilla;
+use Mojo::Base 'Mojolicious::Plugin';
+use Mojo::Util qw/dumper/;
+
+use Mojo::UserAgent;
+
+has [ qw/app config/ ];
+has [ qw/_bz_url _ua/ ];
+
+sub register {
+ my ($self, $app, $args) = @_;
+
+ $self->app($app);
+ $self->config($args->{config});
+
+ $app->log->debug("Registered BugLink::Bugzilla");
+
+}
+
+sub get_statuses {
+ my ($self, $bug_keys) = @_;
+
+ $self->_connect() if(!defined($self->_ua));
+
+ my $res = $self->_ua->get($self->_bz_url->path('/rest/bug'), { Accept => 'application/json' },
+ form => {
+ id => join(",", @$bug_keys),
+ include_fields => 'id,summary,status,resolution',
+ api_key => $self->config->{API}->{api_key},
+ }
+ )->res;
+
+ my $statuses = {};
+ foreach my $bug(@{$res->json->{bugs}}) {
+ my $closed = ($bug->{status} =~ /^(?:RESOLVED|VERIFIED)$/);
+
+ $statuses->{$bug->{id}} = {
+ closed => $closed,
+ status => $bug->{status},
+ resolution => $bug->{resolution},
+ summary => $bug->{summary},
+ };
+ }
+ return $statuses;
+}
+
+sub get_status {
+ my ($self, $bug_key) = @_;
+
+ my $statuses = $self->get_statuses([$bug_key]);
+
+ return $statuses->{$bug_key};
+}
+
+sub _connect {
+ my $self = shift;
+
+ $self->_ua(Mojo::UserAgent->new);
+
+ $self->_bz_url(Mojo::URL->new($self->config->{API}->{URL}));
+}
+
+1;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/CrashTest/Plugin/BugLink/Redmine.pm Sun Dec 04 01:16:39 2016 +0100
@@ -0,0 +1,82 @@
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package CrashTest::Plugin::BugLink::Redmine;
+use Mojo::Base 'Mojolicious::Plugin';
+use Mojo::Util qw/dumper/;
+
+use Mojo::UserAgent;
+
+has [ qw/app config/ ];
+has [ qw/_redmine_url _ua/ ];
+
+sub register {
+ my ($self, $app, $args) = @_;
+
+ $self->app($app);
+ $self->config($args->{config});
+
+ $app->log->debug("Registered BugLink::Redmine");
+
+}
+
+sub get_statuses {
+ my ($self, $bug_keys) = @_;
+
+ my $statuses = {};
+
+ # Seems there is not bulk API in redmine ...
+ # /issues.json does not allow filters on issue #id
+ for my $bug_key(@$bug_keys) {
+ $statuses->{$bug_key} = $self->get_status($bug_key);
+ }
+
+ return $statuses;
+}
+
+sub get_status {
+ my ($self, $bug_key) = @_;
+
+ $self->_connect() if(!defined($self->_ua));
+
+ # Redmine requires the .json extension. We also provide Accept header for consistency
+ my $res = $self->_ua->get($self->_redmine_url->path('/issues/')->path($bug_key . ".json"), { Accept => 'application/json' })->res;
+ my $j = $res->json;
+ my $issue = $j->{issue};
+
+ my $resolution;
+ foreach my $cf(@{$issue->{custom_fields}}) {
+ if($cf->{name} =~ /resolution/i) {
+ $resolution = $cf->{value};
+ }
+ }
+ my $status = $issue->{status}->{name};
+ my $closed = ($status =~ /^(?:Resolved|Closed)$/i);
+
+ return {
+ closed => $closed,
+ status => $status,
+ resolution => $resolution,
+ summary => $issue->{subject},
+ };
+}
+
+sub _connect {
+ my $self = shift;
+
+ $self->_ua(Mojo::UserAgent->new);
+
+ $self->_redmine_url(Mojo::URL->new($self->config->{API}->{URL}));
+}
+
+1;
--- a/lib/CrashTest/Plugin/Storage/Sql/Model/CrashReport.pm Sun Nov 27 17:17:43 2016 +0100
+++ b/lib/CrashTest/Plugin/Storage/Sql/Model/CrashReport.pm Sun Dec 04 01:16:39 2016 +0100
@@ -104,6 +104,33 @@
return ($results, $pager);
}
+sub get {
+ my ($self, $uuid) = @_;
+
+ my @extra_cols;
+ foreach my $extra_col(@{$self->app->config->{WebInterface}->{ExtraColumns}->{Index}}) {
+ push @extra_cols, $extra_col->{db_column} . " AS " . $extra_col->{id};
+ }
+ my $extra_columns = join(",", @extra_cols);
+
+ my $result = $self->db->query("
+ SELECT crash_reports.*,
+ product.distributor AS p_distributor, product.name AS p_name, product.version AS p_version, product.release_channel AS p_release_channel,
+ crash_user.os AS u_os, crash_user.cpu_arch AS u_cpu_arch, crash_user.cpu_count AS u_cpu_count, crash_user.extra_info AS u_extra_info,
+ (SELECT json_agg(to_json(bug_links)) FROM bug_links WHERE bug_links.crash_group_id = crash_reports.crash_group_id) AS bug_links,
+ $extra_columns
+ FROM crash_reports
+ LEFT JOIN crash_users AS crash_user ON crash_reports.crash_user_id = crash_user.id
+ LEFT JOIN products AS product ON crash_reports.product_id = product.id
+ LEFT JOIN crash_groups AS crash_group ON crash_reports.crash_group_id = crash_group.id
+ WHERE crash_reports.uuid = ?
+ ",
+ $uuid
+ )->expand->hash;
+
+ return $result;
+}
+
sub get_processed_data {
my ($self, $uuid) = @_;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/CrashTest/files/templates/report/bugs.html.ep Sun Dec 04 01:16:39 2016 +0100
@@ -0,0 +1,14 @@
+% if(defined($bug_links) && scalar(@$bug_links) > 0) {
+ %= t table => (class => 'table table-hover table-bordered table-condensed bug-links') => begin
+ %= t tr => begin
+ %= t th => "Bug"
+ %= t th => "Status"
+ %= t th => "Resolution"
+ %= t th => "Summary"
+ % end
+ % for my $bug(@$bug_links) {
+ %= bug_status($bug, $bugs_status)
+ % }
+ % end
+% }
+
--- a/lib/CrashTest/files/templates/report/crash.html.ep Sun Nov 27 17:17:43 2016 +0100
+++ b/lib/CrashTest/files/templates/report/crash.html.ep Sun Dec 04 01:16:39 2016 +0100
@@ -11,6 +11,7 @@
% if(defined($crash_group)) {
%= link_to "In crash group \"" . $crash_group->{title} . "\"" => url_for('group', uuid => $crash_group->{uuid})
% }
+ %= include("report/bugs", bug_links => $crash->{bug_links}, bugs_status => $bugs_status);
% end
% end