]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 1538115 - Add shortcuts for tracking & status flags
authorKohei Yoshino <kohei.yoshino@gmail.com>
Wed, 22 May 2019 15:43:19 +0000 (11:43 -0400)
committerGitHub <noreply@github.com>
Wed, 22 May 2019 15:43:19 +0000 (11:43 -0400)
Bugzilla/Hook.pm
Bugzilla/Memcached.pm
Bugzilla/Search.pm
extensions/BMO/Extension.pm
extensions/BMO/template/en/default/hook/global/user-error-errors.html.tmpl
extensions/TrackingFlags/Extension.pm

index d4ee572b3a9e6a8dd50bf634a1a7da7bf75d42e2..15c7b5446981287a6d275c30044ae86346cf6980 100644 (file)
@@ -542,6 +542,21 @@ table should be joined with the C<bugs> table. If omitted, LEFT is used.
 
 =back
 
+=head2 search_params_to_data_structure
+
+This happens in L<Bugzilla::Search/_params_to_data_structure> and allows you to
+modify the search params in any way, before the all the params are processed as
+a data structure. It's useful especially when you want to accept synonyms for
+param names and/or values.
+
+Params:
+
+=over
+
+=item C<search> - The L<Bugzilla::Search> object.
+
+=back
+
 =head2 search_operator_field_override
 
 This allows you to modify L<Bugzilla::Search/OPERATOR_FIELD_OVERRIDE>,
index b833e064f78092ac95c8613d259afd09144e2f2f..10a45caeea160a22f05d9729eaad8f7ba30ab819 100644 (file)
@@ -176,6 +176,45 @@ sub clear_params {
   $self->_inc_prefix("params");
 }
 
+sub set_data {
+  my ($self, $args) = @_;
+  return unless $self->{memcached};
+
+  if (exists $args->{key}) {
+    my $key        = $self->_resource_prefix . '.' . $args->{key};
+    my $expires_in = $args->{expires_in};
+
+    return $self->_set($key, {
+      value   => $args->{value},
+      expires => $expires_in ? time() + $expires_in : 0,
+    });
+  }
+  else {
+    ThrowCodeError('params_required',
+      {function => "Bugzilla::Memcached::set_data", params => ['key']});
+  }
+}
+
+sub get_data {
+  my ($self, $args) = @_;
+  return unless $self->{memcached};
+
+  if (exists $args->{key}) {
+    my $key   = $self->_resource_prefix . '.' . $args->{key};
+    my $cache = $self->_get($key);
+
+    return undef unless $cache;
+    return $cache->{value} if $cache->{expires} == 0 || $cache->{expires} > time();
+
+    $self->_delete($key);
+    return undef;
+  }
+  else {
+    ThrowCodeError('params_required',
+      {function => "Bugzilla::Memcached::get_data", params => ['key']});
+  }
+}
+
 sub set_bloomfilter {
   my ($self, $args) = @_;
   return unless $self->{memcached};
@@ -339,6 +378,10 @@ sub _params_prefix {
   return $_[0]->_prefix("params");
 }
 
+sub _resource_prefix {
+  return $_[0]->_prefix("resource");
+}
+
 sub _bloomfilter_prefix {
   return $_[0]->_prefix("bloomfilter");
 }
@@ -467,6 +510,13 @@ Bugzilla's configuration (such as fields, products, components, groups, etc).
 Values set with C<set_config> are automatically cleared when changes are made
 to Bugzilla's configuration.
 
+=item C<set_data({ key =E<gt> $key, value =E<gt> $value, expires_in =E<gt> $expires_in })>
+
+Adds the C<value> using the C<key> while identifying the generic data such as
+decoded JSON retrieved from a remote API endpoint. Optionally, data retention
+period can be specified with C<expires_in> in seconds. If it's not set, the
+stored data never expires.
+
 =back
 
 =head2 Getting
@@ -493,6 +543,11 @@ Return C<value> with the specified C<table> and C<name>.
 Return C<value> with the specified C<key> from the configuration cache.  See
 C<set_config> for more information.
 
+=item C<get_data({ key =E<gt> $key })>
+
+Return C<value> with the specified C<key> from the generic data cache.  See
+C<set_data> for more information.
+
 =back
 
 =head2 Clearing
index b5ce8ad52d2297d877a75a37079f34262db6d317..b9f1ea283357559eb5a4dfdc43ae9c733fbe8b36 100644 (file)
@@ -1763,6 +1763,9 @@ sub _charts {
 sub _params_to_data_structure {
   my ($self) = @_;
 
+  # Allow to modify params before being processed
+  Bugzilla::Hook::process('search_params_to_data_structure', {search => $self});
+
   # First we get the "special" charts, representing all the normal
   # fields on the search page. This may modify _params, so it needs to
   # happen first.
index cf3695b09e4af6e3c34949dd079858abace3a950..f1e98c1cdd537ec2098e90b1f8c08628d7a409ac 100644 (file)
@@ -59,11 +59,27 @@ use Sys::Syslog qw(:DEFAULT);
 use Text::Balanced qw( extract_bracketed extract_multiple );
 use JSON::MaybeXS;
 use Mojo::File qw(path);
+use Mojo::UserAgent;
 
 use Bugzilla::Extension::BMO::Constants;
 use Bugzilla::Extension::BMO::FakeBug;
 use Bugzilla::Extension::BMO::Data;
 
+use constant PD_ENDPOINT => 'https://product-details.mozilla.org/1.0/';
+use constant PRODUCT_CHANNELS => {
+  'firefox' => {
+    'nightly' => {label => 'Nightly', json_key => 'FIREFOX_NIGHTLY'},
+    'beta'    => {label => 'Beta',    json_key => 'LATEST_FIREFOX_DEVEL_VERSION'},
+    'release' => {label => 'Release', json_key => 'LATEST_FIREFOX_VERSION'},
+    'esr'     => {label => 'ESR',     json_key => 'FIREFOX_ESR'},
+  },
+  'thunderbird' => {
+    'nightly' => {label => 'Daily',   json_key => 'LATEST_THUNDERBIRD_NIGHTLY_VERSION'},
+    'beta'    => {label => 'Beta',    json_key => 'LATEST_THUNDERBIRD_DEVEL_VERSION'},
+    'release' => {label => 'Release', json_key => 'LATEST_THUNDERBIRD_VERSION'},
+  },
+};
+
 our $VERSION = '0.1';
 
 #
@@ -1539,6 +1555,22 @@ sub install_update_db {
       }
     }
   }
+
+  # Add pronouns for Firefox/Thunderbird Status/Tracking Flags such as
+  # cf_tracking_firefox_nightly
+  foreach my $type ('status', 'tracking') {
+    foreach my $product (keys %{PRODUCT_CHANNELS()}) {
+      foreach my $channel (keys %{PRODUCT_CHANNELS->{$product}}) {
+        unless (Bugzilla::Field->new({name => "cf_${type}_${product}_${channel}"})) {
+          Bugzilla::Field->create({
+            name        => "cf_${type}_${product}_${channel}",
+            description => "${type}-${product}-${channel}",
+            buglist     => 1,
+          });
+        }
+      }
+    }
+  }
 }
 
 # return the Bugzilla::Field::Choice object for the specified field and value.
