diff options
author | Dave Lawrence <dlawrence@mozilla.com> | 2012-05-24 18:57:14 +0200 |
---|---|---|
committer | Dave Lawrence <dlawrence@mozilla.com> | 2012-05-24 18:57:14 +0200 |
commit | 95052864ee662a67639a3080b3ecf7a1e24ba9e3 (patch) | |
tree | c1e771d9b53c622e44c27162fa5c40b5bfb5fa15 | |
parent | d1fdd23fc3155f8e78d724713efccaae3d9ab5b9 (diff) | |
download | bugzilla-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.pm | 67 | ||||
-rw-r--r-- | extensions/BMO/template/en/default/global/choose-product.html.tmpl | 48 | ||||
-rw-r--r-- | extensions/BMO/web/js/choose_product.js | 69 | ||||
-rw-r--r-- | extensions/BMO/web/styles/choose_product.css | 32 |
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; +} |