summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDave Lawrence <dlawrence@mozilla.com>2012-05-24 18:57:14 +0200
committerDave Lawrence <dlawrence@mozilla.com>2012-05-24 18:57:14 +0200
commit95052864ee662a67639a3080b3ecf7a1e24ba9e3 (patch)
treec1e771d9b53c622e44c27162fa5c40b5bfb5fa15
parentd1fdd23fc3155f8e78d724713efccaae3d9ab5b9 (diff)
downloadbugzilla-95052864ee662a67639a3080b3ecf7a1e24ba9e3.tar.gz
bugzilla-95052864ee662a67639a3080b3ecf7a1e24ba9e3.tar.xz
Bug 747193 - Add search field to product chooser to allow selecting product/component based on keyword search
r=glob
-rw-r--r--extensions/BMO/lib/WebService.pm67
-rw-r--r--extensions/BMO/template/en/default/global/choose-product.html.tmpl48
-rw-r--r--extensions/BMO/web/js/choose_product.js69
-rw-r--r--extensions/BMO/web/styles/choose_product.css32
4 files changed, 208 insertions, 8 deletions
diff --git a/extensions/BMO/lib/WebService.pm b/extensions/BMO/lib/WebService.pm
index 95bdcbdf3..cd3b9a92c 100644
--- a/extensions/BMO/lib/WebService.pm
+++ b/extensions/BMO/lib/WebService.pm
@@ -26,6 +26,7 @@ use base qw(Bugzilla::WebService);
use Bugzilla::Constants;
use Bugzilla::Error;
+use Bugzilla::Util qw(detaint_natural trick_taint);
use Bugzilla::WebService::Util qw(validate);
use Bugzilla::Field;
@@ -99,6 +100,72 @@ sub getBugsVerifier {
return \%users;
}
+sub prod_comp_search {
+ my ($self, $params) = @_;
+ my $user = Bugzilla->user;
+ my $dbh = Bugzilla->switch_to_shadow_db();
+
+ my $search = $params->{'search'};
+ $search || ThrowCodeError('param_required',
+ { function => 'Bug.prod_comp_search', param => 'search' });
+
+ my $limit = detaint_natural($params->{'limit'})
+ ? $dbh->sql_limit($params->{'limit'})
+ : '';
+
+ # We do this in the DB directly as we want it to be fast and
+ # not have the overhead of loading full product objects
+
+ # All products which the user has "Entry" access to.
+ my $enterable_ids = $dbh->selectcol_arrayref(
+ 'SELECT products.id FROM products
+ LEFT JOIN group_control_map
+ ON group_control_map.product_id = products.id
+ AND group_control_map.entry != 0
+ AND group_id NOT IN (' . $user->groups_as_string . ')
+ WHERE group_id IS NULL
+ AND products.isactive = 1');
+
+ if (scalar @$enterable_ids) {
+ # And all of these products must have at least one component
+ # and one version.
+ $enterable_ids = $dbh->selectcol_arrayref(
+ 'SELECT DISTINCT products.id FROM products
+ WHERE ' . $dbh->sql_in('products.id', $enterable_ids) .
+ ' AND products.id IN (SELECT DISTINCT components.product_id
+ FROM components
+ WHERE components.isactive = 1)
+ AND products.id IN (SELECT DISTINCT versions.product_id
+ FROM versions
+ WHERE versions.isactive = 1)');
+ }
+
+ return { products => [] } if !scalar @$enterable_ids;
+
+ my @list;
+ foreach my $word (split(/[\s,]+/, $search)) {
+ if ($word ne "") {
+ my $sql_word = $dbh->quote($word);
+ trick_taint($sql_word);
+ # XXX CONCAT_WS is MySQL specific
+ my $field = "CONCAT_WS(' ', products.name, components.name, components.description)";
+ push(@list, $dbh->sql_iposition($sql_word, $field) . " > 0");
+ }
+ }
+
+ my $products = $dbh->selectall_arrayref("
+ SELECT products.name AS product,
+ components.name AS component
+ FROM products
+ INNER JOIN components ON products.id = components.product_id
+ WHERE (" . join(" AND ", @list) . ")
+ AND products.id IN (" . join(",", @$enterable_ids) . ")
+ ORDER BY products.name $limit",
+ { Slice => {} });
+
+ return { products => $products };
+}
+
1;
__END__
diff --git a/extensions/BMO/template/en/default/global/choose-product.html.tmpl b/extensions/BMO/template/en/default/global/choose-product.html.tmpl
index 9daae1d25..159919169 100644
--- a/extensions/BMO/template/en/default/global/choose-product.html.tmpl
+++ b/extensions/BMO/template/en/default/global/choose-product.html.tmpl
@@ -32,29 +32,58 @@
[% IF target == "enter_bug.cgi" %]
[% title = "Enter $terms.Bug" %]
[% h2 = BLOCK %]First, you must pick a product on which to enter [% terms.abug %]. [% END %]
+ [% yui = [ 'autocomplete' ] %]
+ [% javascript_urls = [ "js/field.js", "js/create_bug.js",
+ "extensions/BMO/web/js/choose_product.js" ] %]
[% ELSIF target == "describecomponents.cgi" %]
[% title = "Browse" %]
[% h2 = "Please specify the product whose components you want described." %]
[% END %]
+[% style_urls = [ "extensions/BMO/web/styles/choose_product.css" ] %]
+
[% DEFAULT title = "Choose a Product" %]
[% PROCESS global/header.html.tmpl %]
-<center>
+<div id="choose_product">
+
<hr>
-<p><span style="font-family: verdana,helvetica;">Looking for technical support or help getting your site to work with Mozilla? <a
-href="http://www.mozilla.org/support/">Visit the mozilla.org support page</a> before filing [% terms.bugs %].</span></p>
+<p>
+ Looking for technical support or help getting your site to work with Mozilla?
+ <a href="http://www.mozilla.org/support/">Visit the mozilla.org support page</a>
+ before filing [% terms.bugs %].
+</p>
<hr>
-</center>
<br>
[% USE Bugzilla %]
[% cgi = Bugzilla.cgi %]
[% SET classification = cgi.param('classification') %]
[% IF NOT ((cgi.param("full")) OR (user.settings.product_chooser.value == 'full_product_chooser')) %]
-[% IF target == "enter_bug.cgi" %]
-<h2 align="center">Which product is affected by the problem you would like to report?</h2>
+
+[% IF target == 'enter_bug.cgi' %]
+<h2>Which product is affected by the problem you would like to report?</h2>
+<div id="prod_comp_search_main">
+ <div id="prod_comp_search_autocomplete">
+ <div id="prod_comp_search_label">
+ Type to find product and component by name or description
+ </div>
+ <input id="prod_comp_search" id="prod_comp_search" type="text" size="60">
+ <div id="prod_comp_search_autocomplete_container"></div>
+ </div>
+</div>
+<script type="text/javascript">
+ if(typeof(YAHOO.bugzilla.prodCompSearch) !== 'undefined'
+ && YAHOO.bugzilla.prodCompSearch != null)
+ {
+ YAHOO.bugzilla.prodCompSearch.init(
+ "prod_comp_search", "prod_comp_search_autocomplete_container", "[% format FILTER js %]");
+ }
+</script>
+
+<h2>or choose from the following selections</h2>
[% END %]
+
<table align="center" border="0" width="600" cellpadding="5" cellspacing="0">
[% INCLUDE easyproduct
name="Core"
@@ -81,7 +110,7 @@ href="http://www.mozilla.org/support/">Visit the mozilla.org support page</a> be
icon="seamonkey.png"
%]
[% INCLUDE easyproduct
- name="Fennec"
+ name="Fennec Native"
icon="fennec.png"
%]
[% INCLUDE easyproduct
@@ -158,11 +187,14 @@ href="http://www.mozilla.org/support/">Visit the mozilla.org support page</a> be
<br>
[% IF target == "enter_bug.cgi" AND user.settings.product_chooser.value != 'full_product_chooser' %]
-<p align="center">You can choose to get this screen by default when you click "New [% terms.Bug %]" by changing your <a href="userprefs.cgi?tab=settings">preferences</a>.</p>
+<p>You can choose to get this screen by default when you click "New [% terms.Bug %]"
+by changing your <a href="userprefs.cgi?tab=settings">preferences</a>.</p>
[% END %]
[% END %]
<br>
+</div>
+
[% PROCESS global/footer.html.tmpl %]
[%###########################################################################%]
diff --git a/extensions/BMO/web/js/choose_product.js b/extensions/BMO/web/js/choose_product.js
new file mode 100644
index 000000000..7298a6b92
--- /dev/null
+++ b/extensions/BMO/web/js/choose_product.js
@@ -0,0 +1,69 @@
+/* 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.
+ */
+
+YAHOO.bugzilla.prodCompSearch = {
+ counter : 0,
+ format : '',
+ dataSource : null,
+ generateRequest : function (enteredText) {
+ YAHOO.bugzilla.prodCompSearch.counter = YAHOO.bugzilla.prodCompSearch.counter + 1;
+ YAHOO.util.Connect.setDefaultPostHeader('application/json', true);
+ var json_object = {
+ method : "BMO.prod_comp_search",
+ id : YAHOO.bugzilla.prodCompSearch.counter,
+ params : [ {
+ search : decodeURIComponent(enteredText)
+ } ]
+ };
+ return YAHOO.lang.JSON.stringify(json_object);
+ },
+ resultListFormat : function(oResultData, enteredText, sResultMatch) {
+ var url = "enter_bug.cgi?product=" + encodeURIComponent(oResultData[0]) +
+ "&component=" + encodeURIComponent(oResultData[1]);
+ if (YAHOO.bugzilla.prodCompSearch.format) {
+ url = url + "&format=" + encodeURIComponent(YAHOO.bugzilla.prodCompSearch.format);
+ }
+ return ("<a href=\"" + url + "\"><b>" +
+ _escapeHTML(oResultData[0]) + "</b> :: " +
+ _escapeHTML(oResultData[1]) + "</a>");
+ },
+ init_ds : function(){
+ this.dataSource = new YAHOO.util.XHRDataSource("jsonrpc.cgi");
+ this.dataSource.connTimeout = 30000;
+ this.dataSource.connMethodPost = true;
+ this.dataSource.connXhrMode = "cancelStaleRequests";
+ this.dataSource.maxCacheEntries = 5;
+ this.dataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
+ this.dataSource.responseSchema = {
+ resultsList : "result.products",
+ metaFields : { error: "error", jsonRpcId: "id"},
+ fields : [ "product", "component" ]
+ };
+ },
+ init : function( field, container, format) {
+ if( this.dataSource == null ){
+ this.init_ds();
+ }
+ this.format = format;
+ var prodCompSearch = new YAHOO.widget.AutoComplete(field, container, this.dataSource);
+ prodCompSearch.generateRequest = this.generateRequest;
+ prodCompSearch.formatResult = this.resultListFormat;
+ prodCompSearch.minQueryLength = 3;
+ prodCompSearch.autoHighlight = false;
+ prodCompSearch.queryDelay = 0.05;
+ prodCompSearch.useIFrame = true;
+ prodCompSearch.maxResultsDisplayed = 25;
+ prodCompSearch.suppressInputUpdate = true;
+ prodCompSearch.textboxFocusEvent.subscribe(function () {
+ var input = YAHOO.util.Dom.get(field);
+ if (input.value && input.value.length > 3) {
+ this.sendQuery(input.value);
+ }
+ });
+ }
+}
diff --git a/extensions/BMO/web/styles/choose_product.css b/extensions/BMO/web/styles/choose_product.css
new file mode 100644
index 000000000..5d4da73ec
--- /dev/null
+++ b/extensions/BMO/web/styles/choose_product.css
@@ -0,0 +1,32 @@
+/* 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. */
+
+#choose_product h2,
+#choose_product p {
+ text-align: center;
+}
+
+#choose_product td h2,
+#choose_product td p {
+ text-align: left;
+}
+
+#prod_comp_search_main {
+ width: 400px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+#prod_comp_search_label {
+ text-align: center;
+}
+
+#prod_comp_search_main li.yui-ac-highlight a {
+ text-decoration: none;
+ color: #FFFFFF;
+ display: block;
+}