summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--extensions/BMO/Extension.pm6
-rw-r--r--extensions/BMO/lib/Reports.pm360
-rw-r--r--extensions/BMO/template/en/default/hook/global/user-error-errors.html.tmpl16
-rw-r--r--extensions/BMO/template/en/default/hook/reports/menu-end.html.tmpl5
-rw-r--r--extensions/BMO/template/en/default/pages/release_tracking_report.html.tmpl98
-rw-r--r--extensions/BMO/web/js/release_tracking_report.js206
-rw-r--r--extensions/BMO/web/styles/reports.css4
7 files changed, 667 insertions, 28 deletions
diff --git a/extensions/BMO/Extension.pm b/extensions/BMO/Extension.pm
index 3cf0e8317..265d09a29 100644
--- a/extensions/BMO/Extension.pm
+++ b/extensions/BMO/Extension.pm
@@ -57,7 +57,8 @@ use Bugzilla::Extension::BMO::Data qw($cf_visible_in_products
use Bugzilla::Extension::BMO::Reports qw(user_activity_report
triage_reports
group_admins
- email_queue_report);
+ email_queue_report
+ release_tracking_report);
our $VERSION = '0.1';
@@ -173,6 +174,9 @@ sub page_before_template {
elsif ($page eq 'email_queue.html') {
email_queue_report($vars);
}
+ elsif ($page eq 'release_tracking_report.html') {
+ release_tracking_report($vars);
+ }
}
sub _get_field_values_sort_key {
diff --git a/extensions/BMO/lib/Reports.pm b/extensions/BMO/lib/Reports.pm
index e9e2670b9..79d04afb2 100644
--- a/extensions/BMO/lib/Reports.pm
+++ b/extensions/BMO/lib/Reports.pm
@@ -20,20 +20,26 @@
package Bugzilla::Extension::BMO::Reports;
use strict;
-use Bugzilla::User;
-use Bugzilla::Util qw(trim detaint_natural);
-use Bugzilla::Error;
+use Bugzilla::Extension::BMO::Data qw($cf_disabled_flags);
+
use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Field;
+use Bugzilla::User;
+use Bugzilla::Util qw(trim detaint_natural trick_taint correct_urlbase);
use Date::Parse;
use DateTime;
+use JSON qw(-convert_blessed_universally);
+use List::MoreUtils qw(uniq);
use base qw(Exporter);
our @EXPORT_OK = qw(user_activity_report
triage_reports
group_admins
- email_queue_report);
+ email_queue_report
+ release_tracking_report);
sub user_activity_report {
my ($vars) = @_;
@@ -59,20 +65,10 @@ sub user_activity_report {
}
Bugzilla::User::match_field({ 'who' => {'type' => 'multi'} });
- ThrowUserError('user_activity_missing_from_date') unless $from;
- my $from_time = _parse_date($from)
- or ThrowUserError('user_activity_invalid_date', { date => $from });
- my $from_dt = DateTime->from_epoch(epoch => $from_time)
- ->set_time_zone('local')
- ->truncate(to => 'day');
+ my $from_dt = _string_to_datetime($from);
$from = $from_dt->ymd();
- ThrowUserError('user_activity_missing_to_date') unless $to;
- my $to_time = _parse_date($to)
- or ThrowUserError('user_activity_invalid_date', { date => $to });
- my $to_dt = DateTime->from_epoch(epoch => $to_time)
- ->set_time_zone('local')
- ->truncate(to => 'day');
+ my $to_dt = _string_to_datetime($to);
$to = $to_dt->ymd();
# add one day to include all activity that happened on the 'to' date
$to_dt->add(days => 1);
@@ -298,6 +294,20 @@ sub user_activity_report {
$vars->{'to'} = $to;
}
+sub _string_to_datetime {
+ my $input = shift;
+ my $time = _parse_date($input)
+ or ThrowUserError('report_invalid_date', { date => $input });
+ return _time_to_datetime($time);
+}
+
+sub _time_to_datetime {
+ my $time = shift;
+ return DateTime->from_epoch(epoch => $time)
+ ->set_time_zone('local')
+ ->truncate(to => 'day');
+}
+
sub _parse_date {
my ($str) = @_;
if ($str =~ /^(-|\+)?(\d+)([hHdDwWmMyY])$/) {
@@ -525,7 +535,7 @@ sub triage_reports {
}
sub group_admins {
- my ($vars, $filter) = @_;
+ my ($vars) = @_;
my $dbh = Bugzilla->dbh;
my $user = Bugzilla->user;
@@ -587,4 +597,320 @@ sub email_queue_report {
$vars->{'now'} = (time);
}
+sub release_tracking_report {
+ my ($vars) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $input = Bugzilla->input_params;
+ my $user = Bugzilla->user;
+
+ my @flag_names = qw(
+ approval-mozilla-release
+ approval-mozilla-beta
+ approval-mozilla-aurora
+ approval-comm-release
+ approval-comm-beta
+ approval-comm-aurora
+ );
+
+ my @flags_json;
+ my @fields_json;
+ my @products_json;
+
+ #
+ # tracking flags
+ #
+
+ my $all_products = $user->get_selectable_products;
+ my @usable_products;
+
+ # build list of flags and their matching products
+
+ foreach my $flag_name (@flag_names) {
+ # grab all matching flag_types
+ my @flag_types = @{Bugzilla::FlagType::match({ name => $flag_name, is_active => 1 })};
+
+ # we need a list of products, based on inclusions/exclusions
+ my @products;
+ my %flag_types;
+ foreach my $flag_type (@flag_types) {
+ $flag_types{$flag_type->name} = $flag_type->id;
+ my $has_all = 0;
+ my @exclusion_ids;
+ my @inclusion_ids;
+ foreach my $flag_type (@flag_types) {
+ if (scalar keys %{$flag_type->inclusions}) {
+ my $inclusions = $flag_type->inclusions;
+ foreach my $key (keys %$inclusions) {
+ push @inclusion_ids, ($inclusions->{$key} =~ /^(\d+)/);
+ }
+ } elsif (scalar keys %{$flag_type->exclusions}) {
+ my $exclusions = $flag_type->exclusions;
+ foreach my $key (keys %$exclusions) {
+ push @exclusion_ids, ($exclusions->{$key} =~ /^(\d+)/);
+ }
+ } else {
+ $has_all = 1;
+ last;
+ }
+ }
+
+ if ($has_all) {
+ push @products, @$all_products;
+ } elsif (scalar @exclusion_ids) {
+ push @products, @$all_products;
+ foreach my $exclude_id (uniq @exclusion_ids) {
+ @products = grep { $_->id != $exclude_id } @products;
+ }
+ } else {
+ foreach my $include_id (uniq @inclusion_ids) {
+ push @products, grep { $_->id == $include_id } @$all_products;
+ }
+ }
+ }
+ @products = uniq @products;
+ push @usable_products, @products;
+ my @product_ids = map { $_->id } sort { lc($a->name) cmp lc($b->name) } @products;
+
+ push @flags_json, {
+ name => $flag_name,
+ id => $flag_types{$flag_name} || 0,
+ products => \@product_ids,
+ fields => [],
+ };
+ }
+ @usable_products = uniq @usable_products;
+
+ # build a list of tracking flags for each product
+ # also build the list of all fields
+
+ my @unlink_products;
+ foreach my $product (@usable_products) {
+ my @fields =
+ grep { _is_active_status_field($_->name) }
+ Bugzilla->active_custom_fields({ product => $product });
+ my @field_ids = map { $_->id } @fields;
+ if (!scalar @fields) {
+ push @unlink_products, $product;
+ next;
+ }
+
+ # product
+ push @products_json, {
+ name => $product->name,
+ id => $product->id,
+ fields => \@field_ids,
+ };
+
+ # add fields to flags
+ foreach my $rh (@flags_json) {
+ if (grep { $_ eq $product->id } @{$rh->{products}}) {
+ push @{$rh->{fields}}, @field_ids;
+ }
+ }
+
+ # add fields to fields_json
+ foreach my $field (@fields) {
+ my $existing = 0;
+ foreach my $rh (@fields_json) {
+ if ($rh->{id} == $field->id) {
+ $existing = 1;
+ last;
+ }
+ }
+ if (!$existing) {
+ push @fields_json, {
+ name => $field->name,
+ id => $field->id,
+ };
+ }
+ }
+ }
+ foreach my $rh (@flags_json) {
+ my @fields = uniq @{$rh->{fields}};
+ $rh->{fields} = \@fields;
+ }
+
+ # remove products which aren't linked with status fields
+
+ foreach my $rh (@flags_json) {
+ my @product_ids;
+ foreach my $id (@{$rh->{products}}) {
+ unless (grep { $_->id == $id } @unlink_products) {
+ push @product_ids, $id;
+ }
+ $rh->{products} = \@product_ids;
+ }
+ }
+
+ #
+ # rapid release dates
+ #
+
+ my @ranges;
+ my $start_date = _string_to_datetime('2011-08-16');
+ my $end_date = $start_date->clone->add(weeks => 6)->add(days => -1);
+ my $now_date = _time_to_datetime((time));
+
+ while ($start_date <= $now_date) {
+ unshift @ranges, {
+ value => sprintf("%s-%s", $start_date->ymd(''), $end_date->ymd('')),
+ label => sprintf("%s and %s", $start_date->ymd('-'), $end_date->ymd('-')),
+ };
+
+ $start_date = $end_date->clone;;
+ $start_date->add(days => 1);
+ $end_date->add(weeks => 6);
+ }
+
+ #
+ # run report
+ #
+
+ if ($input->{q}) {
+ my $q = _parse_query($input->{q});
+
+ my @where;
+ my @params;
+ my $query = "
+ SELECT a.bug_id
+ FROM bugs_activity a
+ INNER JOIN flags f ON f.bug_id = a.bug_id
+ INNER JOIN bugs b ON b.bug_id = a.bug_id
+ WHERE ";
+
+ push @where, "(a.fieldid = ?)";
+ push @params, $q->{field_id};
+
+ push @where, "(a.bug_when >= ?)";
+ push @params, $q->{start_date} . ' 00:00:00';
+ push @where, "(a.bug_when < ?)";
+ push @params, $q->{end_date} . ' 00:00:00';
+
+ push @where, "(a.added LIKE ?)";
+ push @params, '%' . $q->{flag_name} . '?%';
+
+ push @where, "(f.type_id IN (SELECT id FROM flagtypes WHERE name = ?))";
+ push @params, $q->{flag_name};
+
+ push @where, "(f.status = ?)";
+ push @params, $q->{flag_status};
+
+ if ($q->{product_id}) {
+ push @where, "(b.product_id = ?)";
+ push @params, $q->{product_id};
+ }
+
+ if (scalar @{$q->{fields}}) {
+ my @fields;
+ foreach my $field (@{$q->{fields}}) {
+ my $op = $field->{value} eq '+' ? '=' : '<>';
+ push @fields, "(b.".$field->{name}." $op 'fixed') ";
+ }
+ my $join = uc $q->{join};
+ push @where, '(' . join(" $join ", @fields) . ')';
+ }
+
+ $query .= join("\nAND ", @where);
+
+ my $bugs = $dbh->selectcol_arrayref($query, undef, @params);
+ push @$bugs, 0 unless @$bugs;
+
+ my $urlbase = correct_urlbase();
+ my $cgi = Bugzilla->cgi;
+ print $cgi->redirect(
+ -url => "${urlbase}buglist.cgi?bug_id=" . join(',', @$bugs)
+ );
+ exit;
+ }
+
+ #
+ # set template vars
+ #
+
+ my $json = JSON->new();
+ if (0) {
+ # debugging
+ $json->shrink(0);
+ $json->canonical(1);
+ $vars->{flags_json} = $json->pretty->encode(\@flags_json);
+ $vars->{products_json} = $json->pretty->encode(\@products_json);
+ $vars->{fields_json} = $json->pretty->encode(\@fields_json);
+ } else {
+ $json->shrink(1);
+ $vars->{flags_json} = $json->encode(\@flags_json);
+ $vars->{products_json} = $json->encode(\@products_json);
+ $vars->{fields_json} = $json->encode(\@fields_json);
+ }
+
+ $vars->{flag_names} = \@flag_names;
+ $vars->{ranges} = \@ranges;
+ $vars->{default_query} = $input->{q};
+ foreach my $field (qw(product flags range)) {
+ $vars->{$field} = $input->{$field};
+ }
+}
+
+sub _parse_query {
+ my $q = shift;
+ my @query = split(/:/, $q);
+ my $query;
+
+ # field_id for flag changes
+ $query->{field_id} = get_field_id('flagtypes.name');
+
+ # flag_name
+ my $flag_name = shift @query;
+ @{Bugzilla::FlagType::match({ name => $flag_name, is_active => 1 })}
+ or ThrowUserError('report_invalid_parameter', { name => 'flag_name' });
+ trick_taint($flag_name);
+ $query->{flag_name} = $flag_name;
+
+ # flag_status
+ my $flag_status = shift @query;
+ $flag_status =~ /^([\?\-\+])$/
+ or ThrowUserError('report_invalid_parameter', { name => 'flag_status' });
+ $query->{flag_status} = $1;
+
+ # date_range -> from_ymd to_ymd
+ my $date_range = shift @query;
+ $date_range =~ /^(\d\d\d\d)(\d\d)(\d\d)-(\d\d\d\d)(\d\d)(\d\d)$/
+ or ThrowUserError('report_invalid_parameter', { name => 'date_range' });
+ $query->{start_date} = "$1-$2-$3";
+ $query->{end_date} = "$4-$5-$6";
+
+ # product_id
+ my $product_id = shift @query;
+ $product_id =~ /^(\d+)$/
+ or ThrowUserError('report_invalid_parameter', { name => 'product_id' });
+ $query->{product_id} = $1;
+
+ # join
+ my $join = shift @query;
+ $join =~ /^(and|or)$/
+ or ThrowUserError('report_invalid_parameter', { name => 'join' });
+ $query->{join} = $1;
+
+ # fields
+ my @fields;
+ foreach my $field (@query) {
+ $field =~ /^(\d+)([\-\+])$/
+ or ThrowUserError('report_invalid_parameter', { name => 'fields' });
+ my ($id, $value) = ($1, $2);
+ my $field_obj = Bugzilla::Field->new($id)
+ or ThrowUserError('report_invalid_parameter', { name => 'field_id' });
+ push @fields, { id => $id, value => $value, name => $field_obj->name };
+ }
+ $query->{fields} = \@fields;
+
+ return $query;
+}
+
+sub _is_active_status_field {
+ my ($field_name) = @_;
+ if ($field_name =~ /^cf_status/) {
+ return !grep { $field_name eq $_ } @$cf_disabled_flags
+ }
+ return 0;
+}
+
1;
diff --git a/extensions/BMO/template/en/default/hook/global/user-error-errors.html.tmpl b/extensions/BMO/template/en/default/hook/global/user-error-errors.html.tmpl
index 85881aca7..eff0e35cc 100644
--- a/extensions/BMO/template/en/default/hook/global/user-error-errors.html.tmpl
+++ b/extensions/BMO/template/en/default/hook/global/user-error-errors.html.tmpl
@@ -22,19 +22,15 @@
[% title = "Missing Username" %]
You must provide at least one email address to report on.
-[% ELSIF error == "user_activity_missing_from_date" %]
- [% title = "Missing Date" %]
- You must provided the period start date.
-
-[% ELSIF error == "user_activity_missing_to_date" %]
- [% title = "Missing Date" %]
- You must provided the period end date.
-
-[% ELSIF error == "user_activity_invalid_date" %]
+[% ELSIF error == "report_invalid_date" %]
[% title = "Invalid Date" %]
The date '[% date FILTER html %]' is invalid.
+[% ELSIF error == "report_invalid_parameter" %]
+ [% title = "Invalid Parameter" %]
+ The value for parameter [% name FILTER html %] is invalid.
+
[% ELSIF error == "invalid_object" %]
- Invalid [% object FILTER html %]: "[% value FILTER html %]"
+ Invalid [% object FILTER html %]: "[% value FILTER html %]"
[% END %]
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 846f12767..7c2b2d753 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
@@ -31,6 +31,11 @@
%]page.cgi?id=triage_reports.html">Triage 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>
[% IF user.in_group('editusers') %]
<li>
<strong><a href="[% urlbase FILTER none
diff --git a/extensions/BMO/template/en/default/pages/release_tracking_report.html.tmpl b/extensions/BMO/template/en/default/pages/release_tracking_report.html.tmpl
new file mode 100644
index 000000000..5e64c6b5e
--- /dev/null
+++ b/extensions/BMO/template/en/default/pages/release_tracking_report.html.tmpl
@@ -0,0 +1,98 @@
+[%# 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.
+ #%]
+
+[% INCLUDE global/header.html.tmpl
+ title = "Release Tracking Report"
+ style_urls = [ "extensions/BMO/web/styles/reports.css" ]
+ javascript_urls = [ "extensions/BMO/web/js/release_tracking_report.js" ]
+%]
+
+<noscript>
+<h1>JavaScript is required to use this report.</h1>
+</noscript>
+
+<script>
+var flags_data = [% flags_json FILTER none %];
+var products_data = [% products_json FILTER none %];
+var fields_data = [% fields_json FILTER none %];
+var default_query = '[% default_query FILTER js %]';
+</script>
+
+<form action="page.cgi" method="get" onSubmit="return onFormSubmit()">
+<input type="hidden" name="id" value="release_tracking_report.html">
+<input type="hidden" name="q" id="q" value="">
+<table>
+
+<tr>
+ <th>Approval:</th>
+ <td>
+ Show bugs where
+ <select id="flag" onChange="onFlagChange()">
+ [% FOREACH flag_name = flag_names %]
+ <option value="[% flag_name FILTER html %]">[% flag_name FILTER html %]</option>
+ [% END %]
+ </select>
+
+ was changed to (and is currently)
+ <select id="flag_value">
+ <option value="?">?</option>
+ <option value="-">-</option>
+ <option value="+">+</option>
+ </select>
+
+ between
+ <select id="range" onChange="serialiseForm()">
+ [% FOREACH range = ranges %]
+ <option value="[% range.value FILTER html %]">
+ [% range.label FILTER html %]
+ </option>
+ [% END %]
+ </select>
+ </td>
+</tr>
+
+<tr>
+ <th>Status:</th>
+ <td>
+ for the product
+ <select id="product" onChange="onProductChange()">
+ </select>
+ </td>
+</tr>
+
+<tr>
+ <td>&nbsp;</td>
+ <td>
+ <select id="op" onChange="serialiseForm()">
+ <option value="and">All selected tracking fields (AND)</option>
+ <option value="or">Any selected tracking fields (OR)</option>
+ </select>
+ [
+ <a href="javascript:void(0)" onClick="selectAllFields()">All</a> |
+ <a href="javascript:void(0)" onClick="selectNoFields()">None</a>
+ ]
+ [
+ <a href="javascript:void(0)" onClick="invertFields()">Invert</a>
+ ]
+ <br>
+ <span id="tracking_span">
+ </span>
+ </td>
+</tr>
+
+<tr>
+ <td>&nbsp;</td>
+ <td colspan="2">
+ <input type="submit" value="Search">
+ <input type="submit" value="Reset" onClick="onFormReset(); return false">
+ </td>
+</tr>
+</table>
+</form>
+
+[% INCLUDE global/footer.html.tmpl %]
diff --git a/extensions/BMO/web/js/release_tracking_report.js b/extensions/BMO/web/js/release_tracking_report.js
new file mode 100644
index 000000000..69bfab000
--- /dev/null
+++ b/extensions/BMO/web/js/release_tracking_report.js
@@ -0,0 +1,206 @@
+/* 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. */
+
+var Dom = YAHOO.util.Dom;
+var flagEl;
+var productEl;
+var trackingEl;
+var selectedFields;
+
+// events
+
+function onFieldToggle(cbEl, id) {
+ if (cbEl.checked) {
+ Dom.removeClass('field_' + id + '_td', 'disabled');
+ selectedFields['field_' + id] = id;
+ } else {
+ Dom.addClass('field_' + id + '_td', 'disabled');
+ selectedFields['field_' + id] = false;
+ }
+ Dom.get('field_' + id + '_select').disabled = !cbEl.checked;
+ serialiseForm();
+}
+
+function onProductChange() {
+ var product = productEl.value;
+ var productData = product == '0' ? getFlagByName(flagEl.value) : getProductById(product);
+ var html = '';
+ selectedFields = new Array();
+
+ if (productData) {
+ // update status fields
+ html = '<table>';
+ for(var i = 0, l = productData.fields.length; i < l; i++) {
+ var field = getFieldById(productData.fields[i]);
+ selectedFields['field_' + field.id] = false;
+ html += '<tr>' +
+ '<td>' +
+ '<input type="checkbox" id="field_' + field.id + '_cb" ' +
+ 'onClick="onFieldToggle(this,' + field.id + ')">' +
+ '</td>' +
+ '<td class="disabled" id="field_' + field.id + '_td">' +
+ '<label for="field_' + field.id + '_cb">' +
+ YAHOO.lang.escapeHTML(field.name) + ':</label>' +
+ '</td>' +
+ '<td>' +
+ '<select disabled id="field_' + field.id + '_select">' +
+ '<option value="+">fixed</option>' +
+ '<option value="-">not fixed</option>' +
+ '</select>' +
+ '</td>' +
+ '</tr>';
+ }
+ html += '</table>';
+ }
+ trackingEl.innerHTML = html;
+ serialiseForm();
+}
+
+function onFlagChange() {
+ var flag = flagEl.value;
+ var flagData = getFlagByName(flag);
+ productEl.options.length = 0;
+
+ if (flagData) {
+ // update product select
+ var currentProduct = productEl.value;
+ productEl.options[0] = new Option('(Any Product)', '0');
+ for(var i = 0, l = flagData.products.length; i < l; i++) {
+ var product = getProductById(flagData.products[i]);
+ var n = productEl.length;
+ productEl.options[n] = new Option(product.name, product.id);
+ productEl.options[n].selected = product.id == currentProduct;
+ }
+ }
+ onProductChange();
+}
+
+// form
+
+function selectAllFields() {
+ for(var i = 0, l = fields_data.length; i < l; i++) {
+ var cb = Dom.get('field_' + fields_data[i].id + '_cb');
+ cb.checked = true;
+ onFieldToggle(cb, fields_data[i].id);
+ }
+ serialiseForm();
+}
+
+function selectNoFields() {
+ for(var i = 0, l = fields_data.length; i < l; i++) {
+ var cb = Dom.get('field_' + fields_data[i].id + '_cb');
+ cb.checked = false;
+ onFieldToggle(cb, fields_data[i].id);
+ }
+ serialiseForm();
+}
+
+function invertFields() {
+ for(var i = 0, l = fields_data.length; i < l; i++) {
+ var el = Dom.get('field_' + fields_data[i].id + '_select');
+ if (el.value == '+') {
+ el.options[1].selected = true;
+ } else {
+ el.options[0].selected = true;
+ }
+ }
+ serialiseForm();
+}
+
+function onFormSubmit() {
+ serialiseForm();
+ return true;
+}
+
+function onFormReset() {
+ deserialiseForm('');
+}
+
+function serialiseForm() {
+ var q = flagEl.value + ':' +
+ Dom.get('flag_value').value + ':' +
+ Dom.get('range').value + ':' +
+ productEl.value + ':' +
+ Dom.get('op').value + ':';
+
+ for(var id in selectedFields) {
+ if (selectedFields[id]) {
+ q += selectedFields[id] + Dom.get(id + '_select').value + ':';
+ }
+ }
+
+ Dom.get('q').value = q;
+
+ if(history && history.replaceState ) {
+ history.replaceState(null, '',
+ 'page.cgi?id=release_tracking_report.html&q=' + encodeURIComponent(q));
+ }
+}
+
+function deserialiseForm(q) {
+ var parts = q.split(/:/);
+ selectValue(flagEl, parts[0]);
+ onFlagChange();
+ selectValue(Dom.get('flag_value'), parts[1]);
+ selectValue(Dom.get('range'), parts[2]);
+ selectValue(productEl, parts[3]);
+ onProductChange();
+ selectValue(Dom.get('op'), parts[4]);
+ for(var i = 5, l = parts.length; i < l; i++) {
+ var part = parts[i];
+ if (part.length) {
+ var value = part.substr(part.length - 1, 1);
+ var id = part.substr(0, part.length - 1);
+ var cb = Dom.get('field_' + id + '_cb');
+ cb.checked = true;
+ onFieldToggle(cb, id);
+ selectValue(Dom.get('field_' + id + '_select'), value);
+ }
+ }
+ serialiseForm();
+}
+
+// utils
+
+YAHOO.util.Event.onDOMReady(function() {
+ flagEl = Dom.get('flag');
+ productEl = Dom.get('product');
+ trackingEl = Dom.get('tracking_span');
+ onFlagChange();
+ deserialiseForm(default_query);
+});
+
+function getFlagByName(name) {
+ for(var i = 0, l = flags_data.length; i < l; i++) {
+ if (flags_data[i].name == name)
+ return flags_data[i];
+ }
+}
+
+function getProductById(id) {
+ for(var i = 0, l = products_data.length; i < l; i++) {
+ if (products_data[i].id == id)
+ return products_data[i];
+ }
+}
+
+function getFieldById(id) {
+ for(var i = 0, l = fields_data.length; i < l; i++) {
+ if (fields_data[i].id == id)
+ return fields_data[i];
+ }
+}
+
+function selectValue(el, value) {
+ for(var i = 0, l = el.options.length; i < l; i++) {
+ if (el.options[i].value == value) {
+ el.options[i].selected = true;
+ return;
+ }
+ }
+ el.options[0].selected = true;
+}
diff --git a/extensions/BMO/web/styles/reports.css b/extensions/BMO/web/styles/reports.css
index 260a95471..f75f72b8b 100644
--- a/extensions/BMO/web/styles/reports.css
+++ b/extensions/BMO/web/styles/reports.css
@@ -35,3 +35,7 @@
#report tr:hover {
background-color: #ccccff;
}
+
+.disabled {
+ color: #888888;
+}