From: Kohei Yoshino Date: Wed, 22 May 2019 15:43:19 +0000 (-0400) Subject: Bug 1538115 - Add shortcuts for tracking & status flags X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=baa7a1590b869cdb657dbe175396b278155bfede;p=thirdparty%2Fbugzilla.git Bug 1538115 - Add shortcuts for tracking & status flags --- diff --git a/Bugzilla/Hook.pm b/Bugzilla/Hook.pm index d4ee572b3..15c7b5446 100644 --- a/Bugzilla/Hook.pm +++ b/Bugzilla/Hook.pm @@ -542,6 +542,21 @@ table should be joined with the C table. If omitted, LEFT is used. =back +=head2 search_params_to_data_structure + +This happens in L 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 - The L object. + +=back + =head2 search_operator_field_override This allows you to modify L, diff --git a/Bugzilla/Memcached.pm b/Bugzilla/Memcached.pm index b833e064f..10a45caee 100644 --- a/Bugzilla/Memcached.pm +++ b/Bugzilla/Memcached.pm @@ -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 are automatically cleared when changes are made to Bugzilla's configuration. +=item C $key, value =E $value, expires_in =E $expires_in })> + +Adds the C using the C while identifying the generic data such as +decoded JSON retrieved from a remote API endpoint. Optionally, data retention +period can be specified with C in seconds. If it's not set, the +stored data never expires. + =back =head2 Getting @@ -493,6 +543,11 @@ Return C with the specified C and C. Return C with the specified C from the configuration cache. See C for more information. +=item C $key })> + +Return C with the specified C from the generic data cache. See +C for more information. + =back =head2 Clearing diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm index b5ce8ad52..b9f1ea283 100644 --- a/Bugzilla/Search.pm +++ b/Bugzilla/Search.pm @@ -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. diff --git a/extensions/BMO/Extension.pm b/extensions/BMO/Extension.pm index cf3695b09..f1e98c1cd 100644 --- a/extensions/BMO/Extension.pm +++ b/extensions/BMO/Extension.pm @@ -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}; 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 a8be3663f..b9c7adb13 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 @@ -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 %] diff --git a/extensions/TrackingFlags/Extension.pm b/extensions/TrackingFlags/Extension.pm index 58a889e98..666f35a6d 100644 --- a/extensions/TrackingFlags/Extension.pm +++ b/extensions/TrackingFlags/Extension.pm @@ -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 {