# HG changeset patch # User Vincent Tondellier # Date 1480810599 -3600 # Node ID 1a0fa98037fa64245f2594bf39a9741b0b8a2c72 # Parent 0a9171619fd33eee3b7aa1c163531bb7aa882262 Add Bug status table in the detailed report. Initial support for Bugzilla and Redmine diff -r 0a9171619fd3 -r 1a0fa98037fa extras/crash_test.conf --- 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 => [ diff -r 0a9171619fd3 -r 1a0fa98037fa lib/CrashTest.pm --- 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; diff -r 0a9171619fd3 -r 1a0fa98037fa lib/CrashTest/Controller/CrashReports.pm --- 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}); diff -r 0a9171619fd3 -r 1a0fa98037fa lib/CrashTest/Helper/BugLinks.pm --- 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; diff -r 0a9171619fd3 -r 1a0fa98037fa lib/CrashTest/Model/BugLink.pm --- /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 . + +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; diff -r 0a9171619fd3 -r 1a0fa98037fa lib/CrashTest/Model/CrashReport.pm --- 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) = @_; diff -r 0a9171619fd3 -r 1a0fa98037fa lib/CrashTest/Plugin/BugLink/Bugzilla.pm --- /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 . + +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; diff -r 0a9171619fd3 -r 1a0fa98037fa lib/CrashTest/Plugin/BugLink/Redmine.pm --- /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 . + +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; diff -r 0a9171619fd3 -r 1a0fa98037fa lib/CrashTest/Plugin/Storage/Sql/Model/CrashReport.pm --- 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) = @_; diff -r 0a9171619fd3 -r 1a0fa98037fa lib/CrashTest/files/templates/report/bugs.html.ep --- /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 +% } + diff -r 0a9171619fd3 -r 1a0fa98037fa lib/CrashTest/files/templates/report/crash.html.ep --- 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