View | Details | Raw Unified | Return to bug 14567
Collapse All | Expand All

(-)a/Koha/SearchEngine/Elasticsearch/Browse.pm (+186 lines)
Line 0 Link Here
1
package Koha::SearchEngine::Elasticsearch::Browse;
2
3
# Copyright 2015 Catalyst IT
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 3 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
=head1 NAME
21
22
Koha::SearchEngine::ElasticSearch::Browse - browse functions for Elasticsearch
23
24
=head1 SYNOPSIS
25
26
    my $browser =
27
      Koha::SearchEngine::Elasticsearch::Browse->new( { index => 'biblios' } );
28
    my $results = $browser->browse(
29
        'prefi', 'title',
30
        {
31
            results   => '500',
32
            fuzziness => 2,
33
        }
34
    );
35
    foreach my $r (@$results) {
36
        push @hits, $r->{text};
37
    }
38
39
=head1 DESCRIPTION
40
41
This provides an easy interface to the "browse" functionality. Essentially,
42
it does a fast prefix search on defined fields. The fields have to be marked
43
as "suggestible" in the database when indexing takes place.
44
45
=head1 METHODS
46
47
=cut
48
49
use base qw(Koha::SearchEngine::Elasticsearch);
50
use Modern::Perl;
51
52
use Catmandu::Store::ElasticSearch;
53
54
use Carp;
55
use Data::Dumper;
56
57
Koha::SearchEngine::Elasticsearch::Browse->mk_accessors(qw( store ));
58
59
=head2 browse
60
61
    my $results = $browser->browse($prefix, $field, \%options);
62
63
Does a prefix search for C<$prefix>, looking in C<$field>. Options are:
64
65
=over 4
66
67
=item count
68
69
The number of results to return. For Koha browse purposes, this should
70
probably be fairly high. Defaults to 500.
71
72
=item fuzziness
73
74
How much allowing for typos and misspellings is done. If 0, then it must match
75
exactly. If unspecified, it defaults to '1', which is probably the most useful.
76
Otherwise, it is a number specifying the Levenshtein edit distance relative to
77
the string length, according to the following lengths:
78
79
=over 4
80
81
=item 0..2
82
83
must match exactly
84
85
=item 3..5
86
87
C<fuzziness> edits allowed
88
89
=item >5
90
91
C<fuzziness>+1 edits allowed
92
93
=back
94
95
In all cases the maximum number of edits allowed is two (an elasticsearch
96
restriction.)
97
98
=back
99
100
=head3 Returns
101
102
This returns an arrayref of hashrefs. Each hashref contains a "text" element
103
that contains the field as returned. There may be other fields in that
104
hashref too, but they're less likely to be important.
105
106
The array will be ordered as returned from Elasticsearch, which seems to be
107
in order of some form of relevance.
108
109
=cut
110
111
sub browse {
112
    my ($self, $prefix, $field, $options) = @_;
113
114
    my $params = $self->get_elasticsearch_params();
115
    $self->store(
116
        Catmandu::Store::ElasticSearch->new(
117
            %$params,
118
        )
119
    ) unless $self->store;
120
121
    my $query = $self->_build_query($prefix, $field, $options);
122
    my $results = $self->store->bag->search(%$query);
123
    return $results->{suggest}{suggestions}[0]{options};
124
}
125
126
=head2 _build_query
127
128
    my $query = $self->_build_query($prefix, $field, $options);
