summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Bugzilla/Search.pm4
-rw-r--r--js/instant-search.js201
-rw-r--r--template/en/default/search/search-instant.html.tmpl83
-rw-r--r--template/en/default/search/tabs.html.tmpl4
4 files changed, 289 insertions, 3 deletions
diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm
index 0c9f84689..0c5b65acf 100644
--- a/Bugzilla/Search.pm
+++ b/Bugzilla/Search.pm
@@ -2797,8 +2797,8 @@ sub _changedby {
sub IsValidQueryType
{
my ($queryType) = @_;
- # BMO: Added google query type
- if (grep { $_ eq $queryType } qw(specific advanced google)) {
+ # BMO: Added google and instant
+ if (grep { $_ eq $queryType } qw(specific advanced google instant)) {
return 1;
}
return 0;
diff --git a/js/instant-search.js b/js/instant-search.js
new file mode 100644
index 000000000..f590c8ef3
--- /dev/null
+++ b/js/instant-search.js
@@ -0,0 +1,201 @@
+/* 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. */
+
+var Dom = YAHOO.util.Dom;
+var Event = YAHOO.util.Event;
+
+Event.onDOMReady(function() {
+ YAHOO.bugzilla.instantSearch.onInit();
+ if (YAHOO.bugzilla.instantSearch.getContent().length >= 4) {
+ YAHOO.bugzilla.instantSearch.doSearch(YAHOO.bugzilla.instantSearch.getContent());
+ } else {
+ YAHOO.bugzilla.instantSearch.reset();
+ }
+ Dom.get('content').focus();
+});
+
+YAHOO.bugzilla.instantSearch = {
+ counter: 0,
+ dataTable: null,
+ dataTableColumns: null,
+ elContent: null,
+ elList: null,
+ currentSearchQuery: '',
+ currentSearchProduct: '',
+
+ onInit: function() {
+ YAHOO.util.Connect.setDefaultPostHeader('text/plain; charset=UTF-8');
+
+ this.elContent = Dom.get('content');
+ this.elList = Dom.get('results');
+
+ Event.addListener(this.elContent, 'keyup', this.onContentKeyUp);
+ Event.addListener(Dom.get('product'), 'change', this.onProductChange);
+ },
+
+ setLabels: function(labels) {
+ this.dataTableColumns = [
+ { key: "id", label: labels.id, formatter: this.formatId },
+ { key: "summary", label: labels.summary, formatter: "text" },
+ { key: "component", label: labels.component, formatter: "text" },
+ { key: "status", label: labels.status, formatter: this.formatStatus },
+ ];
+ },
+
+ initDataTable: function() {
+ var dataSource = new YAHOO.util.XHRDataSource("jsonrpc.cgi");
+ dataSource.connTimeout = 15000;
+ dataSource.connMethodPost = true;
+ dataSource.connXhrMode = "cancelStaleRequests";
+ dataSource.maxCacheEntries = 3;
+ dataSource.responseSchema = {
+ resultsList : "result.bugs",
+ metaFields : { error: "error", jsonRpcId: "id" }
+ };
+ // DataSource can't understand a JSON-RPC error response, so
+ // we have to modify the result data if we get one.
+ dataSource.doBeforeParseData =
+ function(oRequest, oFullResponse, oCallback) {
+ if (oFullResponse.error) {
+ oFullResponse.result = {};
+ oFullResponse.result.bugs = [];
+ if (console)
+ console.error("JSON-RPC error:", oFullResponse.error);
+ }
+ return oFullResponse;
+ };
+ dataSource.subscribe('dataErrorEvent',
+ function() {
+ YAHOO.bugzilla.instantSearch.currentSearchQuery = '';
+ }
+ );
+
+ this.dataTable = new YAHOO.widget.DataTable(
+ 'results',
+ this.dataTableColumns,
+ dataSource,
+ {
+ initialLoad: false,
+ MSG_EMPTY: 'No matching bugs found.',
+ MSG_ERROR: 'An error occurred while searching for bugs, please try again.'
+ }
+ );
+ },
+
+ formatId: function(el, oRecord, oColumn, oData) {
+ el.innerHTML = '<a href="show_bug.cgi?id=' + oData + '" target="_blank">' + oData + '</a>';
+ },
+
+ formatStatus: function(el, oRecord, oColumn, oData) {
+ var resolution = oRecord.getData('resolution');
+ var bugStatus = display_value('bug_status', oData);
+ if (resolution) {
+ el.innerHTML = bugStatus + ' ' + display_value('resolution', resolution);
+ } else {
+ el.innerHTML = bugStatus;
+ }
+ },
+
+ reset: function() {
+ Dom.addClass(this.elList, 'hidden');
+ this.elList.innerHTML = '';
+ this.currentSearchQuery = '';
+ this.currentSearchProduct = '';
+ },
+
+ onContentKeyUp: function(e) {
+ clearTimeout(YAHOO.bugzilla.instantSearch.lastTimeout);
+ YAHOO.bugzilla.instantSearch.lastTimeout = setTimeout(function() {
+ YAHOO.bugzilla.instantSearch.doSearch(YAHOO.bugzilla.instantSearch.getContent()) },
+ 600);
+ },
+
+ onProductChange: function(e) {
+ YAHOO.bugzilla.instantSearch.doSearch(YAHOO.bugzilla.instantSearch.getContent());
+ },
+
+ doSearch: function(query) {
+ if (query.length < 4)
+ return;
+
+ // don't query if we already have the results (or they are pending)
+ var product = Dom.get('product').value;
+ if (YAHOO.bugzilla.instantSearch.currentSearchQuery == query &&
+ YAHOO.bugzilla.instantSearch.currentSearchProduct == product)
+ return;
+ YAHOO.bugzilla.instantSearch.currentSearchQuery = query;
+ YAHOO.bugzilla.instantSearch.currentSearchProduct = product;
+
+ // initialise the datatable as late as possible
+ YAHOO.bugzilla.instantSearch.initDataTable();
+
+ try {
+ // run the search
+ Dom.removeClass(YAHOO.bugzilla.instantSearch.elList, 'hidden');
+
+ YAHOO.bugzilla.instantSearch.dataTable.showTableMessage(
+ 'Searching...&nbsp;&nbsp;&nbsp;' +
+ '<img src="extensions/GuidedBugEntry/web/images/throbber.gif"' +
+ ' width="16" height="11">',
+ YAHOO.widget.DataTable.CLASS_LOADING
+ );
+ var jsonObject = {
+ version: "1.1",
+ method: "Bug.possible_duplicates",
+ id: ++YAHOO.bugzilla.instantSearch.counter,
+ params: {
+ product: YAHOO.bugzilla.instantSearch.getProduct(),
+ summary: query,
+ limit: 20,
+ include_fields: [ "id", "summary", "status", "resolution", "component" ]
+ }
+ };
+
+ YAHOO.bugzilla.instantSearch.dataTable.getDataSource().sendRequest(
+ YAHOO.lang.JSON.stringify(jsonObject),
+ {
+ success: YAHOO.bugzilla.instantSearch.onSearchResults,
+ failure: YAHOO.bugzilla.instantSearch.onSearchResults,
+ scope: YAHOO.bugzilla.instantSearch.dataTable,
+ argument: YAHOO.bugzilla.instantSearch.dataTable.getState()
+ }
+ );
+
+ } catch(err) {
+ if (console)
+ console.error(err.message);
+ }
+ },
+
+ onSearchResults: function(sRequest, oResponse, oPayload) {
+ YAHOO.bugzilla.instantSearch.dataTable.onDataReturnInitializeTable(sRequest, oResponse, oPayload);
+ },
+
+ getContent: function() {
+ var content = YAHOO.lang.trim(this.elContent.value);
+ // work around chrome bug
+ if (content == YAHOO.bugzilla.instantSearch.elContent.getAttribute('placeholder')) {
+ return '';
+ } else {
+ return content;
+ }
+ },
+
+ getProduct: function() {
+ var result = [];
+ var name = Dom.get('product').value;
+ result.push(name);
+ if (products[name] && products[name].related) {
+ for (var i = 0, n = products[name].related.length; i < n; i++) {
+ result.push(products[name].related[i]);
+ }
+ }
+ return result;
+ }
+
+};
+
diff --git a/template/en/default/search/search-instant.html.tmpl b/template/en/default/search/search-instant.html.tmpl
new file mode 100644
index 000000000..59cd8c551
--- /dev/null
+++ b/template/en/default/search/search-instant.html.tmpl
@@ -0,0 +1,83 @@
+[%# 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.
+ #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Instant Search"
+ javascript_urls = [ 'extensions/GuidedBugEntry/web/js/products.js',
+ 'js/instant-search.js', ]
+ yui = [ 'datatable', 'container' ]
+%]
+
+[% default.product.push('Firefox') UNLESS default.product.size %]
+
+<script>
+YAHOO.bugzilla.instantSearch.setLabels( {
+ id: "[% field_descs.bug_id FILTER js %]",
+ summary: "[% field_descs.short_desc FILTER js %]",
+ component: "[% field_descs.component FILTER js %]",
+ status: "[% field_descs.bug_status FILTER js %]",
+});
+</script>
+
+[% WRAPPER search/tabs.html.tmpl %]
+
+<p>
+ This page provides instant results, however only the [% terms.bug %]'s summary
+ is searched. Products related to the selected product may also be searched.
+</p>
+
+<table>
+ <tr>
+ <td align="right" valign="baseline">
+ <b><label for="product">Product:</label></b>
+ </td>
+ <td>
+ <select name="product" id="product">
+ [% IF Param('useclassification') %]
+ [% FOREACH c = classification %]
+ <optgroup label="[% c.name FILTER html %]">
+ [% FOREACH p = user.get_selectable_products(c.id) %]
+ [% IF p.components.size %]
+ <option value="[% p.name FILTER html %]"
+ [% " selected" IF lsearch(default.product, p.name) != -1 %]>
+ [% p.name FILTER html %]
+ </option>
+ [% END %]
+ [% END %]
+ </optgroup>
+ [% END %]
+ [% ELSE %]
+ [% FOREACH p = product %]
+ <option value="[% p.name FILTER html %]"
+ [% " selected" IF lsearch(default.product, p.name) != -1 %]>
+ [% p.name FILTER html %]
+ </option>
+ [% END %]
+ [% END %]
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td align="right" valign="baseline">
+ <b><label for="content">Words:</label></b>
+ </td>
+ <td>
+ <input id="content" spellcheck="true" size="60"
+ value="[% default.content.0 FILTER html %]">
+ </td>
+ </tr>
+</table>
+<br>
+
+<div id="results"></div>
+
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/search/tabs.html.tmpl b/template/en/default/search/tabs.html.tmpl
index 6a2f7f70a..26ad4f39b 100644
--- a/template/en/default/search/tabs.html.tmpl
+++ b/template/en/default/search/tabs.html.tmpl
@@ -24,7 +24,9 @@
#%]
[% WRAPPER global/tabs.html.tmpl
- tabs = [ { name => 'specific', label => "Simple Search",
+ tabs = [ { name => 'instant', label => "Instant Search",
+ link => "query.cgi?format=instant" },
+ { name => 'specific', label => "Simple Search",
link => "query.cgi?format=specific" },
{ name => 'advanced', label => "Advanced Search",
link => "query.cgi?format=advanced" },