diff options
Diffstat (limited to 'extensions/ProdCompSearch')
5 files changed, 163 insertions, 175 deletions
diff --git a/extensions/ProdCompSearch/lib/WebService.pm b/extensions/ProdCompSearch/lib/WebService.pm index b9a03eb27..3bf0e1377 100644 --- a/extensions/ProdCompSearch/lib/WebService.pm +++ b/extensions/ProdCompSearch/lib/WebService.pm @@ -15,10 +15,31 @@ use base qw(Bugzilla::WebService); use Bugzilla::Error; use Bugzilla::Util qw(detaint_natural trick_taint trim); +############# +# Constants # +############# + use constant PUBLIC_METHODS => qw( prod_comp_search ); +sub rest_resources { + return [ + qr{^/prod_comp_search/(.*)$}, { + GET => { + method => 'prod_comp_search', + params => sub { + return { search => $_[0] } + } + } + } + ] +} + +################## +# Public Methods # +################## + sub prod_comp_search { my ($self, $params) = @_; my $user = Bugzilla->user; @@ -104,6 +125,10 @@ sub prod_comp_search { return { products => $products }; } +################### +# Private Methods # +################### + sub _build_terms { my ($query, $product, $component) = @_; my $dbh = Bugzilla->dbh(); diff --git a/extensions/ProdCompSearch/template/en/default/pages/prodcompsearch.html.tmpl b/extensions/ProdCompSearch/template/en/default/pages/prodcompsearch.html.tmpl index 5b39315b5..6e28d88e5 100644 --- a/extensions/ProdCompSearch/template/en/default/pages/prodcompsearch.html.tmpl +++ b/extensions/ProdCompSearch/template/en/default/pages/prodcompsearch.html.tmpl @@ -10,15 +10,15 @@ [% PROCESS global/header.html.tmpl title = "File a $terms.Bug" - javascript_urls = [ "js/yui3/yui/yui-min.js", - "extensions/ProdCompSearch/web/js/prod_comp_search.js" ] + javascript_urls = [ "extensions/ProdCompSearch/web/js/prod_comp_search.js" ] style_urls = [ "extensions/ProdCompSearch/web/styles/prod_comp_search.css" ] %] <div id="prod_comp_search_main"> [% PROCESS prodcompsearch/form.html.tmpl - query_header = "File a $terms.Bug:" - script_name = "enter_bug.cgi" + input_label = "File a $terms.Bug:" + script_name = "enter_bug.cgi" + auto_focus = 1 %] </div> diff --git a/extensions/ProdCompSearch/template/en/default/prodcompsearch/form.html.tmpl b/extensions/ProdCompSearch/template/en/default/prodcompsearch/form.html.tmpl index 38f87dc1a..4239a9738 100644 --- a/extensions/ProdCompSearch/template/en/default/prodcompsearch/form.html.tmpl +++ b/extensions/ProdCompSearch/template/en/default/prodcompsearch/form.html.tmpl @@ -6,35 +6,48 @@ # defined by the Mozilla Public License, v. 2.0. #%] -[% DEFAULT max_results = 100 %] -<script type="text/javascript"> - [% IF script_name %] - ProdCompSearch.script_name = '[% script_name FILTER js %]'; - [% END %] - [% IF format %] - ProdCompSearch.format = '[% format FILTER js %]'; - [% END %] - [% IF cloned_bug_id %] - ProdCompSearch.cloned_bug_id = '[% cloned_bug_id FILTER js %]'; - [% END %] - [% IF new_tab %] - ProdCompSearch.new_tab = true; - [% END %] - ProdCompSearch.max_results = [% max_results FILTER js %]; -</script> +[%# + # parameters (all are optional, defaults below) + # id : id and prefix of elements + # script_name : .cgi to redirect to + # max_results : maximum results displayed + # input_label : input field label + # auto_focus : focus the search form on page load + # format : format parameter passed to cgi + # cloned_bug_id : cloned_bug_id parameter + # new_tab : open in a new tab + # anchor_component : append #component to url + #%] + +[% + DEFAULT id = "pcs"; + DEFAULT max_results = 100; + DEFAULT script_name = "enter_bug.cgi"; +%] -<div id="prod_comp_search_form" class="yui3-skin-sam"> - <div id="prod_comp_search_header"> +<div class="pcs-form"> + <div class="pcs-header"> [% input_label FILTER none %] - <img id="prod_comp_throbber" src="extensions/ProdCompSearch/web/images/throbber.gif" - class="bz_default_hidden" width="16" height="11"> - <span id="prod_comp_no_components" class="bz_default_hidden"> - No components found</span> - <span id="prod_comp_too_many_components" class="bz_default_hidden"> + <img id="[% id FILTER html %]-throbber" + src="extensions/ProdCompSearch/web/images/throbber.gif" + style="display:none" width="16" height="11"> + <span class="pcs-message" id="[% id FILTER html %]-no_components" style="display:none"> + No components found + </span> + <span class="pcs-message" id="[% id FILTER html %]-too_many_components" style="display:none"> Result limited to [% max_results FILTER html %] components - <span id="prod_comp_error" class="bz_default_hidden"> - An error occured</span> + </span> + <span class="pcs-message" id="[% id FILTER html %]-error" style="display:none"> + An error occured + </span> </div> - <input id="prod_comp_search" type="text" size="50" - placeholder="Search by product and component keywords"> + <input type="text" class="prod_comp_search" id="[% id FILTER html %]" size="50" + placeholder="Search by product and component keywords" + data-script_name="[% script_name FILTER html %]" + data-format="[% format FILTER html %]" + data-cloned_bug_id="[% cloned_bug_id FILTER html %]" + data-new_tab="[% new_tab ? "1" : "0" %]" + data-anchor_component="[% anchor_component ? "1" : "0" %]" + data-max_results="[% max_results FILTER html %]" + [% "autofocus" IF auto_focus %]> </div> diff --git a/extensions/ProdCompSearch/web/js/prod_comp_search.js b/extensions/ProdCompSearch/web/js/prod_comp_search.js index f294994e3..ae7353779 100644 --- a/extensions/ProdCompSearch/web/js/prod_comp_search.js +++ b/extensions/ProdCompSearch/web/js/prod_comp_search.js @@ -7,138 +7,100 @@ // Product and component search to file a new bug -var ProdCompSearch = { - script_name: 'enter_bug.cgi', - script_choices: ['enter_bug.cgi', 'describecomponents.cgi'], - format: null, - cloned_bug_id: null, - new_tab: null, - max_results: 100 -}; - -YUI({ - base: 'js/yui3/', - combine: false -}).use("node", "json-stringify", "autocomplete", "escape", - "datasource-io", "datasource-jsonschema", function(Y) { - Y.on("domready", function() { - var counter = 0, - dataSource = null, - autoComplete = null; - - var resultListFormat = function(query, results) { - return Y.Array.map(results, function(result) { - var data = result.raw; - result.text = data.product + ' :: ' + data.component; - return Y.Escape.html(result.text); - }); - }; - - var requestTemplate = function(query) { - counter = counter + 1; - var json_object = { - version: "1.1", - method : "PCS.prod_comp_search", - id : counter, - params : { search: query, limit: ProdCompSearch.max_results } - }; - return Y.JSON.stringify(json_object); - }; - - var dataSource = new Y.DataSource.IO({ - source: 'jsonrpc.cgi', - ioConfig: { - method: "POST", - headers: { 'Content-Type': 'application/json' } - }, - on: { - error: function(e) { - if (console.error && e.response.meta.error) { - console.error(e.response.meta.error.message); - } - Y.one("#prod_comp_throbber").addClass('bz_default_hidden'); - Y.one("#prod_comp_error").removeClass('bz_default_hidden'); - } +$(function() { + 'use strict'; + $('.prod_comp_search').autocomplete({ + minLength: 3, + delay: 500, + source: function(request, response) { + var el = this.element; + var id = '#' + el.prop('id'); + $(id + '-throbber').show(); + $(id + '-no_components').hide(); + $(id + '-too_many_components').hide(); + $(id + '-error').hide(); + var url = 'rest/prod_comp_search/' + encodeURIComponent(request.term) + + '?limit=' + (el.data('max_results') + 1); + if (BUGZILLA.api_token) { + url += '&Bugzilla_api_token=' + encodeURIComponent(BUGZILLA.api_token); } - }); - - dataSource.plug(Y.Plugin.DataSourceJSONSchema, { - schema: { - resultListLocator : "result.products", - resultFields : [ "product", "component" ], - metaFields : { error : 'error' } - } - }); - - var input = Y.one('#prod_comp_search'); - - input.plug(Y.Plugin.AutoComplete, { - activateFirstItem: false, - enableCache: true, - source: dataSource, - minQueryLength: 3, - queryDelay: 0.05, - resultFormatter: resultListFormat, - suppressInputUpdate: true, - maxResults: ProdCompSearch.max_results, - scrollIntoView: true, - requestTemplate: requestTemplate, - on: { - query: function(e) { - Y.one("#prod_comp_throbber").removeClass('bz_default_hidden'); - Y.one("#prod_comp_no_components").addClass('bz_default_hidden'); - Y.one("#prod_comp_too_many_components").addClass('bz_default_hidden'); - Y.one("#prod_comp_error").addClass('bz_default_hidden'); - }, - results: function(e) { - Y.one("#prod_comp_throbber").addClass('bz_default_hidden'); - input.ac.set('activateFirstItem', e.results.length == 1); - if (e.results.length == 0) { - Y.one("#prod_comp_no_components").removeClass('bz_default_hidden'); - } - else if (e.results.length + 1 > ProdCompSearch.max_results) { - Y.one("#prod_comp_too_many_components").removeClass('bz_default_hidden'); - } - }, - select: function(e) { - // Only redirect if the script_name is a valid choice - if (Y.Array.indexOf(ProdCompSearch.script_choices, ProdCompSearch.script_name) == -1) - return; - - var data = e.result.raw; - var url = ProdCompSearch.script_name + - "?product=" + encodeURIComponent(data.product) + - "&component=" + encodeURIComponent(data.component); - if (ProdCompSearch.script_name == 'enter_bug.cgi') { - if (ProdCompSearch.format) - url += "&format=" + encodeURIComponent(ProdCompSearch.format); - if (ProdCompSearch.cloned_bug_id) - url += "&cloned_bug_id=" + encodeURIComponent(ProdCompSearch.cloned_bug_id); - } - if (ProdCompSearch.script_name == 'describecomponents.cgi') { - url += "#" + encodeURIComponent(data.component); - } - if (ProdCompSearch.new_tab) { - window.open(url, '_blank'); - } - else { - window.location.href = url; - } + $.ajax({ + url: url, + contentType: 'application/json' + }) + .done(function(data) { + $(id + '-throbber').hide(); + if (data.error) { + $(id + '-error').show(); + console.log(data.message); + return false; + } + if (data.products.length === 0) { + $(id + '-no_components').show(); } - }, - after: { - select: function(e) { - if (ProdCompSearch.new_tab) { - input.set('value',''); + else if (data.products.length > el.data('max_results')) { + $(id + '-too_many_components').show(); + } + var current_product = ""; + var prod_comp_array = []; + var base_params = []; + if (el.data('format')) { + base_params.push('format=' + encodeURIComponent(el.data('format'))); + } + if (el.data('cloned_bug_id')) { + base_params.push('cloned_bug_id=' + encodeURIComponent(el.data('cloned_bug_id'))); + } + $.each(data.products, function() { + var params = base_params.slice(); + params.push('product=' + encodeURIComponent(this.product)); + if (this.product != current_product) { + prod_comp_array.push({ + label: this.product, + url: el.data('script_name') + '?' + params.join('&') + }); + current_product = this.product; } + params.push('component=' + encodeURIComponent(this.component)); + var url = el.data('script_name') + '?' + params.join('&'); + if (el.data('anchor_component')) { + url += "#" + encodeURIComponent(this.component); + } + prod_comp_array.push({ + label: this.product + ' :: ' + this.component, + url: url + }); + }); + response(prod_comp_array); + }) + .fail(function(xhr, error_text) { + if (xhr.responseJSON.error) { + error_text = xhr.responseJSON.message; } + $(id + '-throbber').hide(); + $(id + '-comp_error').show(); + console.log(error_text); + }); + }, + focus: function(event, ui) { + event.preventDefault(); + }, + select: function(event, ui) { + event.preventDefault(); + var el = $(this); + el.val(ui.item.label); + if (el.data('new_tab')) { + window.open(ui.item.url, '_blank'); } - }); - - input.on('focus', function (e) { - if (e.target.value && e.target.value.length > 3) { - dataSource.load(e.target.value); + else { + window.location.href = ui.item.url; } - }); + } + }) + .focus(function(event) { + var el = $(event.target); + if (el.val().length >= el.autocomplete('option', 'minLength')) { + el.autocomplete('search'); + } }); + $('.prod_comp_search:focus').select(); }); diff --git a/extensions/ProdCompSearch/web/styles/prod_comp_search.css b/extensions/ProdCompSearch/web/styles/prod_comp_search.css index ccb5887c4..b898672b4 100644 --- a/extensions/ProdCompSearch/web/styles/prod_comp_search.css +++ b/extensions/ProdCompSearch/web/styles/prod_comp_search.css @@ -5,23 +5,11 @@ * This Source Code Form is "Incompatible With Secondary Licenses", as * defined by the Mozilla Public License, v. 2.0. */ -#prod_comp_search_main { - width: 400px; - margin-right: auto; - margin-left: auto; -} - -#prod_comp_search_form .yui3-aclist-input { - width: 360px; -} - -#prod_comp_search_form .yui3-aclist-content { - max-height: 500px; - overflow: auto; +.pcs-message { + color: red; } -#prod_comp_no_components, -#prod_comp_error, -#prod_comp_too_many_components { - color: red; +#prod_comp_search_main { + width: 400px; + margin: 0 auto; } |