summaryrefslogtreecommitdiffstats
path: root/extensions/ProdCompSearch
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/ProdCompSearch')
-rw-r--r--extensions/ProdCompSearch/lib/WebService.pm78
-rw-r--r--extensions/ProdCompSearch/template/en/default/prodcompsearch/form.html.tmpl2
-rw-r--r--extensions/ProdCompSearch/web/js/prod_comp_search.js13
-rw-r--r--extensions/ProdCompSearch/web/styles/prod_comp_search.css2
4 files changed, 64 insertions, 31 deletions
diff --git a/extensions/ProdCompSearch/lib/WebService.pm b/extensions/ProdCompSearch/lib/WebService.pm
index 2e2592879..3f8f0b75e 100644
--- a/extensions/ProdCompSearch/lib/WebService.pm
+++ b/extensions/ProdCompSearch/lib/WebService.pm
@@ -13,14 +13,14 @@ use warnings;
use base qw(Bugzilla::WebService);
use Bugzilla::Error;
-use Bugzilla::Util qw(detaint_natural trick_taint);
+use Bugzilla::Util qw(detaint_natural trick_taint trim);
sub prod_comp_search {
my ($self, $params) = @_;
my $user = Bugzilla->user;
my $dbh = Bugzilla->switch_to_shadow_db();
- my $search = $params->{'search'};
+ my $search = trim($params->{'search'} || '');
$search || ThrowCodeError('param_required',
{ function => 'PCS.prod_comp_search', param => 'search' });
@@ -57,16 +57,33 @@ sub prod_comp_search {
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);
- # note: CONCAT_WS is MySQL specific
- my $field = "CONCAT_WS(' ', products.name, products.description,
- components.name, components.description)";
- push(@list, $dbh->sql_iposition($sql_word, $field) . " > 0");
- }
+ trick_taint($search);
+ my @terms;
+ my @order;
+
+ if ($search =~ /^(.*?)::(.*)$/) {
+ my ($product, $component) = (trim($1), trim($2));
+ push @terms, _build_terms($product, 1, 0);
+ push @terms, _build_terms($component, 0, 1);
+ push @order, "products.name != " . $dbh->quote($product) if $product ne '';
+ push @order, "components.name != " . $dbh->quote($component) if $component ne '';
+ push @order, "products.name";
+ push @order, "components.name";
+ } else {
+ push @terms, _build_terms($search, 1, 1);
+ push @order, "products.name != " . $dbh->quote($search);
+ push @order, "components.name != " . $dbh->quote($search);
+ push @order, "products.name";
+ push @order, "components.name";
+ }
+ return { products => [] } if !scalar @terms;
+
+ # To help mozilla staff file bmo administration bugs into the right
+ # component, sort bmo first when searching for 'bugzilla'
+ if ($search =~ /bugzilla/i && $search !~ /^bugzilla\s*::/i
+ && ($user->in_group('mozilla-corporation') || $user->in_group('mozilla-foundation')))
+ {
+ unshift @order, "products.name != 'bugzilla.mozilla.org'";
}
my $products = $dbh->selectall_arrayref("
@@ -74,27 +91,30 @@ sub prod_comp_search {
components.name AS component
FROM products
INNER JOIN components ON products.id = components.product_id
- WHERE (" . join(" AND ", @list) . ")
+ WHERE (" . join(" AND ", @terms) . ")
AND products.id IN (" . join(",", @$enterable_ids) . ")
- ORDER BY products.name $limit",
+ ORDER BY " . join(", ", @order) . " $limit",
{ Slice => {} });
- # To help mozilla staff file bmo administration bugs into the right
- # component, sort bmo in front of bugzilla.
- if ($user->in_group('mozilla-corporation') || $user->in_group('mozilla-foundation')) {
- $products = [
- sort {
- return 1 if $a->{product} eq 'Bugzilla'
- && $b->{product} eq 'bugzilla.mozilla.org';
- return -1 if $b->{product} eq 'Bugzilla'
- && $a->{product} eq 'bugzilla.mozilla.org';
- return lc($a->{product}) cmp lc($b->{product})
- || lc($a->{component}) cmp lc($b->{component});
- } @$products
- ];
- }
-
return { products => $products };
}
+sub _build_terms {
+ my ($query, $product, $component) = @_;
+ my $dbh = Bugzilla->dbh();
+
+ my @fields;
+ push @fields, 'products.name', 'products.description' if $product;
+ push @fields, 'components.name', 'components.description' if $component;
+ # note: CONCAT_WS is MySQL specific
+ my $field = "CONCAT_WS(' ', ". join(',', @fields) . ")";
+
+ my @terms;
+ foreach my $word (split(/[\s,]+/, $query)) {
+ push(@terms, $dbh->sql_iposition($dbh->quote($word), $field) . " > 0")
+ if $word ne '';
+ }
+ return @terms;
+}
+
1;
diff --git a/extensions/ProdCompSearch/template/en/default/prodcompsearch/form.html.tmpl b/extensions/ProdCompSearch/template/en/default/prodcompsearch/form.html.tmpl
index 8d4f46e07..b3d1deab1 100644
--- a/extensions/ProdCompSearch/template/en/default/prodcompsearch/form.html.tmpl
+++ b/extensions/ProdCompSearch/template/en/default/prodcompsearch/form.html.tmpl
@@ -28,6 +28,8 @@
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_error" class="bz_default_hidden">
+ An error occured</span>
</div>
<input id="prod_comp_search" type="text" size="50"
placeholder="Search by product and component keywords">
diff --git a/extensions/ProdCompSearch/web/js/prod_comp_search.js b/extensions/ProdCompSearch/web/js/prod_comp_search.js
index 7cb1ec73b..f6c3f011c 100644
--- a/extensions/ProdCompSearch/web/js/prod_comp_search.js
+++ b/extensions/ProdCompSearch/web/js/prod_comp_search.js
@@ -48,13 +48,23 @@ YUI({
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');
+ }
}
});
dataSource.plug(Y.Plugin.DataSourceJSONSchema, {
schema: {
resultListLocator : "result.products",
- resultFields : [ "product", "component" ]
+ resultFields : [ "product", "component" ],
+ metaFields : { error : 'error' }
}
});
@@ -75,6 +85,7 @@ YUI({
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_error").addClass('bz_default_hidden');
},
results: function(e) {
Y.one("#prod_comp_throbber").addClass('bz_default_hidden');
diff --git a/extensions/ProdCompSearch/web/styles/prod_comp_search.css b/extensions/ProdCompSearch/web/styles/prod_comp_search.css
index 71829023c..a89856e22 100644
--- a/extensions/ProdCompSearch/web/styles/prod_comp_search.css
+++ b/extensions/ProdCompSearch/web/styles/prod_comp_search.css
@@ -15,6 +15,6 @@
width: 360px;
}
-#prod_comp_no_components {
+#prod_comp_no_components, #prod_comp_error {
color: red;
}