Bugzilla – Attachment 69614 Details for
Bug 14567
Add an elasticsearch driven browse interface to the OPAC
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 14567: Add a browse interface to the OPAC
Bug-14567-Add-a-browse-interface-to-the-OPAC.patch (text/plain), 27.96 KB, created by
Nick Clemens (kidclamp)
on 2017-12-07 14:57:28 UTC
(
hide
)
Description:
Bug 14567: Add a browse interface to the OPAC
Filename:
MIME Type:
Creator:
Nick Clemens (kidclamp)
Created:
2017-12-07 14:57:28 UTC
Size:
27.96 KB
patch
obsolete
>From faff3e4ce7acde8f9b5356f9c5c912f456ad3658 Mon Sep 17 00:00:00 2001 >From: Robin Sheat <robin@catalyst.net.nz> >Date: Tue, 21 Jul 2015 17:14:09 +1200 >Subject: [PATCH] Bug 14567: Add a browse interface to the OPAC > >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 - Have ES running and some records in it >2 - Browse to http://localhost:8080/cgi-bin/koha/opac-browse.pl (or url >for your site) >3 - Test searching for author, title, and subject >4 - Verify that results are returned in expected order >5 - Experiment with fuzziness >6 - Click any result and verify specific titles are correct >7 - Click through title to record and verify it is the correct record > >Signed-off-by: Sebastian Hierl <s.hierl@aarome.org> >--- > Koha/SearchEngine/Elasticsearch/Browse.pm | 186 +++++++++++++++++++++ > Koha/SearchEngine/Elasticsearch/Search.pm | 13 +- > .../opac-tmpl/bootstrap/en/modules/opac-browse.tt | 89 ++++++++++ > koha-tmpl/opac-tmpl/bootstrap/js/browse.js | 172 +++++++++++++++++++ > koha-tmpl/opac-tmpl/bootstrap/less/opac.less | 101 +++++++++++ > opac/opac-browse.pl | 114 +++++++++++++ > t/Koha_SearchEngine_Elasticsearch_Browse.t | 68 ++++++++ > 7 files changed, 738 insertions(+), 5 deletions(-) > create mode 100644 Koha/SearchEngine/Elasticsearch/Browse.pm > create mode 100644 koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-browse.tt > create mode 100644 koha-tmpl/opac-tmpl/bootstrap/js/browse.js > create mode 100755 opac/opac-browse.pl > create mode 100755 t/Koha_SearchEngine_Elasticsearch_Browse.t > >diff --git a/Koha/SearchEngine/Elasticsearch/Browse.pm b/Koha/SearchEngine/Elasticsearch/Browse.pm >new file mode 100644 >index 0000000..9eb1536 >--- /dev/null >+++ b/Koha/SearchEngine/Elasticsearch/Browse.pm >@@ -0,0 +1,186 @@ >+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; >+ >+use Carp; >+use Data::Dumper; >+ >+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 C<< <robin@catalyst.net.nz> >> >+ >+=back >+ >+=cut >diff --git a/Koha/SearchEngine/Elasticsearch/Search.pm b/Koha/SearchEngine/Elasticsearch/Search.pm >index 4d584f7..14c02dc 100644 >--- a/Koha/SearchEngine/Elasticsearch/Search.pm >+++ b/Koha/SearchEngine/Elasticsearch/Search.pm >@@ -78,17 +78,20 @@ Returns > =cut > > sub search { >- my ($self, $query, $page, $count, %options) = @_; >+ my ( $self, $query, $page, $count, %options ) = @_; > > my $params = $self->get_elasticsearch_params(); > my %paging; >+ > # 20 is the default number of results per page > $paging{limit} = $count || 20; >+ > # ES/Catmandu doesn't want pages, it wants a record to start from. >- if (exists $options{offset}) { >+ if ( exists $options{offset} ) { > $paging{start} = $options{offset}; >- } else { >- $page = (!defined($page) || ($page <= 0)) ? 0 : $page - 1; >+ } >+ else { >+ $page = ( !defined($page) || ( $page <= 0 ) ) ? 0 : $page - 1; > $paging{start} = $page * $paging{limit}; > } > $self->store( >@@ -168,7 +171,7 @@ sub search_compat { > # consumers of this expect a name-spaced result, we provide the default > # configuration. > my %result; >- $result{biblioserver}{hits} = $results->total; >+ $result{biblioserver}{hits} = $results->total; > $result{biblioserver}{RECORDS} = \@records; > return (undef, \%result, $self->_convert_facets($results->{aggregations}, $expanded_facet)); > } >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-browse.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-browse.tt >new file mode 100644 >index 0000000..eba2c10 >--- /dev/null >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-browse.tt >@@ -0,0 +1,89 @@ >+[% USE Koha %] >+[% INCLUDE 'doc-head-open.inc' %] >+<title>[% IF ( LibraryNameTitle ) %][% LibraryNameTitle %][% 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 ( 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 %]/[% theme %]/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 %]/[% theme %]/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 --> >+ </div><!-- / .row-fluid --> >+ </div><!-- / .container-fluid --> >+</div><!-- / .main --> >+[% INCLUDE 'opac-bottom.inc' %] >+[% BLOCK jsinclude %] >+<script type="text/javascript" src="[% interface %]/[% theme %]/js/browse.js"> >+</script> >+[% END %] >diff --git a/koha-tmpl/opac-tmpl/bootstrap/js/browse.js b/koha-tmpl/opac-tmpl/bootstrap/js/browse.js >new file mode 100644 >index 0000000..26272b1 >--- /dev/null >+++ b/koha-tmpl/opac-tmpl/bootstrap/js/browse.js >@@ -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'); >+ } >+ }); >+ >+ }); >+ >+}); >diff --git a/koha-tmpl/opac-tmpl/bootstrap/less/opac.less b/koha-tmpl/opac-tmpl/bootstrap/less/opac.less >index 8423e6c..ddab8ae 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/less/opac.less >+++ b/koha-tmpl/opac-tmpl/bootstrap/less/opac.less >@@ -312,6 +312,107 @@ td { > } > } > >+/*opac browse search*/ >+#browse-search { >+ >+ form { >+ label { >+ display: inline-block; >+ margin-right:5px; >+ } >+ >+ [type=submit] { >+ margin-top: 10px; >+ } >+ } >+ >+ #browse-resultswrapper { >+ margin-top: 4em; >+ >+ @media (min-width: 768px) and (max-width: 984px) { >+ margin-top: 2em; >+ } >+ >+ @media (max-width: 767px) { >+ margin-top: 1em; >+ } >+ } >+ #browse-searchresults, #browse-selectionsearch { >+ border: 1px solid #E3E3E3; >+ .border-radius-all(4px); >+ padding: 0; >+ overflow-y: auto; >+ max-height: 31em; >+ margin-bottom: 2em; >+ } >+ #browse-searchresults { >+ max-height: 31em; >+ list-style: none; >+ padding: 10px; >+ >+ a { >+ display: block; >+ margin-bottom: 5px; >+ >+ &.selected { >+ background-color:#EEE; >+ } >+ } >+ >+ li:last-child a { >+ margin-bottom: 0; >+ } >+ >+ @media (max-width: 767px) { >+ max-height: 13em; >+ } >+ } >+ #browse-selection { >+ margin-top: -40px; >+ padding-top: 0; >+ >+ @media (max-width: 767px) { >+ margin-top: 0; >+ } >+ } >+ #browse-selectionsearch ol { >+ list-style: none; >+ margin: 0; >+ >+ li { >+ padding: 1em; >+ >+ &:nth-child(odd) { >+ background-color: #F4F4F4; >+ } >+ } >+ } >+ #browse-selectionsearch p.subjects { >+ font-size: 0.9em; >+ margin-bottom: 0; >+ } >+ #browse-selectionsearch h4 { >+ margin: 0; >+ } >+ .error, .no-results { >+ background-color: #EEE; >+ border: 1px solid #E8E8E8; >+ text-align: left; >+ padding: 0.5em; >+ .border-radius-all(3px); >+ } >+ .loading { >+ text-align: center; >+ >+ img { >+ margin:0.5em 0; >+ position: relative; >+ left: -5px; >+ } >+ } >+} >+/*end browse search*/ >+ > /* Override Bootstrap alert */ > .alert { > background: #fffbe5; /* Old browsers */ >diff --git a/opac/opac-browse.pl b/opac/opac-browse.pl >new file mode 100755 >index 0000000..6aa1a00 >--- /dev/null >+++ b/opac/opac-browse.pl >@@ -0,0 +1,114 @@ >+#!/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; >+ >+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)'; >+ >+# This is the temporary entrance point to the API. Once bug #13799 is settled, >+# it should be ported to using that. >+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} ); >+ print header( >+ -type => 'application/json', >+ -charset => 'utf-8' >+ ); >+ print to_json( \@output ); >+} >+ >+# This is a temporary, MARC21-only thing that will grab titles, and authors >+# 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->{es_id}; >+ my $biblio = Koha::Biblios->find({ biblionumber=>$biblionumber }); >+warn $biblio->title; >+ push @output, >+ { >+ id => $biblionumber, >+ title => $biblio->title, >+ author => $biblio->author, >+ }; >+ }; >+ my @sorted = sort { lc($a->{title}) cmp lc($b->{title}) } @output; >+ return @sorted; >+} >diff --git a/t/Koha_SearchEngine_Elasticsearch_Browse.t b/t/Koha_SearchEngine_Elasticsearch_Browse.t >new file mode 100755 >index 0000000..d792734 >--- /dev/null >+++ b/t/Koha_SearchEngine_Elasticsearch_Browse.t >@@ -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(); >-- >2.1.4
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 14567
:
41096
|
41804
|
41805
|
41806
|
56253
|
56254
|
56255
|
56256
|
56262
|
56263
|
56264
|
56270
|
56271
|
56272
|
56273
|
57141
|
61660
|
64477
|
64478
|
64779
|
69614
|
83127
|
83128
|
85401
|
85402
|
85403
|
85887
|
85888
|
86453
|
98509
|
99060
|
99292
|
99298