129
130
Arguments are the same as for L<browse>. This will return a query structure
131
for elasticsearch to use.
132
133
=cut
134
135
sub _build_query {
136
    my ( $self, $prefix, $field, $options ) = @_;
137
138
    $options = {} unless $options;
139
    my $f = $options->{fuzziness} // 1;
140
    my $l = length($prefix);
141
    my $fuzzie;
142
    if ( $l <= 2 ) {
143
        $fuzzie = 0;
144
    }
145
    elsif ( $l <= 5 ) {
146
        $fuzzie = $f;
147
    }
148
    else {
149
        $fuzzie = $f + 1;
150
    }
151
    $fuzzie = 2 if $fuzzie > 2;
152
153
    my $size = $options->{count} // 500;
154
    my $query = {
155
        # this is an annoying thing, if we set size to 0 it gets rewritten
156
        # to 10. There's a bug somewhere in one of the libraries.
157
        size    => 1,
158
        suggest => {
159
            suggestions => {
160
                text       => $prefix,
161
                completion => {
162
                    field => $field . '__suggestion',
163
                    size  => $size,
164
                    fuzzy => {
165
                        fuzziness => $fuzzie,
166
                    }
167
                }
168
            }
169
        }
170
    };
171
    return $query;
172
}
173
174
1;
175
176
__END__
177
178
=head1 AUTHOR
179
180
=over 4
181
182
=item Robin Sheat C<< <robin@catalyst.net.nz> >>
183
184
=back
185
186
=cut
(-)a/Koha/SearchEngine/Elasticsearch/Search.pm (-1 / +1 lines)
Lines 81-87 Returns Link Here
81
=cut
81
=cut
82
82
83
sub search {
83
sub search {
84
    my ($self, $query, $page, $count, %options) = @_;
84
    my ( $self, $query, $page, $count, %options ) = @_;
85
85
86
    my $params = $self->get_elasticsearch_params();
86
    my $params = $self->get_elasticsearch_params();
87
    # 20 is the default number of results per page
87
    # 20 is the default number of results per page
(-)a/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss (+100 lines)
Lines 3337-3341 $star-selected: #EDB867; Link Here
3337
3337
3338
/* END jQuery Bar Rating plugin for star ratings */
3338
/* END jQuery Bar Rating plugin for star ratings */
3339
3339
3340
/*opac browse search*/
3341
#browse-search {
3342
3343
    form {
3344
        label {
3345
            display: inline-block;
3346
            margin-right:5px;
3347
        }
3348
3349
        [type=submit] {
3350
            margin-top: 10px;
3351
        }
3352
    }
3353
3354
    #browse-resultswrapper {
3355
       margin-top: 4em;
3356
3357
        @media (min-width: 768px) and (max-width: 984px) {
3358
            margin-top: 2em;
3359
        }
3360
3361
        @media (max-width: 767px) {
3362
            margin-top: 1em;
3363
        }
3364
    }
3365
    #browse-searchresults, #browse-selectionsearch {
3366
        border: 1px solid #E3E3E3;
3367
        .border-radius-all(4px);
3368
        padding: 0;
3369
        overflow-y: auto;
3370
        max-height: 31em;
3371
        margin-bottom: 2em;
3372
    }
3373
    #browse-searchresults {
3374
        max-height: 31em;
3375
        list-style: none;
3376
        padding: 10px;
3377
3378
        a {
3379
            display: block;
3380
            margin-bottom: 5px;
3381
3382
            &.selected {
3383
                background-color:#EEE;
3384
            }
3385
        }
3386
3387
        li:last-child a {
3388
            margin-bottom: 0;
3389
        }
3390
3391
        @media (max-width: 767px) {
3392
            max-height: 13em;
3393
        }
3394
    }
3395
    #browse-selection {
3396
        margin-top: -40px;
3397
        padding-top: 0;
3398
3399
        @media (max-width: 767px) {
3400
            margin-top: 0;
3401
        }
3402
    }
3403
    #browse-selectionsearch ol {
3404
        list-style: none;
3405
        margin: 0;
3406
3407
        li {
3408
            padding: 1em;
3409
3410
            &:nth-child(odd) {
3411
                background-color: #F4F4F4;
3412
            }
3413
        }
3414
    }
3415
   #browse-selectionsearch p.subjects {
3416
        font-size: 0.9em;
3417
        margin-bottom: 0;
3418
    }
3419
    #browse-selectionsearch h4 {
3420
        margin: 0;
3421
    }
3422
    .error, .no-results {
3423
        background-color: #EEE;
3424
        border: 1px solid #E8E8E8;
3425
        text-align: left;
3426
        padding: 0.5em;
3427
        .border-radius-all(3px);
3428
    }
3429
    .loading {
3430
        text-align: center;
3431
3432
        img {
3433
            margin:0.5em 0;
3434
            position: relative;
3435
            left: -5px;
3436
        }
3437
    }
