$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};
return $_[0]->_prefix("params");
}
+sub _resource_prefix {
+ return $_[0]->_prefix("resource");
+}
+
sub _bloomfilter_prefix {
return $_[0]->_prefix("bloomfilter");
}
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
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
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';
#
}
}
}
+
+ # 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.
}
}
+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};