diff options
-rw-r--r-- | extensions/BMO/Extension.pm | 9 | ||||
-rw-r--r-- | extensions/BMO/lib/Reports/Triage.pm | 134 | ||||
-rw-r--r-- | extensions/BMO/template/en/default/hook/reports/menu-end.html.tmpl | 19 | ||||
-rw-r--r-- | extensions/BMO/template/en/default/pages/triage_owners.html.tmpl | 136 | ||||
-rw-r--r-- | extensions/BMO/web/js/triage_owners.js | 54 | ||||
-rw-r--r-- | extensions/BMO/web/styles/triage_reports.css | 22 |
6 files changed, 364 insertions, 10 deletions
diff --git a/extensions/BMO/Extension.pm b/extensions/BMO/Extension.pm index da62861ca..e54a674ba 100644 --- a/extensions/BMO/Extension.pm +++ b/extensions/BMO/Extension.pm @@ -190,9 +190,14 @@ sub page_before_template { require Bugzilla::Extension::BMO::Reports::UserActivity; Bugzilla::Extension::BMO::Reports::UserActivity::report($vars); - } elsif ($page eq 'triage_reports.html') { + } + elsif ($page eq 'triage_reports.html') { + require Bugzilla::Extension::BMO::Reports::Triage; + Bugzilla::Extension::BMO::Reports::Triage::unconfirmed($vars); + } + elsif ($page eq 'triage_owners.html') { require Bugzilla::Extension::BMO::Reports::Triage; - Bugzilla::Extension::BMO::Reports::Triage::report($vars); + Bugzilla::Extension::BMO::Reports::Triage::owners($vars); } elsif ($page eq 'group_admins.html') { require Bugzilla::Extension::BMO::Reports::Groups; diff --git a/extensions/BMO/lib/Reports/Triage.pm b/extensions/BMO/lib/Reports/Triage.pm index e9a987b54..90c77459f 100644 --- a/extensions/BMO/lib/Reports/Triage.pm +++ b/extensions/BMO/lib/Reports/Triage.pm @@ -16,13 +16,24 @@ use Bugzilla::Constants; use Bugzilla::Error; use Bugzilla::Product; use Bugzilla::User; -use Bugzilla::Util qw(detaint_natural); +use Bugzilla::Util qw(detaint_natural trim); use Date::Parse; +use JSON::XS; +use List::MoreUtils qw(any); + # set an upper limit on the *unfiltered* number of bugs to process use constant MAX_NUMBER_BUGS => 4000; -sub report { +use constant DEFAULT_OWNER_PRODUCTS => ( + 'Core', + 'Firefox', + 'Firefox for Android', + 'Firefox for iOS', + 'Toolkit', +); + +sub unconfirmed { my ($vars, $filter) = @_; my $dbh = Bugzilla->dbh; my $input = Bugzilla->input_params; @@ -217,4 +228,123 @@ sub report { $vars->{'input'} = $input; } +sub owners { + my ($vars, $filter) = @_; + my $dbh = Bugzilla->dbh; + my $input = Bugzilla->input_params; + my $user = Bugzilla->user; + + Bugzilla::User::match_field({ 'owner' => {'type' => 'multi'} }); + + my @products; + if (!$input->{product} && $input->{owner}) { + @products = @{ $user->get_selectable_products }; + } + else { + my @product_names = $input->{product} ? ($input->{product}) : DEFAULT_OWNER_PRODUCTS; + foreach my $name (@product_names) { + push(@products, Bugzilla::Product->check({ name => $name })); + } + } + + my @component_ids; + if (@products == 1 && $input->{'component'}) { + my $ra_components = ref($input->{'component'}) + ? $input->{'component'} + : [ $input->{'component'} ]; + foreach my $component_name (@$ra_components) { + my $component = Bugzilla::Component->check({ name => $component_name, product => $products[0] }); + push @component_ids, $component->id; + } + } + + my @owner_names = split(/[,;]+/, $input->{owner}) if $input->{owner}; + my @owner_ids; + foreach my $name (@owner_names) { + $name = trim($name); + next unless $name; + push(@owner_ids, login_to_id($name, THROW_ERROR)); + } + + my $sql = "SELECT products.name, components.name, components.id, components.triage_owner_id + FROM components JOIN products ON components.product_id = products.id + WHERE products.id IN (" . join(',', map { $_->id } @products) . ")"; + if (@component_ids) { + $sql .= " AND components.id IN (" . join(',', @component_ids) . ")"; + } + if (@owner_ids) { + $sql .= " AND components.triage_owner_id IN (" . join(',', @owner_ids) . ")"; + } + $sql .= " ORDER BY products.name, components.name"; + + my $rows = $dbh->selectall_arrayref($sql); + + my $bug_count_sth = $dbh->prepare(" + SELECT COUNT(bugs.bug_id) + FROM bugs INNER JOIN components AS map_component ON bugs.component_id = map_component.id + INNER JOIN bug_status AS map_bug_status ON bugs.bug_status = map_bug_status.value + INNER JOIN priority AS map_priority ON bugs.priority = map_priority.value + WHERE bugs.resolution IN ('') + AND bugs.priority IN ('--') + AND bugs.creation_ts >= '2016-06-01' + AND (NOT( EXISTS ( + SELECT 1 + FROM bugs bugs_1 + LEFT JOIN attachments AS attachments_1 ON bugs_1.bug_id = attachments_1.bug_id + LEFT JOIN flags AS flags_1 ON bugs_1.bug_id = flags_1.bug_id AND (flags_1.attach_id = attachments_1.attach_id OR flags_1.attach_id IS NULL) + LEFT JOIN flagtypes AS flagtypes_1 ON flags_1.type_id = flagtypes_1.id + WHERE bugs_1.bug_id = bugs.bug_id AND CONCAT(flagtypes_1.name, flags_1.status) = 'needinfo?'))) + AND bugs.component_id = ?"); + + my @results; + foreach my $row (@$rows) { + my ($product_name, $component_name, $component_id, $triage_owner_id) = @$row; + my $triage_owner = $triage_owner_id + ? Bugzilla::User->new({ id => $triage_owner_id, cache => 1 }) + : ""; + my $data = { + product => $product_name, + component => $component_name, + owner => $triage_owner, + }; + $data->{buglist_url} = 'priority=--&resolution=---&f1=creation_ts&o1=greaterthaneq&v1=2016-06-01'. + '&f2=flagtypes.name&o2=notequals&v2=needinfo%3F'; + if ($triage_owner) { + $data->{buglist_url} .= '&f3=triage_owner&o3=equals&v3=' . $triage_owner->login; + } + $bug_count_sth->execute($component_id); + ($data->{bug_count}) = $bug_count_sth->fetchrow_array(); + push @results, $data; + } + $vars->{results} = \@results; + + my $json_data = { products => [] }; + foreach my $product (@{ $user->get_selectable_products }) { + my $prod_data = { + name => $product->name, + components => [], + }; + foreach my $component (@{ $product->components }) { + my $selected = 0; + if ($input->{product} + && $input->{product} eq $product->name + && $input->{component}) + { + $selected = 1 if (ref $input->{component} && any { $_ eq $component->name } @{ $input->{component} }); + $selected = 1 if (!ref $input->{componet} && $input->{component} eq $component->name); + } + my $comp_data = { + name => $component->name, + selected => $selected + }; + push(@{ $prod_data->{components} }, $comp_data); + } + push(@{ $json_data->{products} }, $prod_data); + } + + $vars->{product} = $input->{product}; + $vars->{owner} = $input->{owner}; + $vars->{json_data} = encode_json($json_data); +} + 1; diff --git a/extensions/BMO/template/en/default/hook/reports/menu-end.html.tmpl b/extensions/BMO/template/en/default/hook/reports/menu-end.html.tmpl index 6d03e9284..825263350 100644 --- a/extensions/BMO/template/en/default/hook/reports/menu-end.html.tmpl +++ b/extensions/BMO/template/en/default/hook/reports/menu-end.html.tmpl @@ -6,6 +6,20 @@ # defined by the Mozilla Public License, v. 2.0. #%] +<h2>Triage Reports</h2> +<ul> + <li> + <strong> + <a href="[% urlbase FILTER none %]page.cgi?id=triage_reports.html">Unconfirmed Report</a> + </strong> - Report on UNCONFIRMED [% terms.bugs %] to assist triage. + </li> + <li> + <strong> + <a href="[% urlbase FILTER none %]page.cgi?id=triage_owners.html">Triage Owners</a> + </strong> - Report on triage owners per product and component. + </li> +</ul> + <h2>Other Reports</h2> <ul> @@ -16,11 +30,6 @@ </li> <li> <strong> - <a href="[% urlbase FILTER none %]page.cgi?id=triage_reports.html">Unconfirmed Report</a> - </strong> - Report on UNCONFIRMED [% terms.bugs %] to assist triage. - </li> - <li> - <strong> <a href="[% urlbase FILTER none %]page.cgi?id=release_tracking_report.html">Release Tracking Report</a> </strong> - For triaging release-train flag information. </li> diff --git a/extensions/BMO/template/en/default/pages/triage_owners.html.tmpl b/extensions/BMO/template/en/default/pages/triage_owners.html.tmpl new file mode 100644 index 000000000..3663c2925 --- /dev/null +++ b/extensions/BMO/template/en/default/pages/triage_owners.html.tmpl @@ -0,0 +1,136 @@ +[%# 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 BMO Extension + # + # The Initial Developer of the Original Code is the Mozilla Foundation + # Portions created by the Initial Developers are Copyright (C) 2011 the + # Initial Developer. All Rights Reserved. + # + # Contributor(s): + # Byron Jones <bjones@mozilla.com> + #%] + +[% PROCESS global/variables.none.tmpl %] + +[% INCLUDE global/header.html.tmpl + title = "Triage Owners" + javascript_urls = [ "js/util.js", "js/field.js", "js/productform.js", + "extensions/BMO/web/js/triage_owners.js" ] + style_urls = [ "skins/standard/buglist.css", + "extensions/BMO/web/styles/triage_reports.css" ] + generate_api_token = 1 +%] + +<noscript> +<h2>Javascript is required to use this report.</h2> +</noscript> + +[% PROCESS "global/field-descs.none.tmpl" %] + +<form id="triageOwners" name="triageOwners" action="page.cgi" method="GET"> + <input type="hidden" name="id" value="triage_owners.html"> + <input type="hidden" name="json_data" id="json_data" data-json_data="[% json_data FILTER html %]"> + + <h3>Show Triage Owners</h3> + + <table id="triage_owners_form"> + <tr> + <th>Product:</th> + <td> + <select name="product" id="product"> + <option value="">__Any__</option> + [% FOREACH p = user.get_selectable_products %] + <option value="[% p.name FILTER html %]" + [% " selected" IF product == p.name %]> + [% p.name FILTER html %] + </option> + [% END %] + </select> + </td> + </tr> + <tr> + <th>Component:</th> + <td> + <select name="component" id="component" multiple size="5"></select> + </td> + </tr> + <tr> + <th>Owner:</th> + <td> + [% INCLUDE global/userselect.html.tmpl + id = "owner" + name = "owner" + value = owner + size = 40 + classes = ["bz_userfield"] + multiple = 5 + %] + </td> + </tr> + <tr> + <td> </td> + <td> + <input type="submit" value="Generate Report"> + </td> + </tr> + </table> +</form> + +[% IF NOT product %] + <p>Displaying components from default product list.</p> +[% END %] + +[% IF results.size > 0 %] + <p> + <small>Each triage owner links to a buglist of all open [% terms.bugs %], since 2016-06-01, without a pending needinfo, where the priority is '--'.</small> + </p> + [% current_product = "" %] + <table border="0" cellspacing="0" id="report" width="100%"> + </tr> + [% FOREACH r = results %] + [% count = loop.count() %] + [% IF current_product != r.product %] + [% current_product = r.product %] + <tr class="product_header"> + <th colspan="3">[% r.product FILTER html %]</th> + </tr> + [% END %] + <tr class="bz_bugitem [% count % 2 == 1 ? "bz_row_odd" : "bz_row_even" %]"> + <td> + [% r.component FILTER html %] + </td> + <td> + [% IF r.owner.id %] + [% INCLUDE global/user.html.tmpl who = r.owner %] + [% ELSE %] + <em>None</em> + [% END %] + </td> + <td> + [% IF r.buglist_url %] + <a href="[% urlbase FILTER none %]buglist.cgi?product=[% r.product FILTER uri %]&component=[% r.component FILTER uri %]&[% r.buglist_url FILTER none %]"> + [% r.bug_count FILTER html +%] [%+ terms.bugs %] found. + </a> + [% ELSE %] + None + [% END %] + </td> + </tr> + [% END %] + </table> + <p> + Found [% results.size %] component[% 's' IF results.size != 1 %]: + </p> +[% ELSE %] + <p>No components found.</p> +[% END %] + +[% INCLUDE global/footer.html.tmpl %] diff --git a/extensions/BMO/web/js/triage_owners.js b/extensions/BMO/web/js/triage_owners.js new file mode 100644 index 000000000..212225586 --- /dev/null +++ b/extensions/BMO/web/js/triage_owners.js @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This Source Code Form is "Incompatible With Secondary Licenses", as + * defined by the Mozilla Public License, v. 2.0. */ + +$(function() { + 'use strict'; + + var json_data = {}; + + function onSelectProduct() { + var component = $('#component'); + var product = $('#product'); + + if (product.val() == '') { + component.empty(); + return; + } + + if (!json_data) return; + + component.empty(); + component.append(new Option('__Any__', '')); + + var products = json_data.products; + for (var i = 0, l = products.length; i < l; i++) { + if (products[i].name != product.val()) continue; + var components = products[i].components; + for (var j = 0, k = components.length; j < k; j++) { + var selected = !!components[j].selected; + component.append(new Option(components[j].name, + components[j].name, + selected, selected)); + } + } + } + + $('#product').change(function() { + onSelectProduct(); + }); + + $('#triageOwners').submit(function() { + // do not pass json_data in the params + $('#json_data').remove(); + return true; + }); + + $(document).ready(function () { + json_data = $('#json_data').data('json_data'); + onSelectProduct(); + }); +}); diff --git a/extensions/BMO/web/styles/triage_reports.css b/extensions/BMO/web/styles/triage_reports.css index 6190fd32c..8eb2c6e87 100644 --- a/extensions/BMO/web/styles/triage_reports.css +++ b/extensions/BMO/web/styles/triage_reports.css @@ -1,3 +1,10 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This Source Code Form is "Incompatible With Secondary Licenses", as + * defined by the Mozilla Public License, v. 2.0. */ + .hidden { display: none; } @@ -14,10 +21,23 @@ background: #ccccff; } -#report td { +#report th, #report td { padding: 1px 10px 1px 10px; } #report-header { background: #dddddd; } + +tr.product_header { + background: #dddddd; +} + +#triage_owners_form th { + text-align: right; + vertical-align: top; +} + +#report th, #report td { + text-align: left; +} |