Browse Source
This is an interface for quick and efficient browsing through records. It presents a page at /cgi-bin/koha/opac-browse.pl that allows you to enter the prefix of an author, title, or subject and it'll give you a list of the options that match that. You can then scroll through these and select the one you're after. Selecting it provides a list of records that match that particular search. To Test: 1 - Apply patches 2 - Update database (updatedatabase on kohadevbox) 3 - Compile the CSS https://wiki.koha-community.org/wiki/Working_with_SCSS_in_the_OPAC_and_staff_client yarn build --view=opac on kohadevbox 4 - Enable the new syspref OpacBrowseSearch 5 - Have ES running and some records in it SearchEngine syspref set to Elasticsearch 6 - Browse to opac home, click 'Browse search' link for your site) 7 - Test searching for author, title, and subject 8 - Verify that results are returned in expected order 9 - Experiment with fuzziness https://www.elastic.co/guide/en/elasticsearch/reference/5.6/common-options.html#fuzziness Options are: exact (0 edits), fuzzy (1 edit), very fuzzy (2 edits) 10 - Click any result and verify specific titles are correct 11 - Click through title to record and verify it is the correct record 12 - Test that disabling pref removes the link on the opac home Signed-off-by: David Nind <david@davidnind.com> Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de> Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>20.05.x
Robin Sheat
9 years ago
committed by
Martin Renvoize
11 changed files with 750 additions and 0 deletions
@ -0,0 +1,183 @@ |
|||||
|
package Koha::SearchEngine::Elasticsearch::Browse; |
||||
|
|
||||
|
# Copyright 2015 Catalyst IT |
||||
|
# |
||||
|
# This file is part of Koha. |
||||
|
# |
||||
|
# Koha is free software; you can redistribute it and/or modify it under the |
||||
|
# terms of the GNU General Public License as published by the Free Software |
||||
|
# Foundation; either version 3 of the License, or (at your option) any later |
||||
|
# version. |
||||
|
# |
||||
|
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY |
||||
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR |
||||
|
# A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU General Public License along |
||||
|
# with Koha; if not, write to the Free Software Foundation, Inc., |
||||
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
|
|
||||
|
=head1 NAME |
||||
|
|
||||
|
Koha::SearchEngine::ElasticSearch::Browse - browse functions for Elasticsearch |
||||
|
|
||||
|
=head1 SYNOPSIS |
||||
|
|
||||
|
my $browser = |
||||
|
Koha::SearchEngine::Elasticsearch::Browse->new( { index => 'biblios' } ); |
||||
|
my $results = $browser->browse( |
||||
|
'prefi', 'title', |
||||
|
{ |
||||
|
results => '500', |
||||
|
fuzziness => 2, |
||||
|
} |
||||
|
); |
||||
|
foreach my $r (@$results) { |
||||
|
push @hits, $r->{text}; |
||||
|
} |
||||
|
|
||||
|
=head1 DESCRIPTION |
||||
|
|
||||
|
This provides an easy interface to the "browse" functionality. Essentially, |
||||
|
it does a fast prefix search on defined fields. The fields have to be marked |
||||
|
as "suggestible" in the database when indexing takes place. |
||||
|
|
||||
|
=head1 METHODS |
||||
|
|
||||
|
=cut |
||||
|
|
||||
|
use base qw(Koha::SearchEngine::Elasticsearch); |
||||
|
use Modern::Perl; |
||||
|
|
||||
|
use Catmandu::Store::ElasticSearch; |
||||
|
|
||||
|
Koha::SearchEngine::Elasticsearch::Browse->mk_accessors(qw( store )); |
||||
|
|
||||
|
=head2 browse |
||||
|
|
||||
|
my $results = $browser->browse($prefix, $field, \%options); |
||||
|
|
||||
|
Does a prefix search for C<$prefix>, looking in C<$field>. Options are: |
||||
|
|
||||
|
=over 4 |
||||
|
|
||||
|
=item count |
||||
|
|
||||
|
The number of results to return. For Koha browse purposes, this should |
||||
|
probably be fairly high. Defaults to 500. |
||||
|
|
||||
|
=item fuzziness |
||||
|
|
||||
|
How much allowing for typos and misspellings is done. If 0, then it must match |
||||
|
exactly. If unspecified, it defaults to '1', which is probably the most useful. |
||||
|
Otherwise, it is a number specifying the Levenshtein edit distance relative to |
||||
|
the string length, according to the following lengths: |
||||
|
|
||||
|
=over 4 |
||||
|
|
||||
|
=item 0..2 |
||||
|
|
||||
|
must match exactly |
||||
|
|
||||
|
=item 3..5 |
||||
|
|
||||
|
C<fuzziness> edits allowed |
||||
|
|
||||
|
=item >5 |
||||
|
|
||||
|
C<fuzziness>+1 edits allowed |
||||
|
|
||||
|
=back |
||||
|
|
||||
|
In all cases the maximum number of edits allowed is two (an elasticsearch |
||||
|
restriction.) |
||||
|
|
||||
|
=back |
||||
|
|
||||
|
=head3 Returns |
||||
|
|
||||
|
This returns an arrayref of hashrefs. Each hashref contains a "text" element |
||||
|
that contains the field as returned. There may be other fields in that |
||||
|
hashref too, but they're less likely to be important. |
||||
|
|
||||
|
The array will be ordered as returned from Elasticsearch, which seems to be |
||||
|
in order of some form of relevance. |
||||
|
|
||||
|
=cut |
||||
|
|
||||
|
sub browse { |
||||
|
my ($self, $prefix, $field, $options) = @_; |
||||
|
|
||||
|
my $params = $self->get_elasticsearch_params(); |
||||
|
$self->store( |
||||
|
Catmandu::Store::ElasticSearch->new( |
||||
|
%$params, |
||||
|
) |
||||
|
) unless $self->store; |
||||
|
|
||||
|
my $query = $self->_build_query($prefix, $field, $options); |
||||
|
my $results = $self->store->bag->search(%$query); |
||||
|
return $results->{suggest}{suggestions}[0]{options}; |
||||
|
} |
||||
|
|
||||
|
=head2 _build_query |
||||
|
|
||||
|
my $query = $self->_build_query($prefix, $field, $options); |
||||
|
|
||||
|
Arguments are the same as for L<browse>. This will return a query structure |
||||
|
for elasticsearch to use. |
||||
|
|
||||
|
=cut |
||||
|
|
||||
|
sub _build_query { |
||||
|
my ( $self, $prefix, $field, $options ) = @_; |
||||
|
|
||||
|
$options = {} unless $options; |
||||
|
my $f = $options->{fuzziness} // 1; |
||||
|
my $l = length($prefix); |
||||
|
my $fuzzie; |
||||
|
if ( $l <= 2 ) { |
||||
|
$fuzzie = 0; |
||||
|
} |
||||
|
elsif ( $l <= 5 ) { |
||||
|
$fuzzie = $f; |
||||
|
} |
||||
|
else { |
||||
|
$fuzzie = $f + 1; |
||||
|
} |
||||
|
$fuzzie = 2 if $fuzzie > 2; |
||||
|
|
||||
|
my $size = $options->{count} // 500; |
||||
|
my $query = { |
||||
|
# this is an annoying thing, if we set size to 0 it gets rewritten |
||||
|
# to 10. There's a bug somewhere in one of the libraries. |
||||
|
size => 1, |
||||
|
suggest => { |
||||
|
suggestions => { |
||||
|
text => $prefix, |
||||
|
completion => { |
||||
|
field => $field . '__suggestion', |
||||
|
size => $size, |
||||
|
fuzzy => { |
||||
|
fuzziness => $fuzzie, |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
return $query; |
||||
|
} |
||||
|
|
||||
|
1; |
||||
|
|
||||
|
__END__ |
||||
|
|
||||
|
=head1 AUTHOR |
||||
|
|
||||
|
=over 4 |
||||
|
|
||||
|
=item Robin Sheat << <robin@catalyst.net.nz> >> |
||||
|
|
||||
|
=back |
||||
|
|
||||
|
=cut |
@ -0,0 +1,10 @@ |
|||||
|
$DBversion = 'XXX'; |
||||
|
if( CheckVersion( $DBversion ) ) { |
||||
|
$dbh->do(q{ |
||||
|
INSERT IGNORE INTO systempreferences (variable,value,options,explanation,type) |
||||
|
VALUES |
||||
|
('OpacBrowseSearch', '0',NULL, "Elasticsearch only: add a page allowing users to 'browse' all items in the collection",'YesNo') |
||||
|
}); |
||||
|
SetVersion( $DBversion ); |
||||
|
print "Upgrade to $DBversion done (Bug 14567: Add OpacBrowseSearch syspref)\n"; |
||||
|
} |
@ -0,0 +1,94 @@ |
|||||
|
[% USE Koha %] |
||||
|
[% USE Asset %] |
||||
|
[% USE raw %] |
||||
|
[% INCLUDE 'doc-head-open.inc' %] |
||||
|
<title>[% IF ( LibraryNameTitle ) %][% LibraryNameTitle | html %][% ELSE %]Koha online[% END %] catalog › Browse our catalog</title> |
||||
|
[% INCLUDE 'doc-head-close.inc' %] |
||||
|
[% BLOCK cssinclude %][% END %] |
||||
|
[% INCLUDE 'bodytag.inc' bodyid='opac-browser' %] |
||||
|
[% INCLUDE 'masthead.inc' %] |
||||
|
|
||||
|
<div class="main"> |
||||
|
<ul class="breadcrumb"> |
||||
|
<li><a href="/cgi-bin/koha/opac-main.pl">Home</a> |
||||
|
<span class="divider">›</span></li> |
||||
|
|
||||
|
<li><a href="#">Browse search</a></li> |
||||
|
</ul> |
||||
|
|
||||
|
<div class="container-fluid"> |
||||
|
<div class="row-fluid"> |
||||
|
[% IF Koha.Preference('SearchEngine') == 'Elasticsearch' && Koha.Preference('OpacBrowseSearch') %] |
||||
|
[% IF ( OpacNav || OpacNavBottom ) %] |
||||
|
|
||||
|
<div class="span2"> |
||||
|
<div id="navigation"> |
||||
|
[% INCLUDE 'navigation.inc' %] |
||||
|
</div> |
||||
|
</div> |
||||
|
[% END %] |
||||
|
|
||||
|
[% IF ( OpacNav ) %] |
||||
|
|
||||
|
<div class="span10"> |
||||
|
[% ELSE %] |
||||
|
|
||||
|
<div class="span12"> |
||||
|
[% END %] |
||||
|
|
||||
|
<div id="browse-search"> |
||||
|
<h1>Browse search</h1> |
||||
|
|
||||
|
<form> |
||||
|
<label for="browse-searchterm">Search for:</label> |
||||
|
<input type="search" id="browse-searchterm" name="searchterm" value=""> |
||||
|
<label for="browse-searchfield" class="hide-text">Search type:</label> |
||||
|
<select id="browse-searchfield" name="searchfield"> |
||||
|
<option value="author">Author</option> |
||||
|
<option value="subject">Subject</option> |
||||
|
<option value="title">Title</option> |
||||
|
</select> |
||||
|
|
||||
|
<div id="browse-searchfuzziness"> |
||||
|
<label for="exact" class="radio inline"><input type="radio" name="browse-searchfuzziness" id="exact" value="0">Exact</label> |
||||
|
<label for="fuzzy" class="radio inline"><input type="radio" name="browse-searchfuzziness" id="fuzzy" value="1" checked="checked"> Fuzzy</label> |
||||
|
<label for="reallyfuzzy" class="radio inline"><input type="radio" name="browse-searchfuzziness" id="reallyfuzzy" value="2"> Really fuzzy</label> |
||||
|
</div> |
||||
|
<button class="btn btn-success" type="submit" accesskey="s">Search</button> |
||||
|
</form> |
||||
|
|
||||
|
<p id="browse-suggestionserror" class="error hidden"> |
||||
|
An error occurred, please try again.</p> |
||||
|
|
||||
|
<div id="browse-resultswrapper" class="hidden"> |
||||
|
<ul id="browse-searchresults" class="span3" start="-1" aria-live="polite"> |
||||
|
<li class="loading hidden"><img src="[% interface | html %]/[% theme |html %]/images/loading.gif" alt=""> Loading</li> |
||||
|
|
||||
|
<li class="no-results hidden">Sorry, there are no results, try a different search term.</li> |
||||
|
</ul> |
||||
|
|
||||
|
<h3 id="browse-selection" class="span9"></h3> |
||||
|
|
||||
|
<div id="browse-selectionsearch" class="span9 hidden"> |
||||
|
<div class="loading hidden"> |
||||
|
<img src="[% interface | html %]/[% theme | html %]/images/loading.gif" alt=""> Loading |
||||
|
</div> |
||||
|
|
||||
|
<div class="no-results hidden">No results</div> |
||||
|
|
||||
|
<ol aria-live="polite"></ol> |
||||
|
</div> |
||||
|
</div><!-- / .results-wrapper --> |
||||
|
</div><!-- / .userbrowser --> |
||||
|
</div><!-- / .span10 --> |
||||
|
[% ELSE %] |
||||
|
<h3>This feature is not enabled</h3> |
||||
|
[% END %] |
||||
|
</div><!-- / .row-fluid --> |
||||
|
</div><!-- / .container-fluid --> |
||||
|
</div><!-- / .main --> |
||||
|
[% INCLUDE 'opac-bottom.inc' %] |
||||
|
[% BLOCK jsinclude %] |
||||
|
[% Asset.js("/js/browse.js") | $raw %] |
||||
|
</script> |
||||
|
[% END %] |
@ -0,0 +1,172 @@ |
|||||
|
jQuery.fn.overflowScrollReset = function() { |
||||
|
$(this).scrollTop($(this).scrollTop() - $(this).offset().top); |
||||
|
return this; |
||||
|
}; |
||||
|
|
||||
|
$(document).ready(function(){ |
||||
|
var xhrGetSuggestions, xhrGetResults; |
||||
|
|
||||
|
$('#browse-search form').submit(function(event) { |
||||
|
// if there is an in progress request, abort it so we
|
||||
|
// don't end up with a race condition
|
||||
|
if(xhrGetSuggestions && xhrGetSuggestions.readyState != 4){ |
||||
|
xhrGetSuggestions.abort(); |
||||
|
} |
||||
|
|
||||
|
var userInput = $('#browse-searchterm').val().trim(); |
||||
|
var userField = $('#browse-searchfield').val(); |
||||
|
var userFuzziness = $('input[name=browse-searchfuzziness]:checked', '#browse-searchfuzziness').val(); |
||||
|
var leftPaneResults = $('#browse-searchresults li').not('.loading, .no-results'); |
||||
|
var rightPaneResults = $('#browse-selectionsearch ol li'); |
||||
|
|
||||
|
event.preventDefault(); |
||||
|
|
||||
|
if(!userInput) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// remove any error states and show the results area (except right pane)
|
||||
|
$('#browse-suggestionserror').addClass('hidden'); |
||||
|
$('#browse-searchresults .no-results').addClass('hidden'); |
||||
|
$('#browse-resultswrapper').removeClass('hidden'); |
||||
|
$('#browse-selection').addClass('hidden').text(""); |
||||
|
$('#browse-selectionsearch').addClass('hidden'); |
||||
|
|
||||
|
// clear any results from left and right panes
|
||||
|
leftPaneResults.remove(); |
||||
|
rightPaneResults.remove(); |
||||
|
|
||||
|
// show the spinner in the left pane
|
||||
|
$('#browse-searchresults .loading').removeClass('hidden'); |
||||
|
|
||||
|
xhrGetSuggestions = $.get(window.location.pathname, {api: "GetSuggestions", field: userField, prefix: userInput, fuzziness: userFuzziness}) |
||||
|
.always(function() { |
||||
|
// hide spinner
|
||||
|
$('#browse-searchresults .loading').addClass('hidden'); |
||||
|
}) |
||||
|
.done(function(data) { |
||||
|
var fragment = document.createDocumentFragment(); |
||||
|
|
||||
|
if (data.length === 0) { |
||||
|
$('#browse-searchresults .no-results').removeClass('hidden'); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// scroll to top of container again
|
||||
|
$("#browse-searchresults").overflowScrollReset(); |
||||
|
|
||||
|
// store the type of search that was performed as an attrib
|
||||
|
$('#browse-searchresults').data('field', userField); |
||||
|
|
||||
|
$.each(data, function(index, object) { |
||||
|
// use a document fragment so we don't need to nest the elems
|
||||
|
// or append during each iteration (which would be slow)
|
||||
|
var elem = document.createElement("li"); |
||||
|
var link = document.createElement("a"); |
||||
|
link.textContent = object.text; |
||||
|
link.setAttribute("href", "#"); |
||||
|
elem.appendChild(link); |
||||
|
fragment.appendChild(elem); |
||||
|
}); |
||||
|
|
||||
|
$('#browse-searchresults').append(fragment.cloneNode(true)); |
||||
|
}) |
||||
|
.fail(function(jqXHR) { |
||||
|
//if 500 or 404 (abort is okay though)
|
||||
|
if (jqXHR.statusText !== "abort") { |
||||
|
$('#browse-resultswrapper').addClass('hidden'); |
||||
|
$('#browse-suggestionserror').removeClass('hidden'); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
$('#browse-searchresults').on("click", 'a', function(event) { |
||||
|
// if there is an in progress request, abort it so we
|
||||
|
// don't end up with a race condition
|
||||
|
if(xhrGetResults && xhrGetResults.readyState != 4){ |
||||
|
xhrGetResults.abort(); |
||||
|
} |
||||
|
|
||||
|
var term = $(this).text(); |
||||
|
var field = $('#browse-searchresults').data('field'); |
||||
|
var rightPaneResults = $('#browse-selectionsearch ol li'); |
||||
|
|
||||
|
event.preventDefault(); |
||||
|
|
||||
|
// clear any current selected classes and add a new one
|
||||
|
$(this).parent().siblings().children().removeClass('selected'); |
||||
|
$(this).addClass('selected'); |
||||
|
|
||||
|
// copy in the clicked text
|
||||
|
$('#browse-selection').removeClass('hidden').text(term); |
||||
|
|
||||
|
// show the right hand pane if it is not shown already
|
||||
|
$('#browse-selectionsearch').removeClass('hidden'); |
||||
|
|
||||
|
// hide the no results element
|
||||
|
$('#browse-selectionsearch .no-results').addClass('hidden'); |
||||
|
|
||||
|
// clear results
|
||||
|
rightPaneResults.remove(); |
||||
|
|
||||
|
// turn the spinner on
|
||||
|
$('#browse-selectionsearch .loading').removeClass('hidden'); |
||||
|
|
||||
|
// do the query for the term
|
||||
|
xhrGetResults = $.get(window.location.pathname, {api: "GetResults", field: field, term: term}) |
||||
|
.always(function() { |
||||
|
// hide spinner
|
||||
|
$('#browse-selectionsearch .loading').addClass('hidden'); |
||||
|
}) |
||||
|
.done(function(data) { |
||||
|
var fragment = document.createDocumentFragment(); |
||||
|
|
||||
|
if (data.length === 0) { |
||||
|
$('#browse-selectionsearch .no-results').removeClass('hidden'); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// scroll to top of container again
|
||||
|
$("#browse-selectionsearch").overflowScrollReset(); |
||||
|
|
||||
|
$.each(data, function(index, object) { |
||||
|
// use a document fragment so we don't need to nest the elems
|
||||
|
// or append during each iteration (which would be slow)
|
||||
|
var elem = document.createElement("li"); |
||||
|
var title = document.createElement("h4"); |
||||
|
var link = document.createElement("a"); |
||||
|
var author = document.createElement("p"); |
||||
|
var destination = window.location.pathname; |
||||
|
|
||||
|
destination = destination.replace("browse", "detail"); |
||||
|
destination = destination + "?biblionumber=" + object.id; |
||||
|
|
||||
|
author.className = "author"; |
||||
|
|
||||
|
link.setAttribute("href", destination); |
||||
|
link.setAttribute("target", "_blank"); |
||||
|
link.textContent = object.title; |
||||
|
title.appendChild(link); |
||||
|
|
||||
|
author.textContent = object.author; |
||||
|
|
||||
|
elem.appendChild(title); |
||||
|
elem.appendChild(author); |
||||
|
fragment.appendChild(elem); |
||||
|
}); |
||||
|
|
||||
|
$('#browse-selectionsearch ol').append(fragment.cloneNode(true)); |
||||
|
}) |
||||
|
.fail(function(jqXHR) { |
||||
|
//if 500 or 404 (abort is okay though)
|
||||
|
if (jqXHR.statusText !== "abort") { |
||||
|
$('#browse-resultswrapper').addClass('hidden'); |
||||
|
$('#browse-suggestionserror').removeClass('hidden'); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
}); |
@ -0,0 +1,112 @@ |
|||||
|
#!/usr/bin/perl |
||||
|
|
||||
|
# This is a CGI script that handles the browse feature. |
||||
|
|
||||
|
# Copyright 2015 Catalyst IT |
||||
|
# |
||||
|
# This file is part of Koha. |
||||
|
# |
||||
|
# Koha is free software; you can redistribute it and/or modify it under the |
||||
|
# terms of the GNU General Public License as published by the Free Software |
||||
|
# Foundation; either version 3 of the License, or (at your option) any later |
||||
|
# version. |
||||
|
# |
||||
|
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY |
||||
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR |
||||
|
# A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU General Public License along |
||||
|
# with Koha; if not, write to the Free Software Foundation, Inc., |
||||
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
|
|
||||
|
use Modern::Perl; |
||||
|
use CGI qw ( -utf8 ); |
||||
|
|
||||
|
use C4::Auth; |
||||
|
use C4::Context; |
||||
|
use C4::Output; |
||||
|
|
||||
|
use Koha::SearchEngine::Elasticsearch; |
||||
|
use Koha::SearchEngine::Elasticsearch::Browse; |
||||
|
use Koha::SearchEngine::Elasticsearch::QueryBuilder; |
||||
|
use Koha::SearchEngine::Elasticsearch::Search; |
||||
|
|
||||
|
use JSON; |
||||
|
use Unicode::Collate; |
||||
|
|
||||
|
my $query = new CGI; |
||||
|
binmode STDOUT, ':encoding(UTF-8)'; |
||||
|
|
||||
|
# If calling via JS, 'api' is used to route to correct step in process |
||||
|
my $api = $query->param('api'); |
||||
|
|
||||
|
if ( !$api ) { |
||||
|
my ( $template, $loggedinuser, $cookie ) = get_template_and_user( |
||||
|
{ |
||||
|
template_name => "opac-browse.tt", |
||||
|
query => $query, |
||||
|
type => "opac", |
||||
|
authnotrequired => ( C4::Context->preference("OpacPublic") ? 1 : 0 ), |
||||
|
} |
||||
|
); |
||||
|
$template->param(); |
||||
|
output_html_with_http_headers $query, $cookie, $template->output; |
||||
|
|
||||
|
|
||||
|
} |
||||
|
elsif ( $api eq 'GetSuggestions' ) { |
||||
|
my $fuzzie = $query->param('fuzziness'); |
||||
|
my $prefix = $query->param('prefix'); |
||||
|
my $field = $query->param('field'); |
||||
|
|
||||
|
# Under a persistent environment, we should probably not reinit this every time. |
||||
|
my $browser = Koha::SearchEngine::Elasticsearch::Browse->new( { index => 'biblios' } ); |
||||
|
my $res = $browser->browse( $prefix, $field, { fuzziness => $fuzzie } ); |
||||
|
|
||||
|
my %seen; |
||||
|
my @sorted = |
||||
|
grep { !$seen{$_->{text}}++ } |
||||
|
sort { lc($a->{text}) cmp lc($b->{text}) } @$res; |
||||
|
print CGI::header( |
||||
|
-type => 'application/json', |
||||
|
-charset => 'utf-8' |
||||
|
); |
||||
|
print to_json( \@sorted ); |
||||
|
} |
||||
|
elsif ( $api eq 'GetResults' ) { |
||||
|
my $term = $query->param('term'); |
||||
|
my $field = $query->param('field'); |
||||
|
|
||||
|
my $builder = Koha::SearchEngine::Elasticsearch::QueryBuilder->new( { index => 'biblios' } ); |
||||
|
my $searcher = Koha::SearchEngine::Elasticsearch::Search->new( |
||||
|
{ index => $Koha::SearchEngine::Elasticsearch::BIBLIOS_INDEX } ); |
||||
|
|
||||
|
my $query = { query => { term => { $field.".raw" => $term } } } ; |
||||
|
my $results = $searcher->search( $query, undef, 500 ); |
||||
|
my @output = _filter_for_output( $results->{hits}->{hits} ); |
||||
|
print CGI::header( |
||||
|
-type => 'application/json', |
||||
|
-charset => 'utf-8' |
||||
|
); |
||||
|
print to_json( \@output ); |
||||
|
} |
||||
|
|
||||
|
# This should probably be done with some templatey gizmo |
||||
|
# in the future. |
||||
|
sub _filter_for_output { |
||||
|
my ($records) = @_; |
||||
|
my @output; |
||||
|
foreach my $rec (@$records) { |
||||
|
my $biblionumber = $rec->{_id}; |
||||
|
my $biblio = Koha::Biblios->find( $biblionumber ); |
||||
|
next unless $biblio; |
||||
|
push @output, |
||||
|
{ |
||||
|
id => $biblionumber, |
||||
|
title => $biblio->title, |
||||
|
author => $biblio->author, |
||||
|
}; |
||||
|
}; |
||||
|
my @sorted = sort { lc($a->{title}) cmp lc($b->{title}) } @output; |
||||
|
return @sorted; |
||||
|
} |
@ -0,0 +1,68 @@ |
|||||
|
#!/usr/bin/perl |
||||
|
|
||||
|
# Copyright 2015 Catalyst IT |
||||
|
# |
||||
|
# This file is part of Koha. |
||||
|
# |
||||
|
# Koha is free software; you can redistribute it and/or modify it under the |
||||
|
# terms of the GNU General Public License as published by the Free Software |
||||
|
# Foundation; either version 3 of the License, or (at your option) any later |
||||
|
# version. |
||||
|
# |
||||
|
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY |
||||
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR |
||||
|
# A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU General Public License along |
||||
|
# with Koha; if not, write to the Free Software Foundation, Inc., |
||||
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
|
|
||||
|
use Modern::Perl; |
||||
|
|
||||
|
use Test::More; |
||||
|
|
||||
|
use_ok('Koha::SearchEngine::Elasticsearch::Browse'); |
||||
|
|
||||
|
# testing browse itself not implemented as it'll require a running ES |
||||
|
can_ok('Koha::SearchEngine::Elasticsearch::Browse', |
||||
|
qw/ _build_query browse /); |
||||
|
|
||||
|
subtest "_build_query tests" => sub { |
||||
|
plan tests => 2; |
||||
|
|
||||
|
my $browse = Koha::SearchEngine::Elasticsearch::Browse->new({index=>'dummy'}); |
||||
|
my $q = $browse->_build_query('foo', 'title'); |
||||
|
is_deeply($q, { size => 1, |
||||
|
suggest => { |
||||
|
suggestions => { |
||||
|
text => 'foo', |
||||
|
completion => { |
||||
|
field => 'title__suggestion', |
||||
|
size => 500, |
||||
|
fuzzy => { |
||||
|
fuzziness => 1, |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, 'No fuzziness or size specified'); |
||||
|
|
||||
|
# Note that a fuzziness of 4 will get reduced to 2. |
||||
|
$q = $browse->_build_query('foo', 'title', { fuzziness => 4, count => 400 }); |
||||
|
is_deeply($q, { size => 1, |
||||
|
suggest => { |
||||
|
suggestions => { |
||||
|
text => 'foo', |
||||
|
completion => { |
||||
|
field => 'title__suggestion', |
||||
|
size => 400, |
||||
|
fuzzy => { |
||||
|
fuzziness => 2, |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, 'Fuzziness and size specified'); |
||||
|
}; |
||||
|
|
||||
|
done_testing(); |
Loading…
Reference in new issue