@@ -2632,6 +2664,138 @@ sub enter_bug_entrydefaultvars {
   }
 }
 
+sub _get_product_version {
+  my ($product, $channel, $detail) = @_;
+  my $key      = "${product}_versions";
+  my $versions = Bugzilla->request_cache->{$key}
+    || Bugzilla->memcached->get_data({key => $key});
+
+  unless ($versions) {
+    my $ua = Mojo::UserAgent->new;
+    if (my $proxy_url = Bugzilla->params->{'proxy_url'}) {
+      $ua->proxy->http($proxy_url);
+    }
+
+    my $response = $ua->get(PD_ENDPOINT . $key . '.json')->result;
+    $versions = Bugzilla->request_cache->{$key}
+      = $response->is_success ? decode_json($response->body) : {};
+    Bugzilla->memcached->set_data({
+      key   => $key,
+      value => $versions,
+      # Cache for 30 minutes if the data is available, otherwise retry in 5 min
+      expires_in => $response->is_success ? 1800 : 300,
+    });
+  }
+
+  my $version = $versions->{PRODUCT_CHANNELS->{$product}->{$channel}->{json_key}};
+  return $version if $detail;
+
+  # Return major version by default
+  return 0 unless $version;
+  my ($major_version) = $version =~ /^(\d+)/;
+  return $major_version;
+}
+
+sub _get_search_param_name {
+  my ($type, $product, $channel) = @_;
+  my $version = _get_product_version($product, $channel);
+  return () unless $version;
+
+  # Return canonical name and its alias
+  $version = '_' . $version if $product eq 'thunderbird';
+  $version = '_esr' . $version if $channel eq 'esr';
+  return ("cf_${type}_${product}${version}", "cf_${type}_${product}_${channel}");
+}
+
+sub search_params_to_data_structure {
+  my ($self, $args) = @_;
+  my $params = $args->{search}->_params;
+
+  # Get the Firefox channel list and put it a regex pattern
+  # Thunderbird doesn't have ESR now but it won't be practically a problem
+  my $channels_re = join('|', keys %{PRODUCT_CHANNELS->{'firefox'}});
+  my $flag_re     = qr/^cf_(status|tracking)_(firefox|thunderbird)_($channels_re)$/;
+
+  # Replace pronouns for Firefox/Thunderbird Status/Tracking Flags, for example,
+  # cf_tracking_firefox_nightly -> cf_tracking_firefox68
+  # cf_tracking_firefox_esr     -> cf_tracking_firefox_esr60
+  for my $key (keys %$params) {
+    # Replace keys
+    if ($key =~ $flag_re) {
+      my ($canonical, $alias) = _get_search_param_name($1, $2, $3);
+      ThrowUserError('product_versions_unavailable') unless $canonical;
+      $params->{$canonical} = delete $params->{$key};
+    }
+
+    # Replace values (custom search)
+    if ($params->{$key} =~ $flag_re) {
+      my ($canonical, $alias) = _get_search_param_name($1, $2, $3);
+      ThrowUserError('product_versions_unavailable') unless $canonical;
+      $params->{$key} = $canonical;
+    }
+  }
+}
+
+sub tf_buglist_columns {
+  my ($self, $args) = @_;
+  my $columns = $args->{columns};
+
+  # Add column aliases for Firefox/Thunderbird Status/Tracking Flags
+  foreach my $type ('status', 'tracking') {
+    foreach my $product (keys %{PRODUCT_CHANNELS()}) {
+      foreach my $channel (keys %{PRODUCT_CHANNELS->{$product}}) {
+        my ($canonical, $alias) = _get_search_param_name($type, $product, $channel);
+
+        if ($columns->{$canonical} && $canonical) {
+          $columns->{$alias}->{name} = $columns->{$canonical}->{name};
+        } else {
+          delete $columns->{$alias};
+        }
+      }
+    }
+  }
+}
+
+sub tf_buglist_column_joins {
+  my ($self, $args) = @_;
+  my $column_joins = $args->{'column_joins'};
+
+  # Add column aliases for Firefox/Thunderbird Status/Tracking Flags
+  foreach my $type ('status', 'tracking') {
+    foreach my $product (keys %{PRODUCT_CHANNELS()}) {
+      foreach my $channel (keys %{PRODUCT_CHANNELS->{$product}}) {
+        my ($canonical, $alias) = _get_search_param_name($type, $product, $channel);
+
+        if ($column_joins->{$canonical} && $canonical) {
+          $column_joins->{$alias} = $column_joins->{$canonical};
+        } else {
+          delete $column_joins->{$alias};
+        }
+      }
+    }
+  }
+}
+
+sub tf_search_operator_field_override {
+  my ($self, $args) = @_;
+  my $operators = $args->{'operators'};
+
+  # Add column aliases for Firefox/Thunderbird Status/Tracking Flags
+  foreach my $type ('status', 'tracking') {
+    foreach my $product (keys %{PRODUCT_CHANNELS()}) {
+      foreach my $channel (keys %{PRODUCT_CHANNELS->{$product}}) {
+        my ($canonical, $alias) = _get_search_param_name($type, $product, $channel);
+
+        if ($operators->{$canonical} && $canonical) {
+          $operators->{$alias} = $operators->{$canonical};
+        } else {
+          delete $operators->{$alias};
+        }
+      }
+    }
+  }
+}
+
 sub app_startup {
   my ($self, $args) = @_;
   my $app = $args->{app};
index a8be3663f0515d51d053a74baa617b89491d0f31..b9c7adb134942591bab7d0ce13203f5cd8093e32 100644 (file)
@@ -46,4 +46,9 @@
   You cannot set this [% terms.bug %]'s status to ASSIGNED because the
   [%+ terms.bug %] is not assigned to a person.
 
+[% ELSIF error == "product_versions_unavailable" %]
+  [% title = "Product Version Info Unavailable" %]
+  The pronouns for Status and Tracking Flags cannot be used at this time.
+  Please try again later or use actual version numbers instead.
+
 [% END %]
index 58a889e981427e0c82197589262e038191c9290b..666f35a6de8cdfa0f33917f82003a6a56a18c930 100644 (file)
@@ -346,6 +346,9 @@ sub buglist_columns {
       title => $flag->description
     };
   }
+
+  # Allow other extensions to alter columns
+  Bugzilla::Hook::process('tf_buglist_columns', {columns => $columns});
 }
 
 sub buglist_column_joins {
@@ -364,6 +367,10 @@ sub buglist_column_joins {
       extra => ['map_' . $flag->name . '.tracking_flag_id = ' . $flag->flag_id]
     };
   }
+
+  # Allow other extensions to alter column_joins
+  Bugzilla::Hook::process('tf_buglist_column_joins',
+    {column_joins => $column_joins});
 }
 
 sub bug_create_cf_accessors {
@@ -424,6 +431,10 @@ sub search_operator_field_override {
       }
     };
   }
+
+  # Allow other extensions to alter operators
+  Bugzilla::Hook::process('tf_search_operator_field_override',
+    {operators => $operators});
 }
 
 sub _tracking_flags_search_nonchanged {