3438
}
3439
/*end browse search*/
3340
3440
3341
@import "responsive";
3441
@import "responsive";
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-browse.tt (+89 lines)
Line 0 Link Here
1
[% USE Koha %]
2
[% INCLUDE 'doc-head-open.inc' %]
3
<title>[% IF ( LibraryNameTitle ) %][% LibraryNameTitle %][% ELSE %]Koha online[% END %] catalog &rsaquo; Browse our catalog</title>
4
[% INCLUDE 'doc-head-close.inc' %]
5
[% BLOCK cssinclude %][% END %]
6
[% INCLUDE 'bodytag.inc' bodyid='opac-browser' %]
7
[% INCLUDE 'masthead.inc' %]
8
9
10
<div class="main">
11
    <ul class="breadcrumb">
12
        <li><a href="/cgi-bin/koha/opac-main.pl">Home</a>
13
        <span class="divider">&rsaquo;</span></li>
14
15
        <li><a href="#">Browse search</a></li>
16
    </ul>
17
18
    <div class="container-fluid">
19
        <div class="row-fluid">
20
        [% IF ( OpacNav || OpacNavBottom ) %]
21
22
            <div class="span2">
23
                <div id="navigation">
24
                [% INCLUDE 'navigation.inc' %]
25
                </div>
26
            </div>
27
        [% END %]
28
29
        [% IF ( OpacNav ) %]
30
31
            <div class="span10">
32
            [% ELSE %]
33
34
            <div class="span12">
35
            [% END %]
36
37
                <div id="browse-search">
38
                    <h1>Browse search</h1>
39
40
                    <form>
41
                        <label for="browse-searchterm">Search for:</label>
42
                        <input type="search" id="browse-searchterm" name="searchterm" value="">
43
                        <label for="browse-searchfield" class="hide-text">Search type:</label>
44
                        <select id="browse-searchfield" name="searchfield">
45
                            <option value="author">Author</option>
46
                            <option value="subject">Subject</option>
47
                            <option value="title">Title</option>
48
                        </select>
49
50
                        <div id="browse-searchfuzziness">
51
                            <label for="exact" class="radio inline"><input type="radio" name="browse-searchfuzziness" id="exact" value="0">Exact</label>
52
                            <label for="fuzzy" class="radio inline"><input type="radio" name="browse-searchfuzziness" id="fuzzy" value="1" checked="checked"> Fuzzy</label>
53
                            <label for="reallyfuzzy" class="radio inline"><input type="radio" name="browse-searchfuzziness" id="reallyfuzzy" value="2"> Really Fuzzy</label>
54
                        </div>
55
                        <button class="btn btn-success" type="submit" accesskey="s">Search</button>
56
                    </form>
57
58
                    <p id="browse-suggestionserror" class="error hidden">
59
                    An error occurred, please try again.</p>
60
61
                    <div id="browse-resultswrapper" class="hidden">
62
                        <ul id="browse-searchresults" class="span3" start="-1" aria-live="polite">
63
                            <li class="loading hidden"><img src="[% interface %]/[% theme %]/images/loading.gif" alt=""> Loading</li>
64
65
                            <li class="no-results hidden">Sorry, there are no results, try a different search term.</li>
66
                        </ul>
67
68
                        <h3 id="browse-selection" class="span9"></h3>
69
70
                        <div id="browse-selectionsearch" class="span9 hidden">
71
                            <div class="loading hidden">
72
                                <img src="[% interface %]/[% theme %]/images/loading.gif" alt=""> Loading
73
                            </div>
74
75
                            <div class="no-results hidden">No results</div>
76
77
                            <ol aria-live="polite"></ol>
78
                        </div>
79
                    </div><!-- / .results-wrapper -->
80
                </div><!-- / .userbrowser -->
81
            </div><!-- / .span10 -->
82
        </div><!-- / .row-fluid -->
83
    </div><!-- / .container-fluid -->
84
</div><!-- / .main -->
85
[% INCLUDE 'opac-bottom.inc' %]
86
[% BLOCK jsinclude %]
87
<script type="text/javascript" src="[% interface %]/[% theme %]/js/browse.js">
88
</script>
89
[% END %]
(-)a/koha-tmpl/opac-tmpl/bootstrap/js/browse.js (+172 lines)
Line 0 Link Here
1
jQuery.fn.overflowScrollReset = function() {
2
    $(this).scrollTop($(this).scrollTop() - $(this).offset().top);
3
    return this;
4
};
5
6
$(document).ready(function(){
7
    var xhrGetSuggestions, xhrGetResults;
8
9
    $('#browse-search form').submit(function(event) {
10
        // if there is an in progress request, abort it so we
11
        // don't end up with  a race condition
12
        if(xhrGetSuggestions && xhrGetSuggestions.readyState != 4){
13
            xhrGetSuggestions.abort();
14
        }
15
16
        var userInput = $('#browse-searchterm').val().trim();
17
        var userField = $('#browse-searchfield').val();
18
        var userFuzziness = $('input[name=browse-searchfuzziness]:checked', '#browse-searchfuzziness').val();
19
        var leftPaneResults = $('#browse-searchresults li').not('.loading, .no-results');
20
        var rightPaneResults = $('#browse-selectionsearch ol li');
21
22
        event.preventDefault();
23
24
        if(!userInput) {
25
            return;
26
        }
27
28
        // remove any error states and show the results area (except right pane)
29
        $('#browse-suggestionserror').addClass('hidden');
30
        $('#browse-searchresults .no-results').addClass('hidden');
31
        $('#browse-resultswrapper').removeClass('hidden');
32
        $('#browse-selection').addClass('hidden').text("");
33
        $('#browse-selectionsearch').addClass('hidden');
34
35
        // clear any results from left and right panes
36
        leftPaneResults.remove();
37
        rightPaneResults.remove();
38
39
        // show the spinner in the left pane
40
        $('#browse-searchresults .loading').removeClass('hidden');
41
42
        xhrGetSuggestions = $.get(window.location.pathname, {api: "GetSuggestions", field: userField, prefix: userInput, fuzziness: userFuzziness})
43
            .always(function() {
44
                // hide spinner
45
                $('#browse-searchresults .loading').addClass('hidden');
46
            })
47
            .done(function(data) {
48
                var fragment = document.createDocumentFragment();
49
50
                if (data.length === 0) {
51
                    $('#browse-searchresults .no-results').removeClass('hidden');
52
53
                    return;
54
                }
55
56
                // scroll to top of container again
57
                $("#browse-searchresults").overflowScrollReset();
58
59
                // store the type of search that was performed as an attrib
60
                $('#browse-searchresults').data('field', userField);
61
62
                $.each(data, function(index, object) {
63
                    // use a document fragment so we don't need to nest the elems
64
                    // or append during each iteration (which would be slow)
65
                    var elem = document.createElement("li");
66
                    var link = document.createElement("a");
67
                    link.textContent = object.text;
68
                    link.setAttribute("href", "#");
69
                    elem.appendChild(link);
70
                    fragment.appendChild(elem);
71
                });
72
73
                $('#browse-searchresults').append(fragment.cloneNode(true));
74
            })
75
            .fail(function(jqXHR) {
76
                //if 500 or 404 (abort is okay though)
77
                if (jqXHR.statusText !== "abort") {
78
                    $('#browse-resultswrapper').addClass('hidden');
79
                    $('#browse-suggestionserror').removeClass('hidden');
80
                }
81
            });
82
    });
83
84
    $('#browse-searchresults').on("click", 'a', function(event) {
85
        // if there is an in progress request, abort it so we
86
        // don't end up with  a race condition
87
        if(xhrGetResults && xhrGetResults.readyState != 4){
88
            xhrGetResults.abort();
89
        }
90
91
        var term = $(this).text();
92
        var field = $('#browse-searchresults').data('field');
93
        var rightPaneResults = $('#browse-selectionsearch ol li');
94
95
        event.preventDefault();
96
97
        // clear any current selected classes and add a new one
98
        $(this).parent().siblings().children().removeClass('selected');
99
        $(this).addClass('selected');
100
101
        // copy in the clicked text
102
        $('#browse-selection').removeClass('hidden').text(term);
103
104
        // show the right hand pane if it is not shown already
105
        $('#browse-selectionsearch').removeClass('hidden');
106
107
        // hide the no results element
108
        $('#browse-selectionsearch .no-results').addClass('hidden');
109
110
        // clear results
111
        rightPaneResults.remove();
112
113
        // turn the spinner on
114
        $('#browse-selectionsearch .loading').removeClass('hidden');
115
116
        // do the query for the term
117
        xhrGetResults = $.get(window.location.pathname, {api: "GetResults", field: field, term: term})
118
            .always(function() {
119
                // hide spinner
120
                $('#browse-selectionsearch .loading').addClass('hidden');
121
            })
122
            .done(function(data) {
123
                var fragment = document.createDocumentFragment();
124
125
                if (data.length === 0) {
126
                    $('#browse-selectionsearch .no-results').removeClass('hidden');
127
128
                    return;
129
                }
130
131
                // scroll to top of container again
132
                $("#browse-selectionsearch").overflowScrollReset();
133
134
                $.each(data, function(index, object) {
135
                    // use a document fragment so we don't need to nest the elems
136
                    // or append during each iteration (which would be slow)
137
                    var elem = document.createElement("li");
138
                    var title = document.createElement("h4");
139
                    var link = document.createElement("a");
140
                    var author = document.createElement("p");
141
                    var destination = window.location.pathname;
142
143
                    destination = destination.replace("browse", "detail");
144
                    destination = destination + "?biblionumber=" + object.id;
145
146
                    author.className = "author";
147
148
                    link.setAttribute("href", destination);
149
                    link.setAttribute("target", "_blank");
150
                    link.textContent = object.title;
151
                    title.appendChild(link);
152
153
                    author.textContent = object.author;
154
155
                    elem.appendChild(title);
156
                    elem.appendChild(author);
157
                    fragment.appendChild(elem);
158
                });
159
160
                $('#browse-selectionsearch ol').append(fragment.cloneNode(true));
161
            })
162
            .fail(function(jqXHR) {
163
                //if 500 or 404 (abort is okay though)
164
                if (jqXHR.statusText !== "abort") {
165
                    $('#browse-resultswrapper').addClass('hidden');
166
                    $('#browse-suggestionserror').removeClass('hidden');
167
                }
168
            });
169
170
    });
171
172
});
(-)a/opac/opac-browse.pl (+114 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# This is a CGI script that handles the browse feature.
4
5
# Copyright 2015 Catalyst IT
6
#
7
# This file is part of Koha.
8
#
9
# Koha is free software; you can redistribute it and/or modify it under the
10
# terms of the GNU General Public License as published by the Free Software
11
# Foundation; either version 3 of the License, or (at your option) any later
12
# version.
13
#
14
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
15
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
17
#
18
# You should have received a copy of the GNU General Public License along
19
# with Koha; if not, write to the Free Software Foundation, Inc.,
20
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22
use Modern::Perl;
23
use CGI;
24
25
use C4::Auth;
26
use C4::Context;
27
use C4::Output;
28
29
use Koha::SearchEngine::Elasticsearch;
30
use Koha::SearchEngine::Elasticsearch::Browse;
31
use Koha::SearchEngine::Elasticsearch::QueryBuilder;
32
use Koha::SearchEngine::Elasticsearch::Search;
33
34
use JSON;
35
use Unicode::Collate;
36
37
my $query = new CGI;
38
binmode STDOUT, ':encoding(UTF-8)';
39
40
# This is the temporary entrance point to the API. Once bug #13799 is settled,
41
# it should be ported to using that.
42
my $api = $query->param('api');
43
44
if ( !$api ) {
45
    my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
46
        {
47
            template_name   => "opac-browse.tt",
48
            query           => $query,
49
            type            => "opac",
50
            authnotrequired => ( C4::Context->preference("OpacPublic") ? 1 : 0 ),
51
        }
52
    );
53
   $template->param();
54
    output_html_with_http_headers $query, $cookie, $template->output;
55
56
57
}
58
elsif ( $api eq 'GetSuggestions' ) {
59
    my $fuzzie = $query->param('fuzziness');
60
    my $prefix = $query->param('prefix');
61
    my $field  = $query->param('field');
62
63
# Under a persistent environment, we should probably not reinit this every time.
64
    my $browser = Koha::SearchEngine::Elasticsearch::Browse->new( { index => 'biblios' } );
65
    my $res = $browser->browse( $prefix, $field, { fuzziness => $fuzzie } );
66
67
    my %seen;
68
    my @sorted =
69
        grep { !$seen{$_->{text}}++ }
70
        sort { lc($a->{text}) cmp lc($b->{text}) } @$res;
71
    print CGI::header(
72
        -type    => 'application/json',
73
        -charset => 'utf-8'
74
    );
75
    print to_json( \@sorted );
76
}
77
elsif ( $api eq 'GetResults' ) {
78
    my $term  = $query->param('term');
79
    my $field = $query->param('field');
80
81
    my $builder  = Koha::SearchEngine::Elasticsearch::QueryBuilder->new( { index => 'biblios' } );
82
    my $searcher = Koha::SearchEngine::Elasticsearch::Search->new(
83
        { index => $Koha::SearchEngine::Elasticsearch::BIBLIOS_INDEX } );
84
85
    my $query = { query => { term => { $field.".raw" => $term } } } ;
86
    my $results = $searcher->search( $query, undef, 500 );
87
    my @output = _filter_for_output( $results->{hits} );
88
    print header(
89
        -type    => 'application/json',
90
        -charset => 'utf-8'
91
    );
92
    print to_json( \@output );
93
}
94
95
# This is a temporary, MARC21-only thing that will grab titles, and authors
96
# This should probably be done with some templatey gizmo
97
# in the future.
98
sub _filter_for_output {
99
    my ($records) = @_;
100
    my @output;
101
    foreach my $rec (@$records) {
102
        my $biblionumber = $rec->{es_id};
103
        my $biblio = Koha::Biblios->find({ biblionumber=>$biblionumber });
104
warn $biblio->title;
105
        push @output,
106
          {
107
            id => $biblionumber,
108
            title    => $biblio->title,
109
            author  => $biblio->author,
110
          };
111
    };
112
    my @sorted = sort { lc($a->{title}) cmp lc($b->{title}) } @output;
113
    return @sorted;
114
}
(-)a/t/Koha_SearchEngine_Elasticsearch_Browse.t (-1 / +68 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
# Copyright 2015 Catalyst IT
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 3 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
use Modern::Perl;
21
22
use Test::More;
23
24
use_ok('Koha::SearchEngine::Elasticsearch::Browse');
25
26
# testing browse itself not implemented as it'll require a running ES
27
can_ok('Koha::SearchEngine::Elasticsearch::Browse',
28
    qw/ _build_query browse /);
29
30
subtest "_build_query tests" => sub {
31
    plan tests => 2;
32
33
    my $browse = Koha::SearchEngine::Elasticsearch::Browse->new({index=>'dummy'});
34
    my $q = $browse->_build_query('foo', 'title');
35
    is_deeply($q, { size => 1,
36
        suggest => {
37
            suggestions => {
38
                text       => 'foo',
39
                completion => {
40
                    field => 'title__suggestion',
41
                    size  => 500,
42
                    fuzzy => {
43
                        fuzziness => 1,
44
                    }
45
                }
46
            }
47
        }
48
    }, 'No fuzziness or size specified');
49
50
    # Note that a fuzziness of 4 will get reduced to 2.
51
    $q = $browse->_build_query('foo', 'title', { fuzziness => 4, count => 400 });
52
    is_deeply($q, { size => 1,
53
        suggest => {
54
            suggestions => {
55
                text       => 'foo',
56
                completion => {
57
                    field => 'title__suggestion',
58
                    size  => 400,
59
                    fuzzy => {
60
                        fuzziness => 2,
61
                    }
62
                }
63
            }
64
        }
65
    }, 'Fuzziness and size specified');
66
};
67
68
done_testing();

Return to bug 14567