"extensions/ProdCompSearch/web/styles/prod_comp_search.css",
];
javascript_urls = [
- "extensions/BMO/web/js/new-bug-frequent-comp.js",
"extensions/ProdCompSearch/web/js/prod_comp_search.js",
];
+++ /dev/null
-/* 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. */
-
-/**
- * Reference or define the Bugzilla app namespace.
- * @namespace
- */
-var Bugzilla = Bugzilla || {}; // eslint-disable-line no-var
-
-/**
- * Show the current user's most-used components on the New Bug page.
- */
-Bugzilla.NewBugFrequentComp = class NewBugFrequentComp {
- /**
- * Initialize a new NewBugFrequentComp instance.
- */
- constructor() {
- this.$container = document.querySelector('#frequent-components');
-
- if (this.$container && BUGZILLA.user.login) {
- this.init();
- }
- }
-
- /**
- * Initialize the UI.
- */
- async init() {
- this.$results = this.$container.querySelector('.results');
- this.$message = this.$results.appendChild(document.createElement('p'));
- this.$message.textContent = 'Loading...';
- this.$results.setAttribute('aria-busy', 'true');
- this.$container.hidden = false;
-
- // Get the current params that may contain `cloned_bug_id` and `format`
- const current_params = new URLSearchParams(location.search);
-
- try {
- const links = (await this.fetch()).map(({ product, component }) => {
- const params = new URLSearchParams(current_params);
-
- params.append('product', product);
- params.append('component', component);
-
- return {
- href: `${BUGZILLA.config.basepath}enter_bug.cgi?${params.toString()}`,
- text: `${product} :: ${component}`,
- };
- });
-
- this.$message.remove();
- this.$results.insertAdjacentHTML('beforeend',
- `<ul>${links.map(({ href, text }) =>
- `<li><a href="${href.htmlEncode()}">${text.htmlEncode()}</a></li>`
- ).join('')}</ul>`
- );
- } catch (error) {
- this.$message.textContent = error.message || 'Your frequent components could not be retrieved.';
- }
-
- this.$results.removeAttribute('aria-busy');
- }
-
- /**
- * Retrieve frequently used components.
- * @param {Number} [max=10] Maximum number of results.
- * @returns {Promise} Results or error.
- */
- async fetch(max = 10) {
- const params = new URLSearchParams({
- email1: BUGZILLA.user.login,
- emailreporter1: '1',
- emailtype1: 'exact',
- chfield: '[Bug creation]',
- chfieldfrom: '-1y',
- chfieldto: 'Now',
- include_fields: 'product,component',
- });
-
- return new Promise((resolve, reject) => {
- bugzilla_ajax({
- url: `${BUGZILLA.config.basepath}rest/bug?${params.toString()}`
- }, response => {
- if (!response.bugs) {
- reject(new Error('Your frequent components could not be retrieved.'));
-
- return;
- }
-
- if (!response.bugs.length) {
- reject(new Error(('Your frequent components could not be found.')));
-
- return;
- }
-
- const results = [];
-
- for (const { product, component } of response.bugs) {
- const index = results.findIndex(result => product === result.product && component === result.component);
-
- if (index > -1) {
- results[index].count++;
- } else {
- results.push({ product, component, count: 1 });
- }
- }
-
- // Sort in descending order
- results.sort((a, b) => (a.count < b.count ? 1 : a.count > b.count ? -1 : 0));
-
- resolve(results.slice(0, max));
- }, () => {
- reject(new Error('Your frequent components could not be retrieved.'));
- });
- });
- }
-};
-
-window.addEventListener('DOMContentLoaded', () => new Bugzilla.NewBugFrequentComp(), { once: true });
sub rest_resources {
return [
- qr{^/prod_comp_search/(.*)$},
+ qr{^/prod_comp_search/find/(.*)$},
{
GET => {
method => 'prod_comp_search',
return {search => $_[0]};
}
}
+ },
+ qr{^/prod_comp_search/frequent},
+ {
+ GET => {
+ method => 'list_frequent_components',
+ }
}
];
}
return {products => $products};
}
+# Get a list of components the user has frequently reported in the past 2 years
+sub list_frequent_components {
+ my ($self) = @_;
+ my $user = Bugzilla->user;
+
+ # Nothing to show if the user is signed out
+ return {results => []} unless $user->id;
+
+ # Select the date of 2 years ago today
+ my ($day, $month, $year) = (localtime(time))[3, 4, 5];
+ my $date = sprintf('%4d-%02d-%02d', $year + 1900 - 2, $month + 1, $day);
+
+ my $dbh = Bugzilla->switch_to_shadow_db();
+ my $sth = $dbh->prepare('
+ SELECT products.name, components.name FROM bugs
+ INNER JOIN products ON bugs.product_id = products.id
+ INNER JOIN components ON bugs.component_id = components.id
+ WHERE bugs.reporter = ? AND bugs.creation_ts > ?
+ AND products.isactive = 1 AND components.isactive = 1
+ GROUP BY components.id ORDER BY count(bugs.bug_id) DESC LIMIT 10;
+ ');
+ $sth->execute($user->id, $date);
+
+ my $results = [];
+ while (my ($product, $component) = $sth->fetchrow_array) {
+ push @$results, {
+ product => $self->type('string', $product),
+ component => $self->type('string', $component),
+ };
+ };
+
+ return {results => $results};
+}
+
###################
# Private Methods #
###################
appendTo: $('#main-inner'),
forceFixPosition: true,
serviceUrl: function(query) {
- return `${BUGZILLA.config.basepath}rest/prod_comp_search/${encodeURIComponent(query)}`;
+ return `${BUGZILLA.config.basepath}rest/prod_comp_search/find/${encodeURIComponent(query)}`;
},
params: params,
deferRequestBy: 250,
})
.data('counter', 0);
});
+
+/**
+ * Reference or define the Bugzilla app namespace.
+ * @namespace
+ */
+var Bugzilla = Bugzilla || {}; // eslint-disable-line no-var
+
+/**
+ * Show the current user's most-used components on the New Bug page.
+ */
+Bugzilla.FrequentComponents = class FrequentComponents {
+ /**
+ * Initialize a new FrequentComponents instance.
+ */
+ constructor() {
+ this.$container = document.querySelector('#frequent-components');
+
+ if (this.$container && BUGZILLA.user.login) {
+ this.init();
+ }
+ }
+
+ /**
+ * Initialize the UI.
+ */
+ async init() {
+ this.$results = this.$container.querySelector('.results');
+ this.$message = this.$results.appendChild(document.createElement('p'));
+ this.$message.textContent = 'Loading...';
+ this.$results.setAttribute('aria-busy', 'true');
+ this.$container.hidden = false;
+
+ // Get the current params that may contain `cloned_bug_id` and `format`
+ const current_params = new URLSearchParams(location.search);
+
+ try {
+ const links = (await this.fetch()).map(({ product, component }) => {
+ const params = new URLSearchParams(current_params);
+
+ params.append('product', product);
+ params.append('component', component);
+
+ return {
+ href: `${BUGZILLA.config.basepath}enter_bug.cgi?${params.toString()}`,
+ text: `${product} :: ${component}`,
+ };
+ });
+
+ this.$message.remove();
+ this.$results.insertAdjacentHTML('beforeend',
+ `<ul>${links.map(({ href, text }) =>
+ `<li><a href="${href.htmlEncode()}">${text.htmlEncode()}</a></li>`
+ ).join('')}</ul>`
+ );
+ } catch (error) {
+ this.$message.textContent = error.message || 'Your frequent components could not be retrieved.';
+ }
+
+ this.$results.removeAttribute('aria-busy');
+ }
+
+ /**
+ * Retrieve frequently used components.
+ * @returns {Promise} Results or error.
+ */
+ async fetch() {
+ return new Promise((resolve, reject) => bugzilla_ajax({
+ url: `${BUGZILLA.config.basepath}rest/prod_comp_search/frequent`
+ }, ({ results }) => {
+ if (!results) {
+ reject(new Error('Your frequent components could not be retrieved.'));
+ } else if (!results.length) {
+ reject(new Error(('Your frequent components could not be found.')));
+ } else {
+ resolve(results);
+ }
+ }, () => {
+ reject(new Error('Your frequent components could not be retrieved.'));
+ }));
+ }
+};
+
+window.addEventListener('DOMContentLoaded', () => new Bugzilla.FrequentComponents(), { once